写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于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 实现思路
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); List<CustomizedRouteDefinition> routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<CustomizedRouteDefinition>>() { }); 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); List<CustomizedRouteDefinition> routeDefinitions = 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 List<String> routeIds = 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(List<CustomizedRouteDefinition> definitions) { log.info(">>>>>>>>>> gateway update route {}", definitions); // 删除缓存routerDefinition List<RouteDefinition> routeDefinitionsExits = 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”要和配置一一对应
启动项目加载配置,可以看到加载路由配置的日志(监听路由变化的日志就不截图了)
也可以通过actuator的接口测试下(可以看到已经路由已经加载到本地内存)
2. 基于数据库(PosgreSQL/Redis)的动态路由
基于数据库,关系型数据库和非关系型数据库,实现思路是一样的,这里我就以Redis来举例子
2.1 相关配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.2 实现思路
上代码
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 Flux<RouteDefinition> getRouteDefinitions() { log.info(">>>>>>>>>> getRouteDefinitions <<<<<<<<<<"); List<RouteDefinition> routeDefinitions = 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 Mono<Void> save(Mono<RouteDefinition> route) { 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 Mono<Void> delete(Mono<String> routeId) { 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 Mono<Boolean> get(Mono<String> routeId) { 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 <<<<<<<<<<"); Flux<RouteDefinition> routeDefinitions = 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 Mono<ResponseEntity<String>> create(@RequestBody RouteDefinition entity) { dynamicRouteService.add(entity); return Mono.just(new ResponseEntity<>("save success", HttpStatus.OK)); } @PostMapping("/update") public Mono<ResponseEntity<String>> update(@RequestBody RouteDefinition entity) { dynamicRouteService.update(entity); return Mono.just(new ResponseEntity<>("update success", HttpStatus.OK)); } @PostMapping("/delete/{id}") public Mono<ResponseEntity<String>> delete(@PathVariable String id) { dynamicRouteService.delete(id); return Mono.just(new ResponseEntity<>("delete success", HttpStatus.OK)); } }
ok,测试下
启动项目,查询下actuator接口,http://localhost:8080/actuator/gateway/routedefinitions 没有任何RouteDefinition
postman插入一条RouteDefinition信息,http://127.0.0.1:8080/local/add
再次查询RouteDefinitions信息,可以看到新添加进来的路由
ok,测试下路由是否生效
可以看到接口有数据返回,日志信息发现通过接口添加的路由生效了,转发到了目标接口
接下来删除路由继续测试下
调用删除接口后,通过actuator查询确认路由被删除了
再次测试目标接口,404 Not Found
3. 基于本地内存Memory的动态路由
基于本地内存的方式比较简单,Spring Boot已经提供了两个组件Spring Boot Admin 和 Spring Boot Actuator,我这边只用Actuator来实现路由动态变化
3.1 相关配置和接口
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
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))
测试一下
项目启动的时候,不配置任何路由, 测试接口http://127.0.0.1:8080/actuator/gateway/routedefinitions 没有任何信息
尝试添加一条路由信息,http://127.0.0.1:8080/actuator/gateway/routes/org.springframework.util.AlternativeJdkIdGenerator@3f203441
最后测试下,路由有没有添加到内存,先刷新缓存http://127.0.0.1:8080/actuator/gateway/refresh,再次请求http://127.0.0.1:8080/actuator/gateway/routedefinitions
可以发现路由已经到本地内存了,目标路由这里就不测试了,下面的基于File的动态路由会再次测试目标路由
4.基于本地File的动态路由
4.1 实现思路
上代码
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 List<String> resourceFileExt; private ConcurrentHashMap<String, String> fileChecksumMap = new ConcurrentHashMap<>(32); private ConcurrentHashMap<String, RouteDefinition> routes = new ConcurrentHashMap<>(32); private BrianRouteDefinitionTransformer transformer = new BrianRouteDefinitionTransformer(); public FileDynamicRouteService(Environment environment) { this.environment = environment; } public boolean refreshRoutes() throws IOException { getAndInitProperties(); List<Resource> resources = 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(List<RouteDefinition> targets) { // 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; } List<RouteDefinition> definitions = 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 List<RouteDefinition> readRouteConfig(List<Resource> resources) { Binder binder = Binder.get(environment); List<RouteDefinition> configs = 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(List<Resource> resources) { if (environment instanceof ConfigurableEnvironment) { MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); List<PropertySourceLoader> propertySourceLoaders = 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, List<PropertySourceLoader> propertySourceLoaders) { propertySourceLoaders.forEach(psl -> { List<String> fileExts = Arrays.asList(psl.getFileExtensions()); String filename = resource.getFilename(); if (fileExts.contains(StringUtils.getFilenameExtension(filename))) { log.info(">>>>>>>>>> load file resource: {}", filename); try { List<PropertySource<?>> propertySourceList = psl.load(filename, resource); propertySourceList.forEach(ps -> { String psName = ps.getName(); PropertySource refreshRoutePropertySource = new RefreshRoutePropertySource(psName, ps); propertySources.addLast(refreshRoutePropertySource); log.info(">>>>>>>>>> MutablePropertySources add propertySource: {}", psName); }); } catch (IOException e) { e.printStackTrace(); } } }); } private void updateFileChecksumMap(List<Resource> resources) throws IOException { fileChecksumMap.clear(); for (Resource resource : resources) { String fileName = resource.getFile().getName(); // todo, or can use a easy way that use lastModified replace checksum -> resource.getFile().lastModified(); String checksum = ChecksumUtil.checkSumByMD5(resource.getFile()); log.info(">>>>>>>>>> fileName:{},checksum:{}", fileName, checksum); fileChecksumMap.put(fileName, checksum); } } private void getAndInitProperties() { if (!StringUtils.hasText(folder)) { folder = environment.getProperty(SEARCH_FOLDER_KEY) == null ? environment.getProperty(DEFAULT_FOLDER_KEY) : environment.getProperty(SEARCH_FOLDER_KEY); resourceFileExt = Arrays.asList(environment.getProperty(RESOURCE_FILE_EXTENSION_KEY, String[].class, DEFAULT_RESOURCE_FILE_EXTENSIONS)); } } private List<Resource> getCustomizedConfigs() { List<Resource> resources = new ArrayList<>(); List<String> exclude = Arrays.asList(EXCLUDES); try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(folder))) { stream.forEach(path -> { if (!path.toFile().isDirectory() && resourceFileExt.contains(StringUtils.getFilenameExtension(path.toFile().getName())) && !exclude.contains(path.toFile().getName()) ) { log.debug(">>>>>>>>>> load file source: {}", path); resources.add(new FileSystemResource(path)); } }); } catch (IOException e) { throw new IllegalStateException(String.format("open %s field, %s", folder, e)); } return resources; } private boolean isRefresh(List<Resource> resources) { if (resources.size() != fileChecksumMap.size()) { return true; } if (!Objects.equals(Arrays.asList(fileChecksumMap.keySet().stream().sorted().toArray()), Arrays.asList(resources.stream().map(res -> res.getFilename()).sorted().toArray()))) { return true; } for (Resource resource : resources) { try { if (!fileChecksumMap.get(resource.getFilename()).equals(ChecksumUtil.checkSumByMD5(resource.getFile()))) { return true; } } catch (IOException e) { log.info(">>>>>>>>>> isRefresh checksum error:{}", e.getMessage()); } } return false; } @Override public Flux<RouteDefinition> getRouteDefinitions() { log.info(")))))))))))))))))))))))))))))) FileDynamicRouteService getRouteDefinitions~~~"); return Flux.fromIterable(routes.values()); } @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.flatMap(r -> { routes.put(r.getId(), r); return Mono.empty(); }); } @Override public Mono<Void> delete(Mono<String> routeId) { return routeId.flatMap(id -> { log.debug(">>>>>>>>>> remove the RouteDefinition id is: {}", id); if (routes.keySet().contains(id)) { routes.remove(id); return Mono.empty(); } return Mono.defer(() -> Mono.error(new NotFoundException(String.format("RouteDefinition not found -> %s", routeId)))); }); } }
BrianRouteDefinition类
主要是为了扩展RouteDefinition,添加一些新的路由配置属性
package com.kawa.spbgateway.route; import lombok.Data; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.validation.annotation.Validated; import java.util.ArrayList; import java.util.List; import java.util.Objects; @Validated @Data public class BrianRouteDefinition extends RouteDefinition { private List<String> apiKeys = new ArrayList<>(); private List<String> auths = new ArrayList<>(); @Override public int hashCode() { return Objects.hash(getId(), getPredicates(), getFilters(), getUri(), getMetadata(), getOrder(), this.apiKeys, this.auths); } @Override public String toString() { return "{" + "id=" + getId() + ", uri=" + getUri() + ", predicates=" + getPredicates() + ", filters=" + getFilters() + ", metadata=" + getMetadata() + ", order=" + getOrder() + ", apiKeys=" + apiKeys + ", auths=" + auths + '}'; } }
BrianConfigGatewayFilterFactory类
该类是为了处理BrianRouteDefinition里面的属性,我这边就简单的赋值给exchange
package com.kawa.spbgateway.filter; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.util.List; import java.util.Objects; import static com.kawa.spbgateway.content.Contents.*; @Slf4j @Component public class BrianConfigGatewayFilterFactory extends AbstractGatewayFilterFactory<BrianConfigGatewayFilterFactory.Config> { public BrianConfigGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return new OrderedGatewayFilter((exchange, chain) -> { initExchangeAttr(config, exchange); return chain.filter(exchange); }, 120); } private void initExchangeAttr(Config config, ServerWebExchange exchange) { if (Objects.nonNull(config.getAuths())) { exchange.getAttributes().put(GATEWAY_CONFIG_CLASS_AUTH, config.getAuths()); } if (Objects.requireNonNull(config.getApiKeys()).size() > 0) { exchange.getAttributes().put(GATEWAY_CONFIG_CLASS_API_KEYS, config.getApiKeys()); } } public static class Config { private String[] auths; private List<String> apiKeys; public String[] getAuths() { return auths; } public void setAuths(String[] auths) { this.auths = auths; } public List<String> getApiKeys() { return apiKeys; } public void setApiKeys(List<String> apiKeys) { this.apiKeys = apiKeys; } } }
RefreshRoutePropertySource类
自定义一个PropertySpurce加了自己的前缀,此处为了方便自己识别,也方便自己管理在内存中的路由
package com.kawa.spbgateway.property; import org.springframework.core.env.PropertySource; import org.springframework.util.Assert; import java.util.Objects; /** * RefreshRoutePropertySource * add a prefix for an existing property source */ public class RefreshRoutePropertySource extends PropertySource { private PropertySource innerPropertySource; private String prefix; public RefreshRoutePropertySource(String prefix, PropertySource origin) { super("RefreshRoutePropertySource-" + origin.getName()); this.innerPropertySource = origin; this.prefix = prefix; } @Override public Object getProperty(String name) { Assert.notNull(name, "name can not be null!"); var target = prefix + "."; if (name.startsWith(target)) { return innerPropertySource.getProperty(name.replace(target, "")); } return null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; RefreshRoutePropertySource that = (RefreshRoutePropertySource) o; return innerPropertySource.equals(that.innerPropertySource) && prefix.equals(that.prefix); } @Override public int hashCode() { return Objects.hash(super.hashCode(), innerPropertySource, prefix); } }
BrianGatewayProperties类
配合Springboot的Binder从内存获取自定义的路由BrianRouteDefinition
package com.kawa.spbgateway.domain; import com.kawa.spbgateway.route.BrianRouteDefinition; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class BrianGatewayProperties { private String url; private List<BrianRouteDefinition> routes =new ArrayList<>(); }
BrianRouteDefinitionTransformer类
将路由配置文件读取的配置信息,赋值给BrianRouteDefinition
package com.kawa.spbgateway.transformer; import com.kawa.spbgateway.config.ApiKeysConfiguration; import com.kawa.spbgateway.route.BrianRouteDefinition; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.util.StringUtils; import java.net.URI; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static com.kawa.spbgateway.content.Contents.*; @Slf4j public class BrianRouteDefinitionTransformer { private String defaultRewritePathRegexp = "^/api/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*)"; private String defaultRewritePathReplacement = "/v$\{version}/$\{path}"; private String extendRewritePathRegexp = "^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*)"; private String extendRewritePathReplacement = "/v$\{version}/$\{path}"; private String defaultRewriteDomainRegexp = "^/api/(?<domain>[a-zA-Z-]*)/v.+/.*"; private String defaultRewriteDomainReplacement = "https://$\{domain}.free.beeceptor.com"; private String extendRewriteDomainRegexp = "^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v.+/.*"; private String extendRewriteDomainReplacement = "https://$\{domain}-$\{region}.free.beeceptor.com"; private List<String> default1FAValues = Arrays.asList("pwd", "sms", "gAuth"); private List<String> default2FAValues = Arrays.asList("pwd+sms", "sms+gAuth"); private ApiKeysConfiguration apiKeys; public RouteDefinition transform(BrianRouteDefinition brianRouteDefinition, String uri) { // add ConfigGatewayFilter FilterDefinition configFilter = new FilterDefinition(); configFilter.setName(CONFIG_GATEWAY_FILTER_CLASS_NAME); HashMap<String, String> configArgs = new HashMap<>(); var apiKeyString = brianRouteDefinition.getApiKeys().stream().map(ak -> apiKeys.getValue(ak)).collect(Collectors.toList()).toString(); configArgs.put(GATEWAY_CONFIG_CLASS_API_KEYS, apiKeyString.substring(1, apiKeyString.length() - 1)); configArgs.put(GATEWAY_CONFIG_CLASS_AUTH, default1FAValues.toString()); if (Objects.nonNull(brianRouteDefinition.getAuths()) && brianRouteDefinition.getAuths().size() > 0) { configArgs.put(GATEWAY_CONFIG_CLASS_AUTH, brianRouteDefinition.getAuths().toString()); } configFilter.setArgs(configArgs); brianRouteDefinition.getFilters().add(configFilter); if (StringUtils.hasText(uri)) { brianRouteDefinition.setUri(URI.create(uri)); // set route id setRouteId(brianRouteDefinition); } long count = brianRouteDefinition.getFilters().stream() .filter(filterDefinition -> filterDefinition.getName().equals(REWRITE_GATEWAY_FILTER_CLASS_NAME)) .count(); // get path value from Prediction config var path = getPathString(brianRouteDefinition); log.info(">>>>>>>>>> route path: {}", path); var replacement = defaultRewriteDomainReplacement.replace("$\", "$"); Pattern pattern = Pattern.compile(defaultRewriteDomainRegexp); Matcher defaultMatcher = pattern.matcher(path); if (defaultMatcher.matches()) { String newDomain = defaultMatcher.replaceAll(replacement); log.info(">>>>>>>>>> redefine the path {{}} and new domain {{}}", path, newDomain); if (Objects.isNull(brianRouteDefinition.getUri())) { brianRouteDefinition.setUri(URI.create(newDomain)); // set route id setRouteId(brianRouteDefinition); } // add RewritePathGatewayFilter if (count < 1L) { addRewriteFilter(brianRouteDefinition, defaultRewritePathRegexp, defaultRewritePathReplacement); } return brianRouteDefinition; } var replacementExt = extendRewriteDomainReplacement.replace("$\", "$"); Pattern patternExt = Pattern.compile(extendRewriteDomainRegexp); Matcher defaultExtMatcher = patternExt.matcher(path); if (defaultExtMatcher.matches()) { String newDomain = defaultExtMatcher.replaceAll(replacementExt); if (Objects.isNull(brianRouteDefinition.getUri())) { brianRouteDefinition.setUri(URI.create(newDomain)); // set route id setRouteId(brianRouteDefinition); } // add RewritePathGatewayFilter if (count < 1L) { addRewriteFilter(brianRouteDefinition, extendRewritePathRegexp, extendRewritePathReplacement); } return brianRouteDefinition; } if (Objects.isNull(brianRouteDefinition.getUri())) { brianRouteDefinition.setUri(URI.create(FALL_BACK_URI + path)); // set route id setRouteId(brianRouteDefinition); } return brianRouteDefinition; } private void setRouteId(BrianRouteDefinition customizedRouteDefinition) { String url = customizedRouteDefinition.getUri().toString(); customizedRouteDefinition.setId(String.format("%s@%s", url, customizedRouteDefinition.hashCode())); } private void addRewriteFilter(BrianRouteDefinition customizedRouteDefinition, String rewritePathRegexp, String rewritePathReplacement) { FilterDefinition rewriteFilter = new FilterDefinition(); rewriteFilter.setName(REWRITE_GATEWAY_FILTER_CLASS_NAME); HashMap<String, String> rewriteFilterArgs = new HashMap<>(); rewriteFilterArgs.put(REWRITE_GATEWAY_FILTER_REGEXP, rewritePathRegexp); rewriteFilterArgs.put(REWRITE_GATEWAY_FILTER_REPLACEMENT, rewritePathReplacement); rewriteFilter.setArgs(rewriteFilterArgs); customizedRouteDefinition.getFilters().add(rewriteFilter); } private String getPathString(BrianRouteDefinition customizedRouteDefinition) { for (PredicateDefinition predicateDefinition : customizedRouteDefinition.getPredicates()) { if (PREDICATE_PATH.equals(predicateDefinition.getName())) { var firstKey = predicateDefinition.getArgs().keySet().iterator().next(); return predicateDefinition.getArgs().get(firstKey); } } return FALL_BACK_URI; } }
ok,测试下,启动项目
INFO [restartedMain] 2021-09-12 21:11:11.451 c.k.s.s.FileRefreshRouteService - >>>>>>>>>> start refresh route <<<<<<<<<< DEBUG [restartedMain] 2021-09-12 21:11:11.455 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml DEBUG [restartedMain] 2021-09-12 21:11:11.455 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml DEBUG [restartedMain] 2021-09-12 21:11:11.455 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml INFO [restartedMain] 2021-09-12 21:11:11.463 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> fileName:card-hk.yml,checksum:3867921742 INFO [restartedMain] 2021-09-12 21:11:11.463 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> fileName:pancake.yml,checksum:2400413005 INFO [restartedMain] 2021-09-12 21:11:11.464 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> fileName:passport-hk.yml,checksum:140450225 INFO [restartedMain] 2021-09-12 21:11:11.465 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file resource: card-hk.yml DEBUG [restartedMain] 2021-09-12 21:11:11.492 o.s.boot.env.OriginTrackedYamlLoader - Loading from YAML: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml] DEBUG [restartedMain] 2021-09-12 21:11:11.514 o.s.boot.env.OriginTrackedYamlLoader - Merging document (no matchers set): {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]}]} DEBUG [restartedMain] 2021-09-12 21:11:11.515 o.s.boot.env.OriginTrackedYamlLoader - Loaded 1 document from YAML resource: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml] INFO [restartedMain] 2021-09-12 21:11:11.516 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> MutablePropertySources add propertySource: card-hk.yml INFO [restartedMain] 2021-09-12 21:11:11.516 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file resource: pancake.yml DEBUG [restartedMain] 2021-09-12 21:11:11.516 o.s.boot.env.OriginTrackedYamlLoader - Loading from YAML: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml] DEBUG [restartedMain] 2021-09-12 21:11:11.517 o.s.boot.env.OriginTrackedYamlLoader - Merging document (no matchers set): {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]}]} DEBUG [restartedMain] 2021-09-12 21:11:11.518 o.s.boot.env.OriginTrackedYamlLoader - Loaded 1 document from YAML resource: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml] INFO [restartedMain] 2021-09-12 21:11:11.518 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> MutablePropertySources add propertySource: pancake.yml INFO [restartedMain] 2021-09-12 21:11:11.518 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file resource: passport-hk.yml DEBUG [restartedMain] 2021-09-12 21:11:11.519 o.s.boot.env.OriginTrackedYamlLoader - Loading from YAML: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml] DEBUG [restartedMain] 2021-09-12 21:11:11.520 o.s.boot.env.OriginTrackedYamlLoader - Merging document (no matchers set): {routes=[{uri=http://passport-hk.${gateway.route.domain.postfix}, predicates=[Path=/api/passport-hk/v1/passport/query], auths=[sms]}]} DEBUG [restartedMain] 2021-09-12 21:11:11.520 o.s.boot.env.OriginTrackedYamlLoader - Loaded 1 document from YAML resource: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml] INFO [restartedMain] 2021-09-12 21:11:11.521 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> MutablePropertySources add propertySource: passport-hk.yml INFO [restartedMain] 2021-09-12 21:11:11.522 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties filename:card-hk.yml INFO [restartedMain] 2021-09-12 21:11:11.531 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties(url=null, routes=[{id=null, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/uuu/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[]}, {id=null, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/er/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[]}]) INFO [restartedMain] 2021-09-12 21:11:11.553 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/hk/card/v1/uuu/query INFO [restartedMain] 2021-09-12 21:11:11.554 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/hk/card/v1/er/query INFO [restartedMain] 2021-09-12 21:11:11.554 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties filename:pancake.yml INFO [restartedMain] 2021-09-12 21:11:11.565 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties(url=null, routes=[{id=null, uri=http://pancake.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=^/api/pancake/v1/*, _genkey_1=^/api/pancake/v1/*/query}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[]}, {id=null, uri=null, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/pancake/v1/coin/query}}], filters=[FilterDefinition{name='RewritePath', args={_genkey_0=/api/pancake/v1/coin/query, _genkey_1=/api/v1/coin/query}}], metadata={}, order=0, apiKeys=[], auths=[]}]) INFO [restartedMain] 2021-09-12 21:11:11.565 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: ^/api/pancake/v1/* INFO [restartedMain] 2021-09-12 21:11:11.566 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/pancake/v1/coin/query INFO [restartedMain] 2021-09-12 21:11:11.566 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> redefine the path {/api/pancake/v1/coin/query} and new domain {https://pancake.free.beeceptor.com} INFO [restartedMain] 2021-09-12 21:11:11.566 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties filename:passport-hk.yml INFO [restartedMain] 2021-09-12 21:11:11.574 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties(url=null, routes=[{id=null, uri=http://passport-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/passport-hk/v1/passport/query}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[sms]}]) INFO [restartedMain] 2021-09-12 21:11:11.575 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/passport-hk/v1/passport/query INFO [restartedMain] 2021-09-12 21:11:11.575 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> redefine the path {/api/passport-hk/v1/passport/query} and new domain {https://passport-hk.free.beeceptor.com} INFO [restartedMain] 2021-09-12 21:11:11.577 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> init add routeDefinition:{id=http://card-hk.free.beeceptor.com@1548203624, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/uuu/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[FilterDefinition{name='BrianConfig', args={auths=[pwd, sms, gAuth], apiKeys=}}, FilterDefinition{name='RewritePath', args={regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}}}], metadata={}, order=0, apiKeys=[], auths=[]} INFO [restartedMain] 2021-09-12 21:11:11.577 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> init add routeDefinition:{id=http://card-hk.free.beeceptor.com@-679151078, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/er/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[FilterDefinition{name='BrianConfig', args={auths=[pwd, sms, gAuth], apiKeys=}}, FilterDefinition{name='RewritePath', args={regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}}}], metadata={}, order=0, apiKeys=[], auths=[]} INFO [restartedMain] 2021-09-12 21:11:11.577 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> init add routeDefinition:{id=http://pancake.free.beeceptor.com@-1468813552, uri=http://pancake.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=^/api/pancake/v1/*, _genkey_1=^/api/pancake/v1/*/query}}], filters=[FilterDefinition{name='BrianConfig', args={auths=[pwd, sms, gAuth], apiKeys=}}], metadata={}, order=0, apiKeys=[], auths=[]} INFO [restartedMain] 2021-09-12 21:11:11.577 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> init add routeDefinition:{id=https://pancake.free.beeceptor.com@1912448187, uri=https://pancake.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/pancake/v1/coin/query}}], filters=[FilterDefinition{name='RewritePath', args={_genkey_0=/api/pancake/v1/coin/query, _genkey_1=/api/v1/coin/query}}, FilterDefinition{name='BrianConfig', args={auths=[pwd, sms, gAuth], apiKeys=}}], metadata={}, order=0, apiKeys=[], auths=[]} INFO [restartedMain] 2021-09-12 21:11:11.577 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> init add routeDefinition:{id=http://passport-hk.free.beeceptor.com@1466789461, uri=http://passport-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/passport-hk/v1/passport/query}}], filters=[FilterDefinition{name='BrianConfig', args={auths=[sms], apiKeys=}}, FilterDefinition{name='RewritePath', args={regexp=^/api/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}}}], metadata={}, order=0, apiKeys=[], auths=[sms]} INFO [restartedMain] 2021-09-12 21:11:11.578 c.k.s.s.FileRefreshRouteService - )))))))))))))))))))))))))))))) FileRefreshRouteService refreshRoute~~~ INFO [restartedMain] 2021-09-12 21:11:11.578 c.k.s.s.FileDynamicRouteService - )))))))))))))))))))))))))))))) FileDynamicRouteService getRouteDefinitions~~~ DEBUG [restartedMain] 2021-09-12 21:11:11.579 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@1548203624 applying {_genkey_0=/api/hk/card/v1/uuu/query} to Path DEBUG [restartedMain] 2021-09-12 21:11:11.627 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@1548203624 applying {_genkey_0=POST} to Method DEBUG [restartedMain] 2021-09-12 21:11:11.634 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@1548203624 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [restartedMain] 2021-09-12 21:11:11.642 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@1548203624 applying filter {regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}} to RewritePath DEBUG [restartedMain] 2021-09-12 21:11:11.679 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://card-hk.free.beeceptor.com@1548203624 DEBUG [restartedMain] 2021-09-12 21:11:11.679 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://passport-hk.free.beeceptor.com@1466789461 applying {_genkey_0=/api/passport-hk/v1/passport/query} to Path DEBUG [restartedMain] 2021-09-12 21:11:11.681 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://passport-hk.free.beeceptor.com@1466789461 applying filter {auths=[sms], apiKeys=} to BrianConfig DEBUG [restartedMain] 2021-09-12 21:11:11.682 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://passport-hk.free.beeceptor.com@1466789461 applying filter {regexp=^/api/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}} to RewritePath DEBUG [restartedMain] 2021-09-12 21:11:11.683 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://passport-hk.free.beeceptor.com@1466789461 DEBUG [restartedMain] 2021-09-12 21:11:11.684 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://pancake.free.beeceptor.com@-1468813552 applying {_genkey_0=^/api/pancake/v1/*, _genkey_1=^/api/pancake/v1/*/query} to Path DEBUG [restartedMain] 2021-09-12 21:11:11.686 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://pancake.free.beeceptor.com@-1468813552 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [restartedMain] 2021-09-12 21:11:11.688 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://pancake.free.beeceptor.com@-1468813552 DEBUG [restartedMain] 2021-09-12 21:11:11.688 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition https://pancake.free.beeceptor.com@1912448187 applying {_genkey_0=/api/pancake/v1/coin/query} to Path DEBUG [restartedMain] 2021-09-12 21:11:11.689 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition https://pancake.free.beeceptor.com@1912448187 applying filter {_genkey_0=/api/pancake/v1/coin/query, _genkey_1=/api/v1/coin/query} to RewritePath DEBUG [restartedMain] 2021-09-12 21:11:11.691 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition https://pancake.free.beeceptor.com@1912448187 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [restartedMain] 2021-09-12 21:11:11.693 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: https://pancake.free.beeceptor.com@1912448187 DEBUG [restartedMain] 2021-09-12 21:11:11.693 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying {_genkey_0=/api/hk/card/v1/er/query} to Path DEBUG [restartedMain] 2021-09-12 21:11:11.695 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying {_genkey_0=POST} to Method DEBUG [restartedMain] 2021-09-12 21:11:11.696 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [restartedMain] 2021-09-12 21:11:11.698 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying filter {regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}} to RewritePath DEBUG [restartedMain] 2021-09-12 21:11:11.700 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://card-hk.free.beeceptor.com@-679151078
查看日志可以看到启动后加载路由的配置,然后每个10秒会定时检查是否刷新,日志如下
INFO [scheduling-1] 2021-09-12 21:16:20.000 c.k.s.s.FileRefreshRouteService - >>>>>>>>>> start refresh route <<<<<<<<<< DEBUG [scheduling-1] 2021-09-12 21:16:20.001 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml DEBUG [scheduling-1] 2021-09-12 21:16:20.001 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml DEBUG [scheduling-1] 2021-09-12 21:16:20.001 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml INFO [scheduling-1] 2021-09-12 21:16:20.002 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> no need refresh route <<<<<<<<<< INFO [scheduling-1] 2021-09-12 21:16:25.000 c.k.s.s.FileRefreshRouteService - >>>>>>>>>> start refresh route <<<<<<<<<< DEBUG [scheduling-1] 2021-09-12 21:16:25.001 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml DEBUG [scheduling-1] 2021-09-12 21:16:25.001 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml DEBUG [scheduling-1] 2021-09-12 21:16:25.001 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml INFO [scheduling-1] 2021-09-12 21:16:25.003 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> no need refresh route <<<<<<<<<<
然后,通过actuator的接口查看,可以看到路由已经生效了
ok,来测试下路由/api/passport-hk/v1/passport/query,可以看到路由生效的
日志也打印相关日志
测试下修改路由是否生效(添加和删除路由配置,还有修改路由文件名,在这里不演示了,代码已经测试过了)
通过日志发现有路由的刷新日志
INFO [scheduling-1] 2021-09-12 21:33:55.001 c.k.s.s.FileRefreshRouteService - >>>>>>>>>> start refresh route <<<<<<<<<< DEBUG [scheduling-1] 2021-09-12 21:33:55.002 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml DEBUG [scheduling-1] 2021-09-12 21:33:55.002 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml DEBUG [scheduling-1] 2021-09-12 21:33:55.003 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file source: /home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml INFO [scheduling-1] 2021-09-12 21:33:55.004 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> fileName:card-hk.yml,checksum:56354776 INFO [scheduling-1] 2021-09-12 21:33:55.004 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> fileName:pancake.yml,checksum:2400413005 INFO [scheduling-1] 2021-09-12 21:33:55.004 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> fileName:passport-hk.yml,checksum:1148328829 INFO [scheduling-1] 2021-09-12 21:33:55.004 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file resource: card-hk.yml DEBUG [scheduling-1] 2021-09-12 21:33:55.005 o.s.boot.env.OriginTrackedYamlLoader - Loading from YAML: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml] DEBUG [scheduling-1] 2021-09-12 21:33:55.008 o.s.boot.env.OriginTrackedYamlLoader - Merging document (no matchers set): {routes=[{uri=http://card-hk.${gateway.route.domain.postfix}, predicates=[Path=/api/hk/card/v1/card/query, Method=POST]}, {uri=http://card-hk.${gateway.route.domain.postfix}, predicates=[Path=/api/hk/card/v1/er/query, Method=POST]}]} DEBUG [scheduling-1] 2021-09-12 21:33:55.008 o.s.boot.env.OriginTrackedYamlLoader - Loaded 1 document from YAML resource: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/card-hk.yml] INFO [scheduling-1] 2021-09-12 21:33:55.009 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> MutablePropertySources add propertySource: card-hk.yml INFO [scheduling-1] 2021-09-12 21:33:55.009 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file resource: pancake.yml DEBUG [scheduling-1] 2021-09-12 21:33:55.009 o.s.boot.env.OriginTrackedYamlLoader - Loading from YAML: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml] DEBUG [scheduling-1] 2021-09-12 21:33:55.010 o.s.boot.env.OriginTrackedYamlLoader - Merging document (no matchers set): {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]}]} DEBUG [scheduling-1] 2021-09-12 21:33:55.011 o.s.boot.env.OriginTrackedYamlLoader - Loaded 1 document from YAML resource: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/pancake.yml] INFO [scheduling-1] 2021-09-12 21:33:55.011 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> MutablePropertySources add propertySource: pancake.yml INFO [scheduling-1] 2021-09-12 21:33:55.011 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> load file resource: passport-hk.yml DEBUG [scheduling-1] 2021-09-12 21:33:55.011 o.s.boot.env.OriginTrackedYamlLoader - Loading from YAML: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml] DEBUG [scheduling-1] 2021-09-12 21:33:55.013 o.s.boot.env.OriginTrackedYamlLoader - Merging document (no matchers set): {routes=[{uri=http://passport-hk.${gateway.route.domain.postfix}, predicates=[Path=/api/passport-hk/v1/passport/query], auths=[sms]}]} DEBUG [scheduling-1] 2021-09-12 21:33:55.013 o.s.boot.env.OriginTrackedYamlLoader - Loaded 1 document from YAML resource: file [/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/passport-hk.yml] INFO [scheduling-1] 2021-09-12 21:33:55.013 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> MutablePropertySources add propertySource: passport-hk.yml INFO [scheduling-1] 2021-09-12 21:33:55.013 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties filename:card-hk.yml INFO [scheduling-1] 2021-09-12 21:33:55.020 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties(url=null, routes=[{id=null, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/card/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[]}, {id=null, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/er/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[]}]) INFO [scheduling-1] 2021-09-12 21:33:55.020 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/hk/card/v1/card/query INFO [scheduling-1] 2021-09-12 21:33:55.020 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/hk/card/v1/er/query INFO [scheduling-1] 2021-09-12 21:33:55.021 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties filename:pancake.yml INFO [scheduling-1] 2021-09-12 21:33:55.026 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties(url=null, routes=[{id=null, uri=http://pancake.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=^/api/pancake/v1/*, _genkey_1=^/api/pancake/v1/*/query}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[]}, {id=null, uri=null, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/pancake/v1/coin/query}}], filters=[FilterDefinition{name='RewritePath', args={_genkey_0=/api/pancake/v1/coin/query, _genkey_1=/api/v1/coin/query}}], metadata={}, order=0, apiKeys=[], auths=[]}]) INFO [scheduling-1] 2021-09-12 21:33:55.026 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: ^/api/pancake/v1/* INFO [scheduling-1] 2021-09-12 21:33:55.026 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/pancake/v1/coin/query INFO [scheduling-1] 2021-09-12 21:33:55.027 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> redefine the path {/api/pancake/v1/coin/query} and new domain {https://pancake.free.beeceptor.com} INFO [scheduling-1] 2021-09-12 21:33:55.027 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties filename:passport-hk.yml INFO [scheduling-1] 2021-09-12 21:33:55.032 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> BrianGatewayProperties(url=null, routes=[{id=null, uri=http://passport-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/passport-hk/v1/passport/query}}], filters=[], metadata={}, order=0, apiKeys=[], auths=[sms]}]) INFO [scheduling-1] 2021-09-12 21:33:55.033 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> route path: /api/passport-hk/v1/passport/query INFO [scheduling-1] 2021-09-12 21:33:55.033 c.k.s.t.BrianRouteDefinitionTransformer - >>>>>>>>>> redefine the path {/api/passport-hk/v1/passport/query} and new domain {https://passport-hk.free.beeceptor.com} INFO [scheduling-1] 2021-09-12 21:33:55.033 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> add routeDefinition:{id=http://card-hk.free.beeceptor.com@-792698083, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/card/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[FilterDefinition{name='BrianConfig', args={auths=[pwd, sms, gAuth], apiKeys=}}, FilterDefinition{name='RewritePath', args={regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}}}], metadata={}, order=0, apiKeys=[], auths=[]} DEBUG [scheduling-1] 2021-09-12 21:33:55.034 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> remove the RouteDefinition id is: http://card-hk.free.beeceptor.com@1548203624 INFO [scheduling-1] 2021-09-12 21:33:55.034 c.k.s.s.FileDynamicRouteService - >>>>>>>>>> delete routeDefinition:{id=http://card-hk.free.beeceptor.com@1548203624, uri=http://card-hk.free.beeceptor.com, predicates=[PredicateDefinition{name='Path', args={_genkey_0=/api/hk/card/v1/uuu/query}}, PredicateDefinition{name='Method', args={_genkey_0=POST}}], filters=[FilterDefinition{name='BrianConfig', args={auths=[pwd, sms, gAuth], apiKeys=}}, FilterDefinition{name='RewritePath', args={regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}}}], metadata={}, order=0, apiKeys=[], auths=[]} INFO [scheduling-1] 2021-09-12 21:33:55.034 c.k.s.s.FileRefreshRouteService - )))))))))))))))))))))))))))))) FileRefreshRouteService refreshRoute~~~ INFO [scheduling-1] 2021-09-12 21:33:55.034 c.k.s.s.FileDynamicRouteService - )))))))))))))))))))))))))))))) FileDynamicRouteService getRouteDefinitions~~~ DEBUG [scheduling-1] 2021-09-12 21:33:55.034 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://passport-hk.free.beeceptor.com@1466789461 applying {_genkey_0=/api/passport-hk/v1/passport/query} to Path DEBUG [scheduling-1] 2021-09-12 21:33:55.035 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://passport-hk.free.beeceptor.com@1466789461 applying filter {auths=[sms], apiKeys=} to BrianConfig DEBUG [scheduling-1] 2021-09-12 21:33:55.036 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://passport-hk.free.beeceptor.com@1466789461 applying filter {regexp=^/api/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}} to RewritePath DEBUG [scheduling-1] 2021-09-12 21:33:55.037 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://passport-hk.free.beeceptor.com@1466789461 DEBUG [scheduling-1] 2021-09-12 21:33:55.037 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://pancake.free.beeceptor.com@-1468813552 applying {_genkey_0=^/api/pancake/v1/*, _genkey_1=^/api/pancake/v1/*/query} to Path DEBUG [scheduling-1] 2021-09-12 21:33:55.038 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://pancake.free.beeceptor.com@-1468813552 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [scheduling-1] 2021-09-12 21:33:55.039 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://pancake.free.beeceptor.com@-1468813552 DEBUG [scheduling-1] 2021-09-12 21:33:55.040 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-792698083 applying {_genkey_0=/api/hk/card/v1/card/query} to Path DEBUG [scheduling-1] 2021-09-12 21:33:55.041 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-792698083 applying {_genkey_0=POST} to Method DEBUG [scheduling-1] 2021-09-12 21:33:55.041 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-792698083 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [scheduling-1] 2021-09-12 21:33:55.042 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-792698083 applying filter {regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}} to RewritePath DEBUG [scheduling-1] 2021-09-12 21:33:55.043 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://card-hk.free.beeceptor.com@-792698083 DEBUG [scheduling-1] 2021-09-12 21:33:55.043 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition https://pancake.free.beeceptor.com@1912448187 applying {_genkey_0=/api/pancake/v1/coin/query} to Path DEBUG [scheduling-1] 2021-09-12 21:33:55.044 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition https://pancake.free.beeceptor.com@1912448187 applying filter {_genkey_0=/api/pancake/v1/coin/query, _genkey_1=/api/v1/coin/query} to RewritePath DEBUG [scheduling-1] 2021-09-12 21:33:55.045 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition https://pancake.free.beeceptor.com@1912448187 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [scheduling-1] 2021-09-12 21:33:55.046 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: https://pancake.free.beeceptor.com@1912448187 DEBUG [scheduling-1] 2021-09-12 21:33:55.046 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying {_genkey_0=/api/hk/card/v1/er/query} to Path DEBUG [scheduling-1] 2021-09-12 21:33:55.047 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying {_genkey_0=POST} to Method DEBUG [scheduling-1] 2021-09-12 21:33:55.047 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying filter {auths=[pwd, sms, gAuth], apiKeys=} to BrianConfig DEBUG [scheduling-1] 2021-09-12 21:33:55.048 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition http://card-hk.free.beeceptor.com@-679151078 applying filter {regexp=^/api/(?<region>[a-zA-Z-]*)/(?<domain>[a-zA-Z-]*)/v(?<version>[0-9])/(?<path>.*), replacement=/v${version}/${path}} to RewritePath DEBUG [scheduling-1] 2021-09-12 21:33:55.049 o.s.c.g.r.RouteDefinitionRouteLocator - RouteDefinition matched: http://card-hk.free.beeceptor.com@-679151078
再次通过actuator的接口查看,可以看到路由的Path已经修改了,/api/hk/card/v1/uuu/query -> /api/hk/card/v1/card/query
到此四类动态路由的实现方式,都介绍完毕了~~~
顺便推荐一个免费好用的Mock Server: https://beeceptor.com/