Java框架 SpringCloud 快速入门: Gateway 路由的过滤器配置

概述

断言决定请求"走不走这条路由",过滤器决定"走过去的时候对请求和响应做哪些加工"。这一节把 GatewayFilter 的两个作用域(路由过滤器、默认过滤器)和常用过滤器工厂的 yaml 写法一次讲透,顺带说清它跟 GlobalFilter 到底差在哪,避免把两者混成一个东西。

纲要

  • 过滤器在网关链路里的位置:断言 → 过滤器链 → 微服务 → 过滤器链 → 响应
  • 两类作用域
    • 路由过滤器(routes[].filters):只对当前这条路由生效
    • 默认过滤器(spring.cloud.gateway.default-filters):对所有路由生效,但它不是 GlobalFilter
  • 常用过滤器工厂
    • AddRequestHeader / AddRequestParameter / AddResponseHeader
    • StripPrefix / PrefixPath / RewritePath
    • SetStatus
  • 参数写法 :- Name=参数,逗号分隔多参数,= 与值之间不留空格
  • 动手验证:给 user-service 加请求头回显接口,再用 curl 走网关观察
  • 执行顺序:route filters 与 default-filters 的关系(讲义口径)
  • 实战坑 :StripPrefix=1 数字算错、值里带逗号被截断、缩进写错、GatewayFilter 当成 GlobalFilter

过滤器在网关链路里的位置

