一:准备
请求网关,顾名思义,所有请求都有网关统一处理,路由至各个服务,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 {
}
}
View Code
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());
}
}
View Code
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);
}
}