SpringCloud Gateway 利用 Mysql 实现动态路由

4,166 阅读3分钟

需求描述

标准网关动态路由功能是重要的一环,将路由、断言以及过滤器信息,持久化到 Mysql 中,通过配置后台页面实现路由、断言、以及过滤器等配置的增删改查。

Spring Cloud Gateway 路由及黑白名单实现背景

Spring Cloud 路由API

Spring Cloud Gateway 通过定义 RouteDefinitionRepository 来实现动态路由.

//保存路由缓存
public interface RouteDefinitionWriter {

   Mono<Void> save(Mono<RouteDefinition> route);

   Mono<Void> delete(Mono<String> routeId);

}
//获取路由缓存
public interface RouteDefinitionLocator {

   Flux<RouteDefinition> getRouteDefinitions();

}

Spring Cloud 配置文件路由加载方式

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {

   private final GatewayProperties properties;

   public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
      this.properties = properties;
   }

   @Override
   public Flux<RouteDefinition> getRouteDefinitions() {
      return Flux.fromIterable(this.properties.getRoutes());
   }

}

Spring Cloud 黑白名 FilterFactory

利用 Spring Cloud Gateway 声明的一个工厂接口 GatewayFilterFactory, 定义 黑白名单过滤器

BlacklistGatewayFilterFactory 类图

WhitelistGatewayFilterFactory 类图

动态路由设计

Spring Cloud Gateway 路由实体类

Spring Cloud Gateway 通过定义 RouteDefinition 类装载路由信息。

package org.springframework.cloud.gateway.route;

public class RouteDefinition {

   //路由 ID
   @NotEmpty
   private String id = UUID.randomUUID().toString();

   //断言数组
   @NotEmpty
   @Valid
   private List<PredicateDefinition> predicates = new ArrayList<>();

   //过滤器数组
   @Valid
   private List<FilterDefinition> filters = new ArrayList<>();

   // 路由地址
   @NotNull
   private URI uri;

   // 路由顺序
   private int order = 0;
}

数据库设计

路由表

drop table if exists gateway_route_t;

create table if not exists gateway_route_t
(
    ID          int auto_increment primary key,
    ROUTE_ID    varchar(255)  not null comment '路由ID',
    ROUTE_ORDER int default 0 null comment '路由顺序',
    URI         varchar(255)  not null comment '路由路径',
    VALID       int default 1 not null comment '是否有效:0-无效,1-有效',
    CREATE_USER varchar(200)  null comment '创建人',
    CREATE_TIME datetime      null comment '创建时间',
    UPDATE_USER varchar(200)  null comment '修改人',
    UPDATE_TIME datetime      null comment '修改时间',
    constraint idx_ROUTE_ID_index unique (ROUTE_ID)
) comment '网关路由信息表' charset = utf8;

路由参数表

drop table if exists gateway_route_param_t;

create table if not exists gateway_route_param_t
(
    ID          int auto_increment primary key,
    ROUTE_ID    varchar(255)  not null comment '路由ID',
    PARAM_NAME  varchar(255)  not null comment '参数name',
    PARAM_KEY   varchar(255)  not null comment '参数 key',
    PARAM_VALUE varchar(255)  not null comment '参数 value',
    TYPE        int           not null comment '参数类型,1为 predicate,2为过 filter',
    VALID       int default 1 not null comment '是否有效:0-无效,1-有效',
    CREATE_USER varchar(200)  null comment '创建人',
    CREATE_TIME datetime      null comment '创建时间',
    UPDATE_USER varchar(200)  null comment '修改人',
    UPDATE_TIME datetime      null comment '修改时间'
) comment '网关路由参数表' charset = utf8;

create index idx_route_id on gateway_route_param_t (ROUTE_ID);

接口设计

