上个月入职了新公司,发现中台网关路由配置是放在apollo上的,所以这次目的是设计一款动态路由配置的网关。
由于Gateway 的传统的通过配置中心及配置文件的方式配置路由略显得笨拙,通过api或者图形界面修改,则显得相对灵活的多。
通过重写 spring cloud gateway 实现API动态更改路由,非常便捷的实现控制请求接入管理。路由信息使用mysql作为持久化存储,初始化启动加载,redis作为临时存储并发布订阅监听。
将项目划分为以下几个模块
| 名称 | 描述 |
|---|---|
| dynroute-api | 一些公共的代码,常量,异常类等。 |
| dynroute-core | 测试应用功能模块 |
| dynroute-gateway | 网关应用,鉴权,负载均衡,路由转发等 |
| dynroute-server | 测试应用功能模块 |
创建数据库表用于保存路由配置
CREATETABLE `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 NULLONUPDATECURRENT_TIMESTAMP COMMENT '修改时间', `del_flag`char(1) DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='路由配置表';
初始化网关配置为空
/** * 配置文件设置为空 redis 加载为准 * @return */@BeanpublicPropertiesRouteDefinitionLocatorpropertiesRouteDefinitionLocator(){returnnewPropertiesRouteDefinitionLocator(newGatewayProperties())}
初始化路由配置
publicvoidinitRoute(){redisTemplate.delete(CacheConstants.ROUTE_KEY); log.info("开始初始化网关路由"); gatewayRouteConfigService.list().forEach(route ->{RouteDefinitionVovo = newRouteDefinitionVo(); vo.setRouteName(route.getRouteName()); vo.setId(route.getRouteId()); vo.setUri(URI.create(route.getUri())); vo.setOrder(route.getOrder()); JSONArrayfilterObj = JSONUtil.parseArray(route.getFilters()); vo.setFilters(filterObj.toList(FilterDefinition.class)); JSONArraypredicateObj = JSONUtil.parseArray(route.getPredicates()); vo.setPredicates(predicateObj.toList(PredicateDefinition.class)); log.info("加载路由ID:{},{}", route.getRouteId(), vo); redisTemplate.setHashValueSerializer(newJackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo)}); // 通知网关重置路由redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "路由信息,网关缓存更新"); log.debug("初始化网关路由结束 ")}
设置监听路由配置,并监听订阅
publicRedisMessageListenerContainerredisContainer(RedisConnectionFactoryredisConnectionFactory){RedisMessageListenerContainercontainer = newRedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory); container.addMessageListener((message, bytes) ->{log.warn("接收到重新JVM 重新加载路由事件"); RouteCacheHolder.removeRouteList(); // 发送刷新路由事件SpringContextHolder.publishEvent(newRefreshRoutesEvent(this))}, newChannelTopic(CacheConstants.ROUTE_JVM_RELOAD_TOPIC)); returncontainer}- 路由配置查询及修改
/** * 路由配置查询及修改 * @param serverRequest * @return */@SneakyThrows@OverridepublicMono<ServerResponse> handle(ServerRequestserverRequest){HttpMethodmethod = serverRequest.method(); if (method == HttpMethod.GET){returnServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(objectMapper.writeValueAsString(Response.ok(gatewayRouteConfigService.list()))))} elseif (method == HttpMethod.PUT){Mono<Response> routeStatus = serverRequest.bodyToMono(JSONArray.class).flatMap(updateRoutes()); BodyInserterbodyInserter = BodyInserters.fromPublisher(routeStatus, Response.class); returnServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(bodyInserter)} returnServerResponse.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 standaloneINSERT 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" } ]新增路由配置后,则可访问


