基于Nacos实现GateWay动态路由功能
开发环境:
SpringBoot: 2.6.5
SpringCloud: 2021.0.0
SpringCloudAlibaba: 2021.0.1.0
Nacos: 2.1.0
代码:
@Slf4j@Componentpublic 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@Componentpublic 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)); }}
Componentpublic 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("进行网关更新:nr{}", configInfo); List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class); log.info("update route : {}", definitionList.toString()); dynamicRouteUtil.updateRoutes(definitionList); } @Override public Executor getExecutor() { log.info("getExecutornr"); 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/gatewayoutes, 可以查看具体的路由信息, 前提是要开启配置
management: endpoints: web: exposure: include: '*' endpoint: health: show-details: always