0
点赞
收藏
分享

微信扫一扫

【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、配置路由存储方式

实现动态路由的关键是 RouteDefinitionRepository 接口,该接口存在一个默认实现InMemoryRouteDefinitionRepository,通过名字我们应该也知道,该实现是将配置文件中配置的信息加载到内存中。

网上好多文章是通过实现 CommandLineRunner 接口,在项目启动时加载配置。我这里介绍的是另处一种加载方式,方便小伙伴借鉴。实现 RouteDefinitionRepository 接口,在项目启动时会调用 getRouteDefinitions() 方法,完成项目初始化操作。

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);
}

举报

相关推荐

0 条评论