基于Nacos实现GateWay动态路由功能
开发环境:
SpringBoot: 2.6.5
SpringCloud: 2021.0.0
SpringCloudAlibaba: 2021.0.1.0
Nacos: 2.1.0
代码:
@Slf4j
@Component
public class MyInMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap<>());
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
Map<String, RouteDefinition> routesSafeCopy = new LinkedHashMap(this.routes);
return Flux.fromIterable(routesSafeCopy.values());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap((r) -> {
if (ObjectUtils.isEmpty(r.getId())) {
return Mono.error(new IllegalArgumentException("id may not be empty"));
} else {
this.routes.put(r.getId(), r);
return Mono.empty();
}
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap((id) -> {
if (this.routes.containsKey(id)) {
this.routes.remove(id);
} else {
log.warn("RouteDefinition not found: " + routeId);
}
return Mono.empty();
});
}
}
@Slf4j
@Component
public class DynamicRouteUtil implements ApplicationEventPublisherAware {
@Resource
private MyInMemoryRouteDefinitionRepository routeDefinitionRepository;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(@NotNull ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void deleteRoute(String id) {
try {
log.info("gateway delete route id {}", id);
this.routeDefinitionRepository.delete(Mono.just(id)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception e) {
log.error("{}:删除路由失败", id);
}
}
public void updateRoutes(List<RouteDefinition> definitions) {
log.info("gateway update routes {}", definitions);
// 获取存在路由列表
List<RouteDefinition> routeDefinitionsExits = this.routeDefinitionRepository
.getRouteDefinitions()
.buffer()
.blockFirst();
// 删除路由
if (CollectionUtils.isNotEmpty(routeDefinitionsExits)) {
for (RouteDefinition routeDefinitionsExit : routeDefinitionsExits) {
deleteRoute(routeDefinitionsExit.getId());
}
}
// 更新路由
definitions.forEach(this::updateRoute);
}
public void updateRoute(RouteDefinition definition) {
// 先删
log.info("gateway delete route {}", definition);
this.routeDefinitionRepository.delete(Mono.just(definition.getId()));
// 后增
this.routeDefinitionRepository.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
public void addRoutes(List<RouteDefinition> definitions) {
if (CollectionUtils.isEmpty(definitions)) {
return;
}
for (RouteDefinition definition : definitions) {
log.info("add route:{}", definition.getId());
this.routeDefinitionRepository.save(Mono.just(definition)).subscribe();
}
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
}
Component
public class DynamicRouteHandle {
@Resource
private DynamicRouteUtil dynamicRouteUtil;
@Resource
private NacosConfigProperties nacosConfigProperties;
private ConfigService configService;
public static final String ROUTE_DATA_ID = "gateway-router.json";
public static final long DEFAULT_TIMEOUT = 30000;
@PostConstruct
public void init() {
log.info("gateway route init...");
try {
configService = initConfigService();
if (configService == null) {
log.warn("initConfigService fail");
return;
}
String configInfo = configService.getConfig(ROUTE_DATA_ID, nacosConfigProperties.getGroup(), DEFAULT_TIMEOUT);
log.info("获取网关当前配置:{}", configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
log.info("获取网关数量:{}", definitionList.size());
dynamicRouteUtil.addRoutes(definitionList);
} catch (Exception e) {
log.error("初始化网关路由时发生错误", e);
}
// 添加监听
dynamicRouteByNacosListener(ROUTE_DATA_ID, nacosConfigProperties.getGroup());
}
public void dynamicRouteByNacosListener(String dataId, String group) {
try {
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("进行网关更新:\n\r{}", configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
log.info("update route : {}", definitionList.toString());
dynamicRouteUtil.updateRoutes(definitionList);
}
@Override
public Executor getExecutor() {
log.info("getExecutor\n\r");
return null;
}
});
} catch (NacosException e) {
log.error("从nacos接收动态路由配置出错!!!", e);
}
}
private ConfigService initConfigService() {
try {
Properties properties = new Properties();
properties.setProperty("serverAddr", nacosConfigProperties.getServerAddr());
properties.setProperty("namespace", nacosConfigProperties.getNamespace());
properties.setProperty("username", nacosConfigProperties.getUsername());
properties.setProperty("password", nacosConfigProperties.getPassword());
return NacosFactory.createConfigService(properties);
} catch (Exception e) {
log.error("初始化网关路由时发生错误", e);
return null;
}
}
}
配置:
gateway-router.json
[{
"id": "user-route",
"order": 0,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/userApp/**"
}
}],
"filters": [],
"uri": "lb://user"
},{
"id": "product-route",
"order": 0,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/productApp/**"
}
}],
"filters": [],
"uri": "lb://product"
}]
注: 如果服务设置了context-path并且与服务名称相同会有问题, 详情: https://blog.csdn.net/weixin_43303455/article/details/122279447, GatewayDiscoveryClientAutoConfiguration.java会为每一个服务创建一个默认路由, 此路由有一个RewritePathGatewayFilter, 会将context-path与serviceId(服务名称)相同的进行置空。
注: 通过 网关服务:ip/actuator/gateway/routes, 可以查看具体的路由信息, 前提是要开启配置
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always