当前位置: 首页 > news >正文

SpringCloud框架学习(第四部分:Gateway网关)

目录

十一、Gateway新一代网关

1.概述

2.Gateway三大核心 

3.工作流程

4.入门配置

5.路由映射

(1)8001 外部添加网关

 (2)服务间调用添加网关

(3)存在问题

6.Gateway高级特性

(1)Route(路由)

 (2)Predicate断言(谓词)

Ⅰ. 介绍

Ⅱ . 配置方法

 Ⅲ . 常用的内置 Route Predicate

① After Route Predicate

② Before Route Predicate 

③ Between Route Predicate 

④ Cookie Route Predicate 

⑤ Header Route Predicate

⑥ Host Route Predicate 

⑦ Path Route Predicate

⑧ Query Route Predicate

⑨ RemoteAddr route predicate

⑩ Method Route Predicate 

Ⅳ . 自定义断言

① 前言

② 编写步骤

③ 测试

(3) Filter(过滤)

Ⅰ. 介绍

Ⅱ . Gateway内置的过滤器

① 请求头(RequestHeader)相关组

② 请求参数(RequestParameter)相关组

③ 回应头(ResponseHeader)相关组

 ④ 前缀和路径相关组

⑤ 其他(Default Filters)

Ⅲ . Gateway 自定义过滤器 

① 自定义全局过滤器

② 自定义局部(条件)过滤器


十一、Gateway新一代网关

1.概述

Gateway:是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring6,Spring Boot 3和 Project Reactor 等技术。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,并为它们提供跨领域的关注点,例如:安全性、监控/度量和恢复能力。

体系定位:

Cloud 全家桶中有个很重要的组件就是网关,在 1.x 版本中都是采用的Zuul网关;

但在 2.x 版本中,zuul 的升级一直跳票,SpringCloud 最后自己研发了一个网关 SpringCloud Gateway 替代 Zuul,那就是 SpringCloud Gateway

所以,gateway 是原 zuul1.x 版的替代

作用:

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

 总结:

Spring Cloud Gateway 组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。

Spring Cloud Gateway 是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点 IP 端口信息,从而加强安全保护。

Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。

Question:nginx 和 gateway网关 到底有什么区别?

网关作为微服务的请求入口,也就是客户端的所有请求都会先到达微服务网关,在通过网关的路由转发到我们的真实服务。

理论上,这就是 nginx 干的活,为什么又多一个网关呢?

网关可以做非常多的事情,比如:解决跨域问题,过滤请求,验证 token 信息,接口服务的保护、限流、安全等。也就是说,对于每一个微服务,如果有一些重复的代码,我们完全可以在网关里进行统一处理,从而减少代码冗余性。

举个例子,假设我们现在需要验证我们的 token 会话信息:

对于 gateway网关,他是由 java 语言编写的,我直接调用后端 java 编写的 api 去连接到 redis 验证即可。

但对于 nginx,它是由 c 语言编写的,想要直接连接到 redis,是需要在中间通过 lua 脚本实现的。对于 java 程序员来说,这是十分繁琐的,还要学习 lua 脚本。

所以,这时候 gateway网关就要比 nginx 方便的多,可以帮助开发人员快速上手。

再举个例子,比如灰度发布,他是需要在中间去连接到我们的 nacos 的:

对于 gateway网关,本身就是由 java 语言编写的,而 nacos 也是由 java 语言编写的,所以连接起来就很方便。

但对于 ngnix,去连接 nacos,就得写 lua 脚本去发送一个 http 请求去连接到 nacos,所以还是学习 lua。

所以,微服务之间需要做的事情,就可以通过网关来实现,更加容易快速上手

如果不是微服务之间的事情,nginx 也是可以做到的。而且相比之下,由于 nginx 是由 c 语言编写的;而 gateway 网关是由 java 编写的(java 底层还是通过 c 封装的),所以 nginx 性能更强,它在处理高并发请求时性能非常优越,适合用作前端的负载均衡器和反向代理。

在大多数情况下,Spring Gateway 和 Nginx 是可以搭配使用的。使用 Nginx 作为前端的反向代理和负载均衡器,负责处理来自外部客户端的请求,然后将请求转发到 Spring Gateway。Spring Gateway 再将请求路由到后端的微服务,负责微服务之间的复杂路由、限流、安全等功能

总结:Nginx 处理高并发、静态资源和边缘服务的流量,而 Spring Gateway 处理更复杂的 API 管理和微服务之间的流量。

2.Gateway三大核心 

① Route(路由)

  • 路由是构建网关的基本模块,它由 ID,目标 URI,一系列的断言和过滤器组成,如果断言为true 则匹配该路由

② Predicate(断言)

  • 参考的是Java8的java.util.function.Predicate
  • 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

③ Filter(过滤)

  • 指的是 Spring 框架中 GatewayFilter 的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

web 前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。

predicate就是我们的匹配条件;

filter,就可以理解为一个无所不能的拦截器;

有了这两个元素,再加上目标 uri ,就可以实现一个具体的路由了

3.工作流程

核心逻辑:路由转发 + 断言判断 + 执行过滤器链

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。

在 “pre” 类型的过滤器可以做:参数校验、权限校验、流量监控、日志输出、协议转换等;

在 “post” 类型的过滤器中可以做:响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

4.入门配置

步骤:

① 建 module(cloud-gateway9527),在 pom 文件中导入依赖

    <dependencies><!--gateway--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency><!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

② 编写 application.yml

server:port: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}

③ 修改主启动类

@SpringBootApplication
@EnableDiscoveryClient //服务注册和发现
public class Main9527
{public static void main(String[] args){SpringApplication.run(Main9527.class,args);}
}

注意:网关和业务无关,所以不写任何业务代码。

④ 先启动 8500 服务中心 Consul,再启动 9527 网关入驻

5.路由映射

(1)8001 外部添加网关

需求:我们目前不想暴露 8001 端口,希望在 8001 真正的支付微服务外面套一层 9527 网关

        (让前端不会直接访问 8001,而是先访问 9527)

