Spring Cloud Gateway路由+断言+过滤
目录
- 介绍
- 核心功能
- 三大核心
- Route以服务名动态获取URL
- Predicate常用断言
- Path Route Predicate
- After Route Predicate
- Before Route Predicate
- Between Route Predicate
- Cookie Route Predicate
- Header Route Predicate
- Host Route Predicate
- Query Route Predicate
- RemoteAddr Route Predicate
- Method Route Predicate
- 自定义Predicate
- 自定义Filter
- 自定义全局Filter统计接口调用耗时情况
- 自定义条件Filter
- 总结
介绍
Spring Cloud Gateway 是 Spring Cloud 生态系统中的一个基于 Spring WebFlux 的 API 网关解决方案,旨在为微服务架构提供统一的入口点,支持路由、过滤、负载均衡等功能。它通过非阻塞的响应式编程模型,提供了高效、灵活的 API 网关能力。
核心功能
- 动态路由: 支持基于路径、Header、Query参数等规则的动态路由。
- 过滤器链: 通过全局过滤器 (Global Filter) 和局部过滤器 (Gateway Filter) 实现请求增强与响应修改。
- 负载均衡: 集成Spring Cloud LoadBalancer,支持多实例流量分发。
- 限流与熔断: 内置限流功能,支持 Redis 令牌桶算法,可结合 Resilience4j 实现熔断。
- 安全与监控: 支持JWT、OAuth2等认证机制,保护后端服务。
三大核心
1. 路由 (Route)
路由是网关的核心,用于定义请求的转发规则。他由ID、目标URL、一系列的断言和过滤器组成,如果断言为true则路由匹配成功。一个路由包含以下属性:
- ID: 路由的唯一标识。
- URI: 目标服务的地址,支持 http、lb(负载均衡)等协议。
- 断言: 定义路由匹配条件,如路径、Header、方法等。
- 过滤器: 对请求或响应进行处理的逻辑。
2. 断言 (Predicate)
断言用于判断请求是否符合路由规则。常见的断言包括:
- Path: 路径匹配。
- Method: HTTP 方法匹配。
- Header: 请求头匹配。
- Query: 请求参数匹配。
- After、Before、Between: 时间范围匹配。
3. 过滤 (Filter)
过滤器用于在请求转发前或响应返回后执行特定逻辑。常见的过滤器包括:
- 全局过滤器: 对所有路由生效,如日志记录、认证校验。
- 局部过滤器: 仅对特定路由生效,如路径重写、请求限流。
Route以服务名动态获取URL
网关服务 cloud-gateway9527 引入依赖
<!-- 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><exclusions><exclusion><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId></exclusion></exclusions>
</dependency>
<!-- 指标监控健康检查的actuator -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
yml配置
server:port: 9527spring:application:name: cloud-gateway# Spring Cloud Consul for Service Discoverycloud:consul:host: localhostport: 8500discovery:service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
订单服务 cloud-feign-order9002:OrderGatewayController
@RestController
public class OrderGatewayController {@Resourceprivate PayFeignApi payFeignApi;@GetMapping("/feign/pay/gateway/get/{id}")public Result getById(@PathVariable("id") Integer id) {return payFeignApi.getById(id);}
}
Feign接口 cloud-common-api:PayFeignApi
@FeignClient("cloud-gateway")
public interface PayFeignApi {@GetMapping("/pay/gateway/get/{id}")public Result getById(@PathVariable("id") Integer id);
}
支付服务 cloud-payment8001:PayGatewayController
@RestController
public class PayGatewayController {@Resourceprivate PayService payService;@GetMapping(value = "/pay/gateway/get/{id}")public Result<Pay> getById(@PathVariable("id") Integer id) {Pay pay = payService.getById(id);return Result.success(pay);}
}
测试结果
启动支付服务8001,启动订单服务9002,访问 http://localhost:9002/feign/pay/gateway/get/1 返回异常。再启动网关服务9527,访问 http://localhost:9002/feign/pay/gateway/get/1 返回成功。
Predicate常用断言
Path Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由
测试结果
访问 http://localhost:9527/pay/gateway/get/1 返回成功。
After Route Predicate
# 获取当前时间串
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- After=2025-04-19T15:51:15.516781+08:00[Asia/Shanghai] # 在某个时间之后可以访问
测试结果
2025-04-19 15:51:15 之前访问 http://localhost:9527/pay/gateway/get/1 返回失败。
2025-04-19 15:51:15 之后访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Before Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Before=2025-04-19T15:51:15.516781+08:00[Asia/Shanghai] # 在某个时间之前可以访问
测试结果
2025-04-19 15:51:15 之前访问 http://localhost:9527/pay/gateway/get/1 返回成功。
2025-04-19 15:51:15 之后访问 http://localhost:9527/pay/gateway/get/1 返回失败。
Between Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Between=2025-04-19T16:11:15.516781+08:00[Asia/Shanghai],2025-04-19T16:12:15.516781+08:00[Asia/Shanghai] # 在某个时间段之间可以访问
测试结果
2025-04-19 16:11:15 到 2025-04-19 16:12:15 时间段之间访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Cookie Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Cookie=username,zzyy # cookie中含有username=zzyy可以访问
测试结果
- 原生命令测试
curl http://localhost:9527/pay/gateway/get/1 --cookie “username=zzyy” 返回成功。 - postman测试
请求头中添加 cookie:username=zzyy,访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Header Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Header=X-Request-Id,\d+ # 请求头中含有X-Request-Id且值为整数的正则表达式可以访问
测试结果
- 原生命令测试
curl http://localhost:9527/pay/gateway/get/1 -H “X-Request-Id:123” 返回成功。 - postman测试
请求头中添加 X-Request-Id:123,访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Host Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Host=**.zzyy.com # 主机地址后必须含有.zzyy.com可以访问
测试结果
- 原生命令测试
curl http://localhost:9527/pay/gateway/get/1 -H “Host:www.zzyy.com” 返回成功。 - postman测试
请求头中添加 Host:www.zzyy.com,访问 http://localhost:9527/pay/gateway/get/1 返回成功。
Query Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Query=uid,\d+ # 请求参数必须含有uid且值为整数的正则表达式可以访问
测试结果
访问 http://localhost:9527/pay/gateway/get/1?uid=123 返回成功。
RemoteAddr Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- RemoteAddr=192.168.42.1/24 # 远程访问地址必须是192.168.42.xx才能访问
测试结果
当前电脑IP为 192.168.42.3,访问 http://192.168.42.3:9527/pay/gateway/get/1 返回成功。
Method Route Predicate
spring:cloud:gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- Method=GET,POST # get/post请求可以访问
测试结果
get 请求访问 http://localhost:9527/pay/gateway/get/1 返回成功。
自定义Predicate
网关服务 cloud-gateway9527 中新建 MyRoutePredicateFactory
// 自定义配置会员等级,按照 铂、金、银和yml配置的会员等级,才可以访问
// 继承 AbstractRoutePredicateFactory
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {// 无参构造方法public MyRoutePredicateFactory() {super(MyRoutePredicateFactory.Config.class);}// 短格式public List<String> shortcutFieldOrder() {return Collections.singletonList("userType");}// 重写apply方法@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new Predicate<ServerWebExchange>() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {// 检查request参数中是否存在userType,且值和config中的相同,则可以访问String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");return userType != null && userType.equals(config.getUserType());}};}// 这个Config类就是路由断言规则public static class Config {private @NotNull String userType; // 铂、金、银和yml配置的会员等级public Config() {}public String getUserType() {return userType;}public void setUserType(String userType) {this.userType = userType;}}
}
yml配置
server:port: 9527spring:application:name: cloud-gateway# Spring Cloud Consul for Service Discoverycloud:consul:host: localhostport: 8500discovery:service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由- My=gold # 自定义断言
测试结果
访问 http://localhost:9527/pay/gateway/get/1?userType=gold 返回成功。
自定义Filter
自定义全局Filter统计接口调用耗时情况
网关服务 cloud-gateway9527 中新建 MyGlobalFilter
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {private static final String START_TIME = "start_time"; // 开始调用方法的时间@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 记录开始时间exchange.getAttributes().put(START_TIME, System.currentTimeMillis());return chain.filter(exchange).then(Mono.fromRunnable(() -> {Long startTime = exchange.getAttribute(START_TIME);if (startTime != null) {URI uri = exchange.getRequest().getURI();log.info("访问接口主机:" + uri.getHost() + ",端口:" + uri.getPort() +",URL:" + uri.getPath() + ",参数:" + uri.getRawQuery() +",时长:" + (System.currentTimeMillis() - startTime) + "毫秒");}}));}// 值越小,优先级越高@Overridepublic int getOrder() {return 0;}
}
测试结果
访问 http://localhost:9527/pay/gateway/get/1 日志输出:访问接口主机:localhost,端口:9527,URL:/pay/gateway/get/1,参数:null,时长:6毫秒
自定义条件Filter
// 继承 AbstractGatewayFilterFactory
@Component
@Slf4j
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {// 无参构造方法public MyGatewayFilterFactory() {super(MyGatewayFilterFactory.Config.class);}// 短格式public List<String> shortcutFieldOrder() {return Arrays.asList("state");}// 重写apply方法@Overridepublic GatewayFilter apply(MyGatewayFilterFactory.Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();log.info("进入自定义条件过滤器MyGatewayFilterFactory, state=" + config.getState());if (request.getQueryParams().containsKey("zzyy")) {return chain.filter(exchange);}exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}};}public static class Config {private String state;public Config() {}public String getState() {return state;}public void setState(String state) {this.state = state;}}
}
yml配置
server:port: 9527spring:application:name: cloud-gateway# Spring Cloud Consul for Service Discoverycloud:consul:host: localhostport: 8500discovery:service-name: ${spring.application.name}gateway:routes:- id: pay_routh1 # pay_routh1 (路由的ID,没有固定规则但要求唯一,建议配合服务名)uri: lb://cloud-payment-service # 服务名predicates:- Path=/pay/gateway/get/** # 断言,路径匹配的进行路由filters:- My=zzyy # 自定义条件过滤器
测试结果
访问 http://localhost:9527/pay/gateway/get/1?zzyy=16 返回成功。
总结
以上主要介绍了 Spring Cloud Gateway 路由、断言、过滤的相关知识,以及自定义 Predicate 和 Filter,想了解更多 Spring Cloud Gateway 知识的小伙伴请参考 Spring Cloud Gateway 官网 进行学习,学习更多 Spring Cloud 实战实用技巧的小伙伴,请关注后期发布的文章,认真看完一定能让你有所收获。