上个月入职了新公司,发现中台网关路由配置是放在apollo上的,所以这次目的是设计一款动态路由配置的网关。
由于Gateway 的传统的通过配置中心及配置文件的方式配置路由略显得笨拙,通过api或者图形界面修改,则显得相对灵活的多。
通过重写 spring cloud gateway 实现API动态更改路由,非常便捷的实现控制请求接入管理。路由信息使用mysql作为持久化存储,初始化启动加载,redis作为临时存储并发布订阅监听。
将项目划分为以下几个模块
| 名称 | 描述 |
|---|---|
| dynroute-api | 一些公共的代码,常量,异常类等。 |
| dynroute-core | 测试应用功能模块 |
| dynroute-gateway | 网关应用,鉴权,负载均衡,路由转发等 |
| dynroute-server | 测试应用功能模块 |
-
创建数据库表用于保存路由配置
CREATE TABLE `gateway_route_config` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `route_name` varchar(30) DEFAULT NULL, `route_id` varchar(30) DEFAULT NULL, `predicates` json DEFAULT NULL COMMENT '断言', `filters` json DEFAULT NULL COMMENT '过滤器', `uri` varchar(50) DEFAULT NULL, `order` int(2) DEFAULT '0' COMMENT '排序', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `del_flag` char(1) DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='路由配置表';
-
初始化网关配置为空
/** * 配置文件设置为空 redis 加载为准 * @return */ @Bean public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator() { return new PropertiesRouteDefinitionLocator(new GatewayProperties()); }
-
初始化路由配置
public void initRoute() { redisTemplate.delete(CacheConstants.ROUTE_KEY); log.info("开始初始化网关路由"); gatewayRouteConfigService.list().forEach(route -> { RouteDefinitionVo vo = new RouteDefinitionVo(); vo.setRouteName(route.getRouteName()); vo.setId(route.getRouteId()); vo.setUri(URI.create(route.getUri())); vo.setOrder(route.getOrder()); JSONArray filterObj = JSONUtil.parseArray(route.getFilters()); vo.setFilters(filterObj.toList(FilterDefinition.class)); JSONArray predicateObj = JSONUtil.parseArray(route.getPredicates()); vo.setPredicates(predicateObj.toList(PredicateDefinition.class)); log.info("加载路由ID:{},{}", route.getRouteId(), vo); redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo); }); // 通知网关重置路由 redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "路由信息,网关缓存更新"); log.debug("初始化网关路由结束 "); }
-
设置监听路由配置,并监听订阅
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory); container.addMessageListener((message, bytes) -> { log.warn("接收到重新JVM 重新加载路由事件"); RouteCacheHolder.removeRouteList(); // 发送刷新路由事件 SpringContextHolder.publishEvent(new RefreshRoutesEvent(this)); }, new ChannelTopic(CacheConstants.ROUTE_JVM_RELOAD_TOPIC)); return container; }
- 路由配置查询及修改
/** * 路由配置查询及修改 * @param serverRequest * @return */ @SneakyThrows @Override public Mono<ServerResponse> handle(ServerRequest serverRequest) { HttpMethod method = serverRequest.method(); if (method == HttpMethod.GET) { return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(objectMapper.writeValueAsString(Response.ok(gatewayRouteConfigService.list())))); } else if (method == HttpMethod.PUT) { Mono<Response> routeStatus = serverRequest.bodyToMono(JSONArray.class).flatMap(updateRoutes()); BodyInserter bodyInserter = BodyInserters.fromPublisher(routeStatus, Response.class); return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(bodyInserter); } return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(objectMapper.writeValueAsString(Response.failed()))); }
从 Github 上下载源码方式
git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U ls -al distribution/target/ // change the $version to your actual path cd distribution/target/nacos-server-$version/nacos/bin //启动nacos startup.cmd -m standalone
INSERT INTO `gateway_route_config`(`id`,`route_name`,`route_id`,`predicates`,`filters`,`uri`,`order`,`create_time`,`update_time`,`del_flag`) VALUES (1,'动态网关测试','dynroute-server','[{\"args\": {\"_genkey_0\": \"/server/**\"}, \"name\": \"Path\"}]','[]','lb://dynroute-server',0,'2021年05月04日 16:44:41','2021年05月05日 04:48:51','0');
实例1配置: 在启动参数VM options 添加 -Dserver.port=9999
这个时候服务core服务是不通的。
新增网关路由配置
[
{
"routeId": "dynroute-server",
"routeName": "动态网关测试",
"predicates": [
{
"args": {
"_genkey_0": "/server/**"
},
"name": "Path"
}
],
"filters": [],
"uri": "lb://dynroute-server",
"order": 1,
"delFlag": "0"
},
{
"routeId": "dynroute-core",
"routeName": "动态网关测试",
"predicates": [
{
"args": {
"_genkey_0": "/core/**"
},
"name": "Path"
}
],
"filters": [],
"uri": "lb://dynroute-core",
"order": 2,
"delFlag": "0"
}
]新增路由配置后,则可访问