首先,了解一下两个网关的一些区别。
zool是同步getway是异步进行
两者均是web网关,处理的是http请求
gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件,而zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等
gateway很好的支持异步,而zuul仅支持同步,那么理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
从框架设计的角度看,gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
编码上看,zuul更加简洁易懂,注释规范清晰,而gateway作为Spring家族的一份子,竟然几乎不注释…
总的来说,在微服务架构,如果使用了Spring Cloud生态的基础组件,则Spring Cloud Gateway相比而言更加具备优势,单从流式编程+支持异步上就足以让开发者选择它了。
大佬总结链接:https://blog.csdn.net/u010681191/article/details/99656413
现在可以开始正式项目配置getway
添加相应的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
这里一起引用了相应的ribbon与hystrix的依赖,在引用依赖的时候要注意:
如果启动项目的时候出错:Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type ‘org.springframework.http.codec.ServerCodecConfigurer’ that could not be found.
原因是getway网关的依赖与web依赖是有冲突的,应该将项目中web依赖去除~
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
配置文件
server:
port: 8399
spring:
application:
name: gblx-gateway
cloud:
config:
discovery:
enabled: true
service-id: gblx-config
#uri: http://localhost:8334
profile: test
name: redis
gateway:
#允许跨域访问
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
#表明Gateway开启服务注册和发现的功能,并且Spring Cloud Gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
#spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了)。
discovery:
locator:
enabled: true
lower-case-service-id: true
#定义路由规则
routes:
- id: gblx-user
uri: lb://gblx-user
predicates:
- Path=/user/s**
filters:
- StripPrefix=1
#定义熔断的方法和路径
- name: Hystrix
args:
name: fallbackCommand
fallbackUri: forward:/web/fallback
#定义redis限流
- name: RequestRateLimiter
args:
#令牌通每秒填充平均速率
redis-rate-limiter.replenishRate: 5
#令牌桶的总容量。
redis-rate-limiter.burstCapacity: 50
rate-limiter: "#{@defaultRedisRateLimiter}"
#用于限流的解析器的Bean对象的名字。它使用SpEL表达式#{@beanName}从Spring容器中获取bean对象。
key-resolver: "#{@apiKeyResolver}"
- id: gblx-order
uri: lb://gblx-order
predicates:
- Path=/clearing/**
filters:
- StripPrefix=1
配置相应的过滤器,需要继承两个类GlobalFilter, Ordered
```java
@Component
public class AuthFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
private static final String TOKEN = "token";
/**
* 过滤的请求路径
*/
@Value("${auth.skip.urls}")
private String[] skipAuthUrls;
@Override
public int getOrder() {
return 1;
}
@Autowired
private JedisCluster jedisCluster;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put("startTime", System.currentTimeMillis());
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//获取请求方式post、get
String method = request.getMethodValue();
//接口请求路径
String url = request.getURI().getPath();
//拿到token
String token = request.getHeaders().getFirst(TOKEN);
//跳过不需要验证的路径
if (Arrays.asList(skipAuthUrls).contains(url)) {
exchange.getResponse().setStatusCode(HttpStatus.OK);
//有些是可带可不带token的接口,判断是否带token转化成功用户id
if (StringUtils.isNotBlank(token)) {
Auth auth = TokenUtils.decodeToken(token);
if (auth != null) {
return returnMono(chain, exchange, auth, url, method);
}
}
return chain.filter(exchange);
}
//token为空
if (StringUtils.isBlank(token)) {
return unauthorized(exchange);
}
//根据传入的APPID获取USER信息用户对象转换为字符串.
Auth auth = TokenUtils.decodeToken(token);
if (null == auth) {
return unauthorized(exchange);
}
//判断是否是最新的token 传入的token --> redis的token
String redisToken = jedisCluster.hget(CacheKeyForRedis.LOGIN, auth.getUserId() + "");
if (StringUtils.isBlank(redisToken)) {
return unauthorized(exchange);
}
if (!StringUtils.equals(redisToken, token)) {
return unauthorized(exchange);
}
return returnMono(chain, exchange, auth, url, method);
}
/**
* 从Flux<DataBuffer>中获取字符串的方法
*
* @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());
});
//获取request body
return bodyRef.get();
}
/**
* 获取请求数据,转换为String
*
* @param value
* @return
*/
private DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
/**
* 网关拒绝,返回401
*
* @param
*/
private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
JSONObject errorJson = new JSONObject();
errorJson.put("status", GatewayErrorType.PARAM_NOT_KEY.getValue());
errorJson.put("msg", GatewayErrorType.PARAM_NOT_KEY.getReasonPhrase());
DataBuffer buffer = serverWebExchange.getResponse()
.bufferFactory().wrap(errorJson.toJSONString().getBytes(StandardCharsets.UTF_8));
ServerHttpResponse response = serverWebExchange.getResponse();
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Flux.just(buffer));
}
private Mono<Void> returnMono(GatewayFilterChain chain, ServerWebExchange exchange, Auth auth, String url, String method) {
//所有校验通过的请求,在请求头存放对象信息
Consumer<HttpHeaders> httpHeaders = httpHeader -> {
httpHeader.set("userId", auth.getUserId().toString());
httpHeader.set("account", auth.getAccount());
};
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(build).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute("startTime");
if (startTime != null) {
long executeTime = (System.currentTimeMillis() - startTime);
logger.info("url:{}, method:{} ,耗时:{}ms", url, method, executeTime);
}
}));
}
/**
* String转map
*
* @param str
* @return
*/
public static Map<String, String> getStringToMap(String str) {
//根据逗号截取字符串数组
String[] str1 = str.split("&");
//创建Map对象
Map<String, String> map = new HashMap<String, String>();
//循环加入map集合
for (int i = 0; i < str1.length; i++) {
//根据":"截取字符串数组
String[] str2 = str1[i].split("=");
if (str2.length <= 1) {
map.put(str2[0], "");
} else {
//str2[0]为KEY,str2[1]为值
map.put(str2[0], str2[1]);
}
}
return map;
}