您当前的位置: 首页 >  gateway

springgateway动态路由的四类实现方式

发布时间:2022-05-25 21:41:56 ,浏览量:0

写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于Nacos, 基于数据库(PosgreSQL/Redis), 基于Memory(内存),而我们公司是第四种方案:基于File(本地文件),通过不同文件来隔离不同业务线的路由,大佬们不要喷,任何方案脱离不了业务场景(各种难言之隐)。下面主要简单介绍下这四种动态路由的实现方式

1.基于Nacos的动态路由

Nacos官方简介

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。主要特性如下:

1. 服务发现和服务健康监测
2. 动态配置服务
3. 动态 DNS 服务
4. 服务及其元数据管理

此处不展开介绍Nacos了,主要讲下Spring Cloud Gateway + Nacos 实现动态路由

1.1 相关版本如下
spring-cloud-starter-gateway:2.1.0.RELEASE
spring-cloud-starter-alibaba-nacos-config:2.2.5.RELEASE
1.2 实现思路

c891ce6a8bd3508dca8ce90cdbdc83bd.png

 ok,上代码

properties配置

### nacos configuration start
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=50f5dcf0-f3c0-4c79-9715-0e25e3959ssd
nacos.gateway.route.config.data-id=server-routes
nacos.gateway.route.config.group=spb-gateway
### nacos configuration end

NacosGatewayConfig配置类

package com.kawa.spbgateway.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class NacosGatewayConfig {
    public static final long DEFAULT_TIMEOUT = 30000;


    public static String NACOS_SERVER_ADDR;


    public static String NACOS_NAMESPACE;


    public static String NACOS_ROUTE_DATA_ID;


    public static String NACOS_ROUTE_GROUP;


    @Value("${spring.cloud.nacos.discovery.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr) {
        NACOS_SERVER_ADDR = nacosServerAddr;
    }


    @Value("${spring.cloud.nacos.discovery.namespace}")
    public void setNacosNamespace(String nacosNamespace) {
        NACOS_NAMESPACE = nacosNamespace;
    }


    @Value("${nacos.gateway.route.config.data-id}")
    public void setNacosRouteDataId(String nacosRouteDataId) {
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
    }


    @Value("${nacos.gateway.route.config.group}")
    public void setNacosRouteGroup(String nacosRouteGroup) {
        NACOS_ROUTE_GROUP = nacosRouteGroup;
    }


    @Bean
    public ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        return objectMapper;
    }
}

NacosDynamicRouteService类

加载和监听路由

package com.kawa.spbgateway.service;


import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kawa.spbgateway.route.CustomizedRouteDefinition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;


import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;


import static com.kawa.spbgateway.config.NacosGatewayConfig.*;


@Service
@Slf4j
@DependsOn({"nacosGatewayConfig"})
public class NacosDynamicRouteService {


    @Autowired
    private NacosRefreshRouteService nacosRefreshRouteService;


    private ConfigService configService;


    @Autowired
    private ObjectMapper objectMapper;


    @PostConstruct
    public void init() {
        log.info(">>>>>>>>>> init gateway route <<<<<<<<<<");
        configService = initConfigService();
        if (null == configService) {
            log.error(">>>>>>> init the ConfigService failed!!!");
        }
        String configInfo = null;
        try {
            configInfo = configService.getConfig(NACOS_ROUTE_DATA_ID, NACOS_ROUTE_GROUP, DEFAULT_TIMEOUT);
            log.info(">>>>>>>>> get the gateway configInfo:
{}", configInfo);
            ListrouteDefinitions = objectMapper.readValue(configInfo, new TypeReference() {
            });


            for (RouteDefinition definition : routeDefinitions) {
                log.info(">>>>>>>>>> load route : {}", definition.toString());
                nacosRefreshRouteService.add(definition);
            }
        } catch (NacosException | JsonProcessingException e) {
            e.printStackTrace();
        }
        dynamicRouteByNacosListener(NACOS_ROUTE_DATA_ID, NACOS_ROUTE_GROUP);
    }


    private void dynamicRouteByNacosListener(String dataId, String group) {
        try {
            configService.addListener(dataId, group, new Listener() {
                @Override
                public Executor getExecutor() {
                    log.info("-------------------getExecutor-------------------");
                    return null;
                }


                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info(">>>>>>>>> listened configInfo change:
  {}", configInfo);
                    ListrouteDefinitions = null;
                    try {
                        routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<>() {
                        });
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    }
                    nacosRefreshRouteService.updateList(routeDefinitions);
                }
            });
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }


    private ConfigService initConfigService() {
        Properties properties = new Properties();
        properties.setProperty("serverAddr", NACOS_SERVER_ADDR);
        properties.setProperty("namespace", NACOS_NAMESPACE);
        try {
            return NacosFactory.createConfigService(properties);
        } catch (NacosException e) {
            e.printStackTrace();
            return null;
        }
    }
}

NacosRefreshRouteService类 

实现路由的更新和刷新本地缓存

package com.kawa.spbgateway.service;


import com.kawa.spbgateway.route.CustomizedRouteDefinition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;


import java.util.ArrayList;
import java.util.List;




@Service
@Slf4j
public class NacosRefreshRouteService implements ApplicationEventPublisherAware {


