token-gateway

对接说明

Token Gateway 是 DeepSeek 等上游模型的中转计费网关:先用用户中心 JWT 签发/重置网关 API Key(sk-),再用该 Key 调用 Chat;余额以「厘」计量。官方通道充值由客户端用 UC JWT 换取短时 rt_ ticket,打开托管充值页后可微信 Native 预下单;支付成功由微信回调幂等入账,回调丢失时可由「我已支付」/页内轮询或后台定时查单补账。本页描述当前已落地能力与接口契约,便于客户端自行对接。

基本逻辑

1

用户中心登录

客户端经用户中心(OAuth / AS)拿到 access_token(JWT)。网关作为 Resource Server 校验该 JWT(与 AS 共享 HS256 密钥)。

2

按 name 签发 / 重置 Key

调用 POST /v1/keys/rotate,带 UC JWT 与 Key 名称(如 ftcs-desktop)。无则创建、有则重置;响应里仅此一次返回明文 api_key。服务端只存哈希,自动确保计费账户存在(默认余额 0)。

3

用 sk 调 Chat

调用 POST /v1/chat/completions(非流式 / 流式 SSE)。鉴权 sk(G0-08);余额预检(G0-11);落计量日志(G0-09);异步结算扣费(G0-10)。

金额单位为(CNY li):1 元 = 1000 厘。接口字段形如 balance_li,禁止用浮点表示金额。

鉴权模型

