第 3 章 · Spring Cloud Gateway 路由、过滤器与限流
本章目标:在 svc-spring-demo 部署 gateway-svc 作为统一入口,对外映射虚构域名 https://api.example.com;掌握 Route、Predicate、Filter 三元模型;实现路径转发、请求头透传、全局 CORS;配置基于 Redis 或 RequestRateLimiter 的限流;理解网关与 Nacos 服务发现 lb:// 协议配合。
学时建议:5~6 小时(含 2 小时路由调试与限流压测)
前置:spring-cloud-web ch01~ch02;spring-boot-web ch07 统一响应;Redis 基础(spring-boot-web ch10 缓存章节)。
3.1 为什么需要 API 网关
客户端若直连 user-svc:8081、product-svc:8082,将面临:
| 问题 | Gateway 解决方案 |
|---|---|
| 多个域名/端口 | 统一 api.example.com |
| 跨域 CORS | 网关集中配置 |
| 鉴权重复 | 全局过滤器校验 Token(进阶) |
| 限流防刷 | RequestRateLimiter |
| 路由灰度 | 按 Header/权重路由(概念) |
浏览器 / SPA
│
▼
┌──────────────────────────────┐
│ gateway-svc :8080 │
│ https://api.example.com │
│ Predicates + Filters │
└──────────┬───────────────────┘
│ lb://user-svc
│ lb://product-svc
│ lb://order-svc
▼
Nacos 服务发现(ch02)
教学占位:api.example.com 本地等价 http://127.0.0.1:8080;禁止暴露真实内网网关地址。
3.2 创建 gateway-svc 模块
3.2.1 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
注意:Gateway 基于 WebFlux,不要与 spring-boot-starter-web(Servlet)混在同一模块,避免冲突。
3.2.2 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class GatewaySvcApplication {
public static void main(String[] args) {
SpringApplication.run(GatewaySvcApplication.class, args);
}
}
3.3 路由配置:YAML 方式
gateway-svc-dev.yaml(Nacos 或本地):
spring:
cloud:
gateway:
discovery:
locator:
enabled: false # 教学关闭自动路由,显式配置更清晰
routes:
- id: route-user
uri: lb://user-svc
predicates:
- Path=/api/v1/users/**, /api/v1/auth/**
# 下游 Controller 自带 /api/v1 前缀,网关不剥离路径,无需 filters
- id: route-product
uri: lb://product-svc
predicates:
- Path=/api/v1/products/**
# 下游 Controller 自带 /api/v1 前缀,网关不剥离路径,无需 filters
- id: route-order
uri: lb://order-svc
predicates:
- Path=/api/v1/orders/**
# 下游 Controller 自带 /api/v1 前缀,网关不剥离路径,无需 filters
- id: route-actuator-deny
uri: no://op
predicates:
- Path=/actuator/**
filters:
- SetStatus=403
| 字段 | 含义 |
|---|---|
id | 路由唯一标识 |
uri | lb://服务名 从 Nacos 负载均衡 |
predicates | 匹配条件,满足则命中 |
filters | 命中后执行的转换(可选) |
关于路径剥离:上面三条路由都没配StripPrefix——因为本模块下游服务的 Controller 本身就映射在/api/v1/下,网关应原样透传**。只有当上游路径带网关层前缀、下游不带时,才需要- StripPrefix=2之类的剥离;而StripPrefix=0是无操作,写出来只是教学噪音。
验证:
curl http://127.0.0.1:8080/api/v1/products?page=0&size=5
# 应转发至 product-svc,响应与直连 8082 一致(需 product 已启动并注册)
3.4 Predicate 常用类型
| Predicate | 示例 | 场景 |
|---|---|---|
Path | /api/v1/orders/** | 按 URL 路径 |
Method | GET, POST | 限制 HTTP 方法 |
Header | X-Request-Id, \d+ | 灰度、调试 |
Query | version, v2 | 版本参数路由 |
After / Before | 时间窗口 | 维护公告期 |
Host | api.example.com | 多域名入口 |
组合示例:仅 POST 创建订单走 order-svc:
predicates:
- Path=/api/v1/orders
- Method=POST
3.5 GatewayFilter 常用类型
| Filter | 作用 |
|---|---|
StripPrefix=n | 去掉路径前 n 段 |
AddRequestHeader | 向下游添加头,如 X-Gateway-Trace |
AddResponseHeader | 响应头 |
RewritePath | 正则改写路径 |
RequestRateLimiter | 限流(见 3.7) |
CircuitBreaker | 与 Resilience4j 集成(ch05) |
3.5.1 透传用户身份(示意)
网关校验 JWT 后向下游注入 Header(鉴权逻辑见 ch10 OAuth2 资源服务器,此处简化):
filters:
- AddRequestHeader=X-User-Id, placeholder-from-jwt
下游 order-svc 从 Header 读取 X-User-Id,信任边界应在网关与内网之间,外网不可直连业务端口。
3.5.2 全局 CORS
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins:
- "https://shop-spa.example.com"
- "http://localhost:5173"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
3.6 Java 代码配置路由(可选)
@Configuration
public class GatewayRouteConfig {
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("route-product-java", r -> r
.path("/api/v1/products/**")
.filters(f -> f
.addRequestHeader("X-From-Gateway", "svc-spring-demo")
.stripPrefix(0))
.uri("lb://product-svc"))
.build();
}
}
YAML 与 Java 二选一或合并;团队规模大时 YAML + Nacos 动态刷新更常见。
3.7 限流:RequestRateLimiter
基于 Redis + Token Bucket(响应式)。
3.7.1 配置类
@Configuration
public class RateLimiterConfig {
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
.getRemoteAddress()
.getAddress()
.getHostAddress()
);
}
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(50, 100); // replenishRate, burstCapacity
}
}