SpringCloudGateway简单的自定义路由加载

1,699 阅读2分钟

环境和版本

<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官方文档中也有提及。

RouteDefinitionLocator官方描述

网上各路大神也有使用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;

重写接口方法以及定义路由对象

  1. 定义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;
}
  1. 因为使用了mybatis plus,service层只是简单的增删改查。

  2. 重写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());
    }
}
  1. 增加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);
    }
}