一:准備
請求網關,顧名思義,所有請求都有網關統一處理,路由至各個服務,getway是spring最新網關,有取代zuul的趨勢,具體請百度。
1.導包
getway包:
<!--gateway 網關依賴,內置webflux 依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
getway熔斷:
<!-- 熔斷--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
getway自帶限流功能,內部使用的是redis
<!-- redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
eureka實例:
<!--eureka 客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2:配置:
server: port: 2001 spring: application: name: dandelion-getway cloud: gateway: discovery: locator: # 是否和服務注冊與發現組件結合,設置為 true 后可以直接使用應用名稱調用服務 enabled: true # 路由配置中心 routes: # 服務中心 - id: dandelion-api #id 以-開頭 唯一即可 uri: lb://dandelion-api #lb://代表服務內轉發,后跟服務名稱 #斷言 和過濾差不多意思,其中有配置具體百度 predicates: - Method=GET #只接受get方法 - Path=/system/** #只接收/system開頭的路徑 # 過濾器配置 getway有兩種過濾方式,GatewayFilter和GlobalFilter # GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上 #GlobalFilter : 全局過濾器,不需要在配置文件中配置,作用在所有的路由上。 filters: # 驗證碼處理 - ImgCodeFilter #設置StripPrefix=1表示從二級url路徑轉發,即http://localhost:2001/auth/demo將會轉發到http://localhost:2002/demo - StripPrefix=1 - name: RequestRateLimiter #固定名稱 args: #配置限流鍵的解析器 key-resolver: '#{@ipRequestLimiter}' #令牌桶每秒填充速率,1s/1次 redis-rate-limiter.replenishRate: 1 # 令牌桶總數量 redis-rate-limiter.burstCapacity: 1 # 降級配置 - name: Hystrix #固定名稱 args: name: fallbackcmd fallbackUri: 'forward:/fallback' redis: host: 192.168.211.128 jedis: pool: max-wait: 300ms timeout: 1 #單位秒 eureka: client: service-url: defaultZone: http://localhost:1001/eureka instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true #訪問路徑可以顯示IP地址 hystrix: command: default: #default全局有效,service id指定應用有效 execution: timeout: enabled: true #是否啟用超時 默認啟用 isolation: thread: timeoutInMilliseconds: 1000 # 命令執行超時時間,默認1000ms
3.過濾器:

package club.dandelion.cloud.getway.filter; import club.dandelion.cloud.common.R; import club.dandelion.cloud.common.cons.Constants; import club.dandelion.cloud.common.exception.ValidateCodeException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URI; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicReference; /** * 驗證碼處理 * * @author jiang */ @Component public class ImgCodeFilter extends AbstractGatewayFilterFactory<ImgCodeFilter.Config> { private final static String AUTH_URL = "/auth/login"; @Autowired private StringRedisTemplate redisTemplate; public ImgCodeFilter() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); URI uri = request.getURI(); // 不是登錄請求,直接向下執行 if (!StringUtils.containsIgnoreCase(uri.getPath(), AUTH_URL)) { return chain.filter(exchange); } try { String bodyStr = resolveBodyFromRequest(request); JSONObject bodyJson = JSONObject.parseObject(bodyStr); String code = (String) bodyJson.get("captcha"); String randomStr = (String) bodyJson.get("randomStr"); // 校驗驗證碼 checkCode(code, randomStr); } catch (Exception e) { e.printStackTrace(); ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); String msg = JSON.toJSONString(R.error(e.getMessage())); DataBuffer bodyDataBuffer = response.bufferFactory().wrap(msg.getBytes()); return response.writeWith(Mono.just(bodyDataBuffer)); } return chain.filter(exchange); }; } /** * 獲取請求體 * * @param serverHttpRequest * @return */ private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { // 獲取請求體 Flux<DataBuffer> body = serverHttpRequest.getBody(); AtomicReference<String> bodyRef = new AtomicReference<>(); body.subscribe(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); DataBufferUtils.release(buffer); bodyRef.set(charBuffer.toString()); }); return bodyRef.get(); } /** * 檢查code * * @param code * @param randomStr */ @SneakyThrows private void checkCode(String code, String randomStr) { if (StringUtils.isBlank(code)) { throw new ValidateCodeException("驗證碼不能為空"); } if (StringUtils.isBlank(randomStr)) { throw new ValidateCodeException("驗證碼不合法"); } String key = Constants.DEFAULT_CODE_KEY + randomStr; String saveCode = redisTemplate.opsForValue().get(key); redisTemplate.delete(key); if (!code.equalsIgnoreCase(saveCode)) { throw new ValidateCodeException("驗證碼不合法"); } } /** * 必須要有 */ public static class Config { } }

package club.dandelion.cloud.getway.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; /** * getway限流配置 * * @author jiang */ @Configuration public class HttpRequestLimiter { @Bean public KeyResolver ipRequestLimiter() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); } }
4.熔斷反饋:
import club.dandelion.cloud.common.R; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import java.util.Optional; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR; /** * 錯誤信息 * * @author jiang */ @Slf4j @Component public class HystrixFallbackHandler implements HandlerFunction<ServerResponse> { @Override public Mono<ServerResponse> handle(ServerRequest serverRequest) { Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); originalUris.ifPresent(originalUri -> log.error("網關執行請求:{}失敗,hystrix服務降級處理", originalUri)); return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromValue(JSON.toJSONString(R.error("服務已被降級熔斷")))); } }
5:路徑路由 相當於controller
import club.dandelion.cloud.getway.handler.HystrixFallbackHandler;
import club.dandelion.cloud.getway.handler.ImgCodeHandler;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
/**
* 沒有controller所以配置路由信息
* <p>
* RouterFunction使用RequestPredicate將傳入請求映射到HandlerFunction。
* <p>
* AllArgsConstructor lombok注解,代表有所有的構造參數 所以下面兩個屬性是根據構造參數注入的
*
* @author jiang
*/
@Configuration
@AllArgsConstructor
public class RouterFunctionConfiguration {
private HystrixFallbackHandler hystrixFallbackHandler;
private ImgCodeHandler imgCodeHandler;
@Bean
public RouterFunction<?> routerFunction() {
return RouterFunctions
.route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
hystrixFallbackHandler)
.andRoute(RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
imgCodeHandler);
}
}