SpringCloud Gateway动态路由 MySQL + Redis | Eddie'Blog
SpringCloud Gateway动态路由 MySQL + Redis

SpringCloud Gateway动态路由 MySQL + Redis

eddie 361 2021-09-06

目录

  • 建议
    • Redis集群模式,或者来多一个二级缓存
    • 暴露的接口,建议只能内网访问

准备数据表

DDL 点击查看
-- auto-generated definition
drop table if exists route;
create table route
(
    id          varchar(64)      not null comment '主键'
        primary key,
    app_id      int              not null comment '应用ID',
    app_name    varchar(64)      not null comment '应用名字',
    target_url  varchar(128)     null comment '目标地址',
    seq    int    default 0 not null comment '执行顺序',
    create_user varchar(64)      not null comment '创建时间',
    update_user varchar(64)      null,
    create_time datetime         null,
    update_time datetime         null,
    deleted     int(1) default 0 not null comment '是否删除(1是0否)'
)
    comment '网关主表';

create index idx_app_id
    on route (app_id);
-- auto-generated definition
drop table if exists route_filter;
create table route_filter
(
    id          bigint auto_increment comment '主键ID'
        primary key,
    route_id    varchar(64) not null,
    filter_name varchar(64) not null comment '过滤器名字',
    deleted     int(1)      null comment '是否删除'
)
    comment '网关过滤器';

create index idx_route_id
    on route_filter (route_id);

-- auto-generated definition
drop table if exists route_filter_args;
create table route_filter_args
(
    id        bigint auto_increment
        primary key,
    filter_id bigint      not null,
    `key`     varchar(64) not null,
    value     varchar(64) null,
    deleted   int(1)      null
);

create index idx_filter_id
    on route_filter_args (filter_id);

-- auto-generated definition
drop table if exists route_metadata;
create table route_metadata
(
    id          bigint auto_increment comment '主键ID'
        primary key,
    route_id    varchar(64)      not null,
    `key`       varchar(64)      not null comment '元数据key',
    value       varchar(128)     null comment '元数据值',
    create_user varchar(64)      null,
    update_user varchar(64)      null,
    create_time datetime         null,
    update_time datetime         null,
    deleted     int(1) default 0 not null comment '是否删除(1是0否)'
)
    comment '网关元数据表';

create index idx_route_id
    on route_metadata (route_id);

-- auto-generated definition
drop table if exists route_predicate;
create table route_predicate
(
    id             bigint auto_increment
        primary key,
    route_id       varchar(64)      not null,
    predicate_name varchar(64)      not null comment '断言名字',
    update_time    datetime         null,
    create_user    varchar(64)      null,
    update_user    varchar(64)      null,
    create_time    datetime         null,
    deleted        int(1) default 0 not null
)
    comment '网关断言';

create index idx_route_id
    on route_predicate (route_id);

-- auto-generated definition
drop table if exists route_predicate_args;
create table route_predicate_args
(
    id           bigint auto_increment
        primary key,
    predicate_id bigint           not null,
    `key`        varchar(64)      null,
    value        varchar(128)     null,
    update_user  varchar(64)      null,
    create_user  varchar(64)      null,
    update_time  datetime         null,
    create_time  datetime         null,
    deleted      int(1) default 0 not null
);

create index idx_predicate_id
    on route_predicate_args (predicate_id);


--

INSERT INTO `gateway_center`.`route`(`id`, `app_id`, `app_name`, `target_url`, `seq`, `create_user`, `update_user`, `create_time`, `update_time`, `deleted`) VALUES ('user_route', 1, 'app1', 'lb://user-center', 0, 'eddie', 'eddie', '2020-07-02 13:11:31', '2020-07-02 13:11:33', 0);
INSERT INTO `gateway_center`.`route`(`id`, `app_id`, `app_name`, `target_url`, `seq`, `create_user`, `update_user`, `create_time`, `update_time`, `deleted`) VALUES ('myRoute', 1, 'app1', 'https://www.baidu.com', 0, 'eddie', 'eddie', '2020-07-02 13:13:23', '2020-07-02 13:13:24', 0);