    private ApplicationEventPublisher publisher;


    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;


    @Autowired
    private RouteDefinitionLocator routeDefinitionLocator;


    private ListrouteIds = new ArrayList<>();


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


    /**
     * 删除路由
     *
     * @param id
     * @return
     */
    public void delete(String id) {
        try {
            log.info(">>>>>>>>>> gateway delete route id {}", id);
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 更新路由
     *
     * @param definitions
     * @return
     */
    public void updateList(Listdefinitions) {
        log.info(">>>>>>>>>> gateway update route {}", definitions);
        // 删除缓存routerDefinition
        ListrouteDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
        if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
            routeDefinitionsExits.forEach(routeDefinition -> {
                log.info("delete routeDefinition:{}", routeDefinition);
                delete(routeDefinition.getId());
            });
        }
        definitions.forEach(definition -> {
            updateById(definition);
        });
    }


    /**
     * 更新路由
     *
     * @param definition
     * @return
     */
    public void updateById(RouteDefinition definition) {
        try {
            log.info(">>>>>>>>>> gateway update route {}", definition);
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 增加路由
     *
     * @param definition
     * @return
     */
    public void add(RouteDefinition definition) {
        log.info(">>>>>>>>>> gateway add route {}", definition);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }


}

测试一下

nacos添加路由配置,注意"Data ID" 和 “Group”要和配置一一对应

440a92931ab90caf0cac5d7a7d2f4553.png

启动项目加载配置,可以看到加载路由配置的日志(监听路由变化的日志就不截图了)

da65628a1170a7211dc2d248dc508b92.png

也可以通过actuator的接口测试下(可以看到已经路由已经加载到本地内存)

7bcefe7b1106432f7a2e854fa640a2ae.png 

2. 基于数据库(PosgreSQL/Redis)的动态路由

基于数据库,关系型数据库和非关系型数据库,实现思路是一样的,这里我就以Redis来举例子

2.1 相关配置
org.springframework.bootspring-boot-starter-data-redis
2.2  实现思路

471024dad4541408f8b3de4e2cc961e6.png

上代码

proerties配置

### redis configuration start
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=10619
spring.redis.password=asdqwe
### redis configuratiokn end

RedisConfiguration类

package com.kawa.spbgateway.config;


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfiguration {
    @Bean
    public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        //设置工厂链接
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置自定义序列化方式
        setSerializeConfig(redisTemplate);
        return redisTemplate;
    }


    private void setSerializeConfig(StringRedisTemplate redisTemplate) {
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
    }
}

RedisDynamicRouteService类

操作redis的类

package com.kawa.spbgateway.service;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


import java.util.ArrayList;
import java.util.List;


@Slf4j
@Service
public class RedisDynamicRouteService {


    public static final String GATEWAY_ROUTES_PREFIX = "brian:sz_home:gateway_dynamic_route:";


    @Autowired
    private StringRedisTemplate redisTemplate;


    @Autowired
    private ObjectMapper objectMapper;


    public FluxgetRouteDefinitions() {
        log.info(">>>>>>>>>> getRouteDefinitions <<<<<<<<<<");
        ListrouteDefinitions = new ArrayList<>();
        redisTemplate.keys(GATEWAY_ROUTES_PREFIX+"*").stream().forEach(key -> {
            String rdStr = redisTemplate.opsForValue().get(key);
            RouteDefinition routeDefinition = null;
            try {
                routeDefinition = objectMapper.readValue(rdStr, RouteDefinition.class);
                routeDefinitions.add(routeDefinition);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }


        });
        return Flux.fromIterable(routeDefinitions);
    }


    public Monosave(Monoroute) {
        return route.flatMap(routeDefinition -> {
            String rdStr = null;
            try {
                rdStr = objectMapper.writeValueAsString(routeDefinition);
                redisTemplate.opsForValue().set(GATEWAY_ROUTES_PREFIX + routeDefinition.getId(), rdStr);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }


            return Mono.empty();
        });
    }


    public Monodelete(MonorouteId) {
        return routeId.flatMap(id -> {
            if (redisTemplate.hasKey(GATEWAY_ROUTES_PREFIX + id)) {
                redisTemplate.delete(GATEWAY_ROUTES_PREFIX + id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("routeDefinition not found, id is: " + id)));
        });
    }


    public Monoget(MonorouteId) {
        return routeId.flatMap(id -> Mono.just(redisTemplate.hasKey(GATEWAY_ROUTES_PREFIX + id)));
    }
}

RedisRefreshRouteService类

动态刷新路由

package com.kawa.spbgateway.service;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


@Slf4j
@Service
public class RedisRefreshRouteService implements ApplicationEventPublisherAware, ApplicationRunner {


    @Autowired
    private RedisDynamicRouteService repository;


    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;


    private ApplicationEventPublisher publisher;


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


    private void loadRoutes(){
        log.info(">>>>>>>>>> init routes from redis <<<<<<<<<<");
        FluxrouteDefinitions = repository.getRouteDefinitions();
        routeDefinitions.subscribe(r-> {
            routeDefinitionWriter.save(Mono.just(r)).subscribe();
        });
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    public void add(RouteDefinition routeDefinition){
        Assert.notNull(routeDefinition.getId(),"routeDefinition is can not be null");
        repository.save(Mono.just(routeDefinition)).subscribe();
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    public void update(RouteDefinition routeDefinition){
        Assert.notNull(routeDefinition.getId(),"routeDefinition is can not be null");
        repository.delete(Mono.just(routeDefinition.getId())).subscribe();
        routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).subscribe();
        repository.save(Mono.just(routeDefinition)).subscribe();
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }




    public void delete(String id){
        Assert.notNull(id,"routeDefinition is can not be null");
        repository.delete(Mono.just(id)).subscribe();
        routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    @Override
    public void run(ApplicationArguments args) throws Exception {
        loadRoutes();
    }
}

RedisDynamicRouteController类

package com.kawa.spbgateway.controller;


import com.kawa.spbgateway.service.RedisRefreshRouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;


@RestController
@RequestMapping("/local")
public class RedisDynamicRouteController {


    @Autowired
    private RedisRefreshRouteService dynamicRouteService;


    @PostMapping("/add")
    public Monocreate(@RequestBody RouteDefinition entity) {
        dynamicRouteService.add(entity);
        return Mono.just(new ResponseEntity<>("save success", HttpStatus.OK));
    }


    @PostMapping("/update")
    public Monoupdate(@RequestBody RouteDefinition entity) {
        dynamicRouteService.update(entity);
        return Mono.just(new ResponseEntity<>("update success", HttpStatus.OK));
    }


    @PostMapping("/delete/{id}")
    public Monodelete(@PathVariable String id) {
        dynamicRouteService.delete(id);
        return Mono.just(new ResponseEntity<>("delete success", HttpStatus.OK));
    }
}

ok,测试下

启动项目,查询下actuator接口,http://localhost:8080/actuator/gateway/routedefinitions 没有任何RouteDefinition

6487d65a8b61fd93d2a4c51168a8cbf8.png

postman插入一条RouteDefinition信息,http://127.0.0.1:8080/local/add

7818c0b078f67a6c7cf722f602060cac.png

再次查询RouteDefinitions信息,可以看到新添加进来的路由

1af9b8250fa08fa3d32083a5cfb85c6c.png

ok,测试下路由是否生效

66fa41f36c9eb666e52919dd03977748.png

可以看到接口有数据返回,日志信息发现通过接口添加的路由生效了,转发到了目标接口

67e3101dacfa128fd993b3411943697d.png

接下来删除路由继续测试下

13d48a52e10c365c9998c21e04f2e7e3.png

调用删除接口后,通过actuator查询确认路由被删除了

90a7920c81f8c704b1f9630a64e31fef.png

再次测试目标接口,404 Not Found

845d27b78b17fdd6b7a36e45617dd78d.png

3. 基于本地内存Memory的动态路由

基于本地内存的方式比较简单,Spring Boot已经提供了两个组件Spring Boot Admin 和 Spring Boot Actuator,我这边只用Actuator来实现路由动态变化

3.1 相关配置和接口
org.springframework.bootspring-boot-starter-actuator
o.s.c.g.a.GatewayControllerEndpoint:
{GET /routes/{id}}: route(String)
{GET /routes}: routes()
{GET /routedefinitions}: routesdef()
{GET /globalfilters}: globalfilters()
{GET /routefilters}: routefilers()
{GET /routepredicates}: routepredicates()
{GET /routes/{id}/combinedfilters}: combinedfilters(String)
{DELETE /routes/{id}}: delete(String)
{POST /routes/{id}}: save(String,RouteDefinition)
{POST /refresh}: refresh()
3.2 实现思路

和上面一样核心接口,routeDefinitionWriter.save(), routeDefinitionWriter.delete(),publisher.publishEvent(new RefreshRoutesEvent(this))

74d9b989e6c086eef20430fcc32a3e5d.png

测试一下

项目启动的时候,不配置任何路由, 测试接口http://127.0.0.1:8080/actuator/gateway/routedefinitions 没有任何信息

af9dfb0ee7b0c5a339a9937af17d51cd.png

尝试添加一条路由信息,http://127.0.0.1:8080/actuator/gateway/routes/org.springframework.util.AlternativeJdkIdGenerator@3f203441

c432f1f0e7858322f72b84b588a4a9c0.png

最后测试下,路由有没有添加到内存,先刷新缓存http://127.0.0.1:8080/actuator/gateway/refresh,再次请求http://127.0.0.1:8080/actuator/gateway/routedefinitions

156474b8ba419f8d10e682b057060ba9.png 

可以发现路由已经到本地内存了,目标路由这里就不测试了,下面的基于File的动态路由会再次测试目标路由

4.基于本地File的动态路由 4.1 实现思路

4ac98f739760764c6167b4c64bc9f626.png

上代码

route配置yml

根据不用业务通过文件名区分开

card-hk.yml

routes:
  - uri: http://card-hk.${gateway.route.domain.postfix}
    predicates:
      - Path=/api/hk/card/v1/uuu/query
      - Method=POST
  - uri: http://card-hk.${gateway.route.domain.postfix}
    predicates:
      - Path=/api/hk/card/v1/er/query
      - Method=POST

pancake.yml

routes:
  - uri: http://pancake.${gateway.route.domain.postfix}
    predicates:
      - Path=^/api/pancake/v1/*,^/api/pancake/v1/*/query


  - predicates:
      - Path=/api/pancake/v1/coin/query
    filters:
      - RewritePath=/api/pancake/v1/coin/query, /api/v1/coin/query

passport-hk.yml

routes:
  - uri: http://passport-hk.${gateway.route.domain.postfix}
    predicates:
      - Path=/api/passport-hk/v1/passport/query
    auths:
      - sms

FileRefreshRouteService类

实现定时任务,启动刷新路由

package com.kawa.spbgateway.service;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;


import java.io.IOException;


@Slf4j
@Service
public class FileRefreshRouteService implements ApplicationEventPublisherAware, CommandLineRunner {


    @Autowired
    private FileDynamicRouteService routeService;


    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;


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


    @Scheduled(cron = "0/5 * * * * ?")
    private void autoRefresh() {
        refreshRoute();
    }


    private synchronized void refreshRoute() {
        try {
            log.info(">>>>>>>>>> start refresh route <<<<<<<<<<");
            if (routeService.refreshRoutes()) {
                log.info(")))))))))))))))))))))))))))))) FileRefreshRouteService refreshRoute~~~");
                applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
            }
        } catch (IOException e) {
            log.error("Refresh route failed :{}", e.getMessage());
            throw new IllegalStateException("Refresh route failed :{}", e);
        }
    }


    @Override
    public void run(String... args) {
        refreshRoute();
    }
}

FileDynamicRouteService类

实现路由刷新的功能,包括checksum路由文件是否修改,是否更新路由

package com.kawa.spbgateway.service;


import com.kawa.spbgateway.domain.BrianGatewayProperties;
import com.kawa.spbgateway.property.RefreshRoutePropertySource;
import com.kawa.spbgateway.transformer.BrianRouteDefinitionTransformer;
import com.kawa.spbgateway.util.ChecksumUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


import static com.kawa.spbgateway.content.Contents.*;


@Service
@Slf4j
public class FileDynamicRouteService implements RouteDefinitionRepository {


    private Environment environment;


    private String folder;


    private ListresourceFileExt;


    private ConcurrentHashMap fileChecksumMap = new ConcurrentHashMap<>(32);


    private ConcurrentHashMap routes = new ConcurrentHashMap<>(32);


    private BrianRouteDefinitionTransformer transformer = new BrianRouteDefinitionTransformer();


    public FileDynamicRouteService(Environment environment) {
        this.environment = environment;
    }


    public boolean refreshRoutes() throws IOException {
        getAndInitProperties();
        Listresources = getCustomizedConfigs();
        if (isRefresh(resources)) {
            updateFileChecksumMap(resources);
            updateRefreshRoutePropertySource(resources);
            refreshRouteCache(readRouteConfig(resources));
            return true;
        }
        log.info(">>>>>>>>>> no need refresh route <<<<<<<<<<");
        return false;
    }


    /**
     * @param targets
     */
    private void refreshRouteCache(Listtargets) {
        // when first load the RouteDefinition
        if (CollectionUtils.isEmpty(routes)) {
            targets.forEach(rd -> {
                // add routeDefinition
                save(Mono.just(rd)).subscribe();
                log.info(">>>>>>>>>> init add routeDefinition:{}", rd);
            });
            return;
        }


        Listdefinitions = new ArrayList<>();
        Collections.addAll(definitions, new RouteDefinition[routes.size()]);
        Collections.copy(definitions, routes.values().stream().collect(Collectors.toList()));


        targets.forEach(rd -> {
            if (Objects.isNull(routes.get(rd.getId()))) {
                // add new RouteDefinition
                save(Mono.just(rd)).subscribe();
                log.info(">>>>>>>>>> add routeDefinition:{}", rd);
            }
            // not null don't update
            if (Objects.nonNull(routes.get(rd.getId())) && rd.equals(routes.get(rd.getId()))) {
                definitions.remove(rd);
            }
        });


        // remove RouteDefinition
        if (Objects.nonNull(definitions)) {
            definitions.forEach(rd -> {
                delete(Mono.just(rd.getId())).subscribe();
                log.info(">>>>>>>>>> delete routeDefinition:{}", rd);
            });
        }
    }


    private ListreadRouteConfig(Listresources) {
        Binder binder = Binder.get(environment);
        Listconfigs = new ArrayList<>();
        resources.stream().map(res -> res.getFilename()).forEach(fn -> {
            if (!fn.isEmpty()) {
                log.info(">>>>>>>>>> BrianGatewayProperties filename:{}", fn);
                BrianGatewayProperties brianGatewayProperties =
                        binder.bindOrCreate(fn, BrianGatewayProperties.class);
                log.info(">>>>>>>>>> {}", brianGatewayProperties);
                brianGatewayProperties.getRoutes().forEach(route -> {
                    configs.add(transformer.transform(route, route.getUri() == null ? null : route.getUri().toString()));
                });
            }
        });
        return configs;
    }




    private void updateRefreshRoutePropertySource(Listresources) {
        if (environment instanceof ConfigurableEnvironment) {
            MutablePropertySources propertySources =
                    ((ConfigurableEnvironment) this.environment).getPropertySources();


            ListpropertySourceLoaders =
                    SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
            if (null != folder) {
                resources.forEach(res -> {
                    addCustomizedResource(propertySources, res, propertySourceLoaders);
                });
            }
        }
    }


    /**
     * @param propertySources
     * @param resource
     * @param propertySourceLoaders
     * @return
     */
    private void addCustomizedResource(MutablePropertySources propertySources, Resource resource,
                                       ListpropertySourceLoaders) {
        propertySourceLoaders.forEach(psl -> {
            ListfileExts = Arrays.asList(psl.getFileExtensions());
            String filename = resource.getFilename();
            if (fileExts.contains(StringUtils.getFilenameExtension(filename))) {
                log.info(">>>>>>>>>> load file resource: {}", filename);
                try {
                    List            
关注
打赏
1688896170
查看更多评论

暂无认证

  • 0浏览

    0关注

    111705博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.1382s