基于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

0 个评论

要回复文章请先登录注册