1. 为什么 ABP VNext 微服务需要一个 Ocelot 统一入口
ABP VNext 把单体拆成 Identity、Administration、订单、库存这些独立服务之后,客户端会立刻遇到一个很现实的问题:每个服务都有自己的域名和端口,前端要维护一长串 Base URL,跨域配置散落各处,鉴权逻辑在每个服务里重复写一遍。这时候 Ocelot API 网关就是那个"统一前台"——所有外部请求先到网关,由网关按路径转发到对应微服务,JWT 校验、限流、熔断、聚合都在这一层收口。
我先把这套组合能做什么讲清楚,方便你判断是否适合当前项目。Ocelot 是 .NET 生态里成熟度较高的轻量网关,配置以 JSON 为主,路由、聚合、负载均衡、QoS 熔断、限流都能通过配置文件声明;ABP VNext 的微服务模板本身已经内置了 Consul 服务发现和 JWT 鉴权,两者拼在一起,网关只负责"转发 + 校验",业务服务专注业务,职责边界很干净。
适合谁:正在用 ABP VNext 6.x/7.x 做微服务拆分、希望对外只暴露一个入口、需要统一 JWT 鉴权和基础安全策略的团队。如果你还在单体阶段,先别急着上网关,等出现"多个服务、多个域名、前端配置爆炸"的信号再引入更划算。
这篇会给出可直接复制的ocelot.json路由片段、Program.cs注册代码、JWT 校验配置,以及网关转发成功和鉴权失败两类验证步骤。中间涉及模型调用或编码辅助时,我会用 TaoToken 的 API 做演示,Key 的获取放在第二节,配置片段放在第三节,你可以照着跑通。
先说清楚整体链路:客户端 → Ocelot 网关(JWT 校验 + 路由)→ Consul 服务发现 → ABP 微服务。网关不碰业务数据,只做入口治理。下面按"环境准备 → 配置 → 验证 → 排错"的顺序展开,每一步都给完整命令和参数。
2. TaoToken 前置准备:拿 Key 与接入信息
在写网关配置之前,先把演示用的模型调用通道准备好。TaoToken 提供 OpenAI 兼容的 API 入口,网关里做 JWT 校验失败排查、或者给某个下游服务加一个"AI 摘要"接口时,可以直接用它验证请求链路是否通。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置里填这个就行。
操作步骤我按实际点选顺序写:
第一步,打开官网,注册并登录控制台。控制台入口在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,登录后能看到当前账户的额度与已创建的 Key 列表。
第二步,进入 API Keys 页面创建密钥,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。点"新建 Key",起一个能区分环境的名称,比如abp-gateway-dev,创建后立刻复制保存——页面只完整显示一次,关掉就看不到了。
第三步,确认模型 ID。不同场景用的模型不一样,网关里如果只是做连通性验证,选一个通用对话模型即可;如果是给下游服务做代码补全类调用,选编码向模型。模型列表和对话调试可以在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里直接试,先确认模型能正常返回,再写进配置。
第四步,把三件套记下来,后面第三节的配置片段会用到:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | OpenAI 兼容根路径,不带 UTM |
| API Key | sk-开头的一串 | 控制台创建,只显示一次 |
| Model ID | 控制台/对话页确认的模型名 | 按场景选,别硬编码在代码里 |
这里有个容易踩的坑:Base URL 末尾不要多加/v1或/chat/completions,很多 OpenAI SDK 会自动拼接路径,多写一段就变成 404。我试过在网关的下游配置里把完整路径写死,结果 SDK 又拼了一次,排查了半小时才发现是路径重复。正确做法是 Base URL 只写到/api,具体路径交给 SDK 或请求方拼接。
另外提醒一句:Key 不要提交到 Git。网关项目的appsettings.Development.json里用占位符,真实值走环境变量Ocelot__Ai__ApiKey,这样本地能跑、仓库不泄露。下一节开始写 Ocelot 的实际配置。
3. 可复制配置:ocelot.json 路由与 JWT 校验
这一节是全文的核心,给出能直接落地的配置文件。先建项目骨架:
dotnet new webapi -n ApiGateway cd ApiGateway dotnet add package Ocelot dotnet add package Ocelot.Provider.Consul dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer项目结构保持简单:ocelot.json放路由,ocelot.Development.json放本地覆盖,Program.cs做注册。先看ocelot.json的完整片段,包含路由聚合、下游服务发现和 JWT 校验三部分:
{ "Routes": [ { "Key": "identity", "DownstreamPathTemplate": "/api/identity/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [{ "Host": "localhost", "Port": 44300 }], "UpstreamPathTemplate": "/gateway/identity/{everything}", "UpstreamHttpMethod": ["GET", "POST", "PUT", "DELETE"], "AuthenticationOptions": { "AuthenticationProviderKey": "GatewayAuth", "AllowedScopes": [] } }, { "Key": "orders", "DownstreamPathTemplate": "/api/orders/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [{ "Host": "localhost", "Port": 44301 }], "UpstreamPathTemplate": "/gateway/orders/{everything}", "UpstreamHttpMethod": ["GET", "POST"], "AuthenticationOptions": { "AuthenticationProviderKey": "GatewayAuth", "AllowedScopes": ["orders"] }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10000, "TimeoutValue": 5000 } } ], "Aggregates": [ { "RouteKeys": ["identity", "orders"], "UpstreamPathTemplate": "/gateway/dashboard", "UpstreamHttpMethod": ["GET"] } ], "GlobalConfiguration": { "BaseUrl": "http://localhost:5000", "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul" } } }几个参数说明一下。AuthenticationProviderKey必须和Program.cs里注册的 JWT 方案名一致,写错就是 401;AllowedScopes留空表示只校验 token 有效性,填了 scope 就要求 token 里带对应 scope;QoSOptions是熔断,连续 3 次异常后断开 10 秒,超时 5 秒。聚合路由/gateway/dashboard会并行请求 identity 和 orders 两个下游,返回合并结果。
接着是Program.cs的注册代码,重点是 JWT 校验和 Ocelot 中间件的顺序:
using Microsoft.AspNetCore.Authentication.JwtBearer; using Ocelot.DependencyInjection; using Ocelot.Middleware; using Ocelot.Provider.Consul; var builder = WebApplication.CreateBuilder(args); builder.Configuration .AddJsonFile("ocelot.json", false, true) .AddJsonFile($"ocelot.{builder.Environment.EnvironmentName}.json", true, true) .AddEnvironmentVariables(prefix: "Ocelot__"); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer("GatewayAuth", options => { options.Authority = builder.Configuration["AuthServer:Authority"]; options.Audience = builder.Configuration["AuthServer:Audience"]; options.RequireHttpsMetadata = false; }); builder.Services.AddAuthorization(); builder.Services.AddOcelot(builder.Configuration).AddConsul(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); await app.UseOcelot(); app.Run();appsettings.json里补上鉴权服务器信息,本地开发用 ABP 自带的 Identity 服务地址:
{ "AuthServer": { "Authority": "http://localhost:44300", "Audience": "Gateway" } }注意UseAuthentication()和UseAuthorization()必须在UseOcelot()之前,顺序反了 JWT 校验不会生效,请求会直接透传到下游。这个顺序问题我在两个项目里都遇到过,表现是"token 明明有效却返回 401",查配置查半天,最后发现是中间件顺序。
如果下游服务需要透传 token,加一个 DelegatingHandler 即可,Ocelot 支持注册全局 handler。配置到这里就完整了,下一节验证转发和鉴权。
4. 验证请求:转发成功与鉴权失败两类结果
配置写完必须验证,否则上线才发现路由不通。分两步:先验证网关能正常转发,再验证 JWT 校验确实拦住了非法请求。
第一步,启动 ABP 的 Identity 服务和订单服务,再启动网关:
dotnet run --project ApiGateway控制台出现监听 5000 端口的日志后,用 curl 发一个不带 token 的请求:
curl -i http://localhost:5000/gateway/orders/list预期返回401 Unauthorized,响应头里带WWW-Authenticate: Bearer。这说明 JWT 校验生效了,请求在网关层就被拦下,没有打到下游。如果返回的是 404 或 502,说明路由或下游地址有问题,先看下一节排错。
第二步,从 Identity 服务拿一个有效 token。ABP 的 Identity 服务一般有/connect/token端点,用密码模式取:
curl -X POST http://localhost:44300/connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password&client_id=Gateway&client_secret=1q2w3e*&username=admin&password=1q2w3E*&scope=orders"拿到access_token后带上再请求:
curl -i http://localhost:5000/gateway/orders/list \ -H "Authorization: Bearer <access_token>"预期返回200 OK和订单列表 JSON。这一步通了,说明"网关校验 → 转发 → 下游响应"整条链路正常。
第三步,验证聚合路由。请求/gateway/dashboard,返回体里应该同时包含 identity 和 orders 两个下游的数据,字段名按下游返回结构合并。如果只有一个下游的数据,检查Aggregates里的RouteKeys是否拼写正确。
第四步,验证熔断。把订单服务停掉,连续请求 4 次/gateway/orders/list,前 3 次返回 502/503,第 4 次开始网关直接返回熔断响应,不再等待下游超时。10 秒后恢复请求,服务重启后能自动恢复转发。
验证过程中如果要用模型接口做连通性测试,可以直接调 TaoToken 的对话接口,确认网关所在网络能出网:
curl -X POST https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"<你的模型ID>","messages":[{"role":"user","content":"ping"}]}'返回 200 且有choices字段就说明网络和 Key 都正常。这一步和网关本身无关,但排障时能快速区分"是网关问题还是网络问题"。
5. 常见报错排查:401、502 与 reading choices
实际跑下来,报错集中在几类,我按出现频率排一下,每条给现象、原因和修法。
401 Unauthorized,token 明明有效。最常见的原因是中间件顺序,UseAuthentication()在UseOcelot()之后。其次是AuthenticationProviderKey和注册的方案名不一致,比如配置写GatewayAuth、代码注册成Bearer。还有一种情况是Authority地址不可达,JWT 校验需要拉取签名密钥,网关访问不到 Identity 服务就会全部 401。排查方法:在网关日志里看是否有Unable to obtain configuration from字样,有就是 Authority 地址问题。
502 Bad Gateway 或 local proxy failed。说明网关收到了请求但转发不出去。先确认下游服务是否启动、端口是否和DownstreamHostAndPorts一致;再确认DownstreamScheme是 http 还是 https,ABP 本地模板默认 https,写成 http 会连接失败。如果用了 Consul 服务发现,检查ServiceDiscoveryProvider的 Host/Port 是否正确,以及下游服务是否已注册到 Consul。
响应体里出现 reading choices 相关报错。这类报错通常出现在网关下游接了模型调用接口的场景,本质是上游返回的不是预期 JSON,解析choices字段时失败。原因可能是 Key 无效返回了错误 JSON、模型 ID 写错、或者请求路径多拼了/v1。修法:先用 curl 直连https://taotoken.net/api/chat/completions确认返回结构,再把网关下游路径对齐;Key 走环境变量注入,别写死在ocelot.json里。
OAuth 相关报错,比如 invalid_client 或 unsupported_grant_type。出现在取 token 阶段,检查client_id/client_secret是否和 Identity 服务里注册的客户端一致,grant_type是否被该客户端允许。ABP 默认客户端可能只开了authorization_code,密码模式需要手动在 Identity 服务里配置。
路由 404,路径对不上。UpstreamPathTemplate和DownstreamPathTemplate的占位符要匹配,{everything}只能出现在末尾。另外 Ocelot 路由匹配是顺序敏感的,更具体的路径要放在更宽泛的前面。
排错时建议打开 Ocelot 的请求日志,在appsettings.Development.json里把日志级别调到 Debug,能看到每条请求匹配到哪个 Route、转发到哪个下游、耗时多少。这比盲猜快得多。
6. 继续深入:文档、模型调试与长期编码方案
配置跑通只是起点,后面还有几件事值得做。第一,把ocelot.json纳入版本管理,但环境差异用ocelot.{Environment}.json覆盖,别把本地端口提交上去。第二,给网关加健康检查端点,K8s 探针和负载均衡都靠它判断实例是否可用。第三,限流策略按路由配置,登录类接口限得严一点,查询类接口放宽。
需要查 Ocelot 的完整配置项时,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有 API 参数和错误码说明,配合官方文档看效率更高。如果要在网关下游接 Claude Code 这类编码工具做辅助开发,参考 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里的接入方式,Base URL、Key、Model ID 三件套和前面一致。
模型调试阶段建议直接用对话页验证,地址是 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,改一个参数看一次返回,比在代码里反复改快。长期做编码和 Agent 类任务的话,Coding Plan 在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,按周期使用比单次调用更省心。
最后给一个实用技巧:网关的 JWT 校验和下游服务的鉴权不要重复做。网关校验通过后,下游服务信任网关传来的用户信息即可,否则每个服务都要拉一次签名密钥,延迟叠加。ABP 里可以通过自定义ClaimsTransformation把网关解析出的 claims 透传给下游,具体做法是在网关加一个 handler 把用户 ID 写进请求头,下游读头即可。这样整条链路的鉴权只发生一次,性能和可维护性都更好。