service-gateway 是整套微服务的流量入口,基于 Spring Cloud Gateway 和 WebFlux。它不实现业务接口,也不访问数据库;它的职责是把外部请求路由到
Provider、Consumer、Mail,并在入口处做限流、熔断、日志、追踪和统一错误响应。
这个模块最重要的工程判断有两个:第一,Gateway 不依赖 service-common,避免 WebFlux 应用被带入 Spring MVC 体系;第二,网关只转发完整
API 前缀,不做 RewritePath。
基本信息
| 项 | 值 |
|---|---|
| 服务名 | service-gateway |
| 默认端口 | 80 |
| 编程模型 | WebFlux |
| 服务发现 | Nacos Discovery |
| 负载均衡 | LoadBalancer + Caffeine |
| 防护 | Sentinel 接口限流、单 IP 限流、路由熔断 |
默认端口是 80,但 Linux 上普通用户不能绑定 1024 以下端口。生产部署建议改成 9000,或由 Nginx、云负载均衡统一收口。
路由模型
生产路由配置:
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: provider-route
uri: lb://service-provider
predicates:
- Path=/api/*/provider/**
order: 1
- id: consumer-route
uri: lb://service-consumer
predicates:
- Path=/api/*/consumer/**
order: 2
- id: mail-route
uri: lb://service-mail
predicates:
- Path=/api/*/mail/**
order: 3
路径里的 * 表示版本号:
/api/v1/provider/user/1 -> service-provider
/api/v1/consumer/user/1 -> service-consumer
/api/v1/mail/send -> service-mail
为什么不做 RewritePath
下游服务已经通过 service-common 接收完整前缀:
Provider: /api/v1/provider/**
Consumer: /api/v1/consumer/**
Mail: /api/v1/mail/**
所以 Gateway 按模块匹配后原样转发即可。如果在网关剥离前缀,下游再自动补前缀,路径语义会绕一圈,排查时也看不出原始路径和目标路径的对应关系。
保留完整前缀还有一个好处:后续 /api/v2/provider/** 只需要在下游启用 v2,Gateway 路由模式不用跟着改。
包结构
com.zjc.gateway
├── GatewayApplication
├── config
│ ├── ObservabilityConfiguration
│ ├── SentinelGatewayConfiguration
│ ├── SentinelGatewayProperties
│ └── SentinelGatewayCircuitBreakerFilterFactory
├── controller
│ └── GatewayFallbackController
├── exception
│ ├── GatewayErrorWebExceptionHandler
│ └── SentinelGatewayBlockRequestHandler
└── filter
└── ServiceGlobalFilter
每一层职责都很明确:
- 启动类只负责 Gateway 应用和 Nacos 发现。
filter记录请求开始、结束和耗时。config/Sentinel*装配限流和熔断。exception输出 WebFlux 版统一 JSON 错误。controller提供熔断后的内部兜底响应。
全局请求日志过滤器
ServiceGlobalFilter 实现 GlobalFilter 和 Ordered:
log.info("开始请求{} {}", method, uri);
return chain.filter(exchange)
.doFinally(signalType -> {
long endTime = System.currentTimeMillis();
log.info("结束请求 {} {},耗时:{}ms,信号:{}",
method, uri, endTime - startTime, signalType);
});
这里用 doFinally,不用 doOnSuccess。WebFlux 请求可能正常结束、异常结束,也可能被取消;doFinally
三种情况都会记录,网关日志不会在异常场景缺一条结束记录。
过滤器 order 是 0,后续如果加鉴权、签名、灰度或租户过滤器,可以用 order 控制执行顺序。
Reactor 上下文传播
Gateway 是异步链路,回调线程可能和发起线程不同。项目专门加了这个配置:
@PostConstruct
public void enableReactorContextPropagation() {
Hooks.enableAutomaticContextPropagation();
}
开启后,Micrometer Tracing 才能把 Reactor Context 中的 trace 上下文恢复到回调线程的 MDC。没有它,开始日志和结束日志可能拿不到稳定的
traceId、spanId。
Sentinel 接口限流
Sentinel 公共配置定义了两类接口规则:
| 规则 | 路径模式 | 全局 QPS | 单 IP QPS |
|---|---|---|---|
provider-user-detail |
/api/[^/]+/provider/user/\d+ |
100 | 10 |
mail-send |
/api/[^/]+/mail/send |
20 | 2 |
zjc:
gateway:
sentinel:
interfaces:
- name: provider-user-detail
pattern: /api/[^/]+/provider/user/\d+
total-qps: 100
per-ip-qps: 10
interval-sec: 1
SentinelGatewayConfiguration 会把每条规则转换成两条 Gateway Flow Rule:一条接口全局 QPS,一条按 CLIENT_IP 参数限流。
被限流时由 SentinelGatewayBlockRequestHandler 返回 HTTP 429 和统一 JSON 结构。
可信代理与真实 IP
单 IP 限流最怕伪造 X-Forwarded-For。这个项目的策略是:
- 先拿 TCP 直连地址。
- 只有直连地址在
trusted-proxies里,才采信X-Forwarded-For的第一个 IP。 - 否则使用直连地址。
默认可信代理只有:
127.0.0.1
::1
0:0:0:0:0:0:0:1
如果生产环境前面有 Nginx、云负载均衡或 WAF,必须把它们的内网直连地址加入 trusted-proxies。否则看到的一直是代理
IP;反过来,也不能把不可信客户端放进白名单。
路由熔断
Gateway 通过默认过滤器挂载 CircuitBreaker:
default-filters:
- name: CircuitBreaker
args:
statusCodes:
- INTERNAL_SERVER_ERROR
- BAD_GATEWAY
- SERVICE_UNAVAILABLE
- GATEWAY_TIMEOUT
熔断阈值:
| 配置 | 含义 |
|---|---|
min-request-amount |
最小统计请求数 |
exception-ratio |
异常比例阈值 |
slow-request-rt-threshold-ms |
慢请求 RT 阈值 |
slow-request-ratio |
慢请求比例阈值 |
stat-interval-ms |
统计窗口 |
recovery-seconds |
半开放恢复等待 |
触发熔断后请求进入 /gateway/fallback/{route},返回 HTTP 503:
{
"success": false,
"code": 503,
"message": "下游服务暂不可用,请稍后再试",
"data": null,
"timestamp": 1787191865837
}
统一错误响应
Gateway 不能复用 service-common 里的 @RestControllerAdvice,因为那是 Spring MVC 的处理机制。这里实现了 WebFlux 的
ErrorWebExceptionHandler:
public class GatewayErrorWebExceptionHandler
implements ErrorWebExceptionHandler, Ordered {
}
状态映射:
| HTTP | 业务码 | 提示 |
|---|---|---|
| 400 | 101 | 请求体格式错误 |
| 401 | 401 | 未认证 |
| 403 | 403 | 无权限 |
| 404 | 102 | 请求路径不存在 |
| 500 | 500 | 服务内部错误 |
| 503 | 503 | 服务不可用 |
这样前端在网关错误和业务错误上看到的是同一种响应结构。注意它不会拦截已经成功转发的业务响应;下游返回什么,Gateway 原样透传。
CORS 边界
CORS 只在 dev Profile 开启,生产默认不配置。开发环境示例:
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns:
- '*'
allowed-methods:
- '*'
allowed-headers:
- '*'
allow-credentials: false
max-age: 3600
开发环境可以宽松;生产如果前端独立域名,应配置明确来源。allow-credentials: true 时不能继续使用 *。
链路追踪
生产 Zipkin endpoint:
http://127.0.0.1:9411/api/v2/spans
采样率 0.1。Gateway 传播 W3C traceparent,下游继续同一条链路:
Client -> Gateway -> Provider
Client -> Gateway -> Consumer -> Provider
Client -> Gateway -> Mail
排查入口请求时,先拿 Gateway 日志里的 traceId,再去 Zipkin 查整棵调用树。
端口暴露策略
生产只暴露 Gateway:
| 服务 | 端口 | 公网 |
|---|---|---|
| Gateway | 80 / 443 / 9000 | 允许 |
| Provider | 9001 | 禁止 |
| Consumer | 9002 | 禁止 |
| 9004 | 禁止 | |
| Nacos | 8848、9848、9849、7848 | 禁止 |
| MySQL | 3306 | 禁止 |
| Zipkin | 9411 | 禁止 |
业务服务的 Swagger UI 也只适合内网调试,不要绕过 Gateway 直接暴露。
模块小结
service-gateway 是整个系统的安全边界和流量边界。它把“入口该做的事情”集中处理:路由、负载均衡、限流、熔断、日志、追踪、统一错误和端口收敛。同时它刻意保持轻量,不写业务逻辑,也不引入公共
MVC 模块,这让 WebFlux 网关和 MVC 业务服务的边界保持干净。