SpringCloudGateway默认根据配置文件或代码在启动时路由定义存在内存中,如果要修改路由配置,需要改动配置文件或代码后重启网关,十分不灵活。
官方提供了RedisRouteDefinitionRepository可以将配置存放之Redis中,使用spring.cloud.gateway.redis-route-definition-repository.enabled开启。
可以实现RouteDefinitionRepository接口,将路由配置保存至Mysql中。
使用的SpringCloud版本为2021.0.5
ORM使用SpringDataR2DBC,相关教程请自行查找。
项目POM引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>com.github.jasync-sql</groupId>
<artifactId>jasync-r2dbc-mysql</artifactId>
</dependency>
路由定义实体
@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<GatewayRouteDefineEntity, String> {
/**
* 查询所有启用的路由定义
* @return 路由定义列表
*/
Flux<GatewayRouteDefineEntity> findAllByEnableTrue();
}
Service层
public interface GatewayRouteDefineService {
/**
* 查询所有实体
* @return 路由实体列表
*/
Flux<GatewayRouteDefineEntity> findAll();
/**
* 保存
* @param entity 路由实体
* @return
*/
Mono<GatewayRouteDefineEntity> save(@RequestBody @Validated GatewayRouteDefineEntity entity);
/**
* 根据id删除实体
* @param id 实体id
* @return
*/
Mono<Void> deleteById(@PathVariable("id") String id);
/**
* 刷新路由定义
* @return
*/
Mono<Void> 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 Flux<GatewayRouteDefineEntity> findAll() {
return gatewayRouteDefineDao.findAllByEnableTrue();
}
@Override
public Mono<GatewayRouteDefineEntity> save(GatewayRouteDefineEntity entity) {
return gatewayRouteDefineDao.save(entity);
}
@Override
public Mono<Void> deleteById(String id) {
return gatewayRouteDefineDao.deleteById(id);
} @Override
public Mono<Void> 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 Flux<RouteDefinition> getRouteDefinitions() {
return gatewayRouteDefineService.findAll()
.map(entity -> {
try {
return GatewayRouteDefineUtils.convert(entity);
} catch (URISyntaxException | JsonProcessingException e) {
throw new RuntimeException(e);
}
});
}
@Override
public Mono<Void> save(Mono<RouteDefinition> 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<Void> delete(Mono<String> 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) {
List<PredicateDefinition> predicateDefinitions = OBJECT_MAPPER.readValue(
entity.getPredicates(),
new TypeReference<List<PredicateDefinition>>() { });
definition.setPredicates(predicateDefinitions);
}
if (entity.getFilters() != null) {
List<FilterDefinition> filterDefinitions = OBJECT_MAPPER.readValue(
entity.getFilters(),
new TypeReference<List<FilterDefinition>>() { });
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 Mono<String> refreshRoute() {
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/?(?<segment>.*)\",\"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会重新加载路由。
————————————————
原文链接:https://blog.csdn.net/Hugh_W/article/details/128295105