
1. 为什么要在 Spring Cloud Gateway 里接一层统一 API 入口如果你手上有多个模型服务、多个后端微服务每个服务各自维护一套 Key、各自处理鉴权、各自写跨域配置时间一长就会变成一团乱麻。Spring Cloud Gateway 的价值就在这里它是 Spring 官方基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 打造的响应式网关目标就是给微服务架构提供一个简单有效的统一 API 路由管理方式用来替代早期的 Zuul。把它放到 AI 应用场景里思路是一样的。你可以让 Gateway 作为所有模型请求的唯一入口对外只暴露一个地址对内根据路径、请求头、参数把流量分发到不同的后端服务。身份认证、权限校验、限流、跨域这些横切关注点全部收敛到网关层做一次后面的业务服务就不用重复实现了。这篇要解决的核心问题是如何用 Spring Cloud Gateway 的路由Route和过滤器Filter机制把多个模型 API 后端统一到一个入口并通过 TaoToken 的统一 Key 和 API 通道完成请求转发与鉴权。适合已经写过 Spring Boot、想给 AI 应用加一层网关的开发者。读完你能拿到可直接复制的 RouteLocator 配置、自定义 GlobalFilter 代码以及一套验证请求是否成功的完整步骤。我试过把三个不同厂商的模型接口挂在同一个 Gateway 后面前端只需要改一个 Base URL后面换模型、加限流、改鉴权都不用动前端代码。下面按搭建顺序一步步来。2. TaoToken 统一 Key 与 API 通道的前置准备在写 Gateway 配置之前先把上游通道准备好。TaoToken 在这里扮演的是「统一 API 入口」的角色你不需要在每个后端服务里分别配置不同厂商的 Key而是通过一个统一的 Key 和统一的 API 地址来转发请求。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基础地址是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于代码里的 Base URL。你需要先拿到一个 API Key。进入控制台创建 Key 的页面在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建后复制保存后面 Gateway 的过滤器里要用它做鉴权透传。这里有个关键点要理解Gateway 本身不生产 Key它做的是「校验客户端带来的 Key 是否合法」以及「把合法的请求转发到上游」。所以你的架构里有两层 Key 概念——客户端访问 Gateway 用的 Key和 Gateway 访问上游 TaoToken 用的 Key。为了简化很多团队会让这两者一致也就是客户端直接带 TaoToken 的 KeyGateway 校验通过后原样透传给上游。模型对话的调试入口在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 你可以先用它确认 Key 能正常调通某个模型再去配 Gateway这样排障时能快速区分是 Key 的问题还是网关的问题。如果你后续要做长期的编码类或 Agent 类应用可以了解下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 配置参数以文档为准。前置准备清单JDK 17 或以上、Maven、一个能跑起来的 Spring Boot 3.x 工程、一个可用的 TaoToken API Key。Nacos 不是必须的如果你只是本地验证可以先用静态 URI 而不是服务发现。3. 可复制的 Gateway 路由与过滤器配置这一节是全文的核心给出能直接落地的配置。先建一个独立的 gateway 模块引入依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency如果你用服务发现再加 Nacos 依赖本地验证可以不加直接用 http URI。3.1 application.yml 路由配置下面这份配置把/api/chat/**转发到 TaoToken 的 API 通道同时用 default-filters 给所有请求加上鉴权头。注意uri指向 https://taotoken.net/api Path断言决定哪些请求走这条路由。server: port: 10010 spring: application: name: gateway cloud: gateway: routes: - id: taotoken-chat uri: https://taotoken.net/api predicates: - Path/api/chat/** filters: - StripPrefix1 - id: taotoken-models uri: https://taotoken.net/api predicates: - Path/api/models/** filters: - StripPrefix1 default-filters: - AddRequestHeaderX-Gateway-Source, spring-cloud-gatewayStripPrefix1的作用是转发前去掉路径的第一段。比如客户端请求/api/chat/v1/messages去掉api后变成/chat/v1/messages再拼到上游。具体去掉几段要和你上游的真实路径对齐配错了会 404这是最常见的坑之一。3.2 用 RouteLocator 写 Java 配置如果你更喜欢用代码而不是 yml可以用 RouteLocator。下面这段等价于上面的 yml方便你做动态路由Configuration public class GatewayRouteConfig { Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(taotoken-chat, r - r .path(/api/chat/**) .filters(f - f.stripPrefix(1) .addRequestHeader(X-Gateway-Source, route-locator)) .uri(https://taotoken.net/api)) .route(taotoken-models, r - r .path(/api/models/**) .filters(f - f.stripPrefix(1)) .uri(https://taotoken.net/api)) .build(); } }3.3 自定义 GlobalFilter 做鉴权路由过滤器GatewayFilter通过配置定义逻辑固定而 GlobalFilter 需要自己写代码能处理所有进入网关的请求。下面这个 AuthorizeFilter 从请求头里取 Authorization校验通过就放行否则返回 401Component public class AuthorizeFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest(); String auth request.getHeaders().getFirst(Authorization); if (auth ! null auth.startsWith(Bearer )) { return chain.filter(exchange); } exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } Override public int getOrder() { return -1; } }order 值越小优先级越高。GlobalFilter 通过实现 Ordered 接口或加 Order 注解指定顺序路由过滤器和 defaultFilter 的 order 由 Spring 按声明顺序从 1 递增。当 order 相同时执行顺序是 defaultFilter 路由过滤器 GlobalFilter。3.4 跨域配置前端直连网关时跨域是绕不开的。Gateway 用 CORS 方案配置很简单spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true corsConfigurations: [/**]: allowedOrigins: - http://localhost:8090 allowedMethods: - GET - POST - PUT - DELETE - OPTIONS allowedHeaders: * allowCredentials: true maxAge: 360000add-to-simple-url-handler-mapping: true是为了解决 OPTIONS 预检请求被拦截的问题漏了这行浏览器会报跨域失败。4. 验证请求与成功结果配置写完启动网关用 curl 验证。先确认网关本身起来了curl -i http://localhost:10010/actuator/health然后带 Key 请求聊天接口。把YOUR_TAOTOKEN_KEY换成你在控制台创建的真实 Keycurl -i -X POST http://localhost:10010/api/chat/v1/messages \ -H Authorization: Bearer YOUR_TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: claude-3-5-sonnet, max_tokens: 128, messages: [{role: user, content: 你好}] }成功的表现是HTTP 状态码 200响应体里能看到模型返回的内容响应头里可能带有你配置的X-Gateway-Source。如果返回 401说明 Authorization 头没带或格式不对如果返回 404多半是 StripPrefix 段数配错了。再验证一下不带 Key 的情况应该被 GlobalFilter 拦下curl -i -X POST http://localhost:10010/api/chat/v1/messages \ -H Content-Type: application/json \ -d {model:claude-3-5-sonnet,messages:[{role:user,content:hi}]}预期返回 401说明鉴权过滤器生效了。这一步能过说明「路由 过滤器 上游转发」整条链路是通的。5. 本篇常见错误排查排障时先看网关日志再看上游返回。下面几个是高频问题。401 UnauthorizedGlobalFilter 没放行。检查请求头里 Authorization 是否存在、是否以Bearer开头注意有个空格。如果你在 default-filters 里又加了一层鉴权头可能把客户端的头覆盖了检查 AddRequestHeader 有没有冲突。404 Not Found路由匹配上了但上游路径不对。最常见的是 StripPrefix 段数配错。比如你请求/api/chat/v1/messagesStripPrefix1 后是/chat/v1/messages如果上游真实路径是/v1/messages那应该用 StripPrefix2。用curl -v看实际转发路径。local proxy failed / connection refused网关连不上上游。检查uri是否写成了https://taotoken.net/api而不是别的地址本地网络是否能访问外网。如果你用了服务发现lb://检查 Nacos 里服务是否注册成功。reading choices 相关报错这类通常是上游返回体解析问题说明请求已经到达上游但响应格式和客户端预期不一致。检查你的请求体字段是否符合上游 API 规范比如 model 名称、messages 结构。OAuth / 认证类报错如果你在 Gateway 里叠加了 OAuth2 资源服务器注意它和自定义 GlobalFilter 的 order 关系。OAuth 的过滤器 order 通常较小会先执行可能导致你的 AuthorizeFilter 拿不到预期的头。排查时把 order 打印出来确认执行顺序。跨域失败浏览器控制台报 CORS 错误。检查add-to-simple-url-handler-mapping是否为 trueallowedOrigins 是否包含前端实际域名带端口allowCredentials 为 true 时 allowedOrigins 不能用*。过滤器不生效确认 GlobalFilter 类上有 Component且被 Spring 扫描到。如果写在别的模块检查包路径是否在启动类的扫描范围内。6. 把统一入口用起来配置跑通之后你的前端只需要把 Base URL 指向网关地址所有模型请求都走同一个入口。后面要加新模型只需要在 Gateway 里加一条路由要做限流加一个 RequestRateLimiter 过滤器要换鉴权方式改 GlobalFilter 一处即可。如果你还没创建 Key去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 建一个然后回到上面的 curl 命令替换掉占位符再跑一遍。接入参数以 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 为准模型列表可以在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 查看。长期做编码或 Agent 应用的话Coding Plan 在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。最后留一个实操建议把 StripPrefix 的段数和上游路径对齐这件事写成一个单元测试或者启动时的自检日志能省掉大量 404 排查时间。