凭证用途
UC JWT
Authorization: Bearer <access_token>
已保护:/v1/keys/**/v1/auth/**/v1/billing/recharge/ticket/health/actuator/health/**
网关 API Key
sk-…
Chat / usage / models:Authorization: Bearer sk-…;非法 401,禁用 403;余额不足 402
充值短时 ticket
rt_…
由 UC JWT 换取;明文仅签发响应返回一次。充值页 / prepay / 查单用 Cookie 或 Bearer rt_…

JWT 身份映射:claim tenant_id + user_code → 计费账户键。缺 claim 时接口返回 401。

已默认引入 RS;须配置与 AS 一致的 OAUTH_JWK_KEY。无 Token 访问受保护路径应返回 401。

已实现接口

GET /health 已落地

存活探活。正式环境需 UC JWT;响应头可带 X-Request-Id

请求头

Authorization必填(正式 RS):Bearer <UC access_token>

入参

无 Query / Body。

成功响应 · 200

{
  "status": "UP"
}

错误

401无/无效 JWT
GET /v1/auth/whoami 已落地

校验 UC JWT 并回显解析到的身份。不建户、不返回余额,适合联调冒烟。

请求头

Authorization必填:Bearer <UC access_token>

入参

无 Query / Body。

成功响应 · 200

{
  "tenantId": "1",
  "userCode": "u_abc",
  "subject": "…",
  "clientId": "ftcs-desktop"
}
tenantId / userCode必有;计费账户绑定键
subject / clientId有 claim 时返回

错误

401无/坏 Token,或缺少 tenant_id / user_code
POST /v1/keys/rotate 已落地

name 签发或重置网关 API Key。无该 name → 创建(action=created);已有 → 原地更新哈希,旧 sk 立即失效(action=rotated)。每次成功响应都含新明文。

请求头

Authorization必填:Bearer <UC access_token>
Content-Typeapplication/json

入参 · Body

{
  "name": "ftcs-desktop"
}
name 必填。trim 后转小写,再校验:长度 1~64,仅 [a-z0-9._-]。非法 → 400 invalid_name

成功响应 · 200

{
  "action": "created",
  "name": "ftcs-desktop",
  "api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "prefix": "sk-xxxx",
  "status": "active",
  "user": {
    "tenant_id": "1",
    "user_code": "u_abc",
    "balance_li": 0,
    "status": "active"
  }
}
actioncreated | rotated
api_key明文 sk;仅本响应返回,勿记入日志
prefix与库中展示前缀一致(明文前 10 字符)
user.*账户摘要;新建余额为 0

错误

401无/坏 UC JWT
400name 缺失或非法(invalid_name
403账户 disabledaccount_disabled
409唯一冲突重试耗尽(极少)
500未配置 GATEWAY_KEY_PEPPER
每次成功 rotate 都会作废旧 sk。本地已有可用 Key 时,不要在每次启动时盲目调用;宜在「本地无 Key / Chat 鉴权失败 / 用户主动重置」时再调。
POST /v1/billing/recharge/ticket 已落地

用 UC JWT 换取官方通道充值用的短时 ticket(前缀 rt_)。服务端只存哈希;明文仅本响应返回一次。过期前可多次用于充值页、微信 prepay / 查单。禁止把长效 access_token 挂在充值 URL。

请求头

Authorization必填:Bearer <UC access_token>

入参

无 Query / Body。

成功响应 · 200

{
  "ticket": "rt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "expires_in": 300,
  "expires_at": "2026-08-05T14:22:00.000Z"
}
ticket明文 rt_ + Base64URL;仅本响应返回,勿记入日志
expires_in有效秒数(与配置 TTL 一致;默认 300)
expires_atUTC 到期时刻(ISO-8601)

错误

401无/坏 UC JWT,或缺少身份 claim
429同身份每小时签发超过 20 张(recharge_ticket_rate_limited
503未配置 GATEWAY_KEY_PEPPERmissing_gateway_key_pepper
TTL 可配:token-gateway.billing.recharge-ticket-ttl / 环境变量 GATEWAY_RECHARGE_TICKET_TTL(默认 300s;启动校验须在 60s~24h)。已发出的票仍按其行内 expires_at
客户端拿到 ticket 后应尽快打开网关充值页(/billing/recharge?ticket=…),勿长期缓存;勿用 sk 调本接口,也勿用 ticket 调 Chat。
GET /billing/recharge 已落地

网关托管充值页。带 ?ticket=rt_… 时验票并 Set-Cookie 后 302 去 query;无有效 Cookie 时展示「缺少登录凭证」。页内调用下方 prepay / 查单。

POST /v1/billing/wechat/prepay 已落地

微信 Native 预下单,返回付款码串 code_url。鉴权:Cookie tg_recharge_ticketAuthorization: Bearer rt_…(Cookie 优先);不用 UC JWT / sk。

Body

{ "amount_yuan": 50.00 }
amount_yuan必填;1.00~100.00,最多两位小数

成功 · 200

{
  "out_trade_no": "tg…",
  "code_url": "weixin://wxpay/bizpayurl?pr=…",
  "amount_yuan": "50.00",
  "amount_fen": 5000,
  "amount_li": 50000,
  "expires_in": 7200,
  "status": "created"
}

错误

401invalid_recharge_ticket
400invalid_amount / invalid_body
503wechat_pay_unavailable(未启用或缺配置)
502wechat_prepay_failed
开关与商户配置:token-gateway.wechat-pay.* / 环境变量 GATEWAY_WECHAT_PAY_ENABLEDWECHAT_*。证书放 important/,勿提交仓库。支付成功见下方 notify 入账。
POST /v1/billing/wechat/notify 已落地

微信支付结果通知。官方 SDK 验签解密。按 trade_state 处理:SUCCESS 幂等入账;CLOSED/REVOKED/PAYERROR 等将未入账单推到 closed/failed;中间态不改库。鉴权为微信签名,勿带 ticket / JWT / sk。

成功应答(微信约定)

{ "code": "SUCCESS", "message": "成功" }
应答体不是网关业务短码 {"reason":…}。验签失败等返回 code=FAIL,微信会重试。
GET /v1/billing/wechat/orders/{outTradeNo} 已落地

按商户订单号读本地订单(须 ticket 且身份匹配,否则 404)。入账后可见 status=credited。主动查微信见下方 sync。

POST /v1/billing/wechat/orders/{outTradeNo}/sync 已落地

用户「我已支付」主动查单:向微信 queryOrderByOutTradeNo,再走与 notify 相同的 applyTradeState(写 last_sync_*)。本地已 credited/closed 可短路不查微信。鉴权同 prepay(ticket)。

成功 · 200(示例)

{
  "out_trade_no": "tg…",
  "status": "credited",
  "trade_state": "SUCCESS",
  "amount_yuan": "50.00",
  "amount_li": 50000,
  "credited_li": 50000,
  "balance_li": 125000,
  "last_sync_trade_state": "SUCCESS",
  "last_sync_result": "CREDITED"
}

错误

401invalid_recharge_ticket
404order_not_found(无单或不属于当前身份)
429sync_rate_limited
503wechat_pay_unavailable
502wechat_query_failed
未支付仍返回 200,status 保持开放态;页以 status === "credited" 判成功。后台定时补单见 token-gateway.wechat-pay.sync-job.*(ShedLock wechatPaySyncJob;须 wechat-pay.enabled=true)。
POST /v1/chat/completions 代理已落地

OpenAI 兼容 Chat:非流式 JSON 或 stream=true SSE,转发 DeepSeek 白名单模型。默认白名单:deepseek-v4-flashdeepseek-v4-pro(可配)。

鉴权Authorization: Bearer sk-…(与 rotate 返回的明文一致)。无效 / 作废 sk → 401;Key 或账户 disabled → 403。无测试 Key / 免鉴权开关。
余额预检(G0-11):鉴权后检查账户 balance_li(默认须 ≥ 1)。不足 → 402 insufficient_balance,不转发上游。接受异步结算透支;与上游失败(通常 502 upstream_*)用状态码区分。

请求头

Authorization必填:Bearer sk-…
Content-Typeapplication/json

入参 · Body(代理层规则)

{
  "model": "deepseek-v4-flash",
  "messages": [{"role": "user", "content": "Hello"}],
  "stream": false
}
model必填;须在白名单,否则 400 model_not_allowed
stream缺省 / false → JSON;true → SSE(出站强制 include_usage=true
其他透传上游

成功响应

非流式:透传上游 JSON。流式:text/event-stream,透传 data: 事件至 [DONE]

本阶段常见错误

401missing_authorization / invalid_authorization / invalid_api_key
403key_disabled / account_disabled
402insufficient_balance(网关用户余额不足;非上游额度)
400model_required / model_not_allowed
503未配置 DEEPSEEK_API_KEYGATEWAY_KEY_PEPPER
502/504上游错误 / 超时(含上游限流等,短码 upstream_*
GET /v1/usage/me 已落地

用网关 sk 查询当前账户余额(厘)。本期不含今日用量汇总。余额为 0 / 负仍返回 200(不做预检)。

请求头

Authorization必填:Bearer sk-…

成功响应 · 200

{
  "user_id": 20,
  "tenant_id": "1",
  "user_code": "u_abc",
  "balance_li": 12340,
  "currency": "CNY",
  "currency_subunit": "li",
  "li_per_yuan": 1000,
  "key": { "name": "ftcs-desktop", "prefix": "sk-ab12" }
}

错误

401 / 403 / 503同 Chat sk 鉴权
GET /v1/models 已落地

返回配置白名单与有效价目的交集(OpenAI list 形态)。鉴权:网关 sk。不含单价。

请求头

Authorization必填:Bearer sk-…

成功响应 · 200

{
  "object": "list",
  "data": [
    { "id": "deepseek-v4-flash", "object": "model", "owned_by": "deepseek" },
    { "id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek" }
  ]
}

规划中(尚未实现)

下列能力仍待后续故事(今日用量汇总等)。

GET /v1/keys 规划

列出当前用户 Key 元信息(不含明文)。

常见错误与约定

对接注意

示例 · rotate(PowerShell)

curl.exe -s -X POST http://127.0.0.1:8088/v1/keys/rotate `
  -H "Authorization: Bearer %ACCESS_TOKEN%" `
  -H "Content-Type: application/json" `
  -d "{\"name\":\"ftcs-desktop\"}"

示例 · 换充值 ticket(PowerShell)

curl.exe -s -X POST http://127.0.0.1:8088/v1/billing/recharge/ticket `
  -H "Authorization: Bearer %ACCESS_TOKEN%"