先把这个组件放到链路里看。用户请求不能直连微服务,必须先过网关(本项目 gateway 端口 10010)。网关内部是两段工作:

  1. 路由断言(Predicate) :根据配置规则判断这条请求该交给哪个微服务。- Path=/user/** 命中 user-service,- Path=/order/** 命中 order-service。
  2. 路由过滤器(GatewayFilter) :断言匹配成功之后,请求不是立刻发往下游,而是先穿过一条过滤器链,链上每个过滤器都能对请求做加工;下游返回响应后,响应同样要再穿过过滤器链才回到客户端。

#mermaid-svg-EgJcyjYseWwexE7i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EgJcyjYseWwexE7i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EgJcyjYseWwexE7i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EgJcyjYseWwexE7i .error-icon{fill:#552222;}#mermaid-svg-EgJcyjYseWwexE7i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EgJcyjYseWwexE7i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EgJcyjYseWwexE7i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EgJcyjYseWwexE7i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EgJcyjYseWwexE7i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EgJcyjYseWwexE7i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EgJcyjYseWwexE7i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EgJcyjYseWwexE7i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EgJcyjYseWwexE7i .marker.cross{stroke:#333333;}#mermaid-svg-EgJcyjYseWwexE7i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EgJcyjYseWwexE7i p{margin:0;}#mermaid-svg-EgJcyjYseWwexE7i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EgJcyjYseWwexE7i .cluster-label text{fill:#333;}#mermaid-svg-EgJcyjYseWwexE7i .cluster-label span{color:#333;}#mermaid-svg-EgJcyjYseWwexE7i .cluster-label span p{background-color:transparent;}#mermaid-svg-EgJcyjYseWwexE7i .label text,#mermaid-svg-EgJcyjYseWwexE7i span{fill:#333;color:#333;}#mermaid-svg-EgJcyjYseWwexE7i .node rect,#mermaid-svg-EgJcyjYseWwexE7i .node circle,#mermaid-svg-EgJcyjYseWwexE7i .node ellipse,#mermaid-svg-EgJcyjYseWwexE7i .node polygon,#mermaid-svg-EgJcyjYseWwexE7i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EgJcyjYseWwexE7i .rough-node .label text,#mermaid-svg-EgJcyjYseWwexE7i .node .label text,#mermaid-svg-EgJcyjYseWwexE7i .image-shape .label,#mermaid-svg-EgJcyjYseWwexE7i .icon-shape .label{text-anchor:middle;}#mermaid-svg-EgJcyjYseWwexE7i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EgJcyjYseWwexE7i .rough-node .label,#mermaid-svg-EgJcyjYseWwexE7i .node .label,#mermaid-svg-EgJcyjYseWwexE7i .image-shape .label,#mermaid-svg-EgJcyjYseWwexE7i .icon-shape .label{text-align:center;}#mermaid-svg-EgJcyjYseWwexE7i .node.clickable{cursor:pointer;}#mermaid-svg-EgJcyjYseWwexE7i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EgJcyjYseWwexE7i .arrowheadPath{fill:#333333;}#mermaid-svg-EgJcyjYseWwexE7i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EgJcyjYseWwexE7i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EgJcyjYseWwexE7i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EgJcyjYseWwexE7i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EgJcyjYseWwexE7i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EgJcyjYseWwexE7i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EgJcyjYseWwexE7i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EgJcyjYseWwexE7i .cluster text{fill:#333;}#mermaid-svg-EgJcyjYseWwexE7i .cluster span{color:#333;}#mermaid-svg-EgJcyjYseWwexE7i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EgJcyjYseWwexE7i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EgJcyjYseWwexE7i rect.text{fill:none;stroke-width:0;}#mermaid-svg-EgJcyjYseWwexE7i .icon-shape,#mermaid-svg-EgJcyjYseWwexE7i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EgJcyjYseWwexE7i .icon-shape p,#mermaid-svg-EgJcyjYseWwexE7i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EgJcyjYseWwexE7i .icon-shape .label rect,#mermaid-svg-EgJcyjYseWwexE7i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EgJcyjYseWwexE7i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EgJcyjYseWwexE7i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EgJcyjYseWwexE7i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不匹配
匹配 user-service
客户端
路由断言

Path=/user/**
下一条路由

Path=/order/**
过滤器 1
过滤器 2
过滤器 N
user-service

8081/8082
过滤器 N 处理响应
过滤器 2 处理响应
过滤器 1 处理响应

关键在"双向"这两个字:GatewayFilter 既能改进入网关的请求 (请求头、请求参数、路径),也能改微服务返回的响应(响应头、状态码)。讲义对它的定义是"GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理",这句话把职责边界画得很清楚。

断言和过滤器的分工,用一张表直接对比:

维度 路由断言 Predicate 路由过滤器 GatewayFilter
回答的问题 请求要不要走这条路由 走过去时怎么加工请求/响应
配置位置 routes[].predicates routes[].filters 或 default-filters
作用时机 路由匹配阶段 路由确定之后、请求转发前后
能否改内容 不能,只做判断 能,改头/改参数/改路径/改状态码
命中失败的后果 换下一条路由,全不中则 404 不影响匹配,只影响加工结果

两类作用域:路由过滤器与默认过滤器

Spring Cloud Gateway 内置了 31 种路由过滤器工厂,但它们按配置位置分两类,作用范围完全不同,这是本篇的骨架。

text 复制代码
spring:
  cloud:
    gateway:
      routes:                      # ---------- 路由列表 ----------
        - id: user-service
          uri: lb://userservice
          predicates:
            - Path=/user/**
          filters:                 # 路由过滤器:只对 user-service 这条路由生效
            - AddRequestHeader=Truth,Itcast is freaking awesome!
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
      default-filters:             # 默认过滤器:对所有路由生效(与 routes 同级)
        - AddRequestHeader=Truth,Itcast is freaking awesome!

对照表格:

维度 路由过滤器(局部) 默认过滤器 default-filters
配置位置 routes[].filters spring.cloud.gateway.default-filters,与 routes 同级
生效范围 只对当前这一条路由 对所有路由都生效
典型用途 某条路由要特殊改造(改路径、加专属头) 全站统一改造(统一鉴权头、统一响应头)
会不会重复 不会 会与 route filters 叠加执行
与 GlobalFilter 的关系 完全无关,都是配置式的 仍然不是 GlobalFilter,两者不是一回事

最后一行要单独说。很多资料把 default-filters 叫成"全局过滤器",这是错的。真正的 GlobalFilter 是一个编程式组件 ,需要你自己写类去实现 GlobalFilter 和 Ordered 接口,写 Java 代码决定业务逻辑;而 default-filters 只是把配置式过滤器的作用域从"一条路由"放宽到"所有路由",逻辑仍然是 Spring 内置工厂的固定逻辑,你改不了它干什么。想拦截请求做自己的业务(比如鉴权、限流判定),default-filters 做不到,必须写 GlobalFilter。GlobalFilter 是下一篇的内容,这里先把边界划清。

前面给的 yaml 不是编的,是 cloud-demo 工程终态里真实的网关配置,两个讲义阶段的工程写法一致:

yaml 复制代码
server:
  port: 10010
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: nacos:8848 # nacos地址
    gateway:
      routes:
        - id: user-service # 路由标示,必须唯一
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
      default-filters:
        - AddRequestHeader=Truth,Itcast is freaking awesome!

讲义里同一件事的写法(3.4.2 请求头过滤器 + 3.4.3 默认过滤器)是先局部后全局两步走:

yaml 复制代码
# 只对 userservice 生效的局部写法
spring:
  cloud:
    gateway:
      routes:
      - id: user-service
        uri: lb://userservice
        predicates:
        - Path=/user/**
        filters: # 过滤器
        - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
yaml 复制代码
# 改写到 default 下,对所有路由生效
spring:
  cloud:
    gateway:
      routes:
      - id: user-service
        uri: lb://userservice
        predicates:
        - Path=/user/**
      default-filters: # 默认过滤项
      - AddRequestHeader=Truth, Itcast is freaking awesome!

注意工程里是 AddRequestHeader=Truth,Itcast is freaking awesome!(逗号后没有空格),讲义里是 AddRequestHeader=Truth, Itcast is freaking awesome!(逗号后有一个空格)。两种都能跑,因为逗号才是参数分隔符,空格会被当作值的一部分。但如果你希望下游拿到的是干净的 Itcast is freaking awesome!,就别在逗号后加空格------加了之后值就变成 Itcast is freaking awesome!(前面多一个空格)。工程里没加空格是有意为之。

常用过滤器工厂

AddRequestHeader:加请求头

最典型也最有价值的用途是网关鉴权后把用户身份透传给下游 。网关统一校验 token,校验通过后把解析出的 userId 塞进请求头,下游微服务直接从请求头拿身份,各自不用再解析一遍 token,也不用把鉴权逻辑复制到每个服务。课程里的示例更简单,加一个固定头 Truth:

yaml 复制代码
filters:
  - AddRequestHeader=Truth,Itcast is freaking awesome!

格式是 AddRequestHeader=<头名>,<头值>。头名 Truth,头值 Itcast is freaking awesome!。

AddRequestParameter:加请求参数

给下游请求追加查询参数,格式 AddRequestParameter=<参数名>,<参数值>:

yaml 复制代码
filters:
  - AddRequestParameter=red,blue

下游最终看到的是 ?red=blue。

AddResponseHeader:加响应头

加工的是返回路径上的响应头,格式 AddResponseHeader=<头名>,<头值>:

yaml 复制代码
filters:
  - AddResponseHeader=X-Response-Red,Blue

常用于统一加跨域头、链路追踪 ID、版本标识之类。

StripPrefix:去掉路径前缀

Gateway 暴露给外部的路径,往往和下游服务真实的接口路径不一致,这时候要把多出来的前缀剥掉。StripPrefix 的参数是去掉的路径段数,不是前缀字符串。

假设网关对外暴露 /api/user/1,而 user-service 的接口是 /user/{id},那么 /api 这一段要去掉:

yaml 复制代码
filters:
  - StripPrefix=1

请求前后路径对比:

阶段 路径 说明
客户端请求 /api/user/1 对外路径,带 /api 前缀
StripPrefix=1 之后 /user/1 去掉 1 段,正好命中下游 /user/{id}
下游收到 /user/1 与 Controller 的 @RequestMapping("/user") + @GetMapping("/{id}") 对上

这里有个必须说清的点:cloud-demo 工程里 gateway 的路由是 - Path=/user/**,转发后路径原样保持 /user/1,而 user-service 的 Controller 恰好也是 @RequestMapping("/user") 下的 @GetMapping("/{id}"),两边天然对齐。这种情况下不要配 StripPrefix 。如果手滑加了 - StripPrefix=1,路径会变成 /1,下游找不到映射,直接 404。StripPrefix 是给"网关路径比下游多一层"的场景用的,只在真有多余前缀时才加。

PrefixPath:加路径前缀

和 StripPrefix 反向操作,给转发给下游的路径前面补一段:

yaml 复制代码
filters:
  - PrefixPath=/user

配了它之后,客户端请求 /1,下游实际收到 /user/1。用于网关省略前缀、由网关补齐的场景。

RewritePath:正则改写路径

前缀增删不够用的时候用正则改写,格式 RewritePath=<匹配正则>,<替换表达式>,配合命名捕获组:

yaml 复制代码
filters:
  - RewritePath=/api/?(?<segment>.*), /$\{segment}

(?<segment>.*) 把 /api/ 后面的部分捕获下来,替换表达式中用 ${segment} 引用。注意 yaml 里要写成 $\{segment} ,那个 \ 是必须的,Spring 在解析属性占位符时要靠它区分,只写 ${segment} 会被当成配置占位符而报错。这个写法跟 StripPrefix 效果类似,但更灵活------可以只改中间某一段,或者做新旧接口路径的兼容映射。

SetStatus:设置响应状态码

直接改写返回给客户端的 HTTP 状态码:

yaml 复制代码
filters:
  - SetStatus=401

常用于按路由维度做灰度或降级,比如某条路由临时下线,直接把状态改成 503。

速查表

工厂名 作用 参数 示例
AddRequestHeader 给下游请求加请求头 头名,头值 - AddRequestHeader=Truth,Itcast is freaking awesome!
RemoveRequestHeader 移除请求头 头名 - RemoveRequestHeader=Cookie
AddRequestParameter 给下游请求加查询参数 参数名,参数值 - AddRequestParameter=red,blue
AddResponseHeader 给响应加响应头 头名,头值 - AddResponseHeader=X-Response-Red,Blue
RemoveResponseHeader 移除响应头 头名 - RemoveResponseHeader=X-Request-Id
StripPrefix 去掉路径前 N 段 段数 - StripPrefix=1
PrefixPath 给路径加前缀 前缀 - PrefixPath=/user
RewritePath 正则改写路径 正则,替换表达式 - RewritePath=/api/?(?<segment>.*), /$\{segment}
SetStatus 设置响应状态码 状态码 - SetStatus=401
RequestRateLimiter 限制请求流量 限流器参数 - RequestRateLimiter=...

讲义 3.4.1 列出的示例清单是 AddRequestHeader、RemoveRequestHeader、AddResponseHeader、RemoveResponseHeader、RequestRateLimiter,一共 31 种,不用全背;名字基本能自解释,真用到的时候去官网查具体语法即可。

参数写法:- Name=参数 的格式细节

这一块的坑最多,先把正确写法摆出来:

yaml 复制代码
filters:
  - AddRequestHeader=Truth,Itcast is freaking awesome!

拆开看:

  • 列表里每个过滤器是一个字符串 ,- 后面整体是 Name=参数 形式,不是嵌套的 map。
  • 参数之间用英文逗号 分隔,Truth 是第一个参数,Itcast is freaking awesome! 是第二个。
  • AddRequestHeader 和 Truth 之间的 = 两边不要有空格 。写成 AddRequestHeader = Truth,... 会让 = 前多出空格,过滤器名解析不出来,配置直接不生效。

正确的和错误的对照:

写法 结果
- AddRequestHeader=Truth,Itcast is freaking awesome! ✅ 正常,头名 Truth
- AddRequestHeader = Truth, Itcast is freaking awesome! ❌ = 前有空格,过滤器名解析失败
- AddRequestHeader: Truth, Itcast is freaking awesome! ❌ 用冒号写成 map,不生效
- AddRequestHeader=Truth;Itcast is freaking awesome! ❌ 用分号而非逗号,参数分不开
- AddRequestHeader=Truth,Itcast,is,freaking,awesome! ❌ 值里含逗号,被截成多个参数,值只剩 Itcast

最后一行是最隐蔽的坑:值本身如果带逗号,会被当成参数分隔符截断 。AddRequestHeader 的头值想带逗号,这个写法就撑不住了,得改用编程式过滤器在代码里拼。请求头的值里带逗号的场景并不罕见(比如 Accept: text/html,application/json),配置式写法在这里是有天花板的。

动手验证

验证分两半:下游要能回显收到的请求头,网关侧改配置重启,然后 curl 观察。

工程结构上,改的是 gateway 模块的 application.yml,回显接口加在 user-service 模块:

text 复制代码
cloud-demo/
├── gateway/                                # 网关服务,端口 10010
│   └── src/main/resources/
│       └── application.yml                 # 改这里:filters / default-filters
├── user-service/                           # 用户服务,端口 8081
│   └── src/main/java/cn/itcast/user/web/
│       └── UserController.java             # 加这里的请求头回显
├── order-service/                          # 订单服务
├── feign-api/
└── eureka-server/

user-service 的 UserController 加一个 @RequestHeader 参数,把网关加的头接出来打印:

java 复制代码
package cn.itcast.user.web;

import cn.itcast.user.config.PatternProperties;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private PatternProperties properties;

    @GetMapping("now")
    public String now() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat()));
    }

    /**
     * 路径: /user/110
     *
     * @param id    用户id
     * @param truth 网关透传的请求头,可能为空
     */
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id,
                          @RequestHeader(value = "Truth", required = false) String truth) {
        System.out.println("truth: " + truth);
        return userService.queryById(id);
    }
}

