SpringCloud Gateway 配合Nacos实现路由规则动态刷新

目的:通过nacos配置中心维护gateway的路由配置,并且在nacos上修改配置文件后无需重启就能够立即刷新路由。

1.引入maven依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 用于将yaml配置解析为javabean -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>2.12.4</version>
</dependency>

2. 编写yaml解析工具类

public class YamlUtil {
    /**
     * 将yaml字符串转成类对象
     * @param yamlStr 字符串
     * @param clazz 目标类
     * @param <T> 泛型
     * @return 目标类
     */
    public static <T> T toBean(String yamlStr, Class<T> clazz){
        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        mapper.findAndRegisterModules();
        try {
            return mapper.readValue(yamlStr, clazz);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将类对象转yaml字符串
     * @param object 对象
     * @return yaml字符串
     */
    public static String toYaml(Object object){
        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        mapper.findAndRegisterModules();
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
        StringWriter stringWriter = new StringWriter();
        try {
            mapper.writeValue(stringWriter, object);
            return stringWriter.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将json转为yaml
     * json 2 yaml
     * @param jsonStr json
     * @return yaml
     * @throws JsonProcessingException Exception
     */
    public static String json2Yaml(String jsonStr) throws JsonProcessingException {
        JsonNode jsonNode = new ObjectMapper().readTree(jsonStr);
        return new YAMLMapper().writeValueAsString(jsonNode);
    }
}

3. 在nacos上创建网关路由规则相关配置文件

gateway-route.yaml

spring:
  cloud:
    gateway:
      routes:
      #物联网微服务
      - id: iotApplication
        uri: lb://iotApplication
        predicates:
          - Path=/iot/**
        filters:
          - StripPrefix=1
      #数据传输微服务
      - id: transferApplication
        uri: lb://transferApplication
        predicates:
          - Path=/transfer/**
        filters:
          - StripPrefix=1
      #测试跳转百度
      - id: Baidu
        uri: http://www.baidu.com
        predicates:
          - Path=/baidu/**
        filters:
          - StripPrefix=1

4. 编写yaml映射类

主要用户将上面的配置文件转为java Bean对象

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GatewayRouteConfigProperties {

    private Spring spring;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class Spring{
        private Cloud cloud;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class Cloud{
        private Gateway gateway;
    }
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class Gateway{
        private List<RouteDefinition> routes;
    }

    public List<RouteDefinition> getRouteDefinition(){
        return spring.cloud.gateway.routes;
    };
}

5. 新增配置类,监听nacos配置文件修改网关配置

@Slf4j
@ConditionalOnBean(NacosConfigProperties.class)
@Configuration
public class DynamicRouteConfiguration implements ApplicationEventPublisherAware  {

    @Autowired
    private NacosConfigProperties nacosConfigProperties;

    @Value("${spring.cloud.gateway.dynamic.data-id:gateway-route}")
    private String dataId;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher applicationEventPublisher;

    //用于记录加载的配置文件
    private static final List<String> ROUTE_LIST = Collections.synchronizedList(new ArrayList<>());

    @PostConstruct
    public void dynamicRouteByNacosListener() {
        try {

            ConfigService configService = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());
            // 程序首次启动, 并加载初始化路由配置
            String initConfigInfo = configService.getConfig(dataId, nacosConfigProperties.getGroup(), 5000);

            this.addRouteConfig(initConfigInfo);

            //添加监听器监听nacos配置文件内的路由变化
            configService.addListener(dataId, nacosConfigProperties.getGroup(), new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    refreshRouteConfig(configInfo);
                }
                @Override
                public Executor getExecutor() {return null;}
            });
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }


    private void publish() {
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 刷新路由配置
     * @param configInfo 配置文件字符串, 必须为json array格式
     */
    private void refreshRouteConfig(String configInfo) {
        clearRoute();
        addRouteConfig(configInfo);
    }

    private void addRouteConfig(String configInfo) {
        try {
            //将yaml转为java对象
            GatewayRouteConfigProperties gatewayRouteConfig = YamlUtil.toBean(configInfo, GatewayRouteConfigProperties.class);

            if( gatewayRouteConfig!=null && CollUtil.isNotEmpty( gatewayRouteConfig.getRouteDefinition() ) ){
                gatewayRouteConfig.getRouteDefinition().parallelStream().forEach(this::addRoute);
            }

            publish();
            log.info("动态配置路由加载完成 {}", JSONUtil.toJsonStr(gatewayRouteConfig));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清除路由信息
     */
    private void clearRoute() {
        ROUTE_LIST.stream().forEach( id ->{
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        });
        ROUTE_LIST.clear();
    }

    /**
     * 添加路由
     */
    private void addRoute(RouteDefinition definition) {
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            ROUTE_LIST.add(definition.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

大功告成,试试动态修改nacos的配置文件,网关能够立即刷新路由规则,不用每次修改规则都要重启了。


版权声明:本文为dndndnnffj原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。