Files
new-api-key-usage/README.md
T
skycurtain 30e71beb09 docs: 实施计划(loop 模式 + 八阶段验收标准)
- loop 机制:取任务 → 实现 → 验证 → 记录 → 提交 → 报告;遇设计空白停车问作者
- 八阶段:数据层与密钥 → key 管理 → 管理面其余 → 同步引擎 → 查看面数据面 → /usage-log 前端 → 管理面前端 → 收尾
- 前提已定:Docker 本地 PG · 真实 key 联调 · loop 内直接提交(作者授权)
2026-09-15 00:03:46 +08:00

101 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# new-api-key-usage
> 针对 NewAPI 平台的 key 用量统计工具
定时用每个 sk-key 轮询网关日志接口,按 `request_id` 去重,攒全量日志到本地数据库;任意时间范围的用量查询,即一条本地聚合查询。
| 项目 | 内容 |
|------|------|
| 状态 | 实现起步(脚手架已落地:`apps/web` · `apps/server` · `packages/shared`;设计文档全部定稿) |
| 技术栈 | Bun + Hono + PostgreSQL · 前端 Vite + Vue 3 + naive-ui |
| 部署 | 实例运营方提供的服务器;交付方式为推送仓库 / 产物,由运营方执行部署 |
## 文档地图
| 文档 | 什么时候读 | 状态 |
|------|-----------|------|
| [`docs/design/api_keys表字段设计.md`](docs/design/api_keys表字段设计.md) | 数据模型第一张表:字段、语义与决策理由 | 定稿(2026-09-11;09-13 修订 key 登记与表名、新增 `disabled_source`) |
| [`docs/design/usage_logs表字段设计.md`](docs/design/usage_logs表字段设计.md) | 数据模型第二张表:日志明细、去重与写入策略 | 定稿(2026-09-11) |
| [`docs/design/purchases表字段设计.md`](docs/design/purchases表字段设计.md) | 数据模型第三张表:购买记录(实付人民币的唯一落点、折算链第二条) | 定稿(2026-09-13;同日修订查看面金额口径) |
| [`docs/design/qpu_history表字段设计.md`](docs/design/qpu_history表字段设计.md) | 数据模型第四张表:换算参数时间线(自动采样、只读) | 定稿(2026-09-13) |
| [`docs/design/交互设计.md`](docs/design/交互设计.md) | 界面设计:方向层 + 细节层(跨页共性、逐页细节、状态与文案) | 定稿(2026-09-13) |
| [`docs/design/API契约.md`](docs/design/API契约.md) | 前端 ↔ 服务端接口契约:约定层 + 逐页端点(前端实现看这篇) | 定稿(2026-09-13) |
| [`docs/design/同步引擎设计.md`](docs/design/同步引擎设计.md) | 采集侧引擎:调度、每轮动作、失败处理、可观测、部署初始化(后端实现看这篇) | 定稿(2026-09-13) |
| [`docs/design/实施计划.md`](docs/design/实施计划.md) | 开发推进方案:loop 模式 + 八阶段验收标准 + 进度表 | 定稿(2026-09-14;进度随 loop 更新) |
| [`docs/upstream/NewAPI-sk-key接口文档.md`](docs/upstream/NewAPI-sk-key接口文档.md) | 查 sk-key 可用接口的字段级事实(源码实证,430 行) | 事实依据 |
| [`docs/upstream/NewAPI-接口范围.md`](docs/upstream/NewAPI-接口范围.md) | 本项目对接的接口范围(9 个接口的用途与鉴权) | 事实依据 |
- 命名与分区:**目录用英文、文档名用中文**;`docs/design/`(我们定的方案与决策)· `docs/upstream/`(NewAPI 的实证事实,不做设计决策)· `docs/deprecated/`(**已废弃的历史文档,只存档、不引用、不作依据**)· 未来的 `docs/delivery/`(给部署方看的部署文档)
- 生命周期:设计文档定稿后不搬家、只改状态(草案 → 讨论中 → 待审阅 → 定稿(日期) → 已实现);被取代时删除(git 历史即考古),尚未入库的历史文档移入 `docs/deprecated/`
## 这是什么项目
- 名字直述用途:NewAPI 网关下按 key 查询用量(key usage)
- 全场景统一小写 kebab-case:仓库名 / 包名 / 文档标题一致,无大写变体
## 背景故事
- 本工具源于一个真实场景:[NewAPI](https://github.com/QuantumNous/new-api)(Go + GORM 的开源 LLM 网关)实例提供订阅服务,多个使用者通过分发 API key 共享同一份订阅额度
- 项目作者是该实例的**普通用户**:购买了订阅额度,创建多个 sk- API key 分发给多名使用者分摊使用成本——**非商业,纯成本分担**
- 关键身份约束:项目作者**只有 sk-key,没有实例的管理员账号**。因此:
- 一切数据获取只能走 key 级接口(账号级接口用 sk-key 访问会 401)
- 工具部署在**实例运营方提供的服务器**上,作者不直接接触服务器——**交付方式是推送仓库 / 产物,由运营方执行部署**
## 核心需求(v2 定稿)
| # | 需求 | 说明 |
|---|------|------|
| 1 | key 用量查询 | 输入 sk-key + 时间范围 → 查模型请求日志与用量。支持一次输入多个 key 合并查询(项目作者借此获得全员视角) |
| 2 | key 登记 | 管理通道手动登记:登记后入库并加入轮询;查看面凭已登记的 key 查询(纯只读) |
**明确不做的**(v1 → v2 的收缩边界):费用分摊计算(后议;折算基础已就绪——两张折算表设计见文档地图)、计费周期管理、多组支持、登录页、admin 角色、会话记忆。
## 架构的根本动因:为什么必须本地攒数据
这不是优化选项,是上游接口的物理约束决定的**唯一路径**:
1. sk-key 可用的接口(`/api/usage/token`、`/api/log/token` 等)返回的都是**累计值**或**最近 1000 条**,没有任何接口支持时间范围过滤
2. 真正适合算"当月用量"的接口(`/api/log/self/stat`,支持时间范围 + quota 汇总)需要账号登录态,sk-key 访问 401——这是"按时间段统计用量"无法纯客户端实现的**核心障碍**
3. `/api/log/token` 硬上限 1000 条、不支持任何参数(分页 / limit / 时间过滤全被忽略)
→ 结论:轮询同步 + `request_id` 去重入库 + 本地数据库。上游接口的完整实证(含源码出处)见 [`docs/upstream/NewAPI-sk-key接口文档.md`](docs/upstream/NewAPI-sk-key接口文档.md)。
## 已定技术决策
| # | 项 | 决策 | 备注 |
|---|----|------|------|
| 1 | 部署形态 | 实例运营方提供的服务器,标准前后端 | 弃 Cloudflare Workers(v1 方案),因运营方愿意提供服务器资源 |
| 2 | 运行时 / 框架 | **Bun + Hono** | 单进程部署、原生 TS;Hono 轻量路由 |
| 3 | 数据库 | **PostgreSQL** | 服务器现有实例 |
| 4 | 鉴权 | 无 admin、无登录页;查看面凭已登记的 key 查询;管理面由 env 口令 | 写操作全部收拢在管理面(key 管理 + 折算数据);key 失效由轮询 401 自动摘除、管理面手动启停补位 |
| 5 | 信任模型 | 实例运营方(= 网关管理员 = 服务器管理员)不是威胁对象——他们本就能看到一切;真正的防护对象是第三方拖库(sk-key 应加密存库,实现时落地) | 无法在对方控制的机器上对抗对方,接受此公理 |
| 6 | 计费口径 | 展示 quota + $ 参考换算(÷500000),并支持折算为实付人民币 | 折算机制(两层时间线)见文档地图中的两张折算表设计 |
| 7 | 代码组织 | 前后端分离 **monorepo**(Bun workspaces:`apps/web` · `apps/server` · `packages/shared`) | **前后端独立部署、分别更新**(2026-09-13 修订:前端静态产物独立托管;后端不托管前端产物);**后端单进程**(API + 同步引擎同进程、单实例);不引入 turbo 等编排工具 |
| 8 | 数据库访问 | **Drizzle ORM + drizzle-kit** | TS 定义 schema;迁移由 drizzle-kit 生成 |
| 9 | 前端 | **Vite + Vue 3** | 页面结构与 UI 组件库选型留技术稿定 |
| 10 | 人民币折算 | 两层时间线(`quota_per_unit` 自动采样 + 购买记录人工登记),**按购买时刻取尺** | 整笔订阅一把尺;历史永远用当时的尺子量;见文档地图中的两张折算表设计 |
| 11 | 管理通道 | `/manage/*` 独立路由 + env 口令;key 管理(登记 / 清单 / 启停)+ 购买记录的 CRUD + 换算参数时间线只读查看 | 本工具唯一写操作入口;查询域保持完全只读;见 [`docs/design/交互设计.md`](docs/design/交互设计.md) §0 |
## 关键事实清单(实测 / 源码确认,实现时不得违反)
> 出处均见 [`docs/upstream/NewAPI-sk-key接口文档.md`](docs/upstream/NewAPI-sk-key接口文档.md),此处仅索引。
1. **`QuotaPerUnit = 500000`**:quota ÷ 500000 = 额度美元(接口①与④交叉实测吻合)——**但它不是实付金额**:折算到实付人民币还需乘「购买汇率」(由运营方定价、可变,历史上调整过一次)。完整折算机制见文档地图中的两张折算表设计
2. **去重必须用 `request_id`**:`/api/log/token` 返回的 `id` 是每次从 1 重编的展示序号,绝不能用于去重
3. **`created_at` 是 UTC Unix 秒**(三字段交叉实测),北京时间输入需转换
4. **1000 条硬上限**:窗口只保留最近 1000 条——实测当前站点的日志速率下约等于 3 天;5 分钟级同步余量充分,但同步停摆超过约 3 天会永久丢数据
5. **`TokenAuthReadOnly`**:用量/日志接口用宽松鉴权,key 过期 / 额度耗尽仍可查询(用完也能对账);**显式禁用会被 401 拒绝**(2026-09-13 源码复核)
6. **`DisplayTokenStatEnabled` 已开启**:billing 接口返回 token 级额度
## 工程注记
- CORS 必须由服务端代理解决(浏览器直连 NewAPI 网关会被拦)——**指上游网关**;本项目前后端之间的连线见下条
- **前后端连线(2026-09-13 修订)**:**同域反代为主**(`/api/*` 反代到后端端口——浏览器视角同源,无 CORS、前端用相对路径)、**跨源直连为辅**(后端 CORS 白名单可配 + 前端运行时 API base;`X-Manage-Token` 自定义头会触发 OPTIONS 预检;凭证不进 Cookie,无需 credentials 模式)
- bun runtime 跑 vue-tsc 存在已知 bug([bun#4754](https://github.com/oven-sh/bun/issues/4754)),类型检查与构建需解耦
## 下一步
1. **实现**:脚手架已落地(命令见 [`AGENTS.md`](AGENTS.md))——按 [`docs/design/API契约.md`](docs/design/API契约.md) 实现 11 个端点(查看面 `/usage-log` 先行),按 [`docs/design/同步引擎设计.md`](docs/design/同步引擎设计.md) 实现采集引擎
2. **部署前确认清单**(开工前向实例运营方确认):服务器 OS / PostgreSQL 实例连接方式 / 有无反代与**能否配置 `/api` 反代规则** / Bun 可安装性 / **后端必须单实例运行**(同步引擎前提)
3. 后议功能:充值折算、多订阅实测等见两张折算表设计文档的「待定事项」