从0到1学SpringCloud——12 gateway 动态配置网关路由规则

2023-02-07 19:48:25

目录

一、前言

二、动态路由

1、数据库设计

2、路由代码

3、效果演示

三、自定义路由

1、代码实现

2、新增路由规则

3、效果演示


一、前言

上一篇文章中介绍了通过配置文件配置网关路由信息,本篇介绍如何数据库动态配置网关路由及过滤器。

二、动态路由

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时

控制台打印:

 

  • 作者:月夜烛峰
  • 原文链接:https://yyzhufeng.blog.csdn.net/article/details/126883661
    更新时间:2023-02-07 19:48:25