接口定义

  • 路由表配置接口

    封装 gateway_route_t dao 层接口.

    /**
     * <功能描述> 路由表接口
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IRouteConfigService extends IService<RouteDomain>
    
  • 路由参数表配置接口

    封装 gateway_route_param_t dao 层接口.

    /**
     * <功能描述> 路由参数表接口
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IRouteParamConfigService extends IService<RouteParamDomain>
    
  • 数据库路由服务接口

    封装 路由表配置服务接口以及路由参数表配置接口, 对外层提供对数据库路由信息的操作.

    /**
     * <功能描述> 数据库路由服务
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IRoutePropertiesService
    
  • 网关路由缓存接口

    封装 RouteDefinitionRepository 接口,对外提供对网关路由缓存的刷新.

    /**
     * <功能描述> 网关缓存路由服务
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IGatewayRouteService extends ApplicationEventPublisherAware
    
  • 路由事件监听接口

    配置需要监听路由变化的 service 实现

    /**
     * <功能描述> 路由事件监听接口
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface RouteEventListener extends ApplicationListener<RefreshCacheEvent>
    
  • 数据库黑白名单配置接口

    /**
     * <功能描述> API Filter 接口定义
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IApiFilterService
    
  • 网关白名单缓存接口

    提供指定路由 API 白名单check 监听路由事件

    /**
     * <功能描述> API 白名单缓存接口
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IApiCacheService extends RouteEventListener
    
  • 路由参数执行校验接口

    封装 提供路由参数的校验的接口.

    /**
     * <功能描述> 路由参数校验
     *
     * @author 20024322
     * @date 2020/12/24 13:20
     */
    public interface IRouteValidateExecutorService
    

接口类图

  • 路由及黑白名单类图

  • 断言及过滤器封装类图

  • 集群缓存刷新事件处理策略类图

路由初始化设计

重载 PropertiesRouteDefinitionLocator

/**
 * <功能描述> 重写 PropertiesRouteDefinitionLocator bean
 * 将配置文件中的路由信息通过 MysqlRouteConfig 载入。
 *
 * @author 20024322
 * @date 2020/12/24 13:20
 */
@Configuration
@AutoConfigureBefore({MysqlRouteConfig.class})
public class PropertiesRouteConfig {
    @Bean
    public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(
            GatewayProperties properties) {
        return new PropertiesRouteDefinitionLocator(new GatewayProperties());
    }
}

定义 initMysqlRouteDefinition Bean 加载数据库及配置文件的路由配置

/**
 * <功能描述> 从Mysql中初始化路由信息
 * 覆盖配置文件中的路由信息
 *
 * @author 20024322
 * @date 2020/12/24 13:20
 */
@Configuration
public class MysqlRouteConfig {
    private final IRoutePropertiesService routePropertiesService;
    private final IGatewayRouteService gatewayRouteService;

    public MysqlRouteConfig(IRoutePropertiesService routePropertiesService, IGatewayRouteService gatewayRouteService) {
        this.routePropertiesService = routePropertiesService;
        this.gatewayRouteService = gatewayRouteService;
    }

    /**
     * 初始化 gatewayProperties 中的 route
     *
     * @param gatewayProperties
     * @return
     */
    @Bean
    public List<RouteDefinition> initMysqlRouteDefinition(GatewayProperties gatewayProperties) {
        List<RouteDefinition> gatewayPropertiesRoutes = gatewayProperties.getRoutes();

        //初始化数据库路由信息
        List<RouteDefinition> routeDefinitionList = routePropertiesService.getRouteDefinitionList();
        if (CollectionUtils.isEmpty(gatewayProperties.getRoutes()) && CollectionUtils.isEmpty(routeDefinitionList)) {
            throw new BizBaseException(HprmcExceptionCode.ROUTE_NOT_FOUND);
        }

        Set<String> routeIds = routeDefinitionList.stream()
                .map(RouteDefinition::getId).collect(Collectors.toSet());
        if (gatewayPropertiesRoutes.stream().anyMatch(r -> routeIds.contains(r.getId()))) {
            throw new BizBaseException(HprmcExceptionCode.ROUTE_INIT_CONFLICT);
        }

        //将配置文件中的路由信息添加到 InMemoryRouteDefinitionRepository 成员变量中
        if (!CollectionUtils.isEmpty(gatewayPropertiesRoutes)) {
            gatewayPropertiesRoutes.forEach(gatewayRouteService::addInMemoryRouteRefresh);
        }

        //写到 InMemoryRouteDefinitionRepository 成员初始化缓存
        if (!CollectionUtils.isEmpty(routeDefinitionList)) {
            routeDefinitionList.forEach(gatewayRouteService::addInMemoryRouteRefresh);
        }
        return routeDefinitionList;
    }
}