目录
一、前言
上一篇文章中介绍了通过配置文件配置网关路由信息,本篇介绍如何数据库动态配置网关路由及过滤器。
二、动态路由
1、数据库设计
CREATE TABLE `zf_gateway_route` (
`route_id` varchar(16) NOT NULL DEFAULT '' COMMENT '路由id',
`uri` varchar(64) DEFAULT NULL COMMENT '路由地址',
`path_name` varchar(8) DEFAULT '' COMMENT 'path断言名称',
`path_pattern` varchar(32) DEFAULT NULL COMMENT 'path断言匹配路径',
`method_name` varchar(8) DEFAULT NULL COMMENT 'method断言名称',
`method_pattern` varchar(8) DEFAULT NULL COMMENT 'method匹配方法',
`msg_name` varchar(32) DEFAULT NULL COMMENT 'header断言名称',
`msg_type` varchar(64) DEFAULT NULL COMMENT '消息类型,多个值之间用,分割',
`filter_name` varchar(16) DEFAULT NULL COMMENT '过滤器名称',
`new_path` varchar(64) DEFAULT NULL COMMENT '转发地址',
`remark` varchar(32) DEFAULT NULL COMMENT '备注',
`status` int(2) DEFAULT NULL COMMENT '状态:0、停用;1、启用',
`index` int(2) DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`route_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='网关路由信息表';
首先设计一张路由表,用来存储路由信息。
添加两条测试数据:
INSERT INTO `zf_gateway_route` (`route_id`, `uri`, `path_name`, `path_pattern`, `method_name`, `method_pattern`, `msg_name`, `msg_type`, `filter_name`, `new_path`, `remark`, `status`, `index`) VALUES ('zhufeng-route-csdn', 'https://blog.csdn.net/', 'Path', '/csdn/**', 'Method', 'Get', NULL, NULL, 'MsgPath', '/nav/java', '第三方测试', 0, 1);
INSERT INTO `zf_gateway_route` (`route_id`, `uri`, `path_name`, `path_pattern`, `method_name`, `method_pattern`, `msg_name`, `msg_type`, `filter_name`, `new_path`, `remark`, `status`, `index`) VALUES ('zhufeng-web-msg', 'lb://zhufeng-web-msg', 'Path', '/msg/info', 'Method', 'Get', NULL, NULL, NULL, NULL, '路由测试', 0, 2);
数据说明:
1、uri:路由地址,也就是通过配置的微服务或者http地址发送请求
2、path_name:断言规则,命名规则为: Name + RoutePredicateFactory,
比如:数据库配置为 Path ,gateway会自动找到 PathRoutePredicateFactory 。
3、path_pattern:请求的路径规则进行适配
4、method_name:断言规则 ,为方便演示,把method单独列了一项,
调用 MethodRoutePredicateFactory 来校验是否为Get或Post请求。
2、路由代码
package com.zhufeng.gateway.db.route;
import com.alibaba.fastjson.JSONObject;
import com.zhufeng.gateway.db.service.GatewayService;
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.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: ZhufengRouteDefinitionRepository
* @Description 动态配置路由规则
* @author 月夜烛峰
* @date 2022/9/15 19:38
*/
@Component
public class ZhufengRouteDefinitionRepository implements RouteDefinitionRepository {
private List<RouteDefinition> routeDefinitions = new ArrayList<RouteDefinition>();
@Resource
private GatewayService gatewayService;
/**
* 初始化参数
*/
@PostConstruct
public void init() {
load();
}
/**
* 通过数据库配置路由规则
*/
private void load() {
List<JSONObject> routeList = gatewayService.findRouteList();
for(JSONObject routeJson:routeList) {
//配置路由规则
RouteDefinition route= new RouteDefinition();
route.setId(routeJson.getString("routeId"));
URI uri = URI.create(routeJson.getString("uri"));
route.setUri(uri);
//配置请求路径规则
PredicateDefinition path = new PredicateDefinition();
path.setName(routeJson.getString("pathName"));
path.addArg("pattern", routeJson.getString("pathPattern"));
route.getPredicates().add(path);
//配置方法规则:Get、Post等
PredicateDefinition method = new PredicateDefinition();
method.setName(routeJson.getString("methodName"));
String methodPattern = routeJson.getString("methodPattern");
String[] methodArr = methodPattern.split(",");
for(int i=0;i<methodArr.length;i++) {
method.addArg("pattern"+0, methodArr[i]);
}
route.getPredicates().add(method);
routeDefinitions.add(route);
}
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routeDefinitions);
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
// TODO Auto-generated method stub
return null;
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
// TODO Auto-generated method stub
return null;
}
}
Service比较简单:
public interface GatewayService {
public List<JSONObject> findRouteList();
}
Service实现:
@Service
public class GatewayServiceImpl implements GatewayService {
@Resource
private GatewayMapper gatewayMapper;
@Override
public List<JSONObject> findRouteList() {
return gatewayMapper.findRouteList();
}
}
Mapper:
@Mapper
public interface GatewayMapper {
public List<JSONObject> findRouteList();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhufeng.gateway.db.mapper.GatewayMapper">
<!--获取所有路由信息-->
<select id="findRouteList" resultType="com.alibaba.fastjson.JSONObject">
SELECT
route_id as routeId,
uri,
path_name as pathName,
path_pattern as pathPattern,
method_name as methodName,
method_pattern as methodPattern,
msg_name as msgName,
msg_type as msgType,
filter_name as filterName,
new_path as newPath,
remark
FROM
zf_gateway_route
WHERE status = 0
</select>
</mapper>
application.properties配置文件:
#服务端口
server.port=9990
#服务名称
spring.application.name=zhufeng-gateway-db
mybatis.type-aliases-package=com.zhufeng.gateway.db.mapper
mybatis.mapper-locations=classpath:mapper/*.xml
3、效果演示
启动前需先启动nacos服务端,再创建两个微服务 zhufeng-web-user、zhufeng-web-msg,
《从0到1学SpringCloud——11 gateway网关路由配置详解》有具体过程。
启动后先测试第一个路由规则,访问 http://127.0.0.1:9990/csdn/test
根据路由规则,会被转发到 blog.csdn.net ,/csdn/test 会被替换为 /nav/java
虽然地址栏还是显示csdn/test,但是实际已经路由到了csdn的java博客专栏,通过标题可以看出Java博客,说明第一个配置生效。
然后测试第二个路由规则,请求被转发到 zhufeng-web-msg 微服务。
启动zhufeng-web-msg微服务,写一个简单的Controller
/**
* @ClassName: MsgController
* @Description 消息路由测试
* @author 月夜烛峰
* @date 2022/7/22 20:33
*/
@RequestMapping("msg")
@RestController
public class MsgController {
@RequestMapping("info")
public String showInfo() {
return "good luck~";
}
}
为了方便演示Get请求和Post请求的不同,使用PostMan来测试,
分别请求 http://127.0.0.1:9990/msg/info
Get请求
Post请求
Get请求可以正常访问,Post请求被拦截,说明数据库配置的路由拦截条件生效。
三、自定义路由
1、代码实现
通过上述配置,简单了解了路由工厂的明明规范,我们可以遵循gateway定义自己的路由信息。
打开Gateway的源码,可以看到内部设置了很多断言规则,都有对应的路由工厂,途中标注的就是刚才用到的两个,仿照源码格式,我们写一个自己的路由工厂。
命名格式:Name+RoutePredicateFactory
Name:ZhufengVersion
工厂类:ZhufengVersionRoutePredicateFactory
校验逻辑:
当发起post请求时,如果Head中的版本号为 1.0 或者版本号不存在时放行,否则拦截。
ZhufengVersionRoutePredicateFactory代码:
package com.zhufeng.gateway.db.route;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @ClassName: ZhufengVersionRoutePredicateFactory
* @Description 动态路由版本判断
* @author 月夜烛峰
* @date 2022/9/15 19:49
*/
@Slf4j
@Component
public class ZhufengVersionRoutePredicateFactory extends AbstractRoutePredicateFactory<ZhufengVersionRoutePredicateFactory.Config> {
public static final String VERSION_KEY = "version";
public ZhufengVersionRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(VERSION_KEY);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
String version = exchange.getRequest().getHeaders().getFirst("version");
log.info("系统版本:{},当前请求版本:{}", config.version, version);
if (config.version == null || config.version.length() == 0) {
log.info("默认不拦截...");
return true;
}
if (version == null || version.length() == 0) {
log.info("header中版本为空,不拦截...");
return true;
}
if (config.version.equals(version)) {
log.info("校验通过,请求放行!当前请求版本:{},系统要求版本:{}", version, config.version);
return true;
}
return false;
}
@Override
public String toString() {
return String.format("version: %s", config.version);
}
};
}
@Validated
public static class Config {
private String version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
}
2、新增路由规则
修改ZhufengRouteDefinitionRepository代码,新增:
代码:
//自定义版本断言(仅作为演示),request请求中,只有版本为1.0时放行
PredicateDefinition version = new PredicateDefinition();
version.setName("ZhufengVersion");
version.addArg("version", "1.0");
route.getPredicates().add(version);
3、效果演示
通过PostMan测试,在Headers中新增version字段
当version为1.0时
当version为2.0时
控制台打印: