【SpringCloud合集-06】Spring Cloud Gateway网关学习笔记

目录

[一、核心认知:Gateway 解决什么问题](#一、核心认知:Gateway 解决什么问题)

[1.1 本质定位](#1.1 本质定位)

[1.2 和 Netflix Zuul 的核心区别](#1.2 和 Netflix Zuul 的核心区别)

二、核心架构与执行流程

[2.1 三大核心概念](#2.1 三大核心概念)

[2.2 完整执行流程](#2.2 完整执行流程)

[三、Spring Cloud 整合实战](#三、Spring Cloud 整合实战)

[3.1 引入依赖](#3.1 引入依赖)

[3.2 基础路由配置](#3.2 基础路由配置)

[示例 1:固定地址转发(直连模式)](#示例 1:固定地址转发(直连模式))

[示例 2:服务发现模式(生产标准用法)](#示例 2:服务发现模式(生产标准用法))

四、核心功能详解

[4.1 常用断言工厂](#4.1 常用断言工厂)

[1. Path 路径断言(最常用)](#1. Path 路径断言(最常用))

[2. After / Before 时间断言](#2. After / Before 时间断言)

[3. Between 时间区间断言](#3. Between 时间区间断言)

[4. Header 请求头断言](#4. Header 请求头断言)

[5. Cookie 断言](#5. Cookie 断言)

[6. Query 请求参数断言](#6. Query 请求参数断言)

[7. Method 请求方法断言](#7. Method 请求方法断言)

[8. Host 域名断言](#8. Host 域名断言)

[9. Weight 权重断言](#9. Weight 权重断言)

多断言组合示例(生产常用写法)

[4.2 常用局部过滤器](#4.2 常用局部过滤器)

[1. StripPrefix 路径截取(最高频)](#1. StripPrefix 路径截取(最高频))

[2. AddRequestHeader / AddResponseHeader](#2. AddRequestHeader / AddResponseHeader)

[3. PrefixPath](#3. PrefixPath)

[4. Retry 重试](#4. Retry 重试)

[4.3 全局过滤器与自定义鉴权](#4.3 全局过滤器与自定义鉴权)

实战:自定义全局鉴权过滤器

五、底层核心原理

[5.1 请求处理全链路源码拆解](#5.1 请求处理全链路源码拆解)

[第一步:DispatcherHandler 分发请求](#第一步:DispatcherHandler 分发请求)

[第二步:RoutePredicateHandlerMapping 匹配路由](#第二步:RoutePredicateHandlerMapping 匹配路由)

[第三步:FilteringWebHandler 执行过滤器链](#第三步:FilteringWebHandler 执行过滤器链)

[5.2 路由加载与动态刷新](#5.2 路由加载与动态刷新)

六、生产级高级特性

[6.1 网关限流(Redis 令牌桶实现)](#6.1 网关限流(Redis 令牌桶实现))

第一步:引入依赖

第二步:配置限流规则

第三步:定义限流维度

[6.2 整合 Sentinel 限流熔断](#6.2 整合 Sentinel 限流熔断)

[6.3 全局跨域配置](#6.3 全局跨域配置)

[6.4 全局异常处理](#6.4 全局异常处理)

七、真实项目踩坑汇总

[1. 启动失败:和 Spring MVC 冲突](#1. 启动失败:和 Spring MVC 冲突)

[2. StripPrefix 路径截取错误](#2. StripPrefix 路径截取错误)

[3. 请求体只能读取一次](#3. 请求体只能读取一次)

[4. 跨域配置不生效](#4. 跨域配置不生效)

[5. 大文件上传超时 / 失败](#5. 大文件上传超时 / 失败)

[6. 过滤器执行顺序混乱](#6. 过滤器执行顺序混乱)

[7. 服务名路由 404](#7. 服务名路由 404)

八、复习速记


(基于 Spring Cloud Alibaba 2.2.9.RELEASE + Spring Cloud Gateway 2.2.9.RELEASE 整理,覆盖实战、原理、踩坑)

一、核心认知:Gateway 解决什么问题

1.1 本质定位

Spring Cloud Gateway 是 Spring 官方推出的第二代微服务网关,替代了已经停止维护的 Netflix Zuul 1.x。它基于 Spring 5、Spring Boot 2、WebFlux、Reactor、Netty 构建,全程异步非阻塞,性能比传统 Servlet 架构的 Zuul 高出数倍。

在微服务架构里,网关是所有外部请求的统一入口,相当于整个系统的「大门保安」,所有客户端不直接访问后端服务,全部先走网关。核心承担这几类工作:

  • 路由转发:根据请求路径、参数等规则,把请求转发到对应的后端服务
  • 统一管控:鉴权、日志、限流、熔断、跨域、黑白名单等通用逻辑,不用每个服务都写一遍
  • 服务透明化:对外只暴露网关地址,后端服务集群、扩缩容对前端完全透明
  • 流量治理:灰度发布、流量染色、版本路由等高级流量调度能力

前提:Gateway 底层是响应式 WebFlux + Netty,不是 Servlet 架构,因此不能和 spring-boot-starter-web 同时引入,否则会直接启动失败,这是最高频的踩坑点。

1.2 和 Netflix Zuul 的核心区别

对比维度 Spring Cloud Gateway Netflix Zuul 1.x
底层架构 Netty + WebFlux,响应式非阻塞 Servlet,同步阻塞
线程模型 少量线程即可支撑高并发,资源占用低 每个请求一个线程,高并发下线程膨胀严重
性能 官方测试是 Zuul 的 2~4 倍 性能较低,易成为瓶颈
维护主体 Spring 官方持续迭代 Netflix 已停止维护
功能丰富度 断言、过滤器、限流、熔断、灰度等全内置 基础路由过滤,高级功能需自己扩展
异步支持 原生支持异步、响应式编程 仅同步模式

面试考点:Gateway 性能高的核心原因是「非阻塞 + 事件驱动」的 Netty 模型,不用为每个请求分配独立线程,大大降低了线程切换和内存开销,高并发下优势极其明显。

二、核心架构与执行流程

2.1 三大核心概念

Gateway 所有功能都围绕三个核心概念展开:

  1. Route(路由) 网关的最小配置单元,一条路由对应一套转发规则。由 ID、目标 URI、断言集合、过滤器集合四部分组成。请求匹配上某条路由后,就会被转发到对应的目标地址。

  2. Predicate(断言) 路由的匹配条件,用来判断请求能不能命中当前路由。支持多种匹配维度:请求路径、请求时间、请求头、Cookie、请求参数、请求方法等。多个断言可以组合使用,全部满足才算匹配成功。 本质是 Java 8 的 Predicate 函数式接口,输入是 ServerWebExchange,输出是 boolean。

  3. Filter(过滤器) 请求转发的前后置处理器,可以修改请求、响应,也可以做鉴权、限流、日志等逻辑。分为两类:

    • 局部过滤器 GatewayFilter:只对配置了它的单条路由生效
    • 全局过滤器 GlobalFilter:对所有路由都生效,无需单独配置

2.2 完整执行流程

流程里两个关键点:

  1. 路由匹配是按顺序匹配,命中第一条就停止,所以配置路由时要把精确的规则放前面,宽泛的放后面,避免被宽泛路由提前拦截。
  2. 过滤器是责任链模式,前置逻辑正序执行,后置逻辑倒序执行,和 Servlet Filter、Spring MVC 拦截器的执行规律一致。

三、Spring Cloud 整合实战

3.1 引入依赖

网关服务单独创建项目,引入网关和服务发现依赖。版本由 Spring Cloud Alibaba 父工程统一管理,无需单独指定。

XML 复制代码
<dependencies>
    <!-- Nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- Spring Cloud Gateway 网关 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>

提醒:绝对不能引入 spring-boot-starter-web,Gateway 依赖的是 spring-boot-starter-webflux,两者冲突会直接启动报错。如果项目里间接引入了 web,一定要排除掉。

3.2 基础路由配置

路由配置支持 YAML 配置和 Java 代码配置两种,生产环境 90% 都用 YAML,直观易维护。

示例 1:固定地址转发(直连模式)

适合后端地址固定的场景,直接写死目标 IP 和端口。

bash 复制代码
server:
  port: 9000

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route  # 路由唯一ID,自定义即可
          uri: http://127.0.0.1:8081  # 转发目标地址
          predicates:
            - Path=/api/user/**  # 断言:路径匹配/api/user开头的请求
          filters:
            - StripPrefix=1  # 过滤器:去掉第一层路径前缀/api

效果:请求 http://网关地址:9000/api/user/1,会被转发到 http://127.0.0.1:8081/user/1

示例 2:服务发现模式(生产标准用法)

结合 Nacos 服务注册中心,用服务名替代固定地址,自动结合 LoadBalancer 做负载均衡,后端扩缩容不用改网关配置。

bash 复制代码
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: false  # 关闭自动按服务名生成路由,手动配置更可控
      routes:
        - id: user-service-route
          uri: lb://user-service  # lb:// 开头代表从注册中心找服务,做负载均衡
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1

        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1

lb:// 是核心前缀,告诉网关启用负载均衡客户端,从注册中心拉取 user-service 的实例列表,按负载均衡策略选择实例转发。

四、核心功能详解

4.1 常用断言工厂

Gateway 内置了十几种断言工厂,覆盖绝大多数匹配场景,几个最常用的如下:

断言类型 作用 配置示例
Path 按请求路径匹配,最常用 - Path=/api/user/**,/api/admin/**
After 指定时间之后的请求才匹配 - After=2025-01-01T00:00:00+08:00[Asia/Shanghai]
Before 指定时间之前的请求才匹配 - Before=2025-06-01T00:00:00+08:00[Asia/Shanghai]
Header 请求头包含指定字段才匹配 - Header=token, [a-z0-9]+
Cookie Cookie 包含指定字段才匹配 - Cookie=userId, \\d+
Query 请求参数包含指定字段才匹配 - Query=source, h5
Method 指定请求方法才匹配 - Method=GET,POST

多个断言是「与」的关系,必须全部满足才能命中路由。

1. Path 路径断言(最常用)

根据请求的 URI 路径匹配,支持 ** 通配符匹配多级路径,* 匹配单级路径。

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            # 匹配所有 /api/user 开头的请求,例如 /api/user/1、/api/user/list/page
            - Path=/api/user/**
          filters:
            - StripPrefix=1

多路径写法:用逗号分隔,命中任意一个路径即可

bash 复制代码
predicates:
  - Path=/api/user/**,/api/member/**
2. After / Before 时间断言
  • After:只有在指定时间之后发起的请求才匹配
  • Before:只有在指定时间之前发起的请求才匹配

时间格式必须是 ZonedDateTime 标准格式,带时区。

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: activity-route
          uri: lb://activity-service
          predicates:
            # 2025年11月1日 0点 之后发起的请求才命中该路由
            - After=2025-11-01T00:00:00+08:00[Asia/Shanghai]

场景:大促活动提前配置好路由,到时间自动生效,不用半夜发版;或者活动结束自动下线,切回普通接口。

3. Between 时间区间断言

请求时间在两个时间点之间才匹配,适合限时活动。

bash 复制代码
predicates:
  # 仅在双十一当天 0点到24点之间生效
  - Between=2025-11-11T00:00:00+08:00[Asia/Shanghai], 2025-11-12T00:00:00+08:00[Asia/Shanghai]
4. Header 请求头断言

根据请求头中的字段匹配,支持正则表达式校验值。

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: gray-route
          uri: lb://user-service-gray
          predicates:
            - Path=/api/user/**
            # 请求头必须包含 version 字段,且值为 v2 才命中灰度路由
            - Header=version, v2

正则写法:值满足正则规则才匹配

java 复制代码
predicates:
  # token 请求头的值必须是纯数字才匹配
  - Header=token, \d+

实战场景:灰度发布时,前端在请求头带上版本号,网关把流量切到灰度集群。

根据 Cookie 中的字段匹配,同样支持正则。

bash 复制代码
predicates:
  # Cookie 中必须有 userType 字段,且值为 vip 才命中
  - Cookie=userType, vip

实战场景:根据用户等级分流,VIP 用户走专属服务集群。

6. Query 请求参数断言

根据 URL 后面的查询参数匹配。

bash 复制代码
predicates:
  # 请求参数中必须包含 source,且值为 h5 才命中
  - Query=source, h5

只判断存在、不校验值:

bash 复制代码
predicates:
  # 只要有 debug 参数就命中,不管值是什么
  - Query=debug
7. Method 请求方法断言

只允许指定的 HTTP 方法命中路由。

bash 复制代码
predicates:
  # 只有 GET 和 POST 请求才命中
  - Method=GET,POST

实战场景:读写分离路由,写操作单独走一条高可用链路。

8. Host 域名断言

根据请求的 Host 域名匹配。

bash 复制代码
predicates:
  # 只有通过 api.example.com 域名访问才命中
  - Host=api.example.com

多域名写法:

bash 复制代码
predicates:
  - Host=api.example.com, m.example.com

场景:一套网关集群承载多个业务域名,按域名分发到不同业务服务。

9. Weight 权重断言

按权重比例把流量分配到不同的目标服务,是灰度发布、流量切分的核心手段。

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-v1-route
          uri: lb://user-service-v1
          predicates:
            - Path=/api/user/**
            # 同组名 weight-group,权重 80
            - Weight=user-weight-group, 80

        - id: user-v2-route
          uri: lb://user-service-v2
          predicates:
            - Path=/api/user/**
            # 同组名,权重 20
            - Weight=user-weight-group, 20

效果:80% 的流量走 v1 版本,20% 的流量走 v2 版本;两条路由必须用同一个分组名才能按权重分配流量。

多断言组合示例(生产常用写法)

同一条路由下多个断言同时生效,全部满足才命中:

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: vip-activity-route
          uri: lb://activity-service
          predicates:
            # 路径匹配
            - Path=/api/activity/seckill
            # 活动时间内
            - Between=2025-11-11T00:00:00+08:00[Asia/Shanghai], 2025-11-11T23:59:59+08:00[Asia/Shanghai]
            # VIP用户Cookie
            - Cookie=userLevel, vip
            # POST请求
            - Method=POST

只有同时满足「路径正确 + 活动时间内 + VIP 用户 + POST 方法」四个条件,才会命中这条秒杀路由。

4.2 常用局部过滤器

局部过滤器只对当前路由生效,需要配置在对应路由的 filters 下。

1. StripPrefix 路径截取(最高频)

作用:去掉 URL 中前面的 N 层路径前缀。

bash 复制代码
filters:
  - StripPrefix=2

请求路径 /api/biz/user/1,截取 2 层前缀后,转发到后端的路径变成 /user/1

为什么要截前缀:网关层用前缀区分不同服务,后端服务本身不需要这个前缀,所以要截掉,是最常用的过滤器,几乎每条路由都会配。

2. AddRequestHeader / AddResponseHeader

作用:给请求 / 响应添加自定义头,常用于传递网关标识、灰度标记、链路追踪 ID。

bash 复制代码
filters:
  - AddRequestHeader=gateway-route, user-service
  - AddResponseHeader=system-version, v1.2.0
3. PrefixPath

作用:给请求路径统一添加前缀,和 StripPrefix 相反。

bash 复制代码
filters:
  - PrefixPath=/inner

请求路径 /user/1,转发时变成 /inner/user/1

4. Retry 重试

作用:后端调用失败时自动重试,适合幂等接口。

bash 复制代码
filters:
  - name: Retry
    args:
      retries: 2  # 重试次数
      statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE  # 触发重试的状态码
      methods: GET  # 只对GET请求重试,避免写接口重复执行

4.3 全局过滤器与自定义鉴权

全局过滤器对所有路由生效,不用每条路由单独配置,适合做全局限流、鉴权、日志、请求 ID 生成等通用逻辑。

内置的核心全局过滤器:

  • LoadBalancerClientFilter:解析 lb:// 前缀,调用负载均衡器选择实例,把服务名替换成实际 IP 端口
  • NettyRoutingFilter:真正发起 HTTP 请求转发的过滤器,是过滤器链的最后一步
  • NettyWriteResponseFilter:把后端返回的响应写回给客户端
实战:自定义全局鉴权过滤器

所有请求必须携带有效 token 才能放行,是网关最常用的能力之一。

java 复制代码
/**
 * 全局鉴权过滤器
 * 实现GlobalFilter + Ordered接口,所有路由自动生效
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 白名单路径直接放行,比如登录、验证码接口
        // 这种可以放配置文件里面动态读取
        if (path.startsWith("/api/user/login") || path.startsWith("/api/common/captcha")) {
            return chain.filter(exchange);
        }

        // 从请求头获取token
        List<String> tokenList = request.getHeaders().get("token");
        if (tokenList == null || tokenList.isEmpty()) {
            // 没有token,返回401未授权
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            
            String result = "{\"code\":401,\"message\":\"未登录,请先登录\"}";
            DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }

        String token = tokenList.get(0);
        // 这里可以调用鉴权服务、校验token有效性
        // 校验通过,把用户ID放到请求头里传给下游服务
        ServerHttpRequest newRequest = request.mutate()
                .header("userId", parseUserIdFromToken(token))
                .build();

        return chain.filter(exchange.mutate().request(newRequest).build());
    }

    @Override
    public int getOrder() {
        // 顺序值越小越先执行,鉴权要放在最前面,比限流、转发都早
        return -100;
    }

    private String parseUserIdFromToken(String token) {
        // 实际项目里解析JWT或者调用用户中心校验
        return "1001";
    }
}

返回响应的时候要用响应式的 Mono 写法,不能像 MVC 里直接 PrintWriter 输出,因为 Gateway 是 WebFlux 异步模型。

五、底层核心原理

5.1 请求处理全链路源码拆解

一次请求从进入网关到转发出去,核心经过三个核心组件,按顺序执行:

第一步:DispatcherHandler 分发请求

它是 WebFlux 的总请求分发器,相当于 Spring MVC 里的 DispatcherServlet。所有请求进来先到这里,它会遍历所有的 HandlerMapping,找到能处理当前请求的那个。 Gateway 自定义的 RoutePredicateHandlerMapping 就是其中一个 HandlerMapping,专门负责路由匹配。

第二步:RoutePredicateHandlerMapping 匹配路由

核心方法 getHandlerInternal,做两件事:

  1. RouteLocator 里拿到所有已加载的路由定义
  2. 按配置顺序遍历每条路由,调用路由上所有断言的 test() 方法
  3. 找到第一条全部断言都匹配成功的路由,包装后返回给 DispatcherHandler

为什么是第一条匹配就返回?因为路由是有优先级的,配置顺序就是优先级顺序,前面的优先级高,匹配上就不再往后找,这也是为什么精确路由要写在宽泛路由前面的原因。

第三步:FilteringWebHandler 执行过滤器链

拿到匹配的路由后,由 FilteringWebHandler 来执行,核心逻辑:

  1. 收集所有全局过滤器 + 当前路由的所有局部过滤器
  2. order 值从小到大排序,组成完整的过滤器链
  3. 依次执行每个过滤器的 filter 方法,形成责任链调用
  4. 最终执行到 NettyRoutingFilter,通过 Netty 客户端向后端服务发起真实的 HTTP 请求
  5. 后端返回响应后,按倒序执行每个过滤器的后置逻辑

5.2 路由加载与动态刷新

所有路由定义都由 RouteDefinitionLocator 负责加载,这是一个顶层接口,有多个实现:

  • PropertiesRouteDefinitionLocator:从 application.yml 配置文件读取路由
  • DiscoveryClientRouteDefinitionLocator:从注册中心自动生成路由(开启动态路由时用)
  • 支持从 Nacos、Apollo 等配置中心加载路由,实现动态刷新

动态刷新原理: Gateway 支持监听配置变更事件,收到变更后会重新调用 RouteLocator 加载路由,更新内存中的路由缓存,不用重启网关就能生效,生产环境做路由变更非常方便。

六、生产级高级特性

6.1 网关限流(Redis 令牌桶实现)

Gateway 内置了基于 Redis 的令牌桶限流过滤器,生产环境最常用。原理是每秒生成固定数量的令牌放到桶里,每个请求拿一个令牌,拿不到就被限流。

第一步:引入依赖
XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
第二步:配置限流规则
bash 复制代码
spring:
  redis:
    host: 127.0.0.1
    port: 6379
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100  # 每秒生成令牌数(平均QPS)
                redis-rate-limiter.burstCapacity: 200 # 令牌桶最大容量(峰值QPS)
                key-resolver: "#{@ipKeyResolver}"     # 限流维度的Bean
第三步:定义限流维度
java 复制代码
/**
 * 按IP地址限流
 * 也可以按用户ID、按接口路径等自定义维度
 */
@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
    );
}

在 Gateway 中,如果配置了 RequestRateLimiter 过滤器,它就需要知道"到底把谁算作一个用户"。

  • 如果不配置 KeyResolver :Gateway 会把所有请求的流量混在一起统计(比如限流 100 QPS,是所有用户加起来只能有 100 QPS)。

  • 配置了这段代码后 :Gateway 会以 IP 为维度来统计流量。

举个例子

你配置了全局限流为 10 次/秒

  • 用户 A(IP:192.168.1.1)一秒内请求了 10 次 → 触发限流,返回 429。

  • 用户 B(IP:192.168.1.2)在同一秒内也请求了 10 次 → 不受影响 ,正常通过。

    因为 KeyResolver 返回的是 IP,Gateway 内部会把这个 IP 作为 Redis(或本地内存)计数器的 Key,各个 IP 的计数器是互相隔离的。

6.2 整合 Sentinel 限流熔断

如果需要更灵活的限流、熔断降级,可以直接整合 Sentinel,和微服务体系统一管控。 引入 sentinel-spring-cloud-gateway-adapter 依赖后,配置网关流控规则、降级规则,支持按路由、按 API 分组限流,支持慢调用熔断、异常比例熔断,和之前 Sentinel 笔记的逻辑完全一致。

6.3 全局跨域配置

网关层统一处理跨域,后端服务就不用再配了,推荐用代码配置,比 YAML 兼容性更好。

java 复制代码
@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 允许的跨域源,生产环境不要写*,指定具体域名
        config.addAllowedOriginPattern("*");
        // 允许所有请求头
        config.addAllowedHeader("*");
        // 允许所有请求方法
        config.addAllowedMethod("*");
        // 允许携带凭证(Cookie)
        config.setAllowCredentials(true);
        // 预检请求缓存时间,3600秒内不用再发预检请求
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

6.4 全局异常处理

Gateway 是 WebFlux 架构,不能用 Spring MVC 的 @RestControllerAdvice 处理异常,需要自定义异常处理器。

默认网关报错会返回白标签错误页,很不友好,自定义后统一返回 JSON 格式,和后端服务的返回格式对齐。核心是实现 ErrorWebExceptionHandler 接口,重写异常处理逻辑,对限流异常、404、500 等做不同的提示。

七、真实项目踩坑汇总

1. 启动失败:和 Spring MVC 冲突

现象:启动时报错 Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway。 原因:引入了 spring-boot-starter-web,或者间接依赖里带了 web。

解决:排除 web 依赖,确保只有 spring-boot-starter-webflux。如果是公共依赖包带进来的,用 exclusion 排除掉。

2. StripPrefix 路径截取错误

现象:转发后后端报 404,抓包发现路径不对。

原因:StripPrefix 的数值和实际前缀层数不匹配,比如有两层前缀只截了一层。

解决:数清楚网关路径有几层前缀,严格对应;复杂路径可以用 Postman 先测单条路由,确认转发路径正确。

3. 请求体只能读取一次

现象:自定义过滤器里读取了请求体做日志、参数校验,转发到后端后后端拿不到请求体。

原因:WebFlux 的请求体是数据流,只能消费一次,读取过就没了。

解决:用 ServerHttpRequestDecorator 包装请求,把请求体缓存起来,下游读取的时候重新生成流。注意大文件请求不要缓存,会占内存。

4. 跨域配置不生效

现象:前端浏览器报跨域错误,明明配了 CorsFilter。 常见原因:

  • 过滤器顺序太低,被其他过滤器抢先返回了错误响应,没走到跨域过滤器
  • 同时配置了 YAML 跨域和代码跨域,配置冲突
  • 后端服务也配了跨域,重复设置导致浏览器拦截 解决:代码配置设置最高优先级,只在网关层做跨域,后端服务关闭跨域配置。

5. 大文件上传超时 / 失败

现象:上传几兆的文件就报错或者超时。

原因:Gateway 默认对请求体大小有限制,内存缓冲区默认不大。

解决:调大 spring.codec.max-in-memory-size 参数,同时调整 Netty 的超时时间;超大文件建议走对象存储直传,不要走网关。

6. 过滤器执行顺序混乱

现象:有时候鉴权在限流后面,有时候又在前面,行为不稳定。

原因:自定义过滤器没指定 order,默认顺序不可控。

解决:所有自定义过滤器都实现 Ordered 接口,显式指定 order 值。约定好顺序规范:鉴权(-100)→ 日志(-50)→ 限流(0)→ 业务过滤器 → 转发(100+)。

7. 服务名路由 404

现象:配置了 lb://user-service 但报 404 或者找不到服务。 排查顺序:

  1. 检查服务有没有正确注册到 Nacos,服务名是不是完全一致(大小写敏感)
  2. 检查网关有没有引入 nacos discovery 依赖
  3. 检查 uri 是不是 lb:// 开头,不是 http://
  4. 检查路径截取是不是正确,后端有没有对应的接口

八、复习速记

  1. 核心定位:Spring 官方第二代网关,基于 Netty + WebFlux 非阻塞架构,高性能,替代 Zuul,是微服务的统一入口。
  2. 三大核心:Route 路由(转发规则)、Predicate 断言(匹配条件)、Filter 过滤器(前后置处理)。
  3. 执行流程:DispatcherHandler 分发 → 路由断言匹配 → 组装过滤器链 → 前置过滤 → Netty 转发 → 后置过滤 → 返回响应。
  4. 过滤器分类:局部过滤器单路由生效,全局过滤器全路由生效;内置负载均衡、转发等核心全局过滤器。
  5. 限流方案:内置 Redis 令牌桶限流,支持 IP、用户、路径多维度;也可整合 Sentinel 实现更丰富的流量治理。
  6. 核心红线:不能和 Spring MVC 共存;请求体只能读一次;路由按顺序匹配,精确规则放前面。
  7. 生产必做:统一鉴权、限流熔断、全局异常、跨域处理、服务发现整合、日志监控、动态路由配置。
相关推荐
鱼听禅1 小时前
ubuntu学习笔记-使用ls-ml-toolkit工具识别Label Studio数据数据训练yolo模型
笔记·学习·ubuntu
喜欢就别7 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (2)--- On-Policy Distillation
人工智能·笔记
Lhappy嘻嘻9 小时前
Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
java·笔记·php
GLDbalala12 小时前
GPU PRO 5 - 4.6 Adaptive Scalable Texture Compression 笔记
笔记
xian_wwq12 小时前
【学习笔记】模型权重管理:Safetensors与私有化Hub(23/35)
笔记·学习
其实防守也摸鱼12 小时前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式
ctrl_v助手12 小时前
Halcon学习笔记2
人工智能·笔记·学习
YUS云生13 小时前
Python学习笔记·第31天:FastAPI入门——路由、路径参数、查询参数与请求体
笔记·python·学习
铅笔侠_小龙虾14 小时前
Rust 学习目录
开发语言·学习·rust