SpringCloudGateway路由定义存至Mysql数据库
SpringCloudGateway默认根据配置文件或代码在启动时路由定义存在内存中,如果要修改路由配置,需要改动配置文件或代码后重启网关,十分不灵活。
官方提供了RedisRouteDefinitionRepository可以将配置存放之Redis中,使用spring.cloud.gateway.redis-route-definition-repository.enabled开启。
可以实现RouteDefinitionRepository接口,将路由配置保存至Mysql中。
使用的SpringCloud版本为2021.0.5,文章编写于2022-12-12日。
ORM使用SpringDataR2DBC,相关教程请自行查找。
项目POM引入
org.springframework.boot spring-boot-starter-data-r2dbc com.github.jasync-sql jasync-r2dbc-mysql
路由定义实体
@Table(name = "`gateway_route_define`") public class GatewayRouteDefineEntity implements Serializable { private static final long serialVersionUID = 1L; @Id private String id; private String uri; private String predicates; private String filters; private String metadata; @Column("`is_enable`") private Boolean enable; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public String getPredicates() { return predicates; } public void setPredicates(String predicates) { this.predicates = predicates; } public String getFilters() { return filters; } public void setFilters(String filters) { this.filters = filters; } public String getMetadata() { return metadata; } public void setMetadata(String metadata) { this.metadata = metadata; } public Boolean isEnable() { return enable; } public void setEnable(Boolean enable) { this.enable = enable; } }
DAO层
@Repository public interface GatewayRouteDefineDao extends ReactiveCrudRepository{ Flux findAllByEnableTrue(); }
Service层
public interface GatewayRouteDefineService { FluxfindAll(); Mono save(@RequestBody @Validated GatewayRouteDefineEntity entity); Mono deleteById(@PathVariable("id") String id); Mono refreshRouteDefinition(); }
@Service public class GatewayRouteDefineServiceImpl implements GatewayRouteDefineService { private static final Logger log = LoggerFactory.getLogger(GatewayRouteDefineServiceImpl.class); private final GatewayRouteDefineDao gatewayRouteDefineDao; private final ApplicationEventPublisher publisher; public GatewayRouteDefineServiceImpl(GatewayRouteDefineDao gatewayRouteDefineDao, ApplicationEventPublisher publisher) { this.gatewayRouteDefineDao = gatewayRouteDefineDao; this.publisher = publisher; } @Override public FluxfindAll() { return gatewayRouteDefineDao.findAllByEnableTrue(); } @Override public Mono save(GatewayRouteDefineEntity entity) { return gatewayRouteDefineDao.save(entity); } @Override public Mono deleteById(String id) { return gatewayRouteDefineDao.deleteById(id); } @Override public Mono refreshRouteDefinition() { this.publisher.publishEvent(new RefreshRoutesEvent(this)); return Mono.empty(); } }
实现RouteDefinitionRepository
@Configuration public class RouteDefinitionConfiguration { @Bean public DbRouteDefinitionRepository dbRouteDefinitionRepository( GatewayRouteDefineService gatewayRouteDefineService) { return new DbRouteDefinitionRepository(gatewayRouteDefineService); } public static class DbRouteDefinitionRepository implements RouteDefinitionRepository { private final GatewayRouteDefineService gatewayRouteDefineService; public DbRouteDefinitionRepository(GatewayRouteDefineService gatewayRouteDefineService) { this.gatewayRouteDefineService = gatewayRouteDefineService; } @Override public FluxgetRouteDefinitions() { return gatewayRouteDefineService.findAll() .map(entity -> { try { return GatewayRouteDefineUtils.convert(entity); } catch (URISyntaxException | JsonProcessingException e) { throw new RuntimeException(e); } }); } @Override public Mono save(Mono route) { return route.flatMap(r -> { try { GatewayRouteDefineEntity entity = GatewayRouteDefineUtils.convert(r); return gatewayRouteDefineService.save(entity).then(); } catch (JsonProcessingException e) { return Mono.error(new RuntimeException(e)); } }); } @Override public Mono delete(Mono routeId) { return routeId.flatMap(gatewayRouteDefineService::deleteById); } } }
工具类
public class GatewayRouteDefineUtils { private static final ObjectMapper OBJECT_MAPPER; static { OBJECT_MAPPER = new ObjectMapper(); OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } public static RouteDefinition convert(GatewayRouteDefineEntity entity) throws URISyntaxException, JsonProcessingException { RouteDefinition definition = new RouteDefinition(); definition.setId(entity.getId()); definition.setUri(new URI(entity.getUri())); if (entity.getPredicates() != null) { ListpredicateDefinitions = OBJECT_MAPPER.readValue( entity.getPredicates(), new TypeReference >() { }); definition.setPredicates(predicateDefinitions); } if (entity.getFilters() != null) { List
filterDefinitions = OBJECT_MAPPER.readValue( entity.getFilters(), new TypeReference >() { }); definition.setFilters(filterDefinitions); } return definition; } public static GatewayRouteDefineEntity convert(RouteDefinition definition) throws JsonProcessingException { GatewayRouteDefineEntity entity = new GatewayRouteDefineEntity(); entity.setId(definition.getId()); entity.setUri(definition.getUri().toString()); entity.setPredicates(OBJECT_MAPPER.writeValueAsString(definition.getPredicates())); entity.setFilters(OBJECT_MAPPER.writeValueAsString(definition.getFilters())); return entity; } }
测试Controller
@RestController public class TestController { private static final Logger log = LoggerFactory.getLogger(TestController.class); private final GatewayRouteDefineService gatewayRouteDefineService; public TestController(GatewayRouteDefineService gatewayRouteDefineService) { this.gatewayRouteDefineService = gatewayRouteDefineService; } @RequestMapping("/refresh_route") public MonorefreshRoute() { return gatewayRouteDefineService.refreshRouteDefinition() .thenReturn("refresh success"); } }
数据库表
CREATE TABLE `gateway_route_define` ( `id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键', `uri` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '目的地址', `predicates` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '断言定义', `filters` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '过滤器定义', `metadata` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '扩展数据', `is_enable` tinyint(4) NULL DEFAULT NULL COMMENT '是否启用 0:否 1:是', `create_time` datetime(3) NULL DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '网关路由定义表' ROW_FORMAT = Dynamic;
插入一条测试数据
INSERT INTO `gateway_route_define` VALUES ('c707a482-77a3-11ed-a1eb-0242ac120002', 'https://www.baidu.com', '[{"name":"Path","args":{"pattern":"/baidu"}}]', '[{"name":"RewritePath","args":{"regexp":"/baidu/?(?.*)","replacement":"/$\\{segment}"}},{"name":"GuavaRateLimiter","args":{"limit":"5"}}]', '{}', 1, '2022-12-12 11:46:55.000');
其实数据库保存的字段就是RouteDefinition类中的字段,其中predicates、filters、metadata字段为JSON格式的数据。
路由定义序列化
Predicate序列化
其实就是对PredicateDefinition类进行序列化,包含name和args两个字段,其中args需要去org.springframework.cloud.gateway.handler.predicate路径下找具体的工厂类,里面有各参数的名称
例如
spring: cloud: gateway: routes: - id: path_route uri: https://example.org predicates: - Path=/red/{segment},/blue/{segment}
这条路由规则中的
Path=/red/{segment},/blue/{segment}
其实现类是PathRoutePredicateFactory,那么args参数就是由PathRoutePredicateFactory.Config序列化而来。其余predicate和filter都是按此规则,这样应该就能理解插入的测试数据中predicates和filters字段的含义了。
测试
启动网关,会自动去数据库加载路由定义,当数据库路由修改后,可以调用GatewayRouteDefineService#refreshRouteDefinition来刷新路由,其实原理就是触发一个RefreshRoutesEvent事件,SpringCloudGateway会重新加载路由。