required = false 是必须的------不是所有请求都会带这个头,不写的话上游没传就抛 MissingRequestHeaderException。

网关配置确认成 default-filters 版本(就是对所有路由生效那一份),重启 gateway 和 user-service,然后走网关发请求:

bash 复制代码
# 走网关访问用户服务,网关会补上 Truth 请求头
curl -i http://localhost:10010/user/1

预期:

观察点 预期结果
响应状态 200 OK,正常返回用户 JSON
user-service 控制台 打印 truth: Itcast is freaking awesome!
再访问 http://localhost:8082/user/2(另一实例) 同样打印,证明 default-filters 对所有实例、所有路由都生效
直连 http://localhost:8081/user/1(绕过网关) 打印 truth: null,证明头是网关加的,不是客户端带的

最后一行是排查的照妖镜:如果直连也打印出了值,说明头不是网关加的,可能是客户端自己带的。想再验证"客户端给的头会被覆盖"这一条,可以这样测:

bash 复制代码
# 客户端自己带一个 Truth 头,看网关是保留还是覆盖
curl -i -H "Truth: client-value" http://localhost:10010/user/1
# 控制台仍然是 Itcast is freaking awesome!,网关的头覆盖了客户端传的

想验证响应头一侧,把配置换成 AddResponseHeader 再 curl 加 -i 看响应头:

yaml 复制代码
default-filters:
  - AddResponseHeader=X-Gateway,cloud-demo
bash 复制代码
curl -i http://localhost:10010/user/1 | grep -i x-gateway
# 预期输出:x-gateway: cloud-demo

执行顺序

同一条路由的 filters 列表里配了多个过滤器时,按声明顺序执行 ,写在上面先执行,写在下面后执行。这一点直接影响结果------比如你先 AddRequestHeader 再 RemoveRequestHeader 同一个头,那个头就会被删掉;顺序反了就删不掉。

至于 default-filters 和 route filters 的先后关系,讲义 3.5.3 过滤器执行顺序 给的口径是:

  • 请求进入网关会碰到三类过滤器:当前路由的过滤器 、DefaultFilter 、GlobalFilter。
  • 三类会合并到一条过滤器链里排序后依次执行,每个过滤器带一个 int 型 order,order 越小越先执行。
  • 路由过滤器和 DefaultFilter 的 order 由 Spring 指定,默认按声明顺序从 1 递增。
  • 当 order 值相同时,执行顺序是 defaultFilter > 路由过滤器 > GlobalFilter。

源码层面:RouteDefinitionRouteLocator#getFilters() 先加载 defaultFilters、再加载某条 route 的 filters,然后合并;FilteringWebHandler#handle() 再把 GlobalFilter 合进来统一按 order 排序。完整的排序规则和 GlobalFilter 的 order 指定方式放在下一篇(过滤器链执行顺序)展开,这里先记住结论。