INSERT INTO `gateway_center`.`route_filter`(`id`, `route_id`, `filter_name`, `deleted`) VALUES (1, 'user_route', 'AddRequestParameter', 0);
INSERT INTO `gateway_center`.`route_filter`(`id`, `route_id`, `filter_name`, `deleted`) VALUES (2, 'before_route', 'AddRequestHeader', 0);

INSERT INTO `gateway_center`.`route_filter_args`(`id`, `filter_id`, `key`, `value`, `deleted`) VALUES (1, 1, '_genkey_0', 'header', 0);
INSERT INTO `gateway_center`.`route_filter_args`(`id`, `filter_id`, `key`, `value`, `deleted`) VALUES (2, 1, '_genkey_1', 'addHeader', 0);
INSERT INTO `gateway_center`.`route_filter_args`(`id`, `filter_id`, `key`, `value`, `deleted`) VALUES (3, 2, '_genkey_0', 'param', 0);
INSERT INTO `gateway_center`.`route_filter_args`(`id`, `filter_id`, `key`, `value`, `deleted`) VALUES (4, 2, '_genkey_1', 'addParam', 0);

INSERT INTO `gateway_center`.`route_predicate`(`id`, `route_id`, `predicate_name`, `update_time`, `create_user`, `update_user`, `create_time`, `deleted`) VALUES (1, 'user_route', 'Path', '2020-07-02 13:14:11', 'eddie', 'eddie', '2020-07-02 13:14:22', 0);
INSERT INTO `gateway_center`.`route_predicate`(`id`, `route_id`, `predicate_name`, `update_time`, `create_user`, `update_user`, `create_time`, `deleted`) VALUES (2, 'myRoute', 'Path', '2020-07-02 13:14:11', 'eddie', 'eddie', '2020-07-02 13:14:22', 0);

INSERT INTO `gateway_center`.`route_predicate_args`(`id`, `predicate_id`, `key`, `value`, `update_user`, `create_user`, `update_time`, `create_time`, `deleted`) VALUES (1, 1, 'pattern', '/users/**', 'eddie', 'eddie', '2020-07-02 13:20:56', '2020-07-02 13:20:57', 0);
INSERT INTO `gateway_center`.`route_predicate_args`(`id`, `predicate_id`, `key`, `value`, `update_user`, `create_user`, `update_time`, `create_time`, `deleted`) VALUES (2, 2, 'pattern', '/home', 'eddie', 'eddie', '2020-07-02 15:06:58', '2020-07-02 15:06:59', 0);



元数据,Spring Cloud Hoxton 之前的版本是没有的

Maven 依赖 (主要部分)

<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-gateway</artifactId>
	</dependency>
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>tk.mybatis</groupId>
		<artifactId>mapper-spring-boot-starter</artifactId>
		<version>2.1.5</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
	</dependency>
	<dependency>
		<groupId>com.google.code.gson</groupId>
		<artifactId>gson</artifactId>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-alibaba-dependencies</artifactId>
			<version>2.2.1.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

配置暴露健康

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

http://localhost:8040/actuator/gateway/routes

