SpringCloud GateWay動態(tài)路由用法
1.網(wǎng)關為什么需要動態(tài)路由?
網(wǎng)關的核心功能就是通過配置不同路由策略在配合注冊中心訪問不同的微服務,而默認是在yaml文件中配置路由策略,而在項目上線后,網(wǎng)關作為所有項目的入口肯定不希望重啟,所以動態(tài)路由是必須的,我們在增加一個服務,在不希望服務重新啟動的前提下能路由到該服務,以及是基于代碼實現(xiàn)的網(wǎng)關動態(tài)路由
2.動態(tài)路由原理
public interface RouteDefinitionRepository
extends RouteDefinitionLocator, RouteDefinitionWriter {
}RouteDefinitionRepository是網(wǎng)關路由的存儲接口,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,即在內存中存儲路由配置,而且在 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)路由設計以及實現(xiàn)
- 方案一:知道動態(tài)路由的原理以后,我們可以基于redis設計一個InRedisRouteDefinitionRepository 實現(xiàn) RouteDefinitionRepository 接口即可,即網(wǎng)關部署多個也能動態(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)關中提供接口實現(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ī)則轉發(fā)的目標uri
*/
private String uri;
/**
* 路由執(zhí)行的順序
*/
private int order;
}@Data
public class GatewayPredicateDefinition implements Serializable {
/**
* 斷言對應的Name
*/
private String name;
/**
* 配置的斷言規(guī)則
*/
private Map<String, String> args = new LinkedHashMap<>();
}@Data
public class GatewayFilterDefinition implements Serializable {
/**
* Filter Name
*/
private String name;
/**
* 對應的路由規(guī)則
*/
private Map<String, String> args = new LinkedHashMap<>();
}業(yè)務層代碼 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), "路由已經存在");
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)關中聚合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中目前只有這一個路由,調用路由新增一個

再次刷新swagger,OK 已經看到新的路由了

redis中也已經看到了路由的配置

6.寫在最后
不可能所有的代碼拿過來就能用,每個人的理解也不盡相同,記錄在這里希望能提供一個思路,能解決到自己遇到的問題,而不是希望大家看到后,說拷貝過來的東西都是垃圾,你可以看,如果沒有幫助到你我也很遺憾。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot多數(shù)據(jù)源切換實現(xiàn)代碼(Mybaitis)
實際工作中我們會遇到springboot項目初始化啟動時候,不能指定具體連接哪個數(shù)據(jù)源的時候,不同的接口連接不同的數(shù)據(jù)源或者前端頁面指定連接某個數(shù)據(jù)源等等情況,就會遇到動態(tài)數(shù)據(jù)源切換的問題,需要的朋友可以參考下2022-04-04
SpringBoot整合RocketMQ批量發(fā)送消息的實現(xiàn)代碼
這篇文章主要介紹了SpringBoot整合RocketMQ批量發(fā)送消息的實現(xiàn),文中通過代碼示例講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-04-04
基于bufferedreader的read()與readline()讀取出錯原因及解決
這篇文章主要介紹了bufferedreader的read()與readline()讀取出錯原因及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
RabbitMQ消息隊列中的Channel信道參數(shù)詳解
這篇文章主要介紹了RabbitMQ消息隊列中的Channel信道參數(shù)詳解,信道是生產消費者與rabbit通信的渠道,生產者publish或者消費者消費一個隊列都是需要通過信道來通信的,信道是建立在TCP上面的虛擬鏈接,需要的朋友可以參考下2023-08-08
詳細聊聊SpringBoot中動態(tài)切換數(shù)據(jù)源的方法
在大型分布式項目中,經常會出現(xiàn)多數(shù)據(jù)源的情況,下面這篇文章主要給大家介紹了關于SpringBoot中動態(tài)切換數(shù)據(jù)源的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-09-09

