好的,下面是优化后的版本。为了提高可读性和规范性,我对内容进行了结构化、简化了部分代码,同时增加了注释说明,便于理解。
1. 引入依赖
在 pom.xml
中添加以下依赖:
<dependencies>
<!-- Spring Cloud Gateway:提供API网关功能 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery:用于服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Loadbalancer:提供客户端负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
2. 启动类 (GateApplication.java
)
定义主启动类,启动 Spring Boot 应用:
@SpringBootApplication // 标明这是Spring Boot应用的入口
public class GateApplication {
public static void main(String[] args) {
SpringApplication.run(GateApplication.class, args); // 启动应用
}
}
3. 配置文件 (application.yml
)
配置网关的基本设置、Nacos 服务发现以及路由规则。
server:
port: 8080 # 配置网关监听的端口
spring:
application:
name: gateway # 应用名称,用于 Nacos 等服务发现
cloud:
nacos:
discovery:
server-addr: xiaotianlong.xyz:8848 # 配置 Nacos 服务器地址
gateway:
routes: # 配置网关的路由规则
# 路由规则 1
- id: service_name # 路由的唯一ID
uri: lb://service_name # 使用负载均衡访问注册到 Nacos 中的服务
predicates:
- Path=/user/** # 请求路径以 `/user/` 开头时触发此路由
filters:
- AddRequestHeader=X-Request-Foo, Bar # 添加请求头
# 路由规则 2
- id: service_name2
uri: lb://service_name2
predicates:
- Path=/order/** # 请求路径以 `/order/` 开头时触发此路由
4. 自定义全局过滤器
定义一个全局过滤器,记录请求的时间并打印日志。
@Component // 声明为Spring组件
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 记录请求开始时间
System.out.println("请求开始时间: " + System.currentTimeMillis());
// 放行请求
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
// 记录请求结束时间
System.out.println("请求结束时间: " + System.currentTimeMillis());
}));
}
@Override
public int getOrder() {
// 过滤器的执行顺序,数字越小优先级越高
return 0;
}
}
5. 自定义Gateway过滤器
创建一个自定义的 Gateway 过滤器工厂,允许动态配置过滤器的参数。
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
// 内部配置类,允许用户配置过滤器的参数
@Data
public static class Config {
private String pattern = "yyyy-MM-dd"; // 设置默认日期格式
private String message = "默认日志信息"; // 自定义日志信息
}
public MyGatewayFilterFactory() {
super(Config.class); // 指定配置类类型
}
@Override
public List<String> shortcutFieldOrder() {
return List.of("pattern", "message"); // 设置快捷字段顺序
}
@Override
public GatewayFilter apply(Config config) {
// 创建过滤器逻辑
return new OrderedGatewayFilter((exchange, chain) -> {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(config.getPattern());
System.out.println("请求开始时间: " + formatter.format(LocalDateTime.now()));
System.out.println(config.getMessage()); // 打印自定义日志信息
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
System.out.println("请求结束时间: " + formatter.format(LocalDateTime.now()));
}));
}, 1);//在这里设置顺序
}
}
6. 配置自定义过滤器
在 application.yml
文件中配置自定义的 MyGatewayFilterFactory
过滤器,使其生效:
spring:
cloud:
gateway:
routes:
- id: example_route
uri: lb://some-service
predicates:
- Path=/somepath/** # 路径匹配条件
filters:
- name: My # 使用自定义过滤器
args:
pattern: "yyyy-MM-dd~HH:mm:ss" # 自定义日期格式
message: "这是一个统计时间的gateway过滤器" # 自定义日志信息
或者
spring:
cloud:
gateway:
routes:
- id: example_route
uri: lb://some-service
predicates:
- Path=/somepath/** # 路径匹配条件
filters:
# 由于设置了shortcutfieldorder,所以可以这样写
- My="yyyy-MM-dd~HH:mm:ss", "这是一个统计时间的gateway过滤器"
案例1:登录检验
@Component
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Resource
private AuthProperties authProperties;
@Resource
private JwtTool jwtTool;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request
ServerHttpRequest request = exchange.getRequest();
//2.判断路径是否需做登录拦截
List<String> excludePaths = authProperties.getExcludePaths();
if (isExclude(request.getPath())) {
//此时不需要拦截,直接放行
return chain.filter(exchange);
}
//3.获得token
String token = request.getHeaders().getFirst("authorization");
//4.检验并解析token
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (Exception e) {
//设置响应状态码为401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//拦截
return response.setComplete();
}
//todo 5.传递用户信息
//6.放行
return chain.filter(exchange);
}
private boolean isExclude(RequestPath path) {
List<String> excludePaths = authProperties.getExcludePaths();
for (String pattern : excludePaths) {
if (antPathMatcher.match(pattern, path.toString())) {
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
}
7.结合Nacos实现动态路由
在Spring Cloud Gateway中,路由的配置默认是在项目启动时通过 org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator
进行加载的。这些配置一旦加载到内存中(通常是通过一个 Map
缓存),就不会随路由变化而更新,也不支持热更新功能。因此,我们需要借助 Nacos 来实现动态的路由更新功能。
这涉及到两个关键问题:
- 如何监听Nacos配置变更?
- 如何把新的路由信息更新到路由表中?
7.1 引入依赖
首先,我们需要在项目中引入Nacos的配置和启动依赖:
<!-- 统一配置管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 加载bootstrap配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
7.2 配置bootstrap.yaml
在网关项目的 resources
目录下,创建 bootstrap.yaml
文件,配置Nacos的服务地址和路由配置的相关信息:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: xiaotianlong.xyz:8848
config:
server-addr: xiaotianlong.xyz:8848
7.3 定义配置监听器
接下来,我们编写一个配置监听器类,用于监听Nacos配置变更并更新路由。监听器需要通过 NacosConfigManager
获取配置内容,并在路由配置更新时,动态更新路由表。
package com.hmall.gateway.route;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.hmall.common.utils.CollUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {
private final RouteDefinitionWriter writer;
private final NacosConfigManager nacosConfigManager;
// 路由配置文件的dataId和group
private final String dataId = "gateway-routes.json";
private final String group = "DEFAULT_GROUP";
// 保存已更新的路由ID
private final Set<String> routeIds = new HashSet<>();
@PostConstruct
public void initRouteConfigListener() throws NacosException {
// 注册Nacos配置监听器并拉取配置
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null; // 默认执行器为null
}
@Override
public void receiveConfigInfo(String configInfo) {
updateConfigInfo(configInfo); // 配置变更时更新路由
}
});
// 首次启动时加载配置
updateConfigInfo(configInfo);
}
private void updateConfigInfo(String configInfo) {
log.debug("监听到路由配置变更:{}", configInfo);
// 1. 反序列化配置
List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
// 2. 清除旧路由配置
routeIds.forEach(routeId -> writer.delete(Mono.just(routeId)).subscribe());
routeIds.clear();
// 3. 判断并更新新的路由配置
if (CollUtils.isEmpty(routeDefinitions)) {
// 如果没有新的路由配置,直接结束
return;
}
// 4. 更新新的路由配置
routeDefinitions.forEach(routeDefinition -> {
// 保存新路由
writer.save(Mono.just(routeDefinition)).subscribe();
// 记录路由ID,方便未来删除
routeIds.add(routeDefinition.getId());
});
}
}
在Nacos控制台,我们可以添加路由配置文件 gateway-routes.json
,类型选择JSON。路由配置的示例内容如下:
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
},
{
"id": "cart",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/carts/**"}
}],
"filters": [],
"uri": "lb://cart-service"
},
{
"id": "user",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
}],
"filters": [],
"uri": "lb://user-service"
},
{
"id": "trade",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/orders/**"}
}],
"filters": [],
"uri": "lb://trade-service"
},
{
"id": "pay",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/pay-orders/**"}
}],
"filters": [],
"uri": "lb://pay-service"
}
]
通过以上配置,网关将能够动态地加载和更新路由配置,使得路由在Nacos配置变更时自动同步更新。