操作数据

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {

    private final RouteMapper routeMapper;

    private final RouteMetadataMapper routeMetadataMapper;

    private final RouteFilterMapper routeFilterMapper;

    private final RouteFilterArgsMapper routeFilterArgsMapper;

    private final RoutePredicateMapper predicateMapper;

    private final RoutePredicateArgsMapper routePredicateArgsMapper;

    private final StringRedisTemplate stringRedisTemplate;

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @Override
    public int addRoute(Route route) {
        //插入路由
        int result = routeMapper.insert(route);
        //插入过滤器
        List<RouteFilter> filters = route.getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (RouteFilter routeFilter : filters) {
                routeFilterMapper.insert(routeFilter);
                List<RouteFilterArgs> filterArgs = routeFilter.getFilterArgs();
                for (RouteFilterArgs routeFilterArgs : filterArgs) {
                    routeFilterArgsMapper.insert(routeFilterArgs);
                }
            }
        }
        //插入元数据
        List<RouteMetadata> metadatas = route.getMetadatas();
        if (!CollectionUtils.isEmpty(metadatas)) {
            for (RouteMetadata routeMetadata : metadatas) {
                routeMetadataMapper.insert(routeMetadata);
            }
        }
        //插入断言
        List<RoutePredicate> predicates = route.getPredicates();
        if (!CollectionUtils.isEmpty(predicates)) {
            for (RoutePredicate routePredicate : predicates) {
                predicateMapper.insert(routePredicate);
                List<RoutePredicateArgs> predicateArgs = routePredicate.getPredicateArgs();
                for (RoutePredicateArgs routePredicateArgs : predicateArgs) {
                    routePredicateArgsMapper.insert(routePredicateArgs);
                }
            }
        }
        stringRedisTemplate.opsForValue().set(GATEWAY_ROUTES, new Gson().toJson(routeList()));
        refresh();
        return result;
    }

    @Override
    public int deleteRoute(Route route) {
        int result = routeMapper.deleteRouteById(route);
        stringRedisTemplate.opsForValue().set(GATEWAY_ROUTES, new Gson().toJson(routeList()));
        refresh();
        return result;
    }

    @Override
    public int deleteRoute() {
        int result = routeMapper.remove();
        stringRedisTemplate.delete(GATEWAY_ROUTES);
        refresh();
        return result;
    }

    @Override
    public void refresh() {
        stringRedisTemplate.delete(GATEWAY_ROUTES);
        stringRedisTemplate.opsForValue().set(GATEWAY_ROUTES, new Gson().toJson(routeList()));
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public List<Route> routeList() {
        try {
            String routeStr = stringRedisTemplate.opsForValue().get(GATEWAY_ROUTES);
            if (!StringUtils.isEmpty(routeStr)) {
                return new Gson().fromJson(routeStr, new TypeToken<List<Route>>() {
                }.getType());
            }
        } catch (Exception e) {
            log.error("[Gateway-1] 从 Redis 获取路由错误:", e);
        }

        List<Route> routes = routeMapper.findAll();
        if (!CollectionUtils.isEmpty(routes)) {
            for (Route route : routes) {
                route.setMetadatas(routeMetadataMapper.selectByRouteId(route.getId()));
                route.setFilters(getRouteFilters(route.getId()));
                route.setPredicates(getRoutePredicates(route.getId()));
            }
        }

        try {
            stringRedisTemplate.opsForValue().set(GATEWAY_ROUTES, new Gson().toJson(routes));
        } catch (Exception e) {
            log.error("[Gateway-2] 从 Redis 获取路由错误:", e);
        }

        return routes;
    }

    /**
     * 查询过滤器数据
     *
     * @param routeId
     * @return
     */
    private List<RouteFilter> getRouteFilters(String routeId) {
        List<RouteFilter> fileters = routeFilterMapper.selectByRouteId(routeId);
        if (!CollectionUtils.isEmpty(fileters)) {
            for (RouteFilter routeFilter : fileters) {
                // 查询过滤器参数集合
                routeFilter.setFilterArgs(routeFilterArgsMapper.selectByFilterId(routeFilter.getId()));
            }
        }
        return fileters;
    }

    /**
     * 查询断言列表
     *
     * @param routeId
     * @return
     */
    private List<RoutePredicate> getRoutePredicates(String routeId) {
        List<RoutePredicate> predicateList = predicateMapper.selectByRouteId(routeId);
        for (RoutePredicate routePredicate : predicateList) {
            // 查询断言参数列表
            routePredicate.setPredicateArgs(routePredicateArgsMapper.selectByPredicateId(routePredicate.getId()));
        }
        return predicateList;

    }
}

配置获取路由信息

