1. 为什么要在 Gateway 里统一收敛模型调用 Key
做 SpringCloud 微服务时,一个很常见的场景是:订单服务、用户服务、内容服务各自需要调用大模型能力,比如智能客服、文案生成、代码补全。最开始图省事,每个服务里都塞一份 API Key,配置文件里写死,或者丢到 Nacos 配置中心。服务少的时候还行,一旦服务数量上到七八个,问题就来了——Key 散落在各处,轮换一次要改一堆地方;某个服务被反编译或者日志打印,Key 就泄露了;想统计一下整个系统到底调了多少 token,根本无从下手。
黑马程序员的课程里讲 Gateway 的时候,核心就三件事:路由、断言、过滤器。路由负责把请求转发到正确的微服务,断言决定什么样的请求能匹配上,过滤器在请求前后做加工。这套机制天然适合做统一入口。我的思路是:把所有对模型的调用都收敛到 Gateway 这一层,由 Gateway 统一持有 TaoToken 的 Key,下游微服务不再直接接触 Key,而是通过 Gateway 暴露的内部接口来调用模型。
这样做有几个直接好处。第一,Key 只存在于 Gateway 的配置里,其他服务完全无感,轮换 Key 只需要改一处。第二,Gateway 基于 WebFlux 的非阻塞模型,在高并发下比 Zuul 那种 Servlet 阻塞式实现更省线程资源,模型调用本身是 IO 密集型,正好匹配。第三,可以在 Gateway 的全局过滤器里统一做鉴权、限流、日志埋点,比如统计每个服务调了多少次模型、消耗了多少 token,这些数据在网关层收集最方便。
TaoToken 在这里扮演的角色是统一的上游通道。它提供兼容 OpenAI 风格的 API 接口,Gateway 只需要配置一个 Base URL 和一个 Key,就能把请求转发过去。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ,API 地址是 https://taotoken.net/api 。你不需要在每个微服务里分别对接不同的模型供应商,Gateway 统一出口,下游服务只管发请求。
这一篇我会从零开始,带你搭一个 Gateway 模块,配置路由和过滤器,把模型调用收敛进来,最后用 curl 验证请求经过网关转发后能正常返回。过程中会给出可复制的 YAML 和 Java 代码片段,也会讲几个我实际踩过的坑。
2. TaoToken 前置准备:拿到统一 Key 和 API 通道
在动手写 Gateway 配置之前,先把 TaoToken 这边的准备工作做完。这一步不复杂,但顺序别搞反,否则后面调试的时候会分不清是网关的问题还是 Key 的问题。
首先你需要一个 TaoToken 账号。打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ,注册登录之后进入控制台。控制台地址是 https://taotoken.net/console ,在里面找到 API Keys 管理页面,路径是 https://taotoken.net/api-keys 。在这里创建一个新的 Key,复制出来保存好。这个 Key 就是后面 Gateway 配置里要填的凭证。
创建 Key 的时候注意几点。一是 Key 只在创建时完整显示一次,关掉页面就看不到了,所以一定要先复制到安全的地方。二是如果你打算在多个环境(开发、测试、生产)用不同的 Key,可以分别创建,方便后续按环境做限流和统计。三是 Key 的权限范围,如果你只是做模型对话调用,默认权限就够了;如果后面要接 Coding Plan 或者 Agent 类的长期任务,可以单独开对应的权限。
拿到 Key 之后,确认一下 API 的基础地址。TaoToken 的 API 入口是 https://taotoken.net/api ,兼容 OpenAI 的接口格式。也就是说,你原来如果用过 OpenAI 的 SDK,只需要把 base_url 换成这个地址,api_key 换成 TaoToken 的 Key,其他代码基本不用动。这一点对 Gateway 转发特别友好,因为 Gateway 只需要做透明的 HTTP 转发,不需要关心上游的协议细节。
如果你还想先验证一下 Key 能不能用,可以打开模型对话页面 https://taotoken.net/model-chat ,在里面直接发一条消息试试。这个页面相当于一个在线的调试工具,能快速确认 Key 有效、额度正常。确认没问题之后,再回到 Gateway 这边配置。
另外提一下 Coding Plan。如果你的微服务里有代码生成、代码补全这类场景,可以了解一下 https://taotoken.net/coding-plan 。它针对编码类任务做了优化,适合长期跑的 Agent 或者 IDE 插件场景。不过这一篇我们主要聚焦在 Gateway 的统一转发上,Coding Plan 的细节后面有机会再展开。
准备工作做完,你手里应该有三样东西:TaoToken 的 API Key、API 基础地址 https://taotoken.net/api 、以及一个确认可用的模型 ID(比如 gpt-4o-mini 或者 claude-3-5-sonnet 这类,具体以你账号里可用的为准)。接下来就可以进入 Gateway 的配置环节了。
3. 可复制配置:Gateway 路由、过滤器与 TaoToken 对接
这一节是核心,我会给出完整的配置片段,你直接复制到项目里就能跑。先说明一下项目结构:我们新建一个独立的 Gateway 模块,比如叫gateway-service,它注册到 Nacos,下游有user-service和order-service两个微服务。Gateway 对外暴露统一入口,对内把模型调用请求转发到 TaoToken。
3.1 引入依赖
在gateway-service的pom.xml里加入 Gateway 和 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> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>注意 Gateway 是基于 WebFlux 的,不要再引入spring-boot-starter-web,否则会冲突,启动时报错说 Servlet 和 Reactive 混用。这是第一个容易踩的坑。
3.2 application.yml 路由配置
下面是完整的application.yml,包含 Nacos 地址、路由规则、以及指向 TaoToken 的转发路由:
server: port: 10010 spring: application: name: gateway-service cloud: nacos: server-addr: 127.0.0.1:8848 gateway: routes: - id: user-service-route uri: lb://user-service predicates: - Path=/user/** filters: - AddRequestHeader=X-Gateway-Source, gateway-service - id: order-service-route uri: lb://order-service predicates: - Path=/order/** - id: taotoken-model-route uri: https://taotoken.net/api predicates: - Path=/ai/** filters: - StripPrefix=1 - AddRequestHeader=Authorization, Bearer ${taotoken.api-key} globalcors: add-to-simple-url-handler-mapping: true cors-configurations: '[/**]': allowedOrigins: - "http://localhost:8090" allowedMethods: - GET - POST - DELETE - PUT - OPTIONS allowedHeaders: "*" allowCredentials: true maxAge: 360000 taotoken: api-key: sk-你的TaoToken密钥 base-url: https://taotoken.net/api这里有几个关键点解释一下。taotoken-model-route这条路由的uri直接写 TaoToken 的 API 地址,Path=/ai/**表示所有以/ai开头的请求都走这条路由。StripPrefix=1会去掉路径的第一段,也就是把/ai/v1/chat/completions变成/v1/chat/completions,这样转发到 TaoToken 的时候路径才是对的。AddRequestHeader自动加上Authorization头,Key 从配置里读,下游服务完全不需要知道 Key 是什么。
3.3 全局过滤器做鉴权
除了配置文件里的 GatewayFilter,我们还需要一个全局过滤器来处理鉴权。比如判断请求头里有没有authorization,值是不是合法的。下面是一个可复制的实现:
@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst("authorization"); if (token == null || token.isEmpty()) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } if (!"admin".equals(token)) { exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }这个过滤器对所有进入网关的请求生效,getOrder()返回 0 表示优先级较高,先于其他过滤器执行。实际项目里你可以把admin换成 JWT 校验逻辑,或者调用用户服务验证 token 有效性。
3.4 用 settings 片段管理 Key(可选)
如果你不想把 Key 明文写在application.yml里,可以用环境变量或者配置中心。比如在 IDE 的运行配置里加一个环境变量TAOTOKEN_API_KEY,然后 YAML 里写成${TAOTOKEN_API_KEY}。这样代码提交到仓库的时候不会泄露 Key。如果你用的是 IntelliJ IDEA,可以在 Run Configuration 的 Environment variables 里填。如果是命令行启动,用export TAOTOKEN_API_KEY=sk-xxx再启动 jar 包。
到这里配置就完成了。启动 Nacos,再启动gateway-service,看到控制台没有报错,说明网关起来了。
4. 验证请求:curl 经网关转发到 TaoToken 返回正常
配置写完不算完,得实际发请求验证。这一节我用 curl 演示完整的验证流程,你可以跟着一步步操作。
4.1 先验证网关本身是否正常
启动 Gateway 之后,先确认端口监听正常:
curl -i http://localhost:10010/user/1如果user-service已经注册到 Nacos 并且正常运行,你会看到用户服务返回的 JSON。如果返回 404,说明路由没匹配上,检查Path断言和 Nacos 服务名是否一致。如果返回 503,说明负载均衡没找到实例,检查user-service是否真的注册成功了。
4.2 验证模型调用路由
接下来验证经过网关转发到 TaoToken 的请求。假设我们要调用对话接口,路径设计成/ai/v1/chat/completions,经过StripPrefix=1之后变成/v1/chat/completions,转发到https://taotoken.net/api/v1/chat/completions。
curl -i -X POST http://localhost:10010/ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "authorization: admin" \ -d '{ "model": "gpt-4o-mini", "messages": [ {"role": "user", "content": "用一句话解释什么是微服务网关"} ] }'注意这里请求头里的authorization: admin是给全局过滤器校验用的,不是给 TaoToken 的。TaoToken 的 Key 由 Gateway 在转发时自动加上,下游和客户端都不需要知道。
如果一切正常,你会看到类似这样的返回:
{ "id": "chatcmpl-xxx", "object": "chat.completion", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "微服务网关是系统的统一入口,负责路由转发、鉴权、限流等跨横切关注点。" } } ] }看到choices数组里有内容,说明请求经过网关转发、TaoToken 处理、再原路返回,整条链路通了。
4.3 验证过滤器是否生效
想确认AddRequestHeader过滤器有没有加上Authorization头,可以在 TaoToken 那边看请求日志,或者临时在 Gateway 里加一个打印日志的过滤器。更简单的办法是故意把 Key 配错,看返回是不是 401。如果返回 401,说明 Key 确实被带上了,只是值不对;如果返回的是其他错误,说明头根本没加上,检查过滤器配置的缩进和路由 id 是否对应。
4.4 验证跨域配置
如果你有前端页面跑在localhost:8090,可以用浏览器发一个跨域请求试试。打开开发者工具,看 Network 面板里 OPTIONS 预检请求是否返回 200,以及实际请求的响应头里有没有Access-Control-Allow-Origin。如果预检失败,检查add-to-simple-url-handler-mapping是否为 true,以及allowedOrigins是否包含了前端地址。
5. 常见报错排查:401、local proxy failed、reading choices
这一节整理几个我在实际配置过程中遇到的报错,以及对应的排查思路。你如果卡住了,可以对照着看。
5.1 401 Unauthorized
这是最常见的。可能的原因有三个。第一,TaoToken 的 Key 没配上或者配错了。检查application.yml里taotoken.api-key的值,确认没有多余空格,确认 Key 没有过期。第二,Authorization头的格式不对。TaoToken 要求的是Bearer sk-xxx,注意Bearer和 Key 之间有一个空格。第三,全局过滤器把请求拦下来了。如果你在全局过滤器里校验的是admin,但 curl 里没带authorization: admin,就会返回 401。这时候要区分是网关返回的 401 还是 TaoToken 返回的 401,看响应体里的错误信息。
5.2 local proxy failed 或 connection refused
这个报错通常出现在 Gateway 启动的时候,或者转发请求的时候。如果是启动时报local proxy failed,大概率是端口被占用了,换个端口或者杀掉占用进程。如果是转发时报connection refused,检查 TaoToken 的 API 地址是否可达。可以在服务器上直接 curl 一下https://taotoken.net/api,看能不能通。如果服务器有网络限制,需要确认出站规则允许访问这个地址。
5.3 reading choices 相关报错
有时候你会看到类似Cannot read property 'choices' of undefined或者reading 'choices'的报错。这通常不是 Gateway 的问题,而是下游服务解析响应的时候,TaoToken 返回的结构和预期不一致。比如你请求的模型 ID 不存在,TaoToken 返回的是错误对象而不是正常的choices数组。解决办法是先直接用 curl 请求 TaoToken,看原始返回是什么,确认模型 ID 正确、请求体格式正确,再排查下游服务的解析逻辑。
5.4 OAuth 或 token 过期
如果你用的是 OAuth 类的鉴权方式,可能会遇到 token 过期的问题。TaoToken 的 Key 一般不会短期过期,但如果你在网关层做了 JWT 校验,JWT 本身是有有效期的。这时候需要在前端或者调用方做 token 刷新。网关层可以配置一个过滤器,在 token 快过期时返回特定状态码,让调用方重新获取。
5.5 路由不匹配
请求返回 404,但服务明明启动了。检查Path断言写的是不是/ai/**,而请求路径是/ai/v1/chat/completions,这是匹配的。如果请求路径是/api/ai/...,那就不匹配了。另外注意StripPrefix的数值,写 1 是去掉一段,写 2 是去掉两段,写错了路径就会不对。
排查的时候有一个通用技巧:在 Gateway 里加日志,打印出实际转发的 URL 和请求头。这样一眼就能看出是路由问题还是鉴权问题。
6. 把模型调用收敛到网关后的长期收益
配置跑通之后,你可能会想,这样折腾一圈到底值不值。我从实际项目经验说几个长期收益。
第一是 Key 管理成本大幅下降。以前每个服务一份 Key,轮换的时候要挨个改配置、重启服务。现在只需要改 Gateway 一处,其他服务完全无感。而且 Key 不再出现在业务代码里,代码审计的时候清爽很多。
第二是流量可观测。所有模型调用都经过 Gateway,你可以在全局过滤器里埋点,统计每个服务、每个接口调了多少次、耗时多少、消耗了多少 token。这些数据对于成本控制和容量规划很有价值。以前分散调用的时候,这些数据根本收不上来。
第三是限流和降级更容易做。Gateway 层可以针对模型调用路由单独配置限流规则,比如每秒最多 100 次,超过就返回 429。下游服务不用各自实现限流逻辑,统一在网关层处理。如果 TaoToken 那边偶尔抖动,网关层也可以做重试或者降级,返回缓存结果或者友好提示。
第四是安全边界更清晰。下游服务不持有 Key,即使某个服务被攻破,攻击者也拿不到 Key。网关层可以做 IP 白名单、请求签名校验等额外防护。对于内部服务之间的调用,还可以用 mTLS 或者内部 token 做二次校验。
如果你后面要接 Coding Plan 做代码生成类的长期任务,网关层也可以统一管理配额和调用频率。Coding Plan 的入口在 https://taotoken.net/coding-plan ,适合需要持续调用模型的场景。把这类调用也收敛到网关,整个系统的模型调用就完全统一了。
最后提醒一点,Gateway 本身也是微服务,要做好高可用。生产环境至少部署两个实例,前面挂 Nginx 或者 SLB。Nacos 也要集群部署,避免单点故障。这些是微服务架构的通用实践,和用不用 TaoToken 无关,但既然做了统一网关,就顺手把高可用一起考虑了。