寫這篇博客主要是爲了彙總下動態路由的多種實現方式,沒有好壞之分,任何的方案都是依賴業務場景需求的,現在網上實現方式主要有: 基於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:\r\n{}", 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:\n\t{}", 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/