@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private final RouteService routeService;

    private static final String HTTP = "http";

    private static final String HTTPS = "https";

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return null;
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return null;
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        log.info("[gateway] change gate start");
        List<RouteDefinition> definitions = getDefinitions();
        log.info("[gateway] change gate success:{}", new Gson().toJson(definitions));
        return Flux.fromIterable(definitions);
    }

    /**
     * 获取网关配置
     *
     * @return
     */
    private List<RouteDefinition> getDefinitions() {

        List<Route> routes = routeService.routeList();
        List<RouteDefinition> routeDefinitions = new ArrayList<>(routes.size());
        for (Route route : routes) {
            RouteDefinition routeDefinition = new RouteDefinition();
            routeDefinition.setId(route.getId());
            routeDefinition.setOrder(route.getSeq());
            routeDefinition.setMetadata(getMetadata(route.getMetadatas()));
            routeDefinition.setFilters(getFilters(route.getFilters()));
            routeDefinition.setPredicates(getPredicates(route.getPredicates()));
            routeDefinition.setUri(getUri(route.getTargetUrl()));
            routeDefinitions.add(routeDefinition);
        }
        return routeDefinitions;
    }

    /**
     * 获取断言列表
     *
     * @param predicates
     * @return
     */
    private List<PredicateDefinition> getPredicates(List<RoutePredicate> predicates) {
        List<PredicateDefinition> definitions = new ArrayList<>(predicates.size());
        for (RoutePredicate routePredicate : predicates) {
            PredicateDefinition definition = new PredicateDefinition();
            definition.setArgs(getPredicateArg(routePredicate.getPredicateArgs()));
            definition.setName(routePredicate.getPredicateName());
            definitions.add(definition);
        }
        return definitions;
    }

    /**
     * 获取断言参数
     *
     * @param predicateArgs
     * @return
     */
    private Map<String, String> getPredicateArg(List<RoutePredicateArgs> predicateArgs) {
        Map<String, String> map = new HashMap<>();
        for (RoutePredicateArgs routePredicateArg : predicateArgs) {
            map.put(routePredicateArg.getKey(), routePredicateArg.getValue());
        }
        return map;
    }

    /**
     * 获取过滤器
     *
     * @param filters
     * @return
     */
    private List<FilterDefinition> getFilters(List<RouteFilter> filters) {
        List<FilterDefinition> definitions = new ArrayList<>(filters.size());
        for (RouteFilter routeFilter : filters) {
            FilterDefinition definition = new FilterDefinition();
            definition.setName(routeFilter.getFilterName());
            definition.setArgs(getFilterArgs(routeFilter.getFilterArgs()));
            definitions.add(definition);
        }
        return definitions;
    }

    /**
     * 获取过滤器参数
     *
     * @param filterArgs
     * @return
     */
    private Map<String, String> getFilterArgs(List<RouteFilterArgs> filterArgs) {
        Map<String, String> map = new HashMap<>();
        for (RouteFilterArgs routeFilterArg : filterArgs) {
            map.put(routeFilterArg.getKey(), routeFilterArg.getValue());
        }
        return map;
    }

    /**
     * 获取地址uri
     *
     * @param url
     * @return
     */
    private URI getUri(String url) {
        if (url.startsWith(HTTP) || url.startsWith(HTTPS)) {
            return UriComponentsBuilder.fromHttpUrl(url).build().toUri();
        }
        return UriComponentsBuilder.fromPath(url).build().toUri();
    }

    /**
     * 获取元数据map
     *
     * @param metadatas
     * @return
     */
    private Map<String, Object> getMetadata(List<RouteMetadata> metadatas) {
        Map<String, Object> map = new HashMap<>();
        for (RouteMetadata routeMetadata : metadatas) {
            map.put(routeMetadata.getKey(), routeMetadata.getValue());
        }
        return map;
    }
}

暴露控制层接口

建议是在Nginx不屏蔽了,或者其他安全手段。

@RestController
@RequestMapping("/route")
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class RouteController {

    private final RouteService routeService;

    @RequestMapping("refresh")
    public String refreshRoute() {
        routeService.refresh();
        return "succee";
    }

    @GetMapping("/list")
    public List<Route> routeList() {
        return routeService.routeList();
    }

    @PutMapping("/deleteOne")
    public Integer deleteRoute(@RequestBody Route route) {
        return routeService.deleteRoute(route);
    }

    @GetMapping("/delete")
    public Integer delete() {
        return routeService.deleteRoute();
    }

    @PostMapping("/add")
    public Integer add(@RequestBody Route route) {
        return routeService.addRoute(route);
    }
}

其余都是按表生成就不黏,看图

图片.png



# SpringCloud