一:OpenFeign
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
String url = "http://product-service/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
虽然 RestTemplate 对 HTTP 进行了封装,使其比直接使用 HTTPClient 更加简单方便,但是这段代码任然存在一些问题。首先,使用 RestTemplate 需要手动拼接 URL,虽然灵活性较高,但在 URL 复杂时容易导致错误,且封装较为繁琐。其次,代码的可读性较差,风格也不统一。
在微服务中,通信通常有两种方式:RPC 和 HTTP。在 Spring Cloud 中,默认采用 HTTP 进行微服务通信,最常用的实现方式包括 RestTemplate 和 OpenFeign。OpenFeign 是一个声明式的 Web 服务客户端,它简化了微服务之间的调用,类似于控制器调用服务。使用 OpenFeign 只需创建一个接口并添加相应的注解,即可方便地实现服务调用。
Spring Cloud Feign 是 Spring 对 Feign 的封装,将 Feign 项目集成到 Spring Cloud 生态系统中。由于 Feign 更名的影响,Spring Cloud Feign 也提供了两个启动器:spring-cloud-starter-feign 和 spring-cloud-starter-openfeign。因为 Feign 已经停止维护了,我们使用的依赖是 spring-cloud-starter-openfeign。
1.1 快速上手
- 首先引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 添加注解
在 order-service 的启动类添加注解 @EnableFeignClients , 开启 OpenFeign 的功能.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients // 开启 OpenFeign 的功能
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
- 编写 OpenFeign 的客户端
import com.bite.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "product-service", path = "/product") // 指定调用的服务名称和服务路径
public interface ProductApi {
@RequestMapping("/{productId}")
ProductInfo getProductById(@PathVariable("productId") Integer productId);
}
@FeignClient 注解用于标识接口为 Feign 客户端,其参数说明如下:name / value 属性用于指定 FeignClient 的名称,即微服务的名称,以便于服务发现。同时,Feign 底层会使用 Spring Cloud LoadBalancer 进行负载均衡。如果需要,也可以使用 url 属性来指定一个具体的 URL。path 属性则用于定义当前 FeignClient 的统一前缀。
- 修改远程调用的方法
@Autowired
private ProductApi productApi;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId()); // 通过 Feign 调用获取产品信息
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
可以看出来 Feign 可以实现远程调用。Feign 简化了与 HTTP 服务之间的交互过程,将 REST 客户端的定义转化为 Java 接口,并通过注解的方式声明请求参数、请求方式等信息,从而使远程调用变得更加方便和高效。
1.2 OpenFeign 参数传递
通过观察,我们可以发现 Feign 客户端与服务提供者的接口声明非常相似。在上述示例中,演示了 Feign 如何从 URL 中获取参数。下面将展示 Feign 参数传递的其他方式,仅提供代码示例,不进行功能说明。
1.2.1 传递单个参数
- 服务提供方 product-service
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/product")
@RestController
public class ProductController {
@RequestMapping("/p1")
public String p1(Integer id) {
return "p1 接收到参数: " + id;
}
}
- Feign 客户端
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "product-service", path = "/product") // 指定 FeignClient 的微服务名称和路径
public interface ProductApi {
@RequestMapping("/p1")
String p1(@RequestParam("id") Integer id);
}
- 服务消费方 order-service
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/feign")
@RestController
public class TestFeignController {
@Autowired
private ProductApi productApi;
@RequestMapping("/o1")
public String o1(Integer id) {
return productApi.p1(id);
}
}
1.2.2 传递多个参数
要传递多个参数的时候,可以使用多个 @RequestParam 注解进行参数绑定。
- 服务提供方 product-service
@RequestMapping("/p2")
public String p2(Integer id, String name) {
return "p2 接收到参数, id: " + id + ", name: " + name;
}
- Feign 客户端
@RequestMapping("/p2")
String p2(@RequestParam("id")Integer id,@RequestParam("name")String name);
- 服务消费方 order-service
@RequestMapping("/o2")
public String o2(@RequestParam("id") Integer id, @RequestParam("name") String name) {
return productApi.p2(id, name);
}
1.2.3 传递对象
- 服务提供方 product-service
@RequestMapping("/p3")
public String p3(ProductInfo productInfo) {
return "接收到对象, productInfo: " + productInfo;
}
- Feign 客户端
@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);
- 服务消费方 order-service
@RequestMapping("/o3")
public String o3(ProductInfo productInfo) {
return productApi.p3(productInfo);
}
1.2.4 传递 Json
- 服务提供方 product-service
@RequestMapping("/p4")
public String p4(@RequestBody ProductInfo productInfo) {
return "接收到对象, productInfo: " + productInfo;
}
- Feign 客户端
@RequestMapping("/p4")
String p4(@RequestBody ProductInfo productInfo);
- 服务消费方 order-service
@RequestMapping("/o4")
public String o4(@RequestBody ProductInfo productInfo) {
System.out.println(productInfo.toString());
return productApi.p4(productInfo);
}
1.3 最佳实践
最佳实践是通过历史迭代积累而成的,在项目实践中总结出的最佳使用方式。通过观察,我们发现 Feign 的客户端与服务提供者的 Controller 代码非常相似。那么,是否存在一种方法可以简化这种重复的写法呢?
1.3.1 Feign 继承方式
Feign 支持继承功能,我们可以将一些常见的操作封装到接口中。可以先定义一个接口,服务提供方实现该接口,服务消费方在编写 Feign 接口时直接继承这个接口,从而实现代码的复用和简化。
1.3.2 Feign 抽取方式
官方推荐使用 Feign 的继承方式,但在企业开发中,更多情况下会将 Feign 接口抽取为一个独立的模块。虽然这种做法与继承相似,但背后的理念不同。具体操作方法是将 Feign 的 Client 抽取到一个独立模块中,并将相关的实体类一起放入该模块,打包成一个 JAR。服务消费方只需依赖这个 JAR 包即可。这种做法在企业中比较常见,通常由服务提供方来实现。
1.3.2.1 创建⼀个module
接口可以放在一个公共的 JAR 包中,供服务提供方和服务消费方共同使用。
1.3.2.2 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.3.2.3 编写 API
复制 ProductApi, ProductInfo 到 product-api 模块中
1.3.2.4 打 Jar 包
1.3.2.5 服务消费方使用 product-api
-
删除 ProductApi, ProductInfo
-
引入依赖
<dependency>
<groupId>org.example</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 指定扫描类: ProductApi
@EnableFeignClients(basePackages = {"com.bite.api"}) // 启用 Feign 客户端功能,扫描 com.bite.api 包中的所有接口,这些接口被标注为 Feign 客户端
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
最后就进行调用:
二:Spring Cloud Gateway
2.1 引入网关
在之前的课程中,我们通过 Eureka 和 Nacos 解决了服务注册与发现的问题,使用 Spring Cloud LoadBalancer 实现了负载均衡,并利用 OpenFeign 解决了远程调用的问题。然而,目前所有微服务的接口均直接对外暴露,外部可以直接访问,这样会带来安全隐患。为了确保对外服务的安全性,微服务接口通常需要进行一定的权限校验。但是由于微服务架构将原本单一应用的多个模块拆分成多个独立服务,我们不得不在每个服务中实现重复的校验逻辑。当需要修改这套逻辑时,开发人员需在多个应用中进行调整,从而增加了维护成本。
为了解决上述问题,一种常用的有效方案是引入 API 网关。API 网关可以集中处理权限校验、路由、负载均衡等功能,从而简化微服务间的交互,减轻开发人员的负担,同时提高系统的安全性和可维护性。
举个例子,当外部人员前往公司办理业务时,首先需要核实其身份。在单体架构下,只有一名员工的话就直接负责身份核实并直接办理事务。然而,随着公司的发展,各部门逐渐分工明确,每个部门都需要先进行身份核实再办理相关业务,这样导致了办事效率低下,增加了员工的工作流程。为了解决这一问题,我们可以设立一个前台,由前台统一负责身份校验。身份校验通过后,其他部门可以直接信任前台的信息,快速办理相关事务,从而提升效率并简化流程,所以 API 网关就类似于前台的功能。
2.2 什么是 API 网关
API 网关是一个服务,通常作为后端服务的唯一入口。其定义类似于设计模式中的外观模式,充当整个微服务架构的“门面”。所有外部客户端的访问请求都需要经过 API 网关进行调度和过滤,从而统一管理和处理各类请求。
核心功能 | 说明 | 类比前台的工作 |
---|---|---|
权限控制 | 作为微服务的入口,API 网关对用户进行权限校验,校验失败时进行拦截。 | 身份验证 |
动态路由 | 所有请求先经过网关,网关根据规则将请求转发至相应的微服务,而不处理业务逻辑。 | 根据外来客户的需求,带客户到指定部门处理。 |
负载均衡 | 当路由目标服务有多个实例时,网关会进行负载均衡,以分配请求流量。 | 一个部门有很多人时,前台会帮客户选择具体的某个人处理。 |
限流 | 当请求流量过高时,网关会根据配置的流量限制进行控制,以避免对后端服务造成过大压力。 | 当到访客户较多时,前台会进行流量限制,例如告知客户明天再来。 |
2.3 快速上手 Gateway
2.3.1 创建网关项目
API 网关也是⼀个服务.
2.3.2 引入网关依赖
<!-- 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 基于 Nacos 实现服务发现的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2.3.3 编写启动类
package com.bite.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
2.3.4 添加 Gateway 的路由配置
创建 application.yml 文件,然后添加如下配置:
server:
port: 10030 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
discovery:
server-addr: 110.41.51.65:10020
gateway:
routes: # 网关路由配置
- id: product-service
# 路由ID, 自定义, 唯一即可
uri: lb://product-service
# 目标服务地址
predicates: # 路由条件
- Path=/product/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
属性 | 说明 |
---|---|
id | 自定义路由 ID,保持唯一性。 |
uri | 目标服务地址,支持普通 URI 和 lb:// 应用注册服务名称。lb:// 表示负载均衡,从注册中心获取服务地址。 |
predicates | 路由条件,根据匹配结果决定是否执行该请求路由。上述代码中,符合 Path 规则的所有请求都会代理到 uri 参数指定的地址。 |
2.3.5 测试
- 通过网关服务访问 product-service:http://127.0.0.1:10030/product/1001,URL 符合 YAML 文件中配置的 /product/** 规则,因此路由会转发到 product-service
- 通过网关服务访问 order-service:http://127.0.0.1:10030/order/1,URL 符合 YAML 文件中配置的 /order/** 规则,因此路由会转发到 order-service
2.4 Predicate
Predicate 是 Java 8 提供的一个函数式编程接口,它接收一个参数并返回一个布尔值,用于条件过滤和请求参数的校验。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
- 定义⼀个 Predicate 实现类
class StringPredicate implements Predicate<String> {
@Override
public boolean test(String str) {
return str.isEmpty();
}
}
- 接着使用这个 Predicate 的实现类
public class PredictTest {
public static void main(String[] args) {
Predicate<String> predicate = new StringPredicate();
System.out.println(predicate.test("")); // 输出结果:true
System.out.println(predicate.test("bite666")); // 输出结果:false
}
}
- 当然还可以用匿名内部类
public class PredictTest {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
System.out.println(predicate.test(""));
System.out.println(predicate.test("bite666"));
}
}
- lambda 也行
public class PredictTest {
public static void main(String[] args) {
Predicate<String> predicate = s -> s.isEmpty();
System.out.println(predicate.test("")); // 输出结果:true
System.out.println(predicate.test("bite666")); // 输出结果:false
}
}
2.5 Route Predicate Factories(路由断言工厂)
路由断言工厂也称为路由谓词工厂,是 Spring Cloud Gateway 中用于路由规则匹配的机制。在配置文件中定义的断言规则实际上只是字符串,这些字符串会被 Route Predicate Factory 读取和解析,从而转变为路由判断的条件。例如,配置中的 Path=/product/** 就表示通过 Path 属性来匹配 URL 前缀为 /product 的请求。
Spring Cloud Gateway 默认提供多种 Route Predicate Factory,这些谓词会分别匹配 HTTP 请求的不同属性。此外,多个谓词可以通过逻辑与进行组合,灵活处理复杂的路由匹配需求。
以下是去掉 “predicates:
-” 的表格:
名称 | 说明 | 示例 |
---|---|---|
After | 该工厂需要一个日期时间(Java 的 ZonedDateTime 对象),匹配指定日期之后的请求。 | After=2017-01-20T17:42:47.789-07:00[America/Denver] |
Before | 匹配指定日期之前的请求。 | Before=2017-01-20T17:42:47.789-07:00[America/Denver] |
Between | 匹配两个指定时间之间的请求,第二个时间参数必须在第一个之后。 | Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求中包含指定 Cookie,且该 Cookie 的值符合指定的正则表达式。 | Cookie=chocolate, ch.p |
Header | 请求中包含指定 Header,且该 Header 的值符合指定的正则表达式。 | Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个 host(根据请求中的 Host 字段进行匹配)。 | Host=**.somehost.org, **.anotherhost.org |
Method | 匹配指定的请求方法。 | Method=GET, POST |
Path | 匹配指定规则的路径。 | Path=/red/{segment}, /blue/{segment} |
Remote Addr | 请求者的 IP 必须在指定范围内。 | RemoteAddr=192.168.1.1/24 |
举个例子吧,首先我们在 application.yml 中添加 Predicate 规则:
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: product-service # 路由 ID,自定义,唯一即可
uri: lb://product-service # 目标服务地址
predicates: # 路由条件
- Path=/product/**
- After=2025-01-01T00:00:00.000+08:00[Asia/Shanghai] # 增加限制路由规则: 请求时间为2026年1⽉1⽇之后
修改时间为2025-01-01, 再次访问 http://127.0.0.1:10030/product/1001
- After=2024-01-01T00:00:00.000+08:00[Asia/Shanghai]
2.6 Gateway Filter Factories(网关过滤器工厂)
Predicate 决定了请求由哪个路由处理。如果在请求处理的前后需要添加一些逻辑,那么此时就需要过滤器来发挥作用。过滤器分为两种类型:Pre 类型和 Post 类型。Pre 类型过滤器在路由处理之前执行,用于进行鉴权、限流等操作;而 Post 类型过滤器则在请求执行完成后、将结果返回给客户端之前执行。
Spring Cloud Gateway 内置了多种过滤器,用于拦截和链式处理 Web 请求,例如权限校验和访问超时设定。从作用范围来看,过滤器分为 GatewayFilter 和 GlobalFilter。GatewayFilter 应用于单个路由或一组路由,而 GlobalFilter 则适用于所有路由,对所有请求生效。
2.6.1 GatewayFilter
GatewayFilter 的配置方式与 Predicate 类似,都可以在配置文件 application.yml 中进行设置。每个过滤器的逻辑是固定的,例如,使用 AddRequestParameterGatewayFilterFactory 只需在配置文件中添加 AddRequestParameter,即可为所有请求添加一个参数。下面通过一个示例来演示 GatewayFilter 的使用方法。
- 在 application.yml 中添加 filter
server:
port: 10030 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
discovery:
server-addr: 110.41.51.65:10020
gateway:
routes: # 网关路由配置
- id: product-service # 路由 ID,自定义,唯一即可
uri: lb://product-service # 目标服务地址
predicates: # 路由条件
- Path=/product/**
- After=2024-01-01T00:00:00.000+08:00[Asia/Shanghai]
filters: # 该filter只添加在了 product-service 路由下, 因此只对 product-service 路由生效, 也就是对 /product/** 的请求生效.
- AddRequestParameter=userName,bite
- id: order-service # 路由 ID,自定义,唯一即可
uri: lb://order-service # 目标服务地址
predicates: # 路由条件
- Path=/order/**
Spring Cloud Gateway 提供了多种内置过滤器,具体信息和详细列表可参考官方文档中的 GatewayFilter Factories 部分。
2.6.2 Default Filters
Default Filters 是指前面添加的过滤器仅对指定路由生效。如果希望过滤器对所有路由生效,可以使用 spring.cloud.gateway.default-filters 属性,该属性需要一个过滤器的列表。
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
2.6.3 GlobalFilter
GlobalFilter 是 Spring Cloud Gateway 中的全局过滤器,其作用与 GatewayFilter 相同,但会应用于所有路由请求。全局过滤器通常用于实现与安全性、性能监控和日志记录等相关的功能。Spring Cloud Gateway 内置了多种全局过滤器,具体可参考相关文档中的 Global Filters 部分。
使用 GlobalFilter 需要添加对应的依赖,添加完依赖后进行相关的配置就可以了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
spring:
cloud:
gateway:
metrics:
enabled: true
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
shutdown:
enabled: true
接着访问 http://127.0.0.1:10030/actuator,此时回显示所有监控的信息链接
2.6.4 过滤器执行顺序
在一个项目中同时存在 GatewayFilter 和 GlobalFilter 时,它们的执行顺序是通过合并到一个过滤器链中来确定。请求路由后,网关会将当前项目中的 GatewayFilter 和 GlobalFilter 进行排序并依次执行。每个过滤器需要指定一个 int 类型的 order 值,默认值为 0,这表示该过滤器的优先级。order 值越小,优先级越高,执行顺序越靠前。
过滤器的 order 值可以通过实现 Order 接口或添加 @Order 注解来指定。Spring Cloud Gateway 提供的过滤器的 order 由 Spring 框架指定,而用户也可以自定义过滤器并自行指定 order。当多个过滤器的 order 值相同时,执行顺序为 defaultFilter > GatewayFilter > GlobalFilter。
2.7 自定义过滤器
Spring Cloud Gateway 提供了过滤器的扩展功能,开发者可以根据实际业务需求自定义过滤器。自定义过滤器同样支持 GatewayFilter 和 GlobalFilter 两种类型。
2.7.1 自定义 GatewayFilter
自定义 GatewayFilter 时,需要实现对应的接口 GatewayFilterFactory。Spring Boot 默认提供了一个抽象类 AbstractGatewayFilterFactory,可以直接使用该类来简化开发。
2.7.1.1 定义 GatewayFilter
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Slf4j
@Service
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.CustomConfig>
implements Ordered {
public CustomGatewayFilterFactory() {
super(CustomConfig.class); // 调用父类构造方法,传入配置类
}
@Override
public GatewayFilter apply(CustomConfig config) {
return (exchange, chain) -> { // 创建并返回 GatewayFilter
log.info("[Pre] Filter Request, name:" + config.getName()); // 记录请求前日志
return chain.filter(exchange).then(Mono.fromRunnable(() -> { // 继续过滤链,处理完后记录响应后日志
log.info("[Post] Response Filter"); // 记录响应后日志
}));
};
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // 配置优先级,order 越大,优先级越低
}
}
@Data
public static class CustomConfig {
private String name;
}
代码说明:
- 类名统一以 GatewayFilterFactory 结尾,因为默认情况下,过滤器的名称将使用该类名的前缀部分。在这个示例中,名称为 Custom,等会看一下 yaml 文件。
- apply 方法同时包含 Pre 和 Post 过滤逻辑,其中 then 方法用于在请求执行结束后处理后续操作。
- CustomConfig 是一个配置类,仅包含一个属性 name,该属性与 YML 配置中的对应项相匹配。
- 该类需要交给 Spring 管理,因此需要使用 @Service 注解。
- getOrder 方法表示该过滤器的优先级,返回值越大,优先级越低。
2.7.1.2 配置过滤器
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: product-service # 路由 ID,自定义,唯一即可
uri: lb://product-service # 目标服务地址
predicates: # 路由条件
- Path=/product/**
filters:
- name: Custom
args:
name: custom filter
2.7.2 自定义 GlobalFilter
GlobalFilter 的实现相对简单,它不需要额外的配置,只需实现 GlobalFilter 接口即可。实现后,该过滤器会自动应用于所有请求,进行全局过滤。
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Service
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("[Pre] CustomGlobalFilter enter..."); // 记录请求进入过滤器的日志
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("[Post] CustomGlobalFilter return..."); // 记录请求完成后的日志
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // 配置优先级,越大表示优先级越低
}
}