动态路由的实现方式多种多样,研究一下基于 nacos 配置文件形式的动态路由。
1. 创建项目,并pom.xml文件引入如下依赖
代码语言:javascript复制<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.olive</groupId>
<artifactId>olive-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
</project>
2. 增加一个配置类
主要配置 nacos 的 dataId 与 group
代码语言:javascript复制import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "route.nacos")
public class GatewayConfig {
private String dataId;
private String group;
private int timeout;
//省略 getter setter
}
3.定义监听路由变化类
InFileRouteDefinitionRepository 类主要是简单 nacos 中的配置文件routes.json 的变化;只要监听到 routes.json 就进行路由更新。
代码语言:javascript复制import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.olive.config.GatewayConfig;
import com.olive.route.model.GatewayRouteDO;
import reactor.core.publisher.Mono;
@Component
public class InFileRouteDefinitionRepository implements ApplicationEventPublisherAware{
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String nacosServer;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Autowired
private GatewayConfig gatewayConfig;
@PostConstruct
private void init() {
dynamicRouteByListener(gatewayConfig.getDataId(), gatewayConfig.getGroup(),
nacosServer, gatewayConfig.getTimeout());
}
/**
* 监听Nacos Server下发的动态路由配置
*/
public void dynamicRouteByListener(String dataId, String group, String nacosServer, int timeout) {
try {
ConfigService configService = NacosFactory.createConfigService(nacosServer);
String content = configService.getConfig(dataId, group, timeout);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
updateConfig(configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
updateConfig(content);
} catch (NacosException e) {
e.printStackTrace();
}
}
private void updateConfig(String configInfo) {
try {
GatewayRouteDO gatewayRouteDO = JSON.parseObject(configInfo, GatewayRouteDO.class);
List<RouteDefinition> routeList = gatewayRouteDO.getRoutes();
if (CollectionUtils.isNotEmpty(routeList)) {
for (RouteDefinition routeDefinition : routeList) {
this.update(routeDefinition);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 增加路由
*/
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
/**
* 更新路由
*/
public String update(RouteDefinition definition) {
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception e) {
return "update fail, not find route routeId: " definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
e.printStackTrace();
return "update route fail";
}
}
/**
* 删除路由
*/
public String delete(String id) {
try {
this.routeDefinitionWriter.delete(Mono.just(id));
return "delete success";
} catch (Exception e) {
e.printStackTrace();
return "delete route fail";
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
对应的 DO 类
代码语言:javascript复制import org.springframework.cloud.gateway.route.RouteDefinition;
import java.io.Serializable;
import java.util.List;
/**
* 动态路由配置信息
*/
public class GatewayRouteDO implements Serializable{
private List<RouteDefinition> routes;
//TODO 省略getter setter
}
4. 增加 application.yml 配置文件
代码语言:javascript复制server:
port: 8089
spring:
application:
name: olive-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.255.10:8848
route:
nacos:
dataId: routes.json
group: DEFAULT_GROUP
timeout: 1000
需要在配置中心 nacos 增加 routes.json 配置文件;这个 json 文件的格式一定要符合 spring-gateway 的 route 格式;否则无法转换。routes.json 内容如下:
代码语言:javascript复制{
"routes": [
{
"filters": [],
"id": "pay_route",
"order": 0,
"predicates": [
{
"args": {
"pattern": "/pay/**"
},
"name": "Path"
}
],
"uri": "lb://pay-service"
},
{
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/user/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"id": "user_route",
"order": 0,
"predicates": [
{
"args": {
"pattern": "/user/**"
},
"name": "Path"
}
],
"uri": "lb://user-center-service"
}
]
}
5. 创建springboot 引导类
代码语言:javascript复制import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class GwApplication {
public static void main(String[] args) {
SpringApplication.run(GwApplication.class, args);
}
}
测试验证只要通过在配置中心 nacos;修改 routes.json 配置文件即可。