• 基于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
    
  • 相关阅读:
    1041. 困于环中的机器人
    95. 不同的二叉搜索树 II
    LeetCode945:使数组唯一的最小增量
    LeetCode:925.长按键入
    LeetCode:926. 将字符串翻转到单调递增
    InteliJ 安装PlantUML插件
    CodeBlock换肤
    正则表达式验证手机号和座机号
    C#中使用反射遍历一个对象属性和值以及百分数
    c#中@的用法
  • 原文地址:https://www.cnblogs.com/llysc/p/16267211.html
Copyright © 2020-2023  润新知