实战坑

  • StripPrefix=1 的数字含义算错 。这个 1 是"去掉几段路径",不是"去掉第 1 段"。/api/user/1 配 StripPrefix=1 得到 /user/1,配 StripPrefix=2 得到 /1。工程里 gateway 路径 /user/** 与下游 /user/{id} 天然对齐,根本不该配 StripPrefix,多配一次就是 404。
  • AddRequestHeader 的值里带逗号 。逗号是参数分隔符,AddRequestHeader=A,B,C 会被拆成三个参数,实际只有前两个生效,值只剩 B。请求头带逗号的场景(Accept、Cache-Control)必须改用编程式过滤器。
  • default-filters 和 route filters 配了同名过滤器 。两边都生效,可能给同一个头加两遍;AddRequestHeader 后一次会覆盖前一次的同名头,看着像没生效,实际是顺序问题。排查时先 grep 一遍两处配置,确认没有重复声明。
  • yml 缩进写错 。filters 必须和 predicates 同级缩进在 route 下面,default-filters 必须和 routes 同级缩进在 gateway 下面。缩进错一层,Spring 直接当未知属性忽略,不报错也不生效,最容易耗时间。改完配置记得看启动日志里有没有绑定异常。
  • 把 GatewayFilter 和 GlobalFilter 混为一谈 。default-filters 配的还是内置的 GatewayFilter 工厂,逻辑固定;想写自己的鉴权/限流逻辑只能实现 GlobalFilter。两者合并进同一条链,但来源、写法、能力都不同。
  • 配置改了没重启 。gateway 的 yml 不是热加载的,filters 改动必须重启网关进程。验证前先确认重启了,否则会误判成"配置不生效"。

API 速览

配置项 位置 说明
spring.cloud.gateway.routes[].filters gateway 的 application.yml 路由过滤器列表,只对当前路由生效
spring.cloud.gateway.default-filters 与 routes 同级 默认过滤器列表,对所有路由生效
AddRequestHeader=name,value 过滤器参数 给下游请求加请求头
AddRequestParameter=name,value 过滤器参数 给下游请求加查询参数
AddResponseHeader=name,value 过滤器参数 给响应加响应头
StripPrefix=n 过滤器参数 去掉路径前 n 段
PrefixPath=/prefix 过滤器参数 给路径加前缀
RewritePath=regex,replacement 过滤器参数 正则改写路径,注意转义 $\{...}
SetStatus=code 过滤器参数 设置响应状态码
@RequestHeader(value="Truth", required=false) 下游 Controller 接收网关透传的请求头

官方文档

总结

  • GatewayFilter 处理的是双向链路:请求进网关时能改,微服务返回响应时也能改。
  • 作用域就两种。写在 routes[].filters 下只对当前路由生效,写在 default-filters 下对所有路由生效。default-filters 不是 GlobalFilter,前者是配置式固定逻辑,后者要自己写代码。
  • AddRequestHeader 的主要价值是网关鉴权后向下游透传用户身份;StripPrefix 解决网关路径与下游路径不一致的问题,段数算错就是 404。
  • 参数格式是 - Name=参数,逗号分隔、= 两侧不留空格、值里不能有逗号。
  • 同一条路由内多个过滤器按声明顺序执行;default-filters 与 route filters 会合并排序,同 order 时 defaultFilter 在前,完整规则见下一篇。
相关推荐
迅猛龙办公室1 小时前
python实现简单进度条
java·前端·python
斯内普吖1 小时前
(开源)宠物饲养系统实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源·宠物
知守观1 小时前
Spring Boot 2.1 → 3.5 迁移推演:这个 2018 年的项目会炸在哪
java·spring boot
Flynt1 小时前
800KB 的 JSON 吃掉 8.5 秒 CPU:jackson 这波修复里,最容易被漏掉的洞藏在字符串里
java·安全
后端LV1 小时前
Caffeine 快到 1450 万 ops/s,为什么我还是不敢说数据一致
java·后端
蜗牛互联网2 小时前
Java 17调用Embeddings API:余弦相似度与FAQ拒答阈值
java·开发语言·人工智能
Q26433650232 小时前
【有源码】基于SpringBoot+Vue的博物馆展览与服务一体化平台-java博物馆数字化展品展示与交互平台设计
java·vue.js·spring boot·spring·毕业设计·javaweb·课程设计
guo_wen_qiang2 小时前
项目使用sentinel遇到的问题
java·sentinel
CarIise2 小时前
IDEA与Maven基础课堂笔记
java·intellij-idea·caffe