天天看點

【SpringCloud】實作動态網關路由-資料庫

本文记录一下我是如何使用 Gateway 搭建网关服务及实现动态路由的,帮助大家学习如何快速搭建网关服务,了解路由相关配置。

1、数据库设计

主表管理路由配置,子表管理路由参数配置。

-- 导出  表 test.gateway_router 结构
CREATE TABLE IF NOT EXISTS `gateway_router` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `router_id` varchar(80) NOT NULL COMMENT '路由ID',
  `uri` varchar(200) CHARACTER SET utf8 COLLATE utf8_german2_ci NOT NULL COMMENT '路由地址',
  `sort` tinyint(4) DEFAULT NULL COMMENT '路由顺序',
  `type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '类型:1-lb模式(默认),2-http模式',
  `valid` tinyint(1) DEFAULT 0 COMMENT '启用状态:0禁用(默认),1启动',
  `memo` varchar(50) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL COMMENT '说明',
  `deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除:1-删除',
  `create_at` datetime NOT NULL,
  `update_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `router_id` (`router_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 COMMENT='网关路由配置';


INSERT INTO `gateway_router` (`id`, `router_id`, `uri`, `sort`, `type`, `valid`, `memo`, `deleted`, `create_at`, `update_at`) VALUES
  (1, 'generator', 'http://127.0.0.1:8001/', 1, 2, 1, '代码生成器', 0, '2022-08-18 15:15:27', '2022-08-18 15:15:28'),
  (2,', 1, 2, 1,  0, '2022-10-15 15:32:40', '2022-10-15 15:32:40');


-- 导出  表 test.gateway_router_item 结构
CREATE TABLE IF NOT EXISTS `gateway_router_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `router_id` varchar(80) NOT NULL DEFAULT '' COMMENT '路由ID',
  `param_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_german2_ci NOT NULL COMMENT '参数name',
  `param_key` varchar(200) CHARACTER SET utf8 COLLATE utf8_german2_ci NOT NULL COMMENT '参数key',
  `param_value` varchar(50) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL COMMENT '参数value',
  `type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '参数类型,1为 predicate,2为过 filter',
  `valid` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '启用状态:0禁用(默认),1启动',
  `deleted` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '逻辑删除:1-删除',
  `create_at` datetime NOT NULL,
  `update_at` datetime NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COMMENT='网关路由参数表';


INSERT INTO `gateway_router_item` (`id`, `router_id`, `param_name`, `param_key`, `param_value`, `type`, `valid`, `deleted`, `create_at`, `update_at`) VALUES
  (1, 'generator', 'Path', 'pattern', '/generator/**', 1, 1, 0, '2022-10-14 21:09:59', '2022-10-14 21:10:00'),
  (4, 'csdn', 'Path', 'pattern', '/csdn', 1, 1, 0, '2022-10-15 21:18:01', '2022-10-15 21:18:02');      

2、动态路由实现

编写态路由实现类,实现 ApplicationEventPublisherAware 接口。

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;

/**
 * 动态路由实现
 *
 * @author H.Yang
 * @date 2022/10/15
 */
@Slf4j
@Component
public class DynamicRouteService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private RedisRouteDefinitionRepository redisRouteDefinitionRepository;

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

    /**
     * 路由定义
     *
     * @param routerId
     * @param uri
     * @param predicates
     * @param filters
     * @return
     */
    public void getRouteDefinition(String routerId, URI uri, List<PredicateDefinition> predicates, List<FilterDefinition> filters) {
        RouteDefinition routeDefinition = new RouteDefinition();

        routeDefinition.setId(routerId);
        routeDefinition.setUri(uri);
        routeDefinition.setPredicates(predicates);
        routeDefinition.setFilters(filters);

        this.addRoute(routeDefinition);
    }

    /**
     * 谓词定义
     *
     * @param key
     * @param name
     * @param val
     * @return
     */
    public PredicateDefinition getPredicateDefinition(String key, String name, String val) {
        PredicateDefinition predicate = new PredicateDefinition();

        // 名称是固定的
        predicate.setName(key);
        predicate.addArg(name, val);

        return predicate;
    }

    /**
     * 过滤器定义
     *
     * @param key
     * @param name
     * @param val
     * @return
     */
    public FilterDefinition getFilterDefinition(String key, String name, String val) {
        FilterDefinition filter = new FilterDefinition();
        
        filter.setName(key);
        filter.addArg(name, val);

        return filter;
    }

    /**
     * 添加路由
     *
     * @param definition
     */
    public void addRoute(RouteDefinition definition) {
        redisRouteDefinitionRepository.save(Mono.just(definition)).subscribe();
    }

    /**
     * 发布
     */
    public void publish() {
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    public void cleanAll() {
        redisRouteDefinitionRepository.cleanAll();
    }


}      

3、配置路由存储方式

import cn.hutool.json.JSONUtil;
import com.xh.jedis.template.JedisPoolRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.LinkedList;
import java.util.List;

/**
 * 配置路由存储方式
 *
 * @author H.Yang
 * @date 2022/10/15
 */
@Slf4j
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    public static final String GATEWAY_ROUTES = "gateway:routes";

    @Autowired
    private JedisPoolRepository jedisPoolRepository;

    /**
     * 启动时就加载当前方法
     * <p>
     * 请注意,此方法很重要,从redis取路由信息的方法,官方核心包要用,核心路由功能都是从redis取的
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        log.debug("loading route....");
        List<RouteDefinition> routeDefinitions = new LinkedList<>();

        jedisPoolRepository.hvals(GATEWAY_ROUTES).stream().forEach(item -> {
            log.debug(item);
            routeDefinitions.add(JSONUtil.toBean(item, RouteDefinition.class));
        });

        return Flux.fromIterable(routeDefinitions);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            jedisPoolRepository.hset(GATEWAY_ROUTES, routeDefinition.getId(), JSONUtil.toJsonStr(routeDefinition));
            return Mono.empty();
        });
    }

    /**
     * 目前使用的全量更新(手动刷新缓存)的方式,因此当前方法不会调用到
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            if (jedisPoolRepository.hexists(GATEWAY_ROUTES, id)) {
                jedisPoolRepository.hdel(GATEWAY_ROUTES, id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("路由文件没有找到: " + routeId)));
        });
    }

    /**
     * 清空缓存
     */
    public void cleanAll() {
        log.debug("clear router cache...");
        jedisPoolRepository.del(GATEWAY_ROUTES);
    }

}      

4、刷新配置

@Autowired
private GatewayRouterService gatewayRouterService;
@Autowired
private GatewayRouterItemService gatewayRouterItemService;
@Autowired
private DynamicRouteService dynamicRouteService;

@Override
public void loadRouter() {
    // 清空缓存
    dynamicRouteService.cleanAll();

    // 查询路由配置
    Map<String, List<GatewayRouterItemEntity>> routerItemMap = gatewayRouterItemService.listValidAll().stream().collect(Collectors.groupingBy(item -> item.getRouterId(), Collectors.mapping(item -> item, Collectors.toList())));

    gatewayRouterService.listValidAll().stream().forEach(item -> {
        List<GatewayRouterItemEntity> routerItemList = routerItemMap.get(item.getRouterId());
        if (routerItemList == null)
            return;

        // 初始化 - 路由配置
        this.routeDefinition(item, routerItemList);
    });

    // 发布 - 配置开始生效
    dynamicRouteService.publish();

}


private void routeDefinition(GatewayRouterEntity item, List<GatewayRouterItemEntity> routerItemList) {
    List<PredicateDefinition> predicates = new LinkedList<>();
    List<FilterDefinition> filters = new ArrayList();

    for (GatewayRouterItemEntity routerItem : routerItemList) {
        if (routerItem.getType() == 1) {
            predicates.add(dynamicRouteService.getPredicateDefinition(routerItem.getParamName(), routerItem.getParamKey(), routerItem.getParamValue()));
            continue;
        }

        filters.add(dynamicRouteService.getFilterDefinition(routerItem.getParamName(), routerItem.getParamKey(), routerItem.getParamValue()));
    }

    URI uri = (item.getType() == 1) ? UriComponentsBuilder.fromUriString("lb://" + item.getUri()).build().toUri() : UriComponentsBuilder.fromHttpUrl(item.getUri()).build().toUri();
    dynamicRouteService.getRouteDefinition(item.getRouterId(), uri, predicates, filters);
}