SpringCloud GateWay動態(tài)路由用法
1.網(wǎng)關(guān)為什么需要動態(tài)路由?
網(wǎng)關(guān)的核心功能就是通過配置不同路由策略在配合注冊中心訪問不同的微服務(wù),而默認是在yaml文件中配置路由策略,而在項目上線后,網(wǎng)關(guān)作為所有項目的入口肯定不希望重啟,所以動態(tài)路由是必須的,我們在增加一個服務(wù),在不希望服務(wù)重新啟動的前提下能路由到該服務(wù),以及是基于代碼實現(xiàn)的網(wǎng)關(guān)動態(tài)路由
2.動態(tài)路由原理
public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter { }
RouteDefinitionRepository是網(wǎng)關(guān)路由的存儲接口,RouteDefinitionLocator 是獲取存儲中的所有路由,RouteDefinitionWriter主要操作路由的存儲和刪除。
public interface RouteDefinitionLocator { Flux<RouteDefinition> getRouteDefinitions(); }
public interface RouteDefinitionWriter { Mono<Void> save(Mono<RouteDefinition> route); Mono<Void> delete(Mono<String> routeId); }
而gateway中RouteDefinitionRepository接口的默認的實現(xiàn)是InMemoryRouteDefinitionRepository,即在內(nèi)存中存儲路由配置,而且在 GatewayAutoConfiguration 配置中也激活了InMemoryRouteDefinitionRepository這個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 中可見存儲路由的是一個帶同步鎖的LinkedHashMap,而存儲刪除都是基于這個map對象操作。
3.動態(tài)路由設(shè)計以及實現(xiàn)
- 方案一:知道動態(tài)路由的原理以后,我們可以基于redis設(shè)計一個InRedisRouteDefinitionRepository 實現(xiàn) RouteDefinitionRepository 接口即可,即網(wǎng)關(guān)部署多個也能動態(tài)解決路由問題
- 方案二:可以基于nacos 配置動態(tài)修改路由(理論上,待驗證)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<>()); } } }
暫時在網(wǎng)關(guān)中提供接口實現(xiàn)路由的動態(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; /** * 路由過濾器集合配置 */ private List<GatewayFilterDefinition> filters; /** * 路由規(guī)則轉(zhuǎn)發(fā)的目標uri */ private String uri; /** * 路由執(zhí)行的順序 */ private int order; }
@Data public class GatewayPredicateDefinition implements Serializable { /** * 斷言對應(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; /** * 對應(yīng)的路由規(guī)則 */ private Map<String, String> args = new LinkedHashMap<>(); }
業(yè)務(wù)層代碼 DynamicRouteService,最主要的是注入RouteDefinitionWriter 我們自己的實現(xià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) { // 判定當前路由以及路徑是否存在 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由于動態(tài)路由引發(fā)不展示的問題
聚合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.測試一下
swagger中目前只有這一個路由,調(diào)用路由新增一個
再次刷新swagger,OK 已經(jīng)看到新的路由了
redis中也已經(jīng)看到了路由的配置
6.寫在最后
不可能所有的代碼拿過來就能用,每個人的理解也不盡相同,記錄在這里希望能提供一個思路,能解決到自己遇到的問題,而不是希望大家看到后,說拷貝過來的東西都是垃圾,你可以看,如果沒有幫助到你我也很遺憾。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot處理url中帶斜杠/\字符的參數(shù)報400問題
這篇文章主要介紹了springboot處理url中帶斜杠/\字符的參數(shù)報400問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01解決Mybatis中mapper.xml文件update,delete及insert返回值問題
這篇文章主要介紹了解決Mybatis中mapper.xml文件update,delete及insert返回值問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11詳解Spring boot+CXF開發(fā)WebService Demo
這篇文章主要介紹了詳解Spring boot+CXF開發(fā)WebService Demo,非常具有實用價值,需要的朋友可以參考下2017-05-05SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復(fù)請求攔截功能
這篇文章主要介紹了SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復(fù)請求攔截,這里我們使用過濾器的方式對進入服務(wù)器的請求進行過濾操作,實現(xiàn)對相同客戶端請求同一個接口的過濾,需要的朋友可以參考下2023-01-01