环境和版本
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<spring-boot.version>2.4.11</spring-boot.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
<security.version>2.2.4.RELEASE</security.version>
<knife4j.version>3.0.3</knife4j.version>
<springfox.version>3.0.0</springfox.version>
<mybatis-plus.version>3.4.2</mybatis-plus.version>
实现思路
首先使用RouteDefinitionLocator实现,springcloud gateway官方文档中也有提及。
网上各路大神也有使用RouteDefinitionRepository实现的,但是有一丢丢复杂,而且小弟接触的项目中没有很花哨RouteDefinition,就只是简单设置Predicate和Filter,就像下面这样:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
实现方法
实现方式极其简单,重写接口的getRouteDefinitions()方法即可。我这里使用的时Mysql保存的自定义路由规则。
Mysql路由表结构
CREATE TABLE `gateway_route` (
`route_id` bigint NOT NULL COMMENT 'routeid',
`route_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '路由名称',
`route_uri` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '路由uri',
`pattern_mode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'Path' COMMENT '路由匹配规则',
`regexp_str` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '匹配表达式',
`enabled` tinyint DEFAULT '1' COMMENT '是否启用',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`route_id`),
UNIQUE KEY `gateway_route_route_uri_IDX` (`route_uri`) USING BTREE,
UNIQUE KEY `gateway_route_regexp_str_IDX` (`regexp_str`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_cs_0900_ai_ci;
重写接口方法以及定义路由对象
- 定义RouteInfo
@Data
@TableName("gateway_route")
public class RouteInfo {
@TableId(type = IdType.ASSIGN_ID)
private Long routeId;
/**
* 路由名称
*/
private String routeName;
/**
* 路由uri
*/
private String routeUri;
/**
* 匹配模式
*/
private String patternMode="Path";
/**
* 匹配表达式
*/
private String regexpStr;
/**
* 是否可用
*/
private Boolean enabled;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 修改时间
*/
private LocalDateTime updateTime;
}
/**
* 路由信息VO
*/
@Data
@ApiModel("路由信息VO")
public class RouteInoVO {
@ApiModelProperty("路由id")
private Long routeId;
@ApiModelProperty(value = "路由名称",example = "用户服务")
private String routeName;
@ApiModelProperty(value = "路由uri",example = "lb://user-service")
private String routeUri;
@ApiModelProperty(value = "匹配路径",example = "/user/**")
private String regexpStr;
}
-
因为使用了mybatis plus,service层只是简单的增删改查。
-
重写
RouteDefinitionLocator,然后实现ApplicationEventPublisherAware传入事件监听,用于手动刷新路由信息,gateway也会有定时刷新机制,貌似没法关闭。
/**
* 从Mysql数据库中读取路由信息,实现EventPublish
*/
@Slf4j
@Component
public class MysqlRouteLocator implements RouteDefinitionLocator, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
// 写死filter的规则
private static final String FILETER_MODE="StripPrefix=1";
@Resource
private RouteInfoService routeInfoService;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> allDefinitionList = getAllDefinitionList();
log.info("刷新路由列表:"+ JSONUtil.toJsonStr(allDefinitionList));
return Flux.fromIterable(allDefinitionList);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher=applicationEventPublisher;
}
public void refreshRoutes(){
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
}
private List<RouteDefinition> getAllDefinitionList() {
// 从service中查询到routeInfo
List<RouteInfo> list = routeInfoService.list();
if (ObjectUtil.isEmpty(list)) return new ArrayList<>();
return list.stream().map(info -> {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(String.valueOf(info.getRouteId()));
routeDefinition.setUri(URI.create(info.getRouteUri()));
PredicateDefinition predicateDefinition = new PredicateDefinition(info.getPatternMode() + "=" + info.getRegexpStr());
routeDefinition.setPredicates(Arrays.asList(predicateDefinition));
FilterDefinition filterDefinition = new FilterDefinition(FILETER_MODE);
routeDefinition.setFilters(Arrays.asList(filterDefinition));
return routeDefinition;
}).collect(Collectors.toList());
}
}
- 增加controller管理接口:
@Api(tags = "路由信息管理")
@RestController
@RequestMapping("/routeInfo")
public class RouteInfoController {
@Resource
private RouteInfoService routeInfoService;
@Resource
private MysqlRouteLocator mysqlRouteLocator;
@ApiOperation("列出所有路由信息")
@GetMapping
public R<List<RouteInoVO>> list(){
List<RouteInfo> list = routeInfoService.list();
if (!ObjectUtil.isEmpty(list)){
List<RouteInoVO> collect = list.stream().map(e -> {
RouteInoVO routeInoVO = new RouteInoVO();
BeanUtils.copyProperties(e, routeInoVO);
return routeInoVO;
}).collect(Collectors.toList());
return R.ok(collect);
}
return R.ok(null);
}
@ApiOperation("新增路由信息")
@PostMapping
public R<Boolean> add(@RequestBody RouteInoVO routeInfoVo){
RouteInfo routeIno = new RouteInfo();
BeanUtils.copyProperties(routeInfoVo,routeIno);
return R.ok(routeInfoService.saveOrUpdate(routeIno));
}
@ApiOperation("删除路由信息")
@ApiImplicitParam(name = "routeIds",value = "逗号分割的id字符串")
@DeleteMapping
public R<Boolean> remove(@RequestParam String routeIds){
List<Long> collect = Arrays.stream(StrUtil.split(routeIds, ",")).map(Long::parseLong).collect(Collectors.toList());
return R.ok(routeInfoService.removeByIds(collect));
}
@ApiOperation("手动触发刷新路由")
@GetMapping("/refresh")
public R<Boolean> refreshRoutes(){
mysqlRouteLocator.refreshRoutes();
return R.ok(true);
}
}