gateWay网关

0.序

在微服务架构中作为各个单元间的通讯。

img

1. 基本规则

  • Route(路由):这是网关的基本构建块。它由一个 ID,一个目标URI,一组断
言和一组过滤器定义。如果断言为真,则路由匹配。


  • Predicate(断言):输入类型是一个 ServerWebExchange。我们可以使用它
来匹配来自HTTP 请求的任何内容,例如 headers 或参数。


  • 过滤器( filter), Gateway中的Filter 分为两种类型的Filter, 分别是Gateway
Filter和Global Filter。过滤器Filter 将会对请求和响应进行修改处理

2.maven

在gateway服务中添加maven依赖即可

image-20250912224955910

1
2
3
4
5
6
7
8
9
<!-- gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

3. 规则分发

所有的前端请求发送到gateway后网关会判断请求路径进行服务分发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server:
port: 9998
spring:
redis:
timeout: 5000
host: ###
port: 6379
password: ###
cloud: # 注意缩进,gateway必须在cloud节点下
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: [GET, POST, PUT, DELETE]
discovery:
locator:
enabled: true # 开启服务发现路由
routes:
- id: user-service
uri: lb://user-service # 服务名必须与Nacos中注册的一致
predicates:
- Path=/user/** # 路径匹配规则
- id: paper-service
uri: lb://paper-service
predicates:
- Path=/paper/**,/exam/**
config:
redisTimeout: 60

4. 自定义拦截

当我们需要对所有的请求进行判断是否正常时,可以使用到拦截器,拦截没有登录的用户返回401等等请求告诉前端没有登陆,这里拦截所有没有带token请求头的对象并返回未登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package org.kangdd.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.kangdd.util.JwtUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class GlobalFilterConfig implements GlobalFilter, Ordered {
//application.yml配置文件中,设置token在redis中的过期时间
@Value("${config.redisTimeout}")
private Long redisTimeout;

@Autowired
private RedisTemplate redisTemplate;

private static final String HEADER_NAME = "Access-Token";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("============过滤器============");

ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String url = request.getURI().getPath();
String token = request.getHeaders().getFirst(HEADER_NAME);

System.out.println("请求URL: " + url);
System.out.println("获取到的token: " + token);

// 白名单校验
if (this.shouldNotFilter(url)) {
return chain.filter(exchange);
}

// Token为空校验
if (StringUtils.isEmpty(token)) {
System.out.println("token为空");
return unAuthorize(exchange);
}

// 关键操作1:判断0号库中是否存在该Token
// 确保RedisTemplate已配置连接0号库(通过RedisTemplateConfig)
Boolean tokenExists = redisTemplate.hasKey(token);
if (tokenExists == null || !tokenExists) {
System.out.println("Redis中不存在该token: " + token);
return unAuthorize(exchange);
}

// 关键操作2:刷新Token过期时间(0号库)
boolean expireSuccess = redisTemplate.expire(token, redisTimeout, TimeUnit.SECONDS);
if (!expireSuccess) {
System.out.println("刷新token过期时间失败: " + token);
}
// 解析用户ID
String userIdStr = String.valueOf(JwtUtil.parseUserIdFromToken(token));
Integer userId = Integer.parseInt(userIdStr);
System.out.println("============登录用户id:" + userId + "============");

// 转发请求
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, token).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
}

@Override
public int getOrder() {
return 0;
}

// 返回未登录的自定义错误
private Mono<Void> unAuthorize(ServerWebExchange exchange) {
// 设置错误状态码为401
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置返回的信息为JSON类型
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 自定义错误信息
String errorMsg = "{\"error\": \"" + "用户未登录或登录超时,请重新登录" + "\"}";
// 将自定义错误响应写入响应体
return exchange.getResponse()
.writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(errorMsg.getBytes())));
}


/**
* 判断当前请求URL是否为白名单地址,以及一些内置的不用登录的接口,
*
* @param url 请求的url。
* @return 是返回true,否返回false。
*/
private boolean shouldNotFilter(String url) {
if (url.startsWith("/user/login")) {
return true;
}
// return false;
return false;
}

}

image-20250912225638117

5.注意

依赖 版本
JDK 11
Maven 2.7.18
spring-cloud-alibaba.version 2021.0.5.0
spring-cloud.version 2021.0.9
spring-boot.version 2.7.18