Spring Cloud - 手写 Gateway 源码,实现自定义局部 FilterFactory

news2025/2/22 21:01:08

目录

一、FilterFactory 分析

1.1、前置知识

1.2、分析源码

1.2.1、整体分析

1.2.2、源码分析

1.3、手写源码

1.3.1、基础框架

1.3.2、实现自定义局部过滤器

1.3.3、加参数的自定义局部过滤器器


一、FilterFactory 分析


1.1、前置知识

前面的学习我们知道,GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,同时,springcloud 也提供了一些内置的 filter.

比如:StripPrefix,表示给请求的 url 中去表指定的 n 个前缀路由,例如 - StripPrefix=2 那么如果你原本的请求是路由是 /user/list/get ,那么经过 StripPrefix 处理后,就会变成 /get.

如果我们需要自己去实现一个像这样的局部过滤器,该怎么实现呢?

1.2、分析源码

1.2.1、整体分析

例如 StripPrefix,他继承了 AbstractGatewayFilterFactory 这个抽象类.

这里暗含了一层意思:在 application.yml 配置文件中,可以在 filters 配置里写上这个类的前缀 StripPrefix,就表示这个类(后面的 GatewayFilterFactory 是固定写法,就表示他是一个网关过滤器).

进一步的,如果我们要自定义一个局部过滤器,例如身份认证 Token 过滤器,我们就创建一个类命名为:Token + GatewayFilter,然后继承 AbstractGatewayFilterFactory 抽象类,就表示他是一个局部过滤器.

1.2.2、源码分析

源码如下:

public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {
    public static final String PARTS_KEY = "parts";

    public StripPrefixGatewayFilterFactory() {
        super(Config.class);
    }

    public List<String> shortcutFieldOrder() {
        return Arrays.asList("parts");
    }

    public GatewayFilter apply(final Config config) {
        return new GatewayFilter() {
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
                String path = request.getURI().getRawPath();
                String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
                StringBuilder newPath = new StringBuilder("/");

                for(int i = 0; i < originalParts.length; ++i) {
                    if (i >= config.getParts()) {
                        if (newPath.length() > 1) {
                            newPath.append('/');
                        }

                        newPath.append(originalParts[i]);
                    }
                }

                if (newPath.length() > 1 && path.endsWith("/")) {
                    newPath.append('/');
                }

                ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
                return chain.filter(exchange.mutate().request(newRequest).build());
            }

            public String toString() {
                return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();
            }
        };
    }

    public static class Config {
        private int parts = 1;

        public Config() {
        }

        public int getParts() {
            return this.parts;
        }

        public void setParts(int parts) {
            this.parts = parts;
        }
    }
}

  • extends AbstractGatewayFilterFactory<Config> :继承 AbstractGatewayFilterFactory 表示他是一个局部过滤器.  传入一个泛型 Config(是一个静态内部类),是因为在配置 filters 的时候,可能需要给参数指定具体的值,例如 - StripPrefix=2,而 Config 就是来处理这里的 2 这个值的.
  • public static final String PARTS_KEY = "parts": 这里就是定义一个常量,后面会用上.
  • StripPrefixGatewayFilterFactory() :构造方法,需要给父类 AbstractGatewayFilterFactory 传递 Config 参数(前面分析过了),将来在 apply 方法中会用上.
  • Config:是一个静态内部类,描述了配置 filters 时,具体要给参数指定的值,并提供了 get 和 set 方法.  这个类就需要传递给父类 AbstractGatewayFilterFactory,最后回传给 apply 方法,在 apply 方法中使用.如果不想给参数指定值,就可以不写 Config 中的内容.
  • shortcutFieldOrder():这个方法是用来指定 filters 配置中参数值的顺序.  也就是说,如果 Config 个中如果有多个参数,那么你在配置 filters 时,指定参数的多个值,顺序是怎样的?他就是用来指定顺序的.
  • apply(Config config):局部过滤器的核心类,用来描述过滤规则的.  这里的参数 Config 就是刚刚讲到的 静态内部类,先传递给父类,然后再回传给了 apply 方法,之后我们就可以直接在 apply 方法中去使用 Config 类中的参数.

1.3、手写源码

1.3.1、基础框架

按照上述分析,不难写出大概模样,例如我们可以模仿源码,创建包 filter.factory ,然后在这个包下定义一个 Token 局部过滤器如下:

@Component // 表示在工厂中创建对象(不能少!)
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {

    public TokenGatewayFilterFactory() {
        super(Config.class);
    }

    /**
     * 核心方法: 处理过滤
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 处理过滤逻辑......
                return chain.filter(exchange);
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return super.shortcutFieldOrder();
    }

    public static class Config {

    }

}

那么我们就可以在配置文件中,添加这个自定义的局部过滤器

1.3.2、实现自定义局部过滤器

例如,自定义一个 Token 局部过滤器,那么就可以创建一个类 filter.factory.TokenGatewayFilterFactory 

在 apply 中的过滤逻辑就是,判断前端是否传入 token,如果没有就抛异常,如果有就去 redis 上看看是否存在这个 token,如果存在就放行,不存在就抛异常.

@Slf4j
@Component // 表示在工厂中创建对象
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public TokenGatewayFilterFactory() {
        super(Config.class);
    }

    /**
     * 核心方法: 处理过滤
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //1.获取 token 信息
                //由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 List
                List<String> tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY);
                if(tokens == null) {
                    throw new RuntimeException("没有 token 令牌!");
                }
                String tokenValue = tokens.get(0);
                log.info("token: {}", tokenValue);
                //2.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)
                if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {
                    throw new RuntimeException("token 令牌不合法!");
                }
                return chain.filter(exchange);
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return super.shortcutFieldOrder();
    }

    public static class Config {

    }

}

a)例如 redis 存储的数据为

b)执行结果如下:

c)如果没有 token 数据,响应如下:

d)如果有 token,但是 token 值错误,响应如下:

1.3.3、加参数的自定义局部过滤器器

如果在配置 filters 的时候,要指定一些参数,例如 isRequire(boolean类型,表示是否传),name(String 类型).

那么就可以在 Config 静态内部类中描述,然后在 shortcutFieldOrder() 方法中指定顺序,最后就可以在 apply 中拿到对应的参数,如下:

@Slf4j
@Component // 表示在工厂中创建对象
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public TokenGatewayFilterFactory() {
        super(Config.class);
    }

    /**
     * 核心方法: 处理过滤
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                log.info("config isRequire: {}", config.isRequire());
                log.info("config name: {}", config.getName());
                //1.拿到 Config 中自定义的参数 isRequire,判断是否要进行过滤
                if(config.isRequire()) {
                    //2.获取 token 信息
                    //由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 List
                    List<String> tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY);
                    if(tokens == null) {
                        throw new RuntimeException("没有 token 令牌!");
                    }
                    String tokenValue = tokens.get(0);
                    log.info("token: {}", tokenValue);
                    //3.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)
                    if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {
                        throw new RuntimeException("token 令牌不合法!");
                    }
                }
                return chain.filter(exchange);
            }
        };
    }

    /**
     * 指定参数值填写的顺序
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("require", "name");
    }

    /**
     * 提供 filter 配置中的参数值
     */
    public static class Config {
        private boolean require;
        private String name;

        public boolean isRequire() {
            return require;
        }

        public void setRequire(boolean require) {
            this.require = require;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

}

Ps:这里的不要命名为 isRequire ,会冲突! 

在配置文件中配置 filters:

执行结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1183267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云服务器搭建flink集群

文章目录 1.集群配置2.修改集群配置3. 访问Web UI4. 提交作业方式5.Yarn部署模式配置5.1 会话模式部署&#xff08;Session Mode&#xff09;5.2 单作业模式(Per-job Mode)5.3 应用模式部署&#xff08;推荐&#xff09;5.3.1 上传HDFS提交&#xff08;推荐&#xff09; 5.4 历…

SpringCloudAlibaba——Sentinel

Sentinel也就是我们之前的Hystrix&#xff0c;而且比Hystrix功能更加的强大。Sentinel是分布式系统的流量防卫兵&#xff0c;以流量为切入点&#xff0c;从流量控制、流量路由、熔断降级等多个维度保护服务的稳定性。 Sentinel采用的是懒加载&#xff0c;这个接口被访问一次&a…

爬取Elastic Stack采集的Nginx内容

以下是一个简单的Go语言爬虫程序&#xff0c;用于爬取Elastic Stack采集的Nginx内容。请注意&#xff0c;这只是一个基本的示例&#xff0c;实际使用时可能需要根据具体情况进行修改和扩展。 package mainimport ("fmt""net/http""io/ioutil" )…

高效接口重试机制的实现

实现一个高效的接口重试机制对于保证系统的稳定性和可靠性至关重要。在面对网络不稳定、服务端故障或者高负载的情况下&#xff0c;接口重试机制能够确保请求的成功执行&#xff0c;同时也需要保证在重试过程中不会造成额外的负担或不必要的延迟。本文将为您介绍高效接口重试机…

工业相机基本知识理解:靶面尺寸、像元尺寸、分辨率

1、靶面尺寸&#xff1a;由Sensor对角线长度表示&#xff0c;单位英寸&#xff0c;这里的1英寸16mm 2、像元尺寸&#xff1a;单个感光元件的大小&#xff0c;一般都是正方形&#xff0c;边长单位um 3、分辨率&#xff1a; Sensor长边像元数 Sensor短边像元数&#xff0c;俗称像…

220v插座led指示灯维修

由于220v是交流电&#xff0c;有反向电压的情况&#xff0c;而led反向通电的时候电阻无穷大&#xff0c;所以分压也无穷大&#xff0c;220v一导通就击穿&#xff0c;即使加了很大的电阻也没用&#xff0c;串联电阻只能作用于二极管正向的时候。 目前有两种方案&#xff1a; 方…

EM@解三角形@正弦定理@余弦定理

文章目录 abstract解三角形基本原理不唯一性 正弦定理直角三角形中的情形推广锐角三角形钝角情形 小结:正弦定理 余弦定理直角三角形中的情形非直角情形小结:余弦定理公式的角余弦形式 abstract 解直角三角形问题正弦定理和余弦定理的推导 对于非直角情形,都是直角情形的推广同…

Springboot项目的多数据源配置

spring boot项目配置多个数据源很常见&#xff01; 话不多说&#xff0c;上代码。 首先先在system账号下创建了一个用户test1,并授予权限 create user test1 identified by 123456; grant connect,resource to test1; 接下来登录test1用户&#xff0c;创建一个表student …

使用表单登录方法模拟登录通信人家园,要求发送登录请求后打印出来的用户名下的用户组类别

目标网站&#xff1a;https://www.txrjy.com/forum.php 一、进入网页&#xff0c;右键“检查” 二、输入用户名和密码&#xff0c;点击“登录”&#xff0c;点击“Network”,上划加载项找到蓝色框中的内容 三、点击第一个加载项&#xff0c;找到URL 四、相关代码&#xff1a; …

数据结构-单链表-力扣题

移除链表元素 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;和前面学的单链表的中间删除数据一样&#xff0c;使要被删除节点的前一个节点指向下要被删除节点的下一个节点&#xff0c;然后把要被删除的节点free掉。 具体实现过程&#xff1a;先…

15 Linux 按键

一、Linux 按键驱动原理 其实案件驱动和 LED 驱动很相似&#xff0c;只不过区别在于&#xff0c;一个是读取GPIO高低电平&#xff0c;一个是从GPIO输出高低电平。 在驱动程序中使用一个整形变量来表示按键值&#xff0c;应用程序通过 read 函数来读取按键值&#xff0c;判断按键…

【Qt之绘制兔纸】

效果 代码 class drawRabbit: public QWidget { public:drawRabbit(QWidget *parent nullptr) : QWidget(parent) {}private:void paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing, true);// 绘制兔子的耳朵painter.s…

【代码随想录】算法训练营 第十五天 第六章 二叉树 Part 2

102. 二叉树的层序遍历 层序遍历&#xff0c;就是一层一层地遍历二叉树&#xff0c;最常见的就是从上到下&#xff0c;从左到右来遍历&#xff0c;遍历的方法依然有两种&#xff0c;第一种是借助队列&#xff0c;第二种则是递归&#xff0c;都算是很简单、很容易理解的方法&am…

新登录接口独立版变现宝升级版知识付费小程序-多领域素材资源知识变现营销系统

源码简介&#xff1a; 资源入口 点击进入 源码亲测无bug&#xff0c;含前后端源码&#xff0c;非线传&#xff0c;修复最新登录接口 梦想贩卖机升级版&#xff0c;变现宝吸取了资源变现类产品的很多优点&#xff0c;摒弃了那些无关紧要的东西&#xff0c;使本产品在运营和变现…

VMware部署CentOS7

一、创建虚拟机 1、点击新建虚拟机 2、选择自定义 下一步 3、点击下一步 4、选择稍后安装操作系统 5、选择linux 下一步 6、选择要安装的centos 版本 这里选择centos7 7、自定义虚拟机名称 设置虚拟机运行空间 8、配置处理器&#xff0c;使用默认 1个处理器 1核 9、修改虚拟机…

用友U8 Cloud 反序列化RCE漏洞复现

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP&#xff0c;主要聚焦成长型、创新型企业&#xff0c;提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友U8 Cloud存在多处&#xff08;FileManageServlet和LoginVideoServlet&#xff09;反序列化漏洞&#xff0c;系统未将…

Vue组件的存放目录问题

注意&#xff1a; .vue文件 本质无区别 1.组件分类 .vue文件分为2类&#xff0c;都是 .vue文件&#xff08;本质无区别&#xff09; 页面组件 &#xff08;配置路由规则时使用的组件&#xff09; 复用组件&#xff08;多个组件中都使用到的组件&#xff09; 2.存放目录 分…

bootstrap3简单玩法

Bootstrap v3 Bootstrap v3 是一个流行的前端框架&#xff0c;它提供了一系列的模板、组件和工具&#xff0c;可以帮助开发者快速地构建响应式的网站和应用程序。 以下是 Bootstrap v3 的一些常见应用&#xff1a; 响应式布局&#xff1a;Bootstrap v3 提供了一个易于使用的网…

Failed to connect to github.com port 443:connection timed out

解决办法&#xff1a; 步骤1&#xff1a; 在这里插入图片描述 步骤2&#xff1a; -步骤3 &#xff1a;在git终端中执行如下命令&#xff1a; git config --global http.proxy http:ip:port git config --global https.proxy http:ip:port git config --global http.proxy htt…

合肥工业大学数字逻辑实验三

** 数字逻辑 实验报告** ✅作者简介:CSDN内容合伙人、信息安全专业在校大学生🏆 🔥系列专栏 :hfut实验课设 📃新人博主 :欢迎点赞收藏关注,会回访! 💬舞台再大,你不上台,永远是个观众。平台再好,你不参与,永远是局外人。能力再大,你不行动,只能看别人成功!…