spring-cloud学习之2.搭建请求网关spring-cloud-getway
一:准备
请求网关,顾名思义,所有请求都有网关统一处理,路由至各个服务,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);
}
}