Skip to content

Architecture

KS-OTO edited this page Sep 21, 2026 · 1 revision

架构

为什么接口只有 3 个

页面挂在边缘云上,而部分平台按节点生存时间计费 —— 端点数直接决定唤醒次数与单次存活时长。 因此所有读数合并成 3 个接口(此前是 13 个):

接口 何时调用 说明
GET /api/status 首屏一次 只回配置状态(哪些平台配了几个 Key、缺哪一半、站点自定义),0 次上游调用,因此极快
GET /api/usage 每次刷新 10 家平台的读数合并成一封信封,见下
GET /api/volc/inference-usage 按需(改模型过滤时) 火山推理用量。故意不合并:它是唯一带用户输入的读数接口,并入会让「改一个模型过滤」退化成「全量重拉 10 家」

/api/usage 的信封形状

{
  "providers": {
    "deepseek": { "data": { "accounts": [] } }, // 成功
    "newapi": { "error": "未配置 …", "code": "NOT_CONFIGURED" }, // 未配置
    "zhipu": { "error": "…", "code": "Zhipu_HTTP_500" }, // 真实失败
  },
  "fetchedAt": 1789616585319,
}

两个设计要点

① 每个平台是独立切片,一家的失败不会污染其余九家(也不会让整封信封变成 500)。 未配置走 NOT_CONFIGURED,前端渲染成中性空态而不是错误墙。未配置的 Key 不影响其他平台出卡。

服务端每个子查询各自容错:同一账号下的多个子接口不共用 Promise.all, 否则任一子接口 500 就把该账号其余数据全部丢掉(这是修过的真实 bug)。

② 服务端有 TTL 缓存 + 单飞(single-flight):命中缓存时上游调用为 0, 并发刷新共享同一次上游请求。TTL 按数据变化节奏分两档:

档位 TTL 覆盖
余额 / 用量类 60 秒 余额、窗口用量、代金券
结构 / 权益类 300 秒 资源包、套餐、座席

默认刷新间隔 180 秒,所以按天计的那几项大约每两次自动刷新才真正打一次上游。

手动点「刷新」会绕过缓存(?refresh=1),因此手动刷新永远拿得到实时数据。

上游调用与鉴权

平台 鉴权方式
火山方舟 火山 v4 签名(Web Crypto 手写,见 sign.ts)
阿里云(BSS / ROA) RPC/ROA 签名 + ACS3-HMAC-SHA256
百度千帆 BCE AK/SK 签名
百炼个人版 控制台会话 Cookie(或 AK/SK 换 access token)
模力方舟代金券 会话 Cookie
其余 Bearer Token / API Key

两处语义坑:

  • New API token 要系统访问令牌,不是 sk- 模型调用密钥;
  • 上游业务码比 HTTP 码信息量大;账号被停用也是 401,但令牌已被认出 → 这种情况不回落账单接口(否则会把「账号停用」误报成「权限不足」)。

目录结构

server/           平台无关 API 核心 + Bun 入口
  app.ts          createAppHandler(env):3 个接口(/api/status + /api/usage + /api/volc/inference-usage)
  cache.ts        上游查询的 TTL 缓存 + 单飞
  index.ts        Bun 入口:Bun.serve + dist/ 静态服务
  env-vars.ts     环境变量的单一事实来源(SERVER_ENV_VARS)
  multi.ts        多账号凭据读取(PREFIX / PREFIX_2 / ...)
  sign.ts         火山引擎 v4 签名(Web Crypto)
  volc.ts         方舟管控面 API 客户端
  deepseek.ts     DeepSeek 余额客户端
  zhipu.ts        智谱客户端(Coding Plan 额度 / 账户余额 / 资源包)
  gitee.ts        模力方舟(Gitee AI)资源包余额客户端
  aliyun.ts       阿里云 RPC/ROA 签名 + BSS 资源包客户端
  aliyun-console.ts 百炼控制台网关(Token Plan 个人版用量 + ACS3 签名)
  tokenplan.ts    阿里云 Model Studio Token Plan 客户端(组织/座席/共享包)
  opencode.ts     OpenCode Go 订阅额度客户端
  newapi.ts       New API 客户端:管理接口优先、账单接口回落,订阅/钱包模式判定
  balances.ts     OpenRouter 余额客户端
  plans.ts        Kimi / MiniMax Token Plan 客户端

src/              Vue 3 前端(TDesign Vue Next + Pinia)
  api.ts          前端 API 客户端(错误信封手写收窄;不引 Zod —— 它整库进客户端)
  tdesign.ts      TDesign 组件显式注册表(整库默认导出不可摇树,实测多 43% 体积)
  stores/         Pinia stores(dashboard 数据编排 / theme 暗色主题)
  types.ts        共享类型(多账号 AccountEnvelope 判别联合 + AccountDetail 统一详情模型)
  detail.ts       详情模型的构件库(field/metric/table/notice/link/windowQuota/cardsOf …)
  utils.ts        展示格式化工具 + 列策略谓词 + 平台卡排序
  modelDocs.ts    平台名 → 官方「可用模型」文档地址
  format.ts       数值精度的唯一入口(渲染层禁 toFixed / Math.round)
  components/     各平台区块组件(AccountSection 统一外壳)
    ui/           共享渲染骨架(AccountCard / AccountCardBody / AccountDetailPanel /
                  DetailSection / DetailFields / DetailTable / MetricTile / UsageBar)
    *Detail.ts    每个平台一个适配器:平台原始响应 → AccountDetail
  assets/layout.css 全站唯一布局层(语义化网格原语 + 断点;组件不写断点)
  assets/theme.css  全站唯一配色入口(覆盖 TDesign 的 --td-*,亮/暗各一套)

e2e/              Playwright E2E 冒烟
worker/           Cloudflare Workers 入口(复用 server/app.ts)
cloud-functions/  EdgeOne Makers 云函数(/api/* 全捕获 + /api/diag 自诊断)
docs/             design-baseline.md(设计系统规格)+ 供应商接口调研
.github/          CI(四道门禁)+ issue / PR 模板 + Dependabot

前端渲染模型

详情视图统一建模成 AccountDetail:字段恒存在的扁平对象,失败时取中性空值。 平台差异收敛在各自的 *Detail.ts 适配器里,Section 组件只做 cardsOf(data.accounts, toXxxDetail) + <AccountCard>,模板里不认平台专属字段。

cardFace 是「卡面 vs 弹窗」的唯一开关 —— 别在组件里挑字段。现有两种用法: 同一账号的另一个订阅、只对部分模型生效的限额(火山日额度就是后者)。

细节见 设计系统 与仓库内的 docs/design-baseline.md。

Clone this wiki locally