Spring Cloud Gateway(十三):基于MongoDB实现动态路由
1、概述
Spring Cloud Gateway 默认的RouteDefinitionWriter实现类是org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository,Route信息保存在当前实例的内存中,这在集群环境中会存在同步问题。本文就基于MongoD自定义一个RouteDefinitionWriter。
基本要点:
1、路由规则存储在MongoDB中(也可以是别的存储介质)
2、网关启动读取已配置好的路由规则
3、首次读取从MongoDB中加载,同时存在缓存中,以后读取直接从缓存读取
4、通过api动态添加路由规则,同时添加到缓存和MongoDB中
2、MongoRouteDefinition
import lombok.Data;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.ArrayList;
import java.util.List;
/**
* 存储在mongodb中的自定义路由信息
*
* @author lkf
* @date 2018-11-29 10-51
*/
@Data
public class MongoRouteDefinition {
/**
* 路由id
*/
@Field("route_id")
private String routeId;
/**
* 路由谓词
*/
private List<PredicateDefinition> predicates = new ArrayList<>();
/**
* 过滤器
*/
private List<FilterDefinition> filters = new ArrayList<>();
/**
* 跳转地址uri
*/
private String uri;
/**
* 路由顺序
*/
private int order;
}
3、自定义RouteDefinitionRepository
package com.lkf.gateway.repository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.data.mongodb.core.MongoTemplate;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.*;
import static java.util.Collections.synchronizedMap;
/**
* 使用Mongo保存自定义路由配置(代替默认的InMemoryRouteDefinitionRepository)
* <p/>
* 首次调用会把自定义路由配置信息加载到缓存中,以后的每次调用都从缓存返回
*
* @author lkf
* @date 2018-11-29 10-48
*/
public class MongoRouteDefinitionRepository implements RouteDefinitionRepository {
private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
private final MongoTemplate mongoTemplate;
private final String mongoCollection;
public MongoRouteDefinitionRepository(MongoTemplate mongoTemplate,String mongoCollection ) {
this.mongoTemplate = mongoTemplate;
this.mongoCollection = mongoCollection;
}
/**
* 获取自定义路由信息
*
* @author lkf
* @date 2018/11/29 12:00
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
//判断本地缓存是否为空,不为空直接返回
if (Objects.nonNull(routes) && routes.size() > 0) {
return getRoutes();
}
//从mongodb获取自定义路由信息
List<MongoRouteDefinition> mongoRouteDefinitionList = mongoTemplate.findAll(MongoRouteDefinition.class, mongoCollection);
List<RouteDefinition> routeDefinitions = new ArrayList<>();
//转换为路由定义对象并存入缓存中
mongoRouteDefinitionList.forEach(mongoRouteDefinition -> {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(mongoRouteDefinition.getRouteId());
routeDefinition.setFilters(mongoRouteDefinition.getFilters());
routeDefinition.setPredicates(mongoRouteDefinition.getPredicates());
routeDefinition.setUri(URI.create(mongoRouteDefinition.getUri()));
routeDefinition.setOrder(mongoRouteDefinition.getOrder());
routes.put(routeDefinition.getId(), routeDefinition);
});
return Flux.fromIterable(routeDefinitions);
}
/**
* 新增路由信息
*
* @param route 路由定义对象
* @return reactor.core.publisher.Mono<java.lang.Void>
* @author lkf
* @date 2018/11/29 13:08
*/
@Override
public Mono<Void> save( Mono<RouteDefinition> route ) {
return route.flatMap(routeDefinition -> {
MongoRouteDefinition mongoRouteDefinition = new MongoRouteDefinition();
mongoRouteDefinition.setRouteId(routeDefinition.getId());
mongoRouteDefinition.setPredicates(routeDefinition.getPredicates());
mongoRouteDefinition.setFilters(routeDefinition.getFilters());
mongoRouteDefinition.setUri(routeDefinition.getUri().toString());
mongoRouteDefinition.setOrder(routeDefinition.getOrder());
mongoTemplate.save(mongoRouteDefinition, mongoCollection);
routes.put(routeDefinition.getId(), routeDefinition);
return Mono.empty();
});
}
/**
* 删除路由信息
*
* @param routeId 路由id
* @return reactor.core.publisher.Mono<java.lang.Void>
* @author lkf
* @date 2018/11/29 13:09
*/
@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)));
});
}
private Flux<RouteDefinition> getRoutes() {
return Flux.fromIterable(routes.values());
}
}
4、自定义网关属性实体 CmpGatewayProperties
/**
* 自定义网关属性实体
*
* @author lkf
* @date 2018-11-29 13-13
*/
@Data
@ConfigurationProperties(prefix = "cmp.gateway.route")
public class CmpGatewayProperties {
private String collection;
}
5、添加配置
## MongoDB 连接地址
spring.data.mongodb.uri=mongodb://账号:密码@ip:37017/库名
## 存放路由规则的 collection
cmp.gateway.route.collection=gateway_route
6、验证
6.1、MongoDB 添加路由规则
{
"_id" : ObjectId("5bff4e9c12cc87ac0f888ddc"),
"route_id" : "service-001",
"predicates" : [
{
"name" : "Path",
"args" : {
"pattern" : "/user/**"
}
}
],
"filters" : [
{
"name" : "RewritePath",
"args" : {
"regexp" : "/user-service/(?<remaining>.*)",
"replacement" : "/${remaining}"
}
}
],
"uri" : "lb://user-service",
"order" : 0
}
6.2、api 获取路由信息
通过网关暴露的api地址:
http://localhost:9000/actuator/gateway/routes
{
"route_id": "service-001",
"route_definition": {
"id": "service-001",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/user/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/user-service/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"uri": "lb://user-service",
"order": 0
},
"order": 0
}
6.3、api 新增路由信息
http://localhost:9000/actuator/gateway/routes/service-002
curl -H "Content-Type:application/json" -X POST --data '{
"predicates" : [
{
"name" : "Path",
"args" : {
"pattern" : "/user/**"
}
}
],
"filters" : [
{
"name" : "RewritePath",
"args" : {
"regexp" : "/user-service/(?<remaining>.*)",
"replacement" : "/${remaining}"
}
}
],
"uri" : "lb://user-service",
"order" : 0
}' http://localhost:9000/actuator/gateway/routes/service-002
再次查询路由,结果如下,已经新增成功: