• SpringCloudAlibaba Gateway(二)详解-内置Predicate、Filter及自定义Predicate、Filter


    Predicate(断言)

    ​ Predicate(断言),用于进行判断,如果返回为真,才会路由到具体服务。SpirnngCloudGateway由路由断言工厂实现,直接配置即生效,当然也支持自定义路由断言工厂。

    内置路由断言工厂实现

    ​ SpringCloudGateway路由断言工厂实现有很多,可以帮助开发者完成不同的功能。

    • AfterRoutePredicateFactory:设定日期参数,允许在指定日期时间之后的请求通过
    - After=2020-01-20T17:42:47.789+08:00[Asia/Shanghai]
    
    • 1
    • BeforeRoutePredicateFactory:设定日期参数,允许在指定日期时间之前的请求通过
    - Before=2020-01-20T17:42:47.789+08:00[Asia/Shanghai]
    
    • 1
    • BetweenRoutePredicateFactory:设定日期区间,使用逗号分隔,表示允许在时间段内的请求通过
    - Between=2020-01-20T17:42:47.789+08:00[Asia/Shanghai],2023-02-20T17:42:47.789+08:00[Asia/Shanghai]
    
    • 1
    • CookieRoutePredicateFactory:设置Cookie的名称和cookie值的正则表达式,判断请求是否含有该Cookie名称且值与正则表达式相匹配
    - Cookie=chocolate, ch.p
    
    • 1
    • HeaderRoutePredicateFactory:设定请求头名称和请求头值的正则表达式,判断请求是否含有该请求头,且值与正则相匹配
    - Header=X-Request-Id, \d+
    
    • 1
    • HostRoutePredicateFactory:设定host,判断请求的host是否满足条件。
    - Host=**.test.com
    
    • 1
    • MethodRoutePredicateFactory:设定请求方式,只允许满足请求类型的请求方式通过
    - Method=GET
    
    • 1
    • PathRoutePredicateFactory:设定路由规则,判断请求地址是否满足设定的路径规则
    - Path=/shop/**
    
    • 1
    • QueryRoutePredicateFactory:设定参数名称和参数值的正则,判断请求是否含有该参数,且值符合正则规则
    - Query=name,z.
    
    • 1
    • RemoteAddrRoutePredicateFactory:设置IP地址段,判断请求的主机地址是否满足条件
    - RemoteAddr=10.xxx.xxx.xxx/24
    
    • 1

    注意:这里是一个IP的地址段,不是单纯的IP地址

    • WeightRoutePredicateFactory:设定权重分组和权重值,同一个分组名的路由根据权重值进行转发
    routes:
      - id: user_group1
      	uri: http://test1.com
      	predicates:
      		- Path=/user/**
      		- Weight=user_grp,8		# 设置user_grp组,80%请求进入user_group1
      - id: user_group2
      	uri: http://test2.com
      	predicates:
      		- Path=/user/**
      		- Weight=user_grp,2		# 设置user_grp组,20%请求进入user_group2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    内置断言实例

    指定POST请求

    server:
      port: 8083
    
    spring:
      application:
        name: gateway   # 服务名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos地址
        gateway:
          routes: # 路由,可配置多个
          - id: user_route  # 路由id,唯一即可,默认UUID
            uri: lb://user  # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
            order: 1  # 路由优先级,默认0,越低优先级越高
            predicates:
            - Path=/user/**   # 断言,匹配规则
            - Method=POST     # 表示需要POST请求
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用CMD测试下:当使用GET请求时,gateway直接打回,告知沒有找到

    curl localhost:8083/user/findById?id=1
    {"timestamp":"2023-08-26T00:24:35.884+00:00","path":"/user/findById","status":404,"error":"Not Found","message":null,"requestId":"0c70f6cf-1"}
    
    • 1
    • 2

    使用POST请求,正确访问(-X POST表示使用POST请求方式)

    curl -X POST  localhost:8083/user/findById?id=1
    Zhangsan
    
    • 1
    • 2

    不指定为POST请求时,请求会被404.

    使用Query路由断言

    使用Query路由断言工厂,且设置参数名为id,正则表达式\d+(一个或多个数字)

    server:
      port: 8083
    
    spring:
      application:
        name: gateway   # 服务名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos地址
        gateway:
          routes: # 路由,可配置多个
          - id: user_route  # 路由id,唯一即可,默认UUID
            uri: lb://user  # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
            order: 1  # 路由优先级,默认0,越低优先级越高
            predicates:
            - Path=/user/**   # 断言,匹配规则
            - Query=id, \d+   # 参数名id,且值是数字(一位或多位)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用cmd调用测试,使用非数字时,被告知404

    curl localhost:8083/user/findById?id=ss
    {"timestamp":"2023-08-26T00:29:38.252+00:00","path":"/user/findById","status":404,"error":"Not Found","message":null,"requestId":"bc0fe260-3"}
    
    • 1
    • 2

    使用数字,符合正则,正确访问

    curl localhost:8083/user/findById?id=1
    Zhangsan
    
    • 1
    • 2

    自定义路由断言工厂

    ​ 在实际的项目开发中,开发者也可以创建自定义的路由断言工厂,进行自己的业务扩展。实际上前面所有的断言工厂都是继承自AbstractRoutePredicateFactory,并且配置是-Method时那么类就是MethodRoutePredicateFactory要创建自定义断言工厂,我们也需要集成自此类,然后重写自己的扩展即可。

    实例:创建自定义断言工厂,只允许id为1~1000的请求通过

    • 编写自定义的断言工厂CustomRoutePredicateFactory,那么配置时就是-Custom
    @Component  // 交给Spring管理
    public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
    
        public static final String[] KEY_ARRAY = {"minId", "maxId"};    // 对象属性
    
        public CustomRoutePredicateFactory() {        // 构造函数,需要给父类初始化一个配置类
            super(CustomRoutePredicateFactory.Config.class);
        }
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList(KEY_ARRAY);
        }
    
        @Override
        public Predicate<ServerWebExchange> apply(Config config) {
    
            return new GatewayPredicate() { // 设置断言逻辑
                @Override
                public String toString() {
                    return String.format("minId:%d, maxId:%d", config.getMinId(), config.getMaxId());
                }
    
                @Override
                public boolean test(ServerWebExchange exchange) {
    
                    // 得到id
                    String id = exchange.getRequest().getQueryParams().getFirst("id");
    
                    if (id != null) {
                        int numberId = Integer.parseInt(id);
                        // 当 请求的id是在此范围内,返回true
                        return numberId >= config.getMinId() && numberId <= config.getMaxId();
                    }
    
    
                    return false;
                }
            };
        }
        @Validated
        public static class Config {   // 配置类(注意这里是静态类,否则属性是注入不进去的)
    
            private Integer minId;  // 最小id
            private Integer maxId;  // 最大id
    
            public Integer getMinId() {
                return minId;
            }
    
            public void setMinId(Integer minId) {
                this.minId = minId;
            }
    
            public Integer getMaxId() {
                return maxId;
            }
    
            public void setMaxId(Integer maxId) {
                this.maxId = maxId;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    yml配置

    server:
      port: 8083
    
    spring:
      application:
        name: gateway   # 服务名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos地址
        gateway:
          routes: # 路由,可配置多个
          - id: user_route  # 路由id,唯一即可,默认UUID
            uri: lb://user  # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
            order: 1  # 路由优先级,默认0,越低优先级越高
            predicates:
            - Path=/user/**   # 断言,匹配规则
            - Custom=1,1000   # 设置值为1,1000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    测试,当id不在范围内

    curl localhost:8083/user/findById?id=0
    {"timestamp":"2023-08-26T01:09:02.567+00:00","path":"/user/findById","status":404,"error":"Not Found","message":null,"requestId":"61dcf9de-1"}
    
    • 1
    • 2

    在范围内

    curl localhost:8083/user/findById?id=1
    Zhangsan
    
    • 1
    • 2

    没问题!~

    Filter过滤器

    ​ 做过J2EE开发的应该不会对这个词感到陌生。在SpringCloudGateway中的过滤器也有类似的功能,都可以在请求和响应间做一些自定义的逻辑。

    ​ Gateway的过滤器又分为局部全局,而且同样有内置和自定义。

    内置局部过滤器

    局部过滤器只针对当前路由的

    • AddRequestHeader:设定Header的值,为原始的请求添加Header
    - AddRequestHeader=X-Request-token, 123456
    
    • 1
    • AddRequestParameter:设定参数名和值,为原始请求添加参数
    - AddRequestParameter=name, zhangsan
    
    • 1
    • AddResponseHeader:设定Header的值,为响应添加Header
    - AddResponseHeader=token, 123456
    
    • 1
    • DedupeResponseHeader:设定去重的Header名称和去重策略,删除响应头中的重复值
    - DedupeResponseHeader=Access-Control-Allow-Credentinals Access-Control-Allow-Origin
    
    • 1

    多个Header使用空格分隔,去重策略:RETAIN_FIRST(保留第一个)RETAIN_LAST(保留最后一个)RETAIN_UNIQUE(保留所有的唯一值)

    • PrefixPath:给请求加前缀,为原始请求添加路径
    - PrefixPath=/userPrefix
    
    • 1
    • RedirectTo:设定HTTP状态码和URL,将原始请求重定向到指定的URL
    - RedirectTo=302, https://test.com
    
    • 1
    • RewritePath:设定原始路径(支持正则)和重写路径(支持正则),重写原始路径
    - RewritePath=/user/findById, /user/test
    
    • 1
    • StripPrefix:设定要拦截的路径数量,用于修改原始路
    - StripPrefix=1	# 如果请求是 /user/findById,那么会被拦截为 /findById
    
    • 1
    • RequestSize:设定请求包大小(单位字节,默认5MB)
    filters:
    	- name: RequestSize
    	  args:
    	  	maxSize: 5000000
    
    • 1
    • 2
    • 3
    • 4

    自定义局部过滤器

    还是按照前面提到的,id在1~1000内允许访问,否则直接拒绝。同路由断言工厂类似,是配置+GatewayFilterFactory的命名然后继承AbstractGatewayFilterFactory

    写一个UserGatewayFilterFactory的类

    @Component
    public class UserGatewayFilterFactory extends AbstractGatewayFilterFactory<UserGatewayFilterFactory.Config> {
    
        private static final String[] KEY_ARRAYS = {"minId", "maxId"};
    
        public UserGatewayFilterFactory() { // 构造函数
            super(UserGatewayFilterFactory.Config.class);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            return (exchange, chain) -> {
    
                // 获取id 
                String id = exchange.getRequest().getQueryParams().getFirst("id");
                if (id != null) {
                    int number = Integer.parseInt(id);
                    if (number >= config.getMinId() && number <= config.getMaxId()) {
                        // 放行
    					return chain.filter(exchange);
                    }
                }
                byte[] bytes = ("您不能访问"+id+"用户的数据").getBytes(StandardCharsets.UTF_8);
                DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(bytes);
                // 设定HTTP状态码
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                // 返回友好提示
                return exchange.getResponse().writeWith(Flux.just(wrap));
            };
        }
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList(KEY_ARRAYS);
        }
    
        public static class Config {
    
            private Integer minId;
            private Integer maxId;
    
            public Integer getMinId() {
                return minId;
            }
    
            public void setMinId(Integer minId) {
                this.minId = minId;
            }
    
            public Integer getMaxId() {
                return maxId;
            }
    
            public void setMaxId(Integer maxId) {
                this.maxId = maxId;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    yml配置

    server:
      port: 8083
    
    spring:
      application:
        name: gateway   # 服务名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos地址
        gateway:
          routes: # 路由,可配置多个
          - id: user_route  # 路由id,唯一即可,默认UUID
            uri: lb://user  # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
            order: 1  # 路由优先级,默认0,越低优先级越高
            predicates:
            - Path=/user/**   # 断言,匹配规则
            filters:
            - User=1,1000		# 设置id为1~1000的过滤
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    测试一手,id空或id并不在范围内

    curl localhost:8083/user/findById
    您不能访问null用户的数据
    
    curl localhost:8083/user/findById?id=2000
    您不能访问2000用户的数据
    
    • 1
    • 2
    • 3
    • 4
    • 5

    成功测试

    curl localhost:8083/user/findById?id=1
    Zhangsan
    
    • 1
    • 2

    全局过滤器

    全局过滤器在前面的文章中也大概了解过几个,如openFeign的超时时间等,其他的也基本类似,用的不是很多。

    通常情况下自定义全局过滤器是比较常用的了,下面看一个案例,客户端访问时的token认证功能的。

    处理类需要实现GlobalFilter和Ordered接口,重写方法

    @Component
    public class TokenFilter implements GlobalFilter, Ordered { // 自定义全局过滤
    
        private final static String token = "123456";   // 这里做个模拟token
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
            // 从请求头中拿到token
            String token = exchange.getRequest().getHeaders().getFirst("token");
            if (token != null) {
                if (token.equals(TokenFilter.token)) {  // 如果请求头中带的token是123456 那么放行
                    // 放行
                    return chain.filter(exchange);
                }
            }
            byte[] bytes = ("您不能访问" + exchange.getRequest().getPath() + "地址").getBytes(StandardCharsets.UTF_8);
            DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(bytes);
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            // 返回友好提示
            return exchange.getResponse().writeWith(Flux.just(wrap));
        }
    
        @Override
        public int getOrder() { // 排序,值越小,优先级越高(说的是过滤器的优先级,当有多个过滤器时,order可以控制它们的执行优先级)
            return 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    下面用工具测试一下,token

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    对传感器采样数据的低通滤波
    JS获取项目Url请求头Path--js中${path}
    2022届物联网毕业生作品【基于QT的智能家居网关的设计与实现】——
    C++基础——引用讲解1
    Arthas(阿尔萨斯):阿里巴巴开源的线上问题诊断工具
    C#WebAPI项目发布和IIS部署
    PhpStorm环境配置与应用
    子集和 DP - 模板详解
    Ubuntu 实现shell文件的开机运行
    SpringBoot实现多数据源的两种方式
  • 原文地址:https://blog.csdn.net/weixin_45248492/article/details/132634767