☰
【Spring Cloud】深入探索统一网关 Gateway 的搭建,断言工厂,过滤器工厂,全局过滤器以及跨域问题:把路由配置改到 TaoToken 的实操记录
2026/10/7 20:07:28 网站建设 项目流程

1. 从零搭建 Gateway 网关:最小路由跑通与微服务结构梳理

Spring Cloud Gateway 是 Spring Cloud 生态里基于 WebFlux 的响应式网关,它承担了统一入口、路由转发、断言匹配、过滤器链处理、跨域处理这些职责。如果你正在做微服务拆分,客户端不应该直接访问每个服务的地址,而是统一打到网关,由网关根据路径、请求头、时间等条件决定转发到哪个服务。这篇内容我会按“先跑通最小路由 → 再逐个加断言工厂 → 加过滤器工厂 → 加全局过滤器 → 处理跨域 → 把下游调用 endpoint 与鉴权配置改到 TaoToken 统一通道”的顺序走一遍,每一步都给可复制的配置和验证命令。

适合谁看:正在学 Spring Cloud Gateway 的同学、需要把网关从 demo 推到可联调状态的后端、以及想把模型调用统一收敛到网关后面的开发者。核心检索词就是 Spring Cloud Gateway 搭建、断言工厂、过滤器工厂、全局过滤器、跨域配置,这几个概念会贯穿全文。

先明确一个结构:网关(gateway,端口 10010)作为唯一入口,注册到 Nacos;下游有 userservice、orderservice 两个微服务,也注册到 Nacos。网关通过lb://userservice这种写法做负载均衡转发。这个结构决定了后面所有配置都围绕spring.cloud.gateway.routes展开。

创建模块时,包结构建议是com.example.gateway,启动类:

@SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }

pom.xml 里两个关键依赖,一个是网关启动器,一个是 Nacos 服务发现:

<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>

注意 Gateway 是基于 WebFlux 的,不要同时引入spring-boot-starter-web,否则启动会报冲突,这是新手最容易踩的坑之一。最小可用的 application.yml 如下:

server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** - id: order-service uri: lb://orderservice predicates: - Path=/order/**

启动 gateway、userservice、orderservice 三个服务后,用 curl 验证:

curl -i http://127.0.0.1:10010/user/1 curl -i http://127.0.0.1:10010/order/1

返回 200 且 body 是用户/订单数据,说明最小路由通了。如果返回 503,多半是 Nacos 里没注册上,或者服务名写错;返回 404 则是断言没匹配上,检查 Path 是否写对。这一步是整个网关的地基,后面所有断言、过滤器都挂在这条路由上。

2. 断言工厂 Predicate Factory 逐个接入与命中验证

断言工厂的作用是把配置文件里的字符串规则解析成真正的路由判断条件。Spring Cloud Gateway 内置了十几种,常用的有 Path、Method、Header、Query、Cookie、Host、After、Before、Between、RemoteAddr、Weight。它们可以组合,只有全部满足才会命中该路由。

先看一个组合示例,给 orderservice 加时间和路径双重断言:

- id: order-service uri: lb://orderservice predicates: - Path=/order/** - After=2024-01-01T17:42:47.789+08:00[Asia/Shanghai]

After表示请求时间必须晚于指定时间点。如果当前时间早于它,请求不会命中这条路由,直接 404。把After换成Before,当前时间满足就会命中。这个对比很适合用来理解断言“命中/不命中”的边界。

Header 断言常用于做灰度或版本区分:

predicates: - Path=/user/** - Header=X-Request-Id, \d+

它要求请求头里必须有X-Request-Id,且值匹配正则\d+。验证:

curl -i -H "X-Request-Id: 12345" http://127.0.0.1:10010/user/1 curl -i -H "X-Request-Id: abc" http://127.0.0.1:10010/user/1

第一条命中,第二条因为不匹配正则而 404。Method 断言限制请求方法:

predicates: - Path=/order/** - Method=GET,POST

Query 断言检查查询参数:

predicates: - Path=/user/** - Query=name, Jack

表示必须带?name=Jack才命中。RemoteAddr 用于限制来源 IP:

predicates: - RemoteAddr=192.168.1.1/24

Weight 用于灰度分流,通常配合两个路由使用:

- id: user-service-v1 uri: lb://userservice predicates: - Path=/user/** - Weight=user-group, 8 - id: user-service-v2 uri: lb://userservice-v2 predicates: - Path=/user/** - Weight=user-group, 2

这样大约 80% 流量走 v1,20% 走 v2。断言工厂不需要死记,用的时候查官方文档即可,关键是理解“多个断言是 AND 关系”,任何一个不满足就整体不命中。验证断言是否命中,最直接的方式就是看返回码:200 是命中,404 是没命中,503 是命中了但下游不可用。

3. 过滤器工厂与全局过滤器:可复制配置与执行顺序

过滤器工厂(GatewayFilter Factory)负责在请求转发前后做加工,比如加请求头、改路径、限流。它配置在filters下,作用于单条路由。默认过滤器default-filters则作用于所有路由。

给 userservice 加一个请求头:

- id: user-service uri: lb://userservice predicates: - Path=/user/** filters: - AddRequestHeader=Hello, GatewayFilterFactory

下游 Controller 里接收这个头:

@GetMapping("/{id}") public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "Hello", required = false) String hello) { System.out.println("Hello: " + hello); return userService.queryById(id); }

重启后访问,控制台会打印Hello: GatewayFilterFactory。如果想让所有路由都带上,用默认过滤器:

spring: cloud: gateway: default-filters: - AddRequestHeader=Hello, GatewayFilterFactory

常用的还有AddResponseHeader、RewritePath、SetPath、RequestRateLimiter。比如重写路径:

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

这样/api/user/1会被改写成/user/1再转发。

全局过滤器(GlobalFilter)需要用代码实现,作用于所有请求。下面这个做简单的鉴权,检查authorization参数是否为admin:

@Component public class AuthorizeFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String authorization = exchange.getRequest().getQueryParams().getFirst("authorization"); if ("admin".equals(authorization)) { return chain.filter(exchange); } exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } @Override public int getOrder() { return -1; } }

验证:

curl -i http://127.0.0.1:10010/user/1 curl -i "http://127.0.0.1:10010/user/1?authorization=admin"

第一条返回 401,第二条返回 200。执行顺序由Ordered的getOrder()决定,值越小越先执行。三类过滤器(路由过滤器、DefaultFilter、GlobalFilter)最终都会被适配成GatewayFilter,合并进同一个过滤器链,按 order 排序执行。order 相同时,顺序是 DefaultFilter > 路由过滤器 > GlobalFilter。理解这一点,排查“为什么我的过滤器没生效”时就有方向了。

4. 跨域 CORS 配置与预检请求验证

跨域是浏览器安全策略导致的:协议、域名、端口三者任一不同就算跨域。浏览器对非简单请求会先发一个 OPTIONS 预检请求,服务器返回合适的 CORS 响应头后,浏览器才发真实请求。Gateway 里统一处理跨域,比在每个微服务里配更省事。

在 gateway 的 application.yml 加:

spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true corsConfigurations: '[/**]': allowedOrigins: - "http://localhost:5500" allowedMethods: - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" allowCredentials: true maxAge: 360000

参数清单说明:allowedOrigins是允许跨域的源,生产环境不要写*配合allowCredentials: true,浏览器会拒绝;allowedMethods列出允许的方法,OPTIONS 必须包含;allowedHeaders允许携带的头;allowCredentials是否允许带 Cookie;maxAge是预检结果缓存时间,单位毫秒。

前端页面用 axios 请求:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> axios.get("http://localhost:10010/user/1?authorization=admin") .then(resp => console.log(resp.data)) .catch(err => console.log(err)) </script>

用 Live Server 起在 5500 端口,打开 DevTools 的 Network 面板,能看到先有一个 OPTIONS 请求返回 200,响应头里有Access-Control-Allow-Origin: http://localhost:5500,随后才是 GET 请求。如果 OPTIONS 返回 403 或没有 CORS 头,检查add-to-simple-url-handler-mapping是否为 true,以及 allowedOrigins 是否和页面源完全一致(包括端口)。

5. 常见报错排查:401、404、503 与 CORS 预检失败

这一节把前面可能遇到的报错集中对照一遍,方便你快速定位。

401 Unauthorized:全局过滤器拦截了。检查请求是否带了authorization=admin,或者过滤器里的判断逻辑是否写反。如果用了 JWT,还要确认 token 解析没抛异常。

404 Not Found:断言没命中。常见原因是 Path 写错、After/Before 时间不满足、Header/Query 条件缺失。排查方法是在 gateway 日志里打开 debug:

logging: level: org.springframework.cloud.gateway: debug

日志会打印匹配了哪些路由、命中了哪些断言。

503 Service Unavailable:断言命中了,但下游不可用。检查 Nacos 里服务是否注册、服务名是否和lb://后面一致、下游端口是否被占用。如果下游是模型调用类服务,还要确认 endpoint 和鉴权配置是否正确。

CORS 预检失败:OPTIONS 请求被拦截或没返回 CORS 头。确认add-to-simple-url-handler-mapping: true,allowedOrigins 精确匹配,allowedMethods 包含 OPTIONS。如果全局过滤器对 OPTIONS 也做鉴权,需要放行 OPTIONS 请求,否则预检直接 401。

OAuth/token 相关报错:如果下游调用需要鉴权,把鉴权信息统一在网关层注入请求头,而不是让每个微服务各自处理。这样下游服务只认网关加的头,安全边界更清晰。

6. 把下游调用 endpoint 与鉴权配置改到 TaoToken 统一通道

前面网关的路由、断言、过滤器、跨域都跑通了,接下来把下游对模型服务的调用收敛到统一通道。TaoToken 提供统一的 API 入口,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。把模型调用的 Base URL、Key、Model ID 三件套配好,下游服务就不用各自维护不同的 endpoint。

在网关路由里,可以把模型调用单独作为一条路由:

- id: model-service uri: lb://model-service predicates: - Path=/ai/** filters: - StripPrefix=1

下游 model-service 的配置里,把模型 endpoint 指向 TaoToken:

model: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model-id: your-model-id

如果你用的是 Claude Code 这类编码工具,配置思路一样:Base URL 填 https://taotoken.net/api ,Key 填申请到的 API Key,Model ID 填对应模型。三件套缺一不可,只填 Base URL 不填 Key 会报 401,Key 对了但 Model ID 写错会报模型不存在。

验证请求:

curl -i https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"your-model-id","messages":[{"role":"user","content":"ping"}]}'

返回 200 且有 choices 字段,说明通道通了。如果报reading choices相关错误,通常是响应体不是预期 JSON,检查 Base URL 是否多了或少了/v1。如果报 local proxy failed,检查网络出口和 DNS。如果报 OAuth 相关错误,确认 Key 的类型和权限范围。

把 Key 管理放到网关层,下游服务通过内部请求头传递,避免 Key 散落在各个服务里。需要申请 Key 或查看接入文档,可以走 API Keys 页面和接入文档;想先验证模型效果,用模型对话页面;如果是长期编码或 Agent 场景,看 Coding Plan。这样网关负责统一入口和鉴权,TaoToken 负责统一模型通道,职责清晰,排查也简单。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询