准备:

① 8001 新建 PayGateWayController

@RestController
public class PayGateWayController
{@ResourcePayService payService;@GetMapping(value = "/pay/gateway/get/{id}")public ResultData<Pay> getById(@PathVariable("id") Integer id){Pay pay = payService.getById(id);return ResultData.success(pay);}@GetMapping(value = "/pay/gateway/info")public ResultData<String> getGatewayInfo(){return ResultData.success("gateway info test:"+ IdUtil.simpleUUID());}
}

② 启动 8001 自测,检查是否通过:

http://localhost:8001/pay/gateway/get/1

http://localhost:8001/pay/gateway/info

步骤:

① 9527 网关 YML 新增配置

server:
  port9527

spring:
  application:
    name: cloud-gateway #以微服务注册进consulnacos服务列表内
  
cloud:
    consul#配置consul地址
      
host: localhost
      port8500
      
discovery:
        prefer-ip-addresstrue
        service-name
: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #
路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          
uri: http://localhost:8001                #匹配后提供服务的路由地址
          
predicates:
            - Path=/pay/gateway/get/**              
断言,路径相匹配的进行路由


        id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          
uri: http://localhost:8001                #匹配后提供服务的路由地址
          
predicates:
            - Path=/pay/gateway/info/**              
断言,路径相匹配的进行路由

② 启动 8001 和 9527

添加网关前:http://localhost:8001/pay/gateway/get/1
                      http://localhost:8001/pay/gateway/info

添加网关后:http://localhost:9527/pay/gateway/get/1
                      http://localhost:9527/pay/gateway/info

隐真示假,映射说明:

 运行结果:

目前 8001 支付微服务前面添加 GateWay 成功:GateWay9527 → Pay8001

 (2)服务间调用添加网关

 需求:我们启动 80 订单微服务,要求访问 9527 网关后才能访问 8001:80 → 9527 → 8001

Question:网关不是用于前端 web 请求转发到后端吗?怎么用于微服务之间了,我们不是有 openfeign 实现服务间接口调用了吗?

没错,微服务之间的确没必要使用网关,但该前提是这些服务都是同一家公司的(系统内环境)

设想,如果淘宝调用顺丰的接口,这是不是微服务之间的调用,但这属于系统外访问,应当先找网关,在找真实服务

我们现在模拟的就是后者!你可以把订单 80 服务和支付 8001 服务理解成两个不同公司的服务。

同时,这也很好的解释了我们一开始将 gateway 和 nginx 做对比,说:微服务之间需要做的事情,就可以通过网关来实现,更加容易快速上手。设想,这里如果使用 nginx 来实现,会不会就没这么简单了?

步骤:

① 修改 PayFeignApi 接口,添加方法

@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{...//GateWay进行网关测试案例01@GetMapping(value = "/pay/gateway/get/{id}")public ResultData getById(@PathVariable("id") Integer id);//GateWay进行网关测试案例02@GetMapping(value = "/pay/gateway/info")public ResultData<String> getGatewayInfo();
}

② feign80 新建 OrderGateWayController

@RestController
public class OrderGateWayController
{@Resourceprivate PayFeignApi payFeignApi;@GetMapping(value = "/feign/pay/gateway/get/{id}")public ResultData getById(@PathVariable("id") Integer id){return payFeignApi.getById(id);}@GetMapping(value = "/feign/pay/gateway/info")public ResultData<String> getGatewayInfo(){return payFeignApi.getGatewayInfo();}
}

③ 测试,启动 80、网关9527、8001

http://localhost/feign/pay/gateway/get/1

http://localhost/feign/pay/gateway/info 

测试成功!

但真的成功了吗?我们关闭网关再测一次(只启动 80 和 8001)

http://localhost/feign/pay/gateway/get/1

 http://localhost/feign/pay/gateway/info 

我们发现,开不开网关的效果都一样,openfeign 直接绕过了网关。

这是为什么呢? 

还是刚才说的,同一家公司(系统内环境),微服务之间无需网关,直接通过 openfeign 调用。

但不同家公司(系统外访问),需要先找网关,在找真实服务。

我们真的将 80 和 8001 分隔开了吗?

在 PayFeignApi 中,我们 feign 接口中的服务名还是 8001,能够直接访问。

如果真的要将 80 和 8001 分隔开,我们这时候是不是应该是 gateway网关的微服务名?让 80 直接通过 feign 先去找网关?

此时,打开 80、网关9527、8001,进行测试:

打开 80、8001,关闭网关9527,进行测试:

当没有网关的时候,发生 503 异常,无法访问到 8001。

至此,才是真正测试成功!

(3)存在问题

在网关9527 的 yml 配置中,我们将映射(地址和端口号)写死 ,后续如果发生改变,只能一个个修改,这是非常不利的!

6.Gateway高级特性

(1)Route(路由)

因为 openfeign 天然支持负载均衡(lb:LoadBalancer),所以我们可以用 lb://微服务名,来动态获取服务 uri,解决映射写死问题!

但如果不支持负载均衡(lb),就只能手动指定服务的实际 URL。

修改 9527网关的 yml:

server:port: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由- id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由

此时,如果 8001 的地址和端口号发生改变,也不会影响到 9527网关的配置,它仍然可以根据服务名动态获取服务 URI。

 (2)Predicate断言(谓词)

Ⅰ. 介绍

官网:Spring Cloud Gatewayicon-default.png?t=O83Ahttps://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gateway-request-predicates-factories

Route Predicate Factories 这个到底是什么?  

Spring Cloud Gateway 将路由匹配作为 Spring WebFlux HandlerMapping 基础架构的一部分。

Spring Cloud Gateway 包括许多内置的 RoutePredicateFactory(路由谓词工厂)。所有这些 Predicate 都与 HTTP 请求的不同属性匹配。多个 RoutePredicateFactory 可以进行组合,并通过逻辑 and。 

启动微服务 gateway9527,看看 IDEA 后台的输出:

 可以看出,gateway 一开始会加载默认自带的 RoutePredicateFactory。

整体架构: 

Ⅱ . 配置方法

两种配置方法,二选一(常用 shortcut 方式)

① Shortcut Configuration

② Fully Expanded Arguments

 Ⅲ . 常用的内置 Route Predicate

以下断言 api,我们均采用 shortcut 方式配置:

① After Route Predicate

作用:设置在某个时刻之后才可以访问(应用场景:到点秒杀)

时间格式依赖于 ZonedDateTime 类,其使用方法可以看下方:

Java时间类--JDK8_jdk8时间类-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/xpy2428507302/article/details/139217068?spm=1001.2014.3001.5501 获取当前时间:

public static void main(String[] args){ZonedDateTime now = ZonedDateTime.now(); // 默认时区System.out.println(now);
}

 配置 yml:

spring:
  cloud:
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001                #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service          #匹配后提供服务的路由地址
          predicates:
            - Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由
            - After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]

如果时间没到,就会显示当前时间,状态码 404 

时间到了之后,就可以正常访问:

② Before Route Predicate 

作用:设置在某个时刻之前才可以访问

spring:
  application:
    name: cloud-gateway #以微服务注册进consulnacos服务列表内
  
cloud:
    consul#配置consul地址
      
host: localhost
      port8500
      
discovery:
        prefer-ip-addresstrue
        service-name
: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          
#uri: http://localhost:8001                #匹配后提供服务的路由地址
          
uri: lb://cloud-payment-service          #匹配后提供服务的路由地址
          
predicates:
            - Path=/pay/gateway/get/**              断言,路径相匹配的进行路由
            
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
            - Before=2023-11-27T15:25:06.424566300+08:00[Asia/Shanghai] #超过规定时间不可访问

③ Between Route Predicate 

作用:设置在某个时间段之内才可以访问

spring:
  application:
    name: cloud-gateway #以微服务注册进consulnacos服务列表内
  
cloud:
    consul#配置consul地址
      
host: localhost
      port8500
      
discovery:
        prefer-ip-addresstrue
        service-name
: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          
#uri: http://localhost:8001                #匹配后提供服务的路由地址
          
uri: lb://cloud-payment-service          #匹配后提供服务的路由地址
          
predicates:
            - Path=/pay/gateway/get/**              断言,路径相匹配的进行路由
            
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
            #- Before=2023-11-20T17:58:13.586918800+08:00[Asia/Shanghai]
            
- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]

④ Cookie Route Predicate 

Cookie Route Predicate需要两个参数,一个是 Cookie name,一个是正则表达式。

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]- Cookie=username,tom

作用路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配。如果匹配上就会访问路由,如果没有匹配上则不访问。

测试方法1: curl 原生命令

不带 cookie 参数:curl http://localhost:9527/pay/gateway/get/1

cookie 参数校验成功:curl http://localhost:9527/pay/gateway/get/1 --cookie "username=tom"

测试方法2:postman

测试方法3:chrome浏览器 

直接打开网址会显示 404,此时 F12 打开开发者模式:

设置 cookie,再次刷新页面,访问成功:

⑤ Header Route Predicate

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。 

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]#- Cookie=username,tom- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式

有关正则表达式的用法,可以看下方:

java正则表达式_java 正则表达式-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/xpy2428507302/article/details/139185880?spm=1001.2014.3001.5501测试方法1:原生命令:

错误:curl http://localhost:9527/pay/gateway/get/1

错误:curl http://localhost:9527/pay/gateway/get/1 -H "X-Request-Id:abcd"

正确:curl http://localhost:9527/pay/gateway/get/1 -H "X-Request-Id:123456"

 测试方法2:postman

⑥ Host Route Predicate 

Host Route Predicate 接收一个参数一组匹配的主机名模板列表,该模板是 Ant 样式的模板用 . 号作为分隔符。

它通过参数中的主机地址作为匹配规则。

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]#- Cookie=username,tom#- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式- Host=**.baidu.com

测试方法1:原生命令
正确: curl http://localhost:9527/pay/gateway/get/1 -H "Host:www.baidu.com"

正确: curl http://localhost:9527/pay/gateway/get/1 -H "Host:java.baidu.com"

错误: curl http://localhost:9527/pay/gateway/get/1 -H "Host:java.baidu.net"

测试方法2:postman 

⑦ Path Route Predicate

作用:路径相匹配的进行路由 

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由- id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/info/**              # 断言,路径相匹配的进行路由
⑧ Query Route Predicate

支持传入两个参数,一个是属性名,一个为正则表达式(可以是属性名)。 

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]#- Cookie=username,tom#- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式#- Host=**.baidu.com- Query=username, \d+  # 要有参数名username并且值还要是整数才能路由

测试:

正确:http://localhost:9527/pay/gateway/get/1?username=123

错误:http://localhost:9527/pay/gateway/get/1?username=abc

⑨ RemoteAddr route predicate

作用: 请求的远程地址与设置的源相匹配的,允许访问路由


Question:什么是 CIPR 网路 IP 划分? 

举例:192.168.31.1/24

表示 IP 地址是:192.168.31.1,

24 表示:网络号为前 24 位,即子网掩码为:255.255.255.0000 0000,即 255.255.255.0

则主机号有 32 − 24 = 8 位,允许在同一网络中最多有 2^8 = 256 个地址(从 192.168.31.0 到 192.168.31.255),其中 192.168.31.0 是网络地址,192.168.31.255 是广播地址,所以实际可用的主机地址为 192.168.31.1 ~ 192.168.31.254。


cmd 输入:ipconfig,查看自己的 ip 地址

我的子网掩码是 255.255.255.0,所以和上面例子一样,前 24 位为网络号。

IP地址:      00001010.11000111.10110100.11001100  
子网掩码:    11111111.11111111.11111111.00000000  
------------------------------------------------  
子网地址:    00001010.11000111.10110100.00000000

将 ip 地址和子网掩码进行与操作,得到所属网络 IP 为 10.199.180.0/24,实际可用的主机地址为 10.199.100.1 ~ 10.199.100.254。

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]#- Cookie=username,tom#- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式#- Host=**.baidu.com#- Query=username, \d+  # 要有参数名username并且值还要是整数才能路由- RemoteAddr=10.199.180.0/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。(10.199.180.1 - 10.199.180.255)

通过本地的 ip 地址进行远程访问: 

⑩ Method Route Predicate 

作用:配置某个请求地址,只能用 Get / Post 方法访问,方法限制 


总结: Predicate 就是为了实现一组匹配规则,让请求过来找到对应的 Route 进行处理。

Ⅳ . 自定义断言
① 前言

Question:原有的断言配置不满足业务怎么办?

此时,我们就需要自定义断言,如何自定义?我们参考一下 AfterRoutePredicateFactory 的源码:

总体架构:

结论:

① 开头任意取名,但是必须以 RoutePredicateFactory 后缀结尾 

     (开头取的名字就是 application.yml 中 predicates 下作为判断的前缀,比如 After)

② 二选一:

  • 要么继承 AbstractRoutePredicateFactory 抽象类
  • 要么实现 RoutePredicateFactory 接口
② 编写步骤

需求说明:自定义配置会员等级userType,按照钻/金/银和yml配置的会员等级进行匹配,判断是否可以访问

i . 新建类名 XXX 需要以 RoutePredicateFactory 结尾并继承 AbstractRoutePredicateFactory 类

注意: @Component 注解不能忘写,必须要保证自定义的断言能被springioc 扫描到

ii .  重写 apply 方法

    @Overridepublic Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config){return null;}

iii . 新建 apply 方法所需要的静态内部类 MyRoutePredicateFactory.Config

  (这个 Config 类就是我们的路由断言规则,非常重要)

    //这个 Config 类就是我们的路由断言规则,非常重要@Validatedpublic static class Config{@Setter@Getter@NotEmptyprivate String userType; //钻、金、银等用户等级}

iv . 空参构造方法,内部调用 super

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

v . 重写apply方法第二版

    @Overridepublic Predicate<ServerWebExchange> apply(Config config){return new Predicate<ServerWebExchange>(){@Overridepublic boolean test(ServerWebExchange serverWebExchange){//检查request的参数里面,userType是否为指定的值,符合配置就通过String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");if (userType == null) {return false;}//如果说参数存在,就和config的数据进行比较if(userType.equals(config.getUserType())) {return true;}return false;}};}

完整代码:

//需求说明:自定义配置会员等级userType,按照钻/金/银和yml配置的会员等级进行匹配,判断是否可以访问
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {public MyRoutePredicateFactory(){super(Config.class);}@Overridepublic Predicate<ServerWebExchange> apply(Config config){return new Predicate<ServerWebExchange>(){@Overridepublic boolean test(ServerWebExchange serverWebExchange){//检查request的参数里面,userType是否为指定的值,符合配置就通过String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");if (userType == null) {return false;}//如果说参数存在,就和config的数据进行比较if(userType.equals(config.getUserType())) {return true;}return false;}};}//这个 Config 类就是我们的路由断言规则,非常重要@Validatedpublic static class Config{@Setter@Getter@NotEmptyprivate String userType; //钻、金、银等用户等级}
}
③ 测试
spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]#- Cookie=username,tom#- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式#- Host=**.baidu.com#- Query=username, \d+  # 要有参数名username并且值还要是整数才能路由#- RemoteAddr=10.199.180.0/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。(10.199.180.1 - 10.199.180.255)- My=diamond  # 表示必须是钻石会员

启动 9527,我们发现控制台报错:

刚才使用的是 shortcut 方式进行的配置, 不妨我们使用 Fully Expanded Arguments 方式进行配置试试:

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: lb://cloud-payment-service           #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/get/**              # 断言,路径相匹配的进行路由#- After=2024-11-18T22:50:13.586918800+08:00[Asia/Shanghai]#- Cookie=username,tom#- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式#- Host=**.baidu.com#- Query=username, \d+  # 要有参数名username并且值还要是整数才能路由#- RemoteAddr=10.199.180.0/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。(10.199.180.1 - 10.199.180.255)#- My=diamond  # 表示必须是钻石会员- name: Myargs:userType: diamond

此时,重启 9527,再次进行测试:

此时,控制台没有发生报错,我们打开浏览器进行测试:

正确:http://localhost:9527/pay/gateway/get/1?userType=diamond

错误:http://localhost:9527/pay/gateway/get/1?userType=gold

测试成功!

Question:那么为什么 shortcut 方式配置会无法失败,而 Fully Expanded Arguments 方式配置就可以成功运行呢?

我们再次打开 AfterRoutePredicateFactory 的源码,我们发现存在一个 shortcutFieldOrder 方法。

从名字就可以看出,该方法名和 shortcut 方式有关,但我们并未实现! 

原因:缺少 shortcutFieldOrder 方法的实现,所以不支持短格式 


所以,我们也需要在自定义的断言类中,重写该方法: 

    @Overridepublic List<String> shortcutFieldOrder() {return Collections.singletonList("userType");}

 此时,我们再使用 shortcut 方式配置,重启 9527,就可以成功运行了!

(3) Filter(过滤)

Ⅰ. 介绍

类似于 SpringMVC 里面的的拦截器 Interceptor,Servlet 的过滤器。

“pre” 和 “post” 分别会在请求被执行前调用和被执行后调用,用来修改请求和响应信息

作用:

  • 请求鉴权
  • 异常处理
  • 记录接口调用时长统计(重点,大厂面试设计题)

类型:

  • i . 全局默认过滤器 Global Filters
    • gateway 出厂默认已有的,直接用即可,主要作用于所有的路由
    • 不需要在配置文件中配置,作用在所有的路由上,实现 GlobalFilter 接口即可
  • ii . 单一内置过滤器 GatewayFilter
    • 也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组
  • iii . 自定义过滤器
Ⅱ . Gateway内置的过滤器

这里只讲解常见的和通用的,分成 5 个相关组进行讲解

① 请求头(RequestHeader)相关组

i . The AddRequestHeader GatewayFilter Factory

作用:手动往请求头中添加 kv 键值对

步骤:

1.在 8001 的 PayGateWayController 新增方法

@RestController
public class PayGateWayController
{...@GetMapping(value = "/pay/gateway/filter")public ResultData<String> getGatewayFilter(HttpServletRequest request){String result = "";Enumeration<String> headers = request.getHeaderNames();while(headers.hasMoreElements()){String headName = headers.nextElement();String headValue = request.getHeader(headName);System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue);if(headName.equalsIgnoreCase("X-Request-name1")|| headName.equalsIgnoreCase("X-Request-name2")) {result = result+headName + "\t " + headValue +" ";}}return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());}
}

细节:

这里的 X-Request-name1 和 X-Request-name2 在原本请求头中并没有,是我后续在 yml 中手动添加的,为了方便显示,我做了一些处理。

2. 9527 网关 YML 添加过滤内容

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址filters:- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置- AddRequestHeader=X-Request-name2,jerry

3. 重启 9527 和 8001 访问地址:http://localhost:9527/pay/gateway/filter

我们发现,控制台打印请求头的结果中,出现了我们手动添加的 X-Request-name1 和 X-Request-name2

反之,如果 9527 的 yml 中没有手动配置,控制台打印请求头的结果中,也将没有 

 ii . The RemoveRequestHeader GatewayFilter Factory

作用: 删除请求头中指定名字的 kv 键值对

我们现在要删除请求头中,名为:sec-fetch-site 的内容

 删除:

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址filters:- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置- AddRequestHeader=X-Request-name2,jerry - RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site

重启 9527 和 8001,访问地址:http://localhost:9527/pay/gateway/filter

发现控制台打印请求头中的结果中, sec-fetch-site 已被删除

iii . The SetRequestHeader GatewayFilter Factory

作用:修改请求头中指定名字的值 

我们现在要修改请求头中,名为:sec-fetch-mode 的内容,修改为 Blue

修改:

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址filters:- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置- AddRequestHeader=X-Request-name2,jerry- RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site- SetRequestHeader=sec-fetch-mode, Blue # 将请求头sec-fetch-mode对应的值修改为Blue

重启 9527 和 8001,访问地址:http://localhost:9527/pay/gateway/filter 

发现控制台打印请求头中的结果中,sec-fetch-mode 的值已经被修改为 Blue

② 请求参数(RequestParameter)相关组

i . The AddRequestParameter GatewayFilter Factory

ii . The RemoveRequestParameter GatewayFilter Factory

我们将上述两个一起测试:

1.修改 8001 的 PayGateWayController

@RestController
public class PayGateWayController
{...@GetMapping(value = "/pay/gateway/filter")public ResultData<String> getGatewayFilter(HttpServletRequest request){String result = "";Enumeration<String> headers = request.getHeaderNames();while(headers.hasMoreElements()){String headName = headers.nextElement();String headValue = request.getHeader(headName);System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue);if(headName.equalsIgnoreCase("X-Request-name1")|| headName.equalsIgnoreCase("X-Request-name2")) {result = result+headName + "\t " + headValue +" ";}}System.out.println("=============================================");String customerId = request.getParameter("customerId");System.out.println("request Parameter customerId: "+customerId);String customerName = request.getParameter("customerName");System.out.println("request Parameter customerName: "+customerName);System.out.println("=============================================");return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());}
}

2. 修改 9527 的 yml

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址filters:- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置- AddRequestHeader=X-Request-name2,jerry- RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site- SetRequestHeader=sec-fetch-mode, Blue # 将请求头sec-fetch-mode对应的值修改为Blue- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v- RemoveRequestParameter=customerName   # 删除url请求参数customerName,你传递过来也是null

重启 9527 和 8001,访问地址:

http://localhost:9527/pay/gateway/filter

http://localhost:9527/pay/gateway/filter?customerId=9999&customerName=z3

我们发现:

如果没有传 customerId,那就是我们设置的默认值:9527001;如果传了,就会覆盖默认值。

不管有没有传 customerName,它都会被删除,取到的值为 null

③ 回应头(ResponseHeader)相关组

i . The AddResponseHeader GatewayFilter Factory

ii . The SetResponseHeader GatewayFilter Factory

iii . The RemoveResponseHeader GatewayFilter Factory

我们将上述两个一起测试:

修改前的响应头:

修改: 

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址filters:- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置- AddRequestHeader=X-Request-name2,jerry- RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site- SetRequestHeader=sec-fetch-mode, Blue # 将请求头sec-fetch-mode对应的值修改为Blue- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k,v- RemoveRequestParameter=customerName   # 删除url请求参数customerName,你传递过来也是null- AddResponseHeader=X-Response-Color, BlueResponse # 新增响应参数X-Response-Color并设值为BlueResponse- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

 重启 9527 和 8001,访问地址:http://localhost:9527/pay/gateway/filter

 ④ 前缀和路径相关组

i . The PrefixPath GatewayFilter Factory

作用:自动添加路径前缀

之前的正确地址:http://localhost:9527/pay/gateway/filter

修改后:

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:#- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址- Path=/gateway/filter/**              # 断言,为配合PrefixPath测试过滤,暂时注释掉/payfilters:- PrefixPath=/pay # 添加路由前缀:/pay#- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置#- AddRequestHeader=X-Request-name2,jerry#- RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site#- SetRequestHeader=sec-fetch-mode, Blue # 将请求头sec-fetch-mode对应的值修改为Blue#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k,v#- RemoveRequestParameter=customerName   # 删除url请求参数customerName,你传递过来也是null#- AddResponseHeader=X-Response-Color, BlueResponse # 新增请求参数X-Response-Color并设值为BlueResponse#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

拆分说明:

之前完整正确地址:
http://localhost:9527/pay/gateway/filter
现在完整组合地址:
PrefixPath + Path
实际调用地址:
http://localhost:9527/gateway/filter
相当于说前缀被过滤器统一管理了。

 测试:重启 9527 和 8001,访问地址:http://localhost:9527/gateway/filter

ii . The SetPath GatewayFilter Factory

作用:访问路径修改

之前的正确地址:http://localhost:9527/pay/gateway/filter

修改后:

server:port: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:#- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址#- Path=/gateway/filter/**              # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay- Path=/XYZ/abc/{segment}           # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代filters:- SetPath=/pay/gateway/{segment}  # {segment}表示占位符,你写abc也行但要上下一致#- PrefixPath=/pay # 添加路由前缀:/pay#- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置#- AddRequestHeader=X-Request-name2,jerry#- RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site#- SetRequestHeader=sec-fetch-mode, Blue # 将请求头sec-fetch-mode对应的值修改为Blue#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v#- RemoveRequestParameter=customerName   # 删除url请求参数customerName,你传递过来也是null#- AddResponseHeader=X-Response-Color, BlueResponse # 新增请求参数X-Response-Color并设值为BlueResponse#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

替换说明:

/XYZ/abc/{segment}{segment} 就是个占位符,等价于 SetPath 后面指定的 {segment} 内容

浏览器访问地址: http://localhost:9527/XYZ/abc/filter

实际微服务地址:http://localhost:9527/pay/gateway/filter

测试:重启 9527 和 8001,访问地址:http://localhost:9527/XYZ/abc/filter

iii . The RedirectTo GatewayFilter Factory

作用:重定向到某个页面 

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 真实地址#- Path=/gateway/filter/**              # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay#- Path=/XYZ/abc/{segment}           # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代filters:- RedirectTo=302, http://www.baidu.com/ # 访问http://localhost:9527/pay/gateway/filter跳转到http://www.baidu.com/#- SetPath=/pay/gateway/{segment}  # {segment}表示占位符,你写abc也行但要上下一致#- PrefixPath=/pay # 添加路由前缀:/pay#- AddRequestHeader=X-Request-name1,tom  # 请求头kv,若一头含有多参则重写一行设置#- AddRequestHeader=X-Request-name2,jerry#- RemoveRequestHeader=sec-fetch-site      # 删除请求头sec-fetch-site#- SetRequestHeader=sec-fetch-mode, Blue # 将请求头sec-fetch-mode对应的值修改为Blue#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v#- RemoveRequestParameter=customerName   # 删除url请求参数customerName,你传递过来也是null#- AddResponseHeader=X-Response-Color, BlueResponse # 新增请求参数X-Response-Color并设值为BlueResponse#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

重启 9527 和 8001,访问地址:http://localhost:9527/pay/gateway/filter,将直接被重定向到百度首页。

⑤ 其他(Default Filters)

配置在此处相当于全局通用,自定义秒变 Global,但作用范围只在网关中定义的所有路由。

因为 Global Filters 在代码中定义,全局作用于所有请求,无论是否匹配路由。

Ⅲ . Gateway 自定义过滤器 
① 自定义全局过滤器

需求:统计接口调用耗时情况(面试题,重点)

解决办法:自定义接口调用耗时统计的全局过滤器 

步骤:

1. 新建类 MyGlobalFilter 并实现 GlobalFilter,Ordered 两个接口

@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){return null;}@Overridepublic int getOrder(){return 0;}
}

细节:

i . MyGlobalFilter 实现了两个接口:GlobalFilter 和 Ordered 

  • GlobalFilter:Spring Cloud Gateway 的接口,用于定义过滤器的行为。
  • Ordered:用来定义过滤器的执行顺序。getOrder() 方法返回值越小,优先级越高

ii . filter 方法是过滤器的核心逻辑,接收 ServerWebExchange 和 GatewayFilterChain 两个参数:

  • ServerWebExchange exchange: 包含 HTTP 请求和响应信息
  • GatewayFilterChain chain: 用于链式调用下一个过滤器。

2. 实现 MyGlobalFilter 类的内部逻辑

@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {private static final String BEGIN_VISIT_TIME = "begin_visit_time";//开始访问时间@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){//先记录下访问接口的开始时间exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());//then是回调方法,当请求处理完成响应回来才执行return chain.filter(exchange).then(Mono.fromRunnable(()->{//获取访问接口的开始时间Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);if (beginVisitTime != null){log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());log.info("访问接口URL: " + exchange.getRequest().getURI().getPath());log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery());log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "毫秒");log.info("###################################################");System.out.println();}}));}@Overridepublic int getOrder(){return 0;}
}

细节:

i . then 是一个 Mono 的操作符,它表示在前一个异步任务完成之后,执行新的任务。在本例中,新的任务是一个 Runnable,用于记录日志和计算请求时间。

ii . Mono.fromRunnable(...) 接收一个 Runnable 接口实现,这个接口只有一个 run() 方法,表示要执行的任务。

3. yml,为了方便测试,我们注释掉之前配置的断言和过滤器

spring:
  application:
    name: cloud-gateway #以微服务注册进consulnacos服务列表内
  
cloud:
    consul#配置consul地址
      
host: localhost
      port8500
      
discovery:
        prefer-ip-addresstrue
        service-name
: ${spring.application.name}
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          
uri: lb://cloud-payment-service                #匹配后提供服务的路由地址
          
predicates:
            - Path=/pay/gateway/get/**              断言,路径相匹配的进行路由

        id: pay_routh2 #pay_routh2                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          
uri: lb://cloud-payment-service
          predicates:
            - Path=/pay/gateway/info/**              断言,路径相匹配的进行路由

        id: pay_routh3 #pay_routh3
          
uri: lb://cloud-payment-service                #匹配后提供服务的路由地址
          
predicates:
            - Path=/pay/gateway/filter/**              断言,路径相匹配的进行路由,默认正确地址

4. 测试:重启 9527 和 8001,访问:

 http://localhost:9527/pay/gateway/info

http://localhost:9527/pay/gateway/get/1

 http://localhost:9527/pay/gateway/filter

解释:

通过这种方法,不仅可以将业务代码和需求完全隔离开,而且还不用像 spring AOP 一样,需要添加注解。

如果新增一个业务,只要 yml 中添加了该路由,什么都不用做,就自动实现该功能!

② 自定义局部(条件)过滤器

同理,和自定义断言一样,我们参考一下 AddResponseHeaderGatewayFilterFactory 的源码:

我们发现,和断言几乎一致!

结论:

① 开头任意取名,但是必须以 GatewayFilterFactory 后缀结尾 

(开头取的名字就是 application.yml 中 filters 下作为判断的前缀,比如 AddResponseHeader)

② 二选一:

  • 要么继承 AbstractGatewayFilterFactory 抽象类
  • 要么实现 GatewayFilterFactory 接口

步骤:

i . 新建类名 XXX 需要以 GatewayFilterFactory 结尾并继承 AbstractGatewayFilterFactory 类

注意: @Component 注解不能忘写,必须要保证自定义的过滤器能被springioc 扫描到 

ii .  重写 apply 方法

    @Overridepublic GatewayFilter apply(MyGatewayFilterFactory.Config config){return null;}

iii . 新建 apply 方法所需要的静态内部类 MyGatewayFilterFactory.Config

  (这个 Config 类就是我们的路由过滤规则,非常重要)

    public static class Config {@Setter@Getterprivate String param;//设定一个参数,该参数的值必须作为key传递才能访问}

需求:请求发送时必须携带一个 key,key 的名字就是 param 的值。

        (比如:param 的值为 name,发送请求时必须携带 name=xxx)

iv . 空参构造方法,内部调用 super

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

v . 重写 apply 方法第二版

    @Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();//请求中带有key(名字为:param的值),进入逻辑if (request.getQueryParams().containsKey(config.getParam())) {//打印request.getQueryParams().get(config.getParam()).forEach((value) -> {System.out.println("获取到的key为" + config.param);System.out.println("获取到的value为" + value);});//放行return chain.filter(exchange);} else {//设置异常状态码exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}}};}

Ⅵ . 重写 shortcutFieldOrder 方法

    @Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param");}

完整代码:

@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {public MyGatewayFilterFactory() {super(Config.class);}@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();//请求中带有key(名字为:param的值),进入逻辑if (request.getQueryParams().containsKey(config.getParam())) {//打印request.getQueryParams().get(config.getParam()).forEach((value) -> {System.out.println("获取到的key为" + config.param);System.out.println("获取到的value为" + value);});//放行return chain.filter(exchange);} else {//设置异常状态码exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}}};}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param");}public static class Config {@Setter@Getterprivate String param;//设定一个参数,该参数的值必须作为key传递才能访问}
}

Ⅶ . yml 文件配置

spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: true #优先使用服务ip进行注册service-name: ${spring.application.name}gateway:routes:- id: pay_routh3 #pay_routh3uri: lb://cloud-payment-service                #匹配后提供服务的路由地址predicates:- Path=/pay/gateway/filter/**              # 断言,路径相匹配的进行路由   默认正确地址#- Path=/gateway/filter/**              # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay#- Path=/XYZ/abc/{segment}           # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代filters:- My=name  # 表示param的值为name,发送请求时必须传递name

Ⅷ . 测试:重启 9527 和 8001,访问地址:

错误:http://localhost:9527/pay/gateway/filter

错误:http://localhost:9527/pay/gateway/filter?abc=java

正确:http://localhost:9527/pay/gateway/filter?name=java 

相关文章:

SpringCloud框架学习(第四部分:Gateway网关)

目录 十一、Gateway新一代网关 1.概述 2.Gateway三大核心 3.工作流程 4.入门配置 5.路由映射 &#xff08;1&#xff09;8001 外部添加网关 &#xff08;2&#xff09;服务间调用添加网关 &#xff08;3&#xff09;存在问题 6.Gateway高级特性 &#xff08;1&#x…...

C++ 类和对象 (上 )

学习本身就是一件很快乐的事情 一. 面向对象和面向过程 我们在学习计算机的过程中经常会听到xxx是一门面向对象的语言 xxx是一门面向过程的语言 那么到底什么是面向对象 什么是面向过程呢&#xff1f; 简单介绍下 面向过程 面向过程关注的是过程 分析出求解问题的步骤&…...

HAProxy面试题及参考答案(精选80道面试题)

目录 什么是 HAProxy? HAProxy 主要有哪些功能? HAProxy 的关键特性有哪些? HAProxy 的主要功能是什么? HAProxy 的作用是什么? 解释 HAProxy 在网络架构中的作用。 HAProxy 与负载均衡器之间的关系是什么? HAProxy 是如何实现负载均衡的? 阐述 HAProxy 的四层…...

探索PyCaret:一个简化机器学习的全栈库

探索PyCaret&#xff1a;一个简化机器学习的全栈库 机器学习领域充满了挑战&#xff0c;从数据预处理、特征工程到模型训练与评估&#xff0c;再到模型部署。对于数据科学初学者或者时间有限的开发者&#xff0c;这一流程可能显得繁琐且复杂。幸运的是&#xff0c;PyCaret 提供…...

英语写作中“联系、关联”associate correlate 及associated的用法

似乎是同义词的associate correlate 实际上意思差别明显&#xff0c;associate 是人们把两者联系在一起&#xff08;主观联系&#xff09;&#xff0c;而correlate 指客观联系。 例如&#xff1a; We always associate sports with health.&#xff08;我们总是将运动和健康联…...

深度学习之目标检测的技巧汇总

1 Data Augmentation 介绍一篇发表在Big Data上的数据增强相关的文献综述。 Introduction 数据增强与过拟合 验证是否过拟合的方法&#xff1a;画出loss曲线&#xff0c;如果训练集loss持续减小但是验证集loss增大&#xff0c;就说明是过拟合了。 数据增强目的 通过数据增强…...

【Flask+Gunicorn+Nginx】部署目标检测模型API完整解决方案

【Ubuntu 22.04FlaskGunicornNginx】部署目标检测模型API完整解决方案 文章目录 1. 搭建深度学习环境1.1 下载Anaconda1.2 打包环境1.3 创建虚拟环境1.4 报错 2. 安装flask3. 安装gunicorn4. 安装Nginx4.1 安装前置依赖4.2 安装nginx4.3 常用命令 5. NginxGunicornFlask5.1 ng…...

Spark核心组件解析:Executor、RDD与缓存优化

Spark核心组件解析&#xff1a;Executor、RDD与缓存优化 Spark Executor Executor 是 Spark 中用于执行任务&#xff08;task&#xff09;的执行单元&#xff0c;运行在 worker 上&#xff0c;但并不等同于 worker。实际上&#xff0c;Executor 是一组计算资源&#xff08;如…...

“AI玩手机”原理揭秘:大模型驱动的移动端GUI智能体

作者&#xff5c;郭源 前言 在后LLM时代&#xff0c;随着大语言模型和多模态大模型技术的日益成熟&#xff0c;AI技术的实际应用及其社会价值愈发受到重视。AI智能体&#xff08;AI Agent&#xff09;技术通过集成行为规划、记忆存储、工具调用等机制&#xff0c;为大模型装上…...

离散数学【关系】中的一些特殊关系

在数学中&#xff0c;关系是描述集合之间元素间关系的方式。以下是对一些常见关系的详细分析及举例&#xff1a; 1. 空关系 (Empty Relation) 空关系是指在一个集合中&#xff0c;没有任何元素之间存在关系。即对于集合中的所有元素&#xff0c;空关系都不包含任何有序对。 …...

docker 配置代理

创建 Docker 服务配置文件&#xff1a; sudo mkdir -p /etc/systemd/system/docker.service.d sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf添加代理配置&#xff1a; [Service] Environment"HTTP_PROXYhttp://<proxy-address>:<port>&q…...

Dockerfile详解:构建简单高效的容器镜像

引言 在容器化技术日益普及的今天&#xff0c;Dockerfile 成为了构建 Docker 镜像的核心工具。通过编写 Dockerfile&#xff0c;开发者可以将应用程序及其依赖打包成一个可移植、可复用的镜像&#xff0c;从而简化部署和运维工作。本文将详细介绍 Dockerfile 的基本概念、常用指…...

RHCD-----shell

要求&#xff1a; 通过shell脚本分析部署nginx网络服务 1.接收用户部署的服务名称 2.判断服务是否安装 ​ 已安装&#xff1b;自定义网站配置路径为/www&#xff1b;并创建共享目录和网页文件&#xff1b;重启服务 ​ 没有安装&#xff1b;安装对应的软件包 3.测试 判断服务是…...

<硬件有关> 内存攒机认知入门,内存的选择 配置 laptop PC 服务器

原因 这不是黑五吗&#xff0c;给我儿子买了台最便宜 ($300) DELL laptop&#xff0c;CPU 是 i5-1235U&#xff0c;但只有 8GB 内存。升级内存吧。 如何选择内存&#xff1a;家用范围 这里不考虑品牌&#xff0c;在我眼里&#xff0c;区别就是价格&#xff0c;还有所谓的物理…...

基于springboot的来访管理系统的设计与实现

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的来访管理系统的设计与实…...

window11编译pycdc.exe

一、代码库和参考链接 在对python打包的exe文件进行反编译时&#xff0c;会使用到uncompyle6工具&#xff0c;但是这个工具只支持python3.8及以下&#xff0c;针对更高的版本的python则不能反编译。 关于反编译参考几个文章&#xff1a; Python3.9及以上Pyinstaller 反编译教…...

11.22.2024 面试后记

Watching those fucking ap’s paper is bullshit and wasting your time. you’d mother fucker directly say I’m not qualified. if I’m qualified, how could I see u at this place. your dad is alread being rich and enjoy the world. 抽了一周时间去看那些教授的文章…...

Bug Fix 20241122:缺少lib文件错误

今天有朋友提醒才突然发现 gitee 上传的代码存在两个很严重&#xff0c;同时也很低级的错误。 因为gitee的默认设置不允许二进制文件的提交&#xff0c; 所以PH47框架下的库文件&#xff08;各逻辑层的库文件&#xff09;&#xff0c;以及Stm32Cube驱动的库文件都没上传到Gi…...

Pinia 实战教程:构建高效的 Vue 3 状态管理系统

前言 在前端开发中&#xff0c;状态管理已成为必不可少的一部分&#xff0c;Vue.js 生态系统中提供了多种状态管理解决方案。Pinia 是 Vue 3 推出的一种全新的状态管理库&#xff0c;旨在取代 Vuex&#xff0c;提供更简洁的 API、更优雅的 TypeScript 支持以及更高效的性能表现…...

springboot3如何集成knife4j 4.x版本及如何进行API注解

1. 什么是Knife4j knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案, 取名knife4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍!knife4j的前身是swagger-bootstrap-ui,swagger-bootstrap-ui自1.9.6版本后,正式更名为knife4j为了契合微服务的架构发展,由于原来…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…...

门静脉高压——表现

一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构&#xff1a;由肠系膜上静脉和脾静脉汇合构成&#xff0c;是肝脏血液供应的主要来源。淤血后果&#xff1a;门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血&#xff0c;引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

leetcode_69.x的平方根

题目如下 &#xff1a; 看到题 &#xff0c;我们最原始的想法就是暴力解决: for(long long i 0;i<INT_MAX;i){if(i*ix){return i;}else if((i*i>x)&&((i-1)*(i-1)<x)){return i-1;}}我们直接开始遍历&#xff0c;我们是整数的平方根&#xff0c;所以我们分两…...