要想上线一个微服务,有不想重新配置网关,那你一定要知道怎么配置gateway动态路由
前期准备
动态路由需要使用redis,所有配置文件需要有redis的配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
准备数据库和实体类
CREATE TABLE `gateway_routes` (
`id` bigint NOT NULL AUTO_INCREMENT,
`route_id` varchar(64) DEFAULT NULL COMMENT '路由id',
`route_uri` varchar(128) DEFAULT NULL COMMENT '转发目标uri',
`route_order` int DEFAULT '0' COMMENT '路由执行顺序',
`predicates` varchar(200) DEFAULT NULL COMMENT '访问路径',
`filters` varchar(100) DEFAULT NULL COMMENT '过滤',
`is_statistic` tinyint(1) DEFAULT '0' COMMENT '是否统计',
`is_billing` tinyint(1) DEFAULT '0' COMMENT '是否计费',
`is_ebl` tinyint(1) DEFAULT '0' COMMENT '是否启用',
`is_del` tinyint(1) DEFAULT '0' COMMENT '0未删,1删除',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='网关路由表';
@ApiModel(value="GatewayRoutes")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "gateway_routes")
public class GatewayRoutes implements Serializable {
@TableId(value = "id", type = IdType.INPUT)
@ApiModelProperty(value="id")
private Long id;
/**
* 路由id
*/
@TableField(value = "route_id")
@ApiModelProperty(value="路由id")
private String routeId;
/**
* 转发目标uri
*/
@TableField(value = "route_uri")
@ApiModelProperty(value="转发目标uri")
private String routeUri;
/**
* 路由执行顺序
*/
@TableField(value = "route_order")
@ApiModelProperty(value="路由执行顺序")
private Integer routeOrder;
/**
* 访问路径
*/
@TableField(value = "predicates")
@ApiModelProperty(value="访问路径")
private String predicates;
/**
* 过滤
*/
@TableField(value = "filters")
@ApiModelProperty(value="过滤")
private String filters;
/**
* 是否统计
*/
@TableField(value = "is_statistic")
@ApiModelProperty(value="是否统计")
private Boolean isStatistic;
/**
* 是否计费
*/
@TableField(value = "is_billing")
@ApiModelProperty(value="是否计费")
private Boolean isBilling;
/**
* 是否启用
*/
@TableField(value = "is_ebl")
@ApiModelProperty(value="是否启用")
private Boolean isEbl;
/**
* 0未删,1删除
*/
@TableField(value = "is_del")
@ApiModelProperty(value="0未删,1删除")
private Boolean isDeleted;
/**
* 创建时间
*/
@TableField(value = "create_time")
@ApiModelProperty(value="创建时间")
private Date createTime;
/**
* 更新时间
*/
@TableField(value = "update_time")
@ApiModelProperty(value="更新时间")
private Date updateTime;
}
初始化和加载路由
@Service
@Slf4j
public class GatewayServiceHandler implements ApplicationEventPublisherAware, CommandLineRunner {
@Autowired
private RedisRouteDefinitionRepository routeDefinitionWriter;
private ApplicationEventPublisher publisher;
private static final String BASE_COLUMN = "id,route_id as routeId,route_uri as routeUri,route_order as routeOrder ,predicates,filters,is_statistic as isStatistic,is_billing as isBilling,is_ebl as isEbl,is_deleted as isDel ,create_time as createTime,update_time as updateTime ";
private static final String QUERY_GATEWAY_ROUTES = "select "+BASE_COLUMN+" from stmobile_gateway_routes where is_deleted = 0";
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* springboot启动后执行 相当于@PostConstruct
* @param args
*/
@Override
public void run(String... args) {
this.loadRouteConfig();
}
/**
* 将数据库的路由配置加载到redis,并刷新路由
*/
public void loadRouteConfig() {
log.info("====开始加载=====网关配置信息=========");
// 1. 首先删除redis里面已经存在的路由配置信息
routeDefinitionWriter.deleteAll();
// 2. 从数据库拿到基本路由配置
List<Map<String, Object>> maps = jdbcTemplate.queryForList(QUERY_GATEWAY_ROUTES);
List<GatewayRoutes> gatewayRouteList = maps.stream().map(stringObjectMap -> {
GatewayRoutes gatewayRoutes = new GatewayRoutes();
try {
BeanUtils.populate(gatewayRoutes, stringObjectMap);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return gatewayRoutes;
}).collect(Collectors.toList());
log.info("数据库网关配置信息:=====>" + JSON.toJSONString(gatewayRouteList));
// 3. 将数据库读取的路由配置,转换为RouteDefinition,并保存到redis
gatewayRouteList.forEach(gatewayRoute -> {
RouteDefinition definition=handleData(gatewayRoute);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
});
// 4. 刷新路由
this.publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("=======网关配置信息===加载完成======");
}
/**
* 保存路由,并刷新路由信息
* @param gatewayRoute
*/
public void saveRoute(GatewayRoutes gatewayRoute){
RouteDefinition definition = handleData(gatewayRoute);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 更新路由,并刷新路由信息
* @param gatewayRoute
*/
public void updateRoute(GatewayRoutes gatewayRoute) {
RouteDefinition definition = handleData(gatewayRoute);
routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 删除路由,并刷新路由信息
* @param routeId
*/
public void deleteRoute(String routeId) {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 路由数据转换的公共方法
* @param gatewayRoute
* @return
*/
private RouteDefinition handleData(GatewayRoutes gatewayRoute){
RouteDefinition definition = new RouteDefinition();
Map<String, String> predicateParams = new HashMap<>(8);
PredicateDefinition predicate = new PredicateDefinition();
FilterDefinition filterDefinition = new FilterDefinition();
Map<String, String> filterParams = new HashMap<>(8);
URI uri = null;
if(gatewayRoute.getRouteUri().startsWith("http")){
//http地址
uri = UriComponentsBuilder.fromHttpUrl(gatewayRoute.getRouteUri()).build().toUri();
}else{
//注册中心
uri = URI.create(gatewayRoute.getRouteUri());
}
// 使用routeId作为definition的id
definition.setId(gatewayRoute.getRouteId());
// 使用数据库的id字段
// definition.setId(gatewayRoute.getId().toString());
// 名称是固定的,spring gateway会根据名称找对应的PredicateFactory
predicate.setName("Path");
predicateParams.put("pattern",gatewayRoute.getPredicates());
predicate.setArgs(predicateParams);
// 名称是固定的, 路径去前缀
filterDefinition.setName("StripPrefix");
filterParams.put("_genkey_0", gatewayRoute.getFilters().toString());
filterDefinition.setArgs(filterParams);
definition.setPredicates(Arrays.asList(predicate));
definition.setFilters(Arrays.asList(filterDefinition));
definition.setUri(uri);
return definition;
}
}
使用redis保存路由
@Repository
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
public static final String GATEWAY_ROUTES = "gateway:routes";
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> routeDefinitions = new ArrayList<>();
redisTemplate.opsForHash().values(GATEWAY_ROUTES).forEach(routeDefinition -> {
routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class));
});
return Flux.fromIterable(routeDefinitions);
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route
.flatMap(routeDefinition -> {
redisTemplate.opsForHash().put(GATEWAY_ROUTES, routeDefinition.getId(),
JSON.toJSONString(routeDefinition));
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTES, id)) {
redisTemplate.opsForHash().delete(GATEWAY_ROUTES, id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("路由文件没有找到: " + routeId)));
});
}
/**
* 清空redis中的路由信息
*/
public void deleteAll(){
redisTemplate.delete(GATEWAY_ROUTES);
}
}
到此为止,复制上面的代码就可以实现一个gateway的动态路由了,还等什么,赶紧测试一下吧。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
上面的代码只是可以将路由保存在redis中,从而加载到gateway的路由里面,要想动态配置,则可以使用消息中间件的方式。
思路:对路由表增删改的时候,对成功操作之后发送一个消息到mq,然后网关这边监听消息,对于不同的消息做出不同的业务。这样就可以在页面操作了。
注意:配置路由的时候,要确保服务可用,所有在保存服务或者修改服务的时候建议检查一下服务在注册中心上是否存在,否则会出现问题,如果出现问题可以试着删除一下redis里面的数据。