SpringCloud GateWay動(dòng)態(tài)路由用法
1.網(wǎng)關(guān)為什么需要?jiǎng)討B(tài)路由?
網(wǎng)關(guān)的核心功能就是通過(guò)配置不同路由策略在配合注冊(cè)中心訪問(wèn)不同的微服務(wù),而默認(rèn)是在yaml文件中配置路由策略,而在項(xiàng)目上線后,網(wǎng)關(guān)作為所有項(xiàng)目的入口肯定不希望重啟,所以動(dòng)態(tài)路由是必須的,我們?cè)谠黾右粋€(gè)服務(wù),在不希望服務(wù)重新啟動(dòng)的前提下能路由到該服務(wù),以及是基于代碼實(shí)現(xiàn)的網(wǎng)關(guān)動(dòng)態(tài)路由
2.動(dòng)態(tài)路由原理
public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter { }
RouteDefinitionRepository是網(wǎng)關(guān)路由的存儲(chǔ)接口,RouteDefinitionLocator 是獲取存儲(chǔ)中的所有路由,RouteDefinitionWriter主要操作路由的存儲(chǔ)和刪除。
public interface RouteDefinitionLocator { Flux<RouteDefinition> getRouteDefinitions(); }
public interface RouteDefinitionWriter { Mono<Void> save(Mono<RouteDefinition> route); Mono<Void> delete(Mono<String> routeId); }
而gateway中RouteDefinitionRepository接口的默認(rèn)的實(shí)現(xiàn)是InMemoryRouteDefinitionRepository,即在內(nèi)存中存儲(chǔ)路由配置,而且在 GatewayAutoConfiguration 配置中也激活了InMemoryRouteDefinitionRepository這個(gè)Bean,代碼如下。
@Bean @ConditionalOnMissingBean(RouteDefinitionRepository.class) public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() { return new InMemoryRouteDefinitionRepository(); }
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { private final Map<String, RouteDefinition> routes = synchronizedMap( new LinkedHashMap<String, RouteDefinition>()); @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 -> { if (routes.containsKey(id)) { routes.remove(id); return Mono.empty(); } return Mono.defer(() -> Mono.error( new NotFoundException("RouteDefinition not found: " + routeId))); }); } @Override public Flux<RouteDefinition> getRouteDefinitions() { return Flux.fromIterable(routes.values()); } }
InMemoryRouteDefinitionRepository 中可見(jiàn)存儲(chǔ)路由的是一個(gè)帶同步鎖的LinkedHashMap,而存儲(chǔ)刪除都是基于這個(gè)map對(duì)象操作。
3.動(dòng)態(tài)路由設(shè)計(jì)以及實(shí)現(xiàn)
- 方案一:知道動(dòng)態(tài)路由的原理以后,我們可以基于redis設(shè)計(jì)一個(gè)InRedisRouteDefinitionRepository 實(shí)現(xiàn) RouteDefinitionRepository 接口即可,即網(wǎng)關(guān)部署多個(gè)也能動(dòng)態(tài)解決路由問(wèn)題
- 方案二:可以基于nacos 配置動(dòng)態(tài)修改路由(理論上,待驗(yàn)證)nacos的配置也是可以熱加載的。
@Slf4j @Configuration("redisRouteDefinition") @AllArgsConstructor public class InRedisRouteDefinitionRepository implements RouteDefinitionRepository { private RedisTemplate redisTemplate; @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.flatMap(r -> { redisTemplate.opsForHash().put(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, r.getId(), new Gson().toJson(r)); return Mono.empty(); }); } @Override public Mono<Void> delete(Mono<String> routeId) { return routeId.flatMap(id -> { Object router = redisTemplate.opsForHash().get(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, id); if (!Objects.isNull(router)) { redisTemplate.opsForHash().delete(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, id); return Mono.empty(); } return Mono.defer(() -> Mono.error( new NotFoundException("RouteDefinition not found: " + routeId))); }); } @Override public Flux<RouteDefinition> getRouteDefinitions() { List<String> values = redisTemplate.opsForHash().values(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG); if (CollUtil.isNotEmpty(values)) { List<RouteDefinition> definitions = values.stream() .map(s -> new Gson().fromJson(s, RouteDefinition.class)) .collect(Collectors.toList()); return Flux.fromIterable(definitions); } else { return Flux.fromIterable(new ArrayList<>()); } } }
暫時(shí)在網(wǎng)關(guān)中提供接口實(shí)現(xiàn)路由的動(dòng)態(tài)增加和修改Controller
@RestController @RequestMapping("/route") @AllArgsConstructor public class RouteController { private DynamicRouteService dynamicRouteService; @PostMapping public void saveRouteDefinition(@RequestBody GatewayRouteDefinition routeDefinition) { dynamicRouteService.saveRouteDefinition(routeDefinition); } @DeleteMapping("/{id}") public void deleteRouteDefinition(@PathVariable String id) { dynamicRouteService.deleteRouteDefinition(id); } @PutMapping public void update(@RequestBody GatewayRouteDefinition routeDefinition) { dynamicRouteService.updateRouteDefinition(routeDefinition); } @GetMapping public IPage<RouterConfig> getRouterConfigByPage(RouterConfigQueryParams params) { return dynamicRouteService.getRouterConfigByPage(params); } }
路由參數(shù)
@Data @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class GatewayRouteDefinition { /** * 路由的Id */ private String id; /** * 路由斷言集合配置 */ private List<GatewayPredicateDefinition> predicates; /** * 路由過(guò)濾器集合配置 */ private List<GatewayFilterDefinition> filters; /** * 路由規(guī)則轉(zhuǎn)發(fā)的目標(biāo)uri */ private String uri; /** * 路由執(zhí)行的順序 */ private int order; }
@Data public class GatewayPredicateDefinition implements Serializable { /** * 斷言對(duì)應(yīng)的Name */ private String name; /** * 配置的斷言規(guī)則 */ private Map<String, String> args = new LinkedHashMap<>(); }
@Data public class GatewayFilterDefinition implements Serializable { /** * Filter Name */ private String name; /** * 對(duì)應(yīng)的路由規(guī)則 */ private Map<String, String> args = new LinkedHashMap<>(); }
業(yè)務(wù)層代碼 DynamicRouteService,最主要的是注入RouteDefinitionWriter 我們自己的實(shí)現(xiàn)類(lèi),替換默認(rèn)的配置
@Service public class DynamicRouteServiceImpl implements DynamicRouteService { @Resource(name = "redisRouteDefinition") private RouteDefinitionWriter routeDefinitionWriter; @Autowired private IRouterConfigService routerConfigService; @Autowired private ObjectMapper objectMapper; @Override public void saveRouteDefinition(GatewayRouteDefinition definition) { // 判定當(dāng)前路由以及路徑是否存在 LambdaQueryWrapper<RouterConfig> wrapper = Wrappers.<RouterConfig>lambdaQuery() .eq(RouterConfig::getRouterName, definition.getId()) .eq(RouterConfig::getRouterPath, definition.getUri()); List<RouterConfig> list = routerConfigService.list(wrapper); BizVerify.verify(CollUtil.isEmpty(list), "路由已經(jīng)存在"); routerConfigService.save(paramsConvert(definition)); RouteDefinition routerDefinition = DynamicRouteUtils.convertToRouteDefinition(definition); routeDefinitionWriter.save(Mono.just(routerDefinition)).subscribe(); } @Override public void updateRouteDefinition(GatewayRouteDefinition routeDefinition) { routerConfigService.updateById(paramsConvert(routeDefinition)); RouteDefinition definition = DynamicRouteUtils.convertToRouteDefinition(routeDefinition); deleteRouteDefinition(definition.getId()); routeDefinitionWriter.save(Mono.just(definition)).subscribe(); } @Override public void deleteRouteDefinition(String routerId) { routerConfigService.removeById(routerId); routeDefinitionWriter .delete(Mono.just(routerId)) .then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build()))) .onErrorResume((t) -> t instanceof NotFoundException, (t) -> Mono.just(ResponseEntity.notFound().build())); } private RouterConfig paramsConvert(GatewayRouteDefinition routeDefinition) { String filterJson = null; String PredicatesJson = null; try { filterJson = objectMapper.writeValueAsString(routeDefinition.getFilters()); PredicatesJson = objectMapper.writeValueAsString(routeDefinition.getPredicates()); } catch (JsonProcessingException e) { e.printStackTrace(); } return new RouterConfig() .setRouterName(routeDefinition.getId()) .setRouterPath(routeDefinition.getUri()) .setRouterOrder(routeDefinition.getOrder()) .setRouterFilters(filterJson) .setRouterPredicates(PredicatesJson); } @Override public IPage<RouterConfig> getRouterConfigByPage(RouterConfigQueryParams params) { LambdaQueryWrapper<RouterConfig> wrapper = Wrappers.<RouterConfig>lambdaQuery() .like(StrUtil.isNotEmpty(params.getRouterName()), RouterConfig::getRouterName, params.getRouterName()); return routerConfigService.page(new Page<>(params.getPageNum(), params.getPageSize()), wrapper); } }
4.網(wǎng)關(guān)中聚合swagger由于動(dòng)態(tài)路由引發(fā)不展示的問(wèn)題
聚合swagger聚合核心代碼
package com.kill.core.provider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import org.springframework.cloud.gateway.route.RouteDefinitionRepository; import org.springframework.cloud.gateway.support.NameUtils; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import springfox.documentation.swagger.web.SwaggerResource; import springfox.documentation.swagger.web.SwaggerResourcesProvider; import java.util.ArrayList; import java.util.List; /** * <pre> * +--------+---------+-----------+---------+ * | | * +--------+---------+-----------+---------+ * </pre> * * @author wangjian * @since 1019/11/01 11:58:32 */ @Component @Primary @AllArgsConstructor public class SwaggerResourceProvider implements SwaggerResourcesProvider { private static final String SWAGGER2URL = "/v2/api-docs"; private RouteDefinitionRepository repository; @Override public List<SwaggerResource> get() { List<SwaggerResource> resources = new ArrayList<>(); repository.getRouteDefinitions().subscribe( route -> { if (CollUtil.isNotEmpty(route.getPredicates())) { route.getPredicates().forEach( predicateDefinition -> { if (CollUtil.isNotEmpty(predicateDefinition.getArgs())) { if (StrUtil.isNotEmpty(predicateDefinition.getArgs().get("pattern"))) { resources.add( swaggerResource(route.getId(), predicateDefinition.getArgs().get("pattern").replace("/**", SWAGGER2URL))); } if (StrUtil.isNotEmpty(predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0"))) { resources.add( swaggerResource(route.getId(), predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL))); } } }); } }); return resources; } private SwaggerResource swaggerResource(String name, String location) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation(location); swaggerResource.setSwaggerVersion("2.0"); return swaggerResource; } }
5.測(cè)試一下
swagger中目前只有這一個(gè)路由,調(diào)用路由新增一個(gè)
再次刷新swagger,OK 已經(jīng)看到新的路由了
redis中也已經(jīng)看到了路由的配置
6.寫(xiě)在最后
不可能所有的代碼拿過(guò)來(lái)就能用,每個(gè)人的理解也不盡相同,記錄在這里希望能提供一個(gè)思路,能解決到自己遇到的問(wèn)題,而不是希望大家看到后,說(shuō)拷貝過(guò)來(lái)的東西都是垃圾,你可以看,如果沒(méi)有幫助到你我也很遺憾。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot處理url中帶斜杠/\字符的參數(shù)報(bào)400問(wèn)題
這篇文章主要介紹了springboot處理url中帶斜杠/\字符的參數(shù)報(bào)400問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01解決Mybatis中mapper.xml文件update,delete及insert返回值問(wèn)題
這篇文章主要介紹了解決Mybatis中mapper.xml文件update,delete及insert返回值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11詳解Spring boot+CXF開(kāi)發(fā)WebService Demo
這篇文章主要介紹了詳解Spring boot+CXF開(kāi)發(fā)WebService Demo,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05SpringBoot通過(guò)注解注入Bean的幾種方式解析
這篇文章主要為大家介紹了SpringBoot注入Bean的幾種方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03SpringBoot基于過(guò)濾器和內(nèi)存實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能
這篇文章主要介紹了SpringBoot基于過(guò)濾器和內(nèi)存實(shí)現(xiàn)重復(fù)請(qǐng)求攔截,這里我們使用過(guò)濾器的方式對(duì)進(jìn)入服務(wù)器的請(qǐng)求進(jìn)行過(guò)濾操作,實(shí)現(xiàn)對(duì)相同客戶端請(qǐng)求同一個(gè)接口的過(guò)濾,需要的朋友可以參考下2023-01-01