Files
new-api-key-usage/docs/design/实施计划.md
T
skycurtain 575643eb13 docs: 实施计划——离线开发与样例数据(§6),阶段 2/4 验收标准适配
- 新增 §6:样例数据(api*.json,未入库)的用途与开发策略——上游客户端接口化(真实 HTTP / 样例文件两实现)+ seed 脚本(阶段 4 落地)+ 真实链路待验清单
- 前提改为「以样例数据为主,真实 key 未开,不阻塞」
- 阶段 2 验收改为「注入样例实现」;阶段 4 增 seed 脚本与 401 状态机单测;阶段 6 用 seed 数据走查
2026-09-15 00:30:29 +08:00

90 lines
9.1 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.
# 实施计划
> 开发过程的推进方案:**loop 模式**(每轮一个可验证批次)+ 八个阶段的验收标准。任务口径全部指向 `docs/design/` 的设计文档;本文件只回答「先做什么、怎样算做完、下一步做什么」。
| 项目 | 内容 |
|------|------|
| 状态 | 定稿(2026-09-14;**进度表随 loop 持续更新**) |
| 关联 | `docs/design/API契约.md` · `docs/design/同步引擎设计.md` · `docs/design/交互设计.md` · 四张表字段设计 · `AGENTS.md`(命令) |
| 范围 | 阶段划分、每批验收标准、loop 机制与推进规则;技术细节一律以设计文档为准 |
| 当前进度 | 阶段 0 / 1 已完成;下一批:**阶段 2**(上游客户端 + key 管理) |
---
## 1. loop 机制(每轮固定动作)
1. **取任务**:计划表(§3)中当前批次的第一项
2. **实现**:以设计文档为准,不自由发挥
3. **验证**:跑该批次的验收命令(type-check / 单测 / 实测 curl / 浏览器走查)
4. **记录**:勾选进度表(日期 + 验证方式)
5. **提交**:一个逻辑单元一个 commit(**作者已授权 loop 内直接提交**,dev 分支;`git push` 永远由作者手工)
6. **报告**:一句话——做了什么 / 验证结果 / 下一项
7. **设计空白的处置(2026-09-14 定)**:作者可能在开发期缺席——遇到设计文档未覆盖的问题,**取最保守默认继续推进**(不破坏数据、不做不可逆操作、后续可改),写入下方「实现假设」节并在报告中显著标注,供作者事后补审。**不停摆。**
- 例外三条——与「后端单实例」「金额口径」「凭证安全」冲突时:跳过该子任务并标注,不自行决定
### 实现假设(loop 中累积,供作者补审)
| # | 假设 | 依据 / 可逆性 |
|---|------|--------------|
| 1 | server 侧单测用 `bun test`(内建);前端仍用 Vitest | 零依赖;可换(已落地:`bun test` 跑 `src/**/*.test.ts`) |
| 2 | 环境变量:全仓单一根 `.env`;服务与 drizzle-kit 经 `src/lib/load-env.ts` 显式加载(Bun 只自动加载 cwd 的 `.env`,不会向上找) | 可改;已落地 |
| 3 | 上游请求超时 30 秒;失败即本轮失败(不单轮重试) | 常量,可调 |
| 4 | key 格式校验仅「非空 + trim」(不强制 `sk-` 前缀,格式真假交上游判定) | 更宽松,避免误拒 |
| 5 | 去重指纹 = `sha256(key_id|created_at|model_name|quota|prompt_tokens|completion_tokens)` | 表设计 §3.1 已给字段清单 |
| 6 | 购买期间匹配:`subscription_id` 优先;同 ID 多条按 `[period_start, period_end)`;无期间记录仅在「同 ID 唯一」时兜底 | 边界情况,可调 |
| 7 | 登记成功后立即异步跑一次该 key 的单轮同步(不等 tick) | 契约 §2.2 已定「立即异步补一轮」 |
| 8 | 前端提交时间用 `toISOString()`(`Z` 形式) | 契约 约定 9 允许 |
| 9 | drizzle-kit 的连库驱动用 `postgres`(postgres.js,纯 JS)——仅 CLI 需要;运行时仍走 Bun 内建 SQL(`drizzle-orm/bun-sql`) | devDependency,可换 `pg` |
| 10 | 上游客户端以**接口 + 可注入实现**组织(真实 HTTP / 样例文件两种实现),引擎与路由依赖接口 | 见 §6;可改 |
## 2. 前提(2026-09-14 定)
- **开发库**:Docker 本地 PostgreSQL(`docker-compose.yml` 已落地;连接串走 `.env`,不入库)——四张表已建
- **联调策略**:**以样例数据为主**(见 §6)——作者未开测试 key;`NEWAPI_BASE` 可达性已验证(HTTP 200 / 5.2KB / 2.0s;`quota_per_unit = 500000`、`quota_display_type = USD`、`usd_exchange_rate = 6.8`,与文档一致)。真实登记 / 采集链路标「待作者补验」,**不阻塞**其他阶段
- **提交**:loop 内直接提交已授权;push 由作者手工(仓库有两个远端)
## 3. 阶段与验收标准
| 批次 | 内容 | 验收标准 | 状态 |
|------|------|---------|------|
| 0 | 脚手架 + 契约类型 + 501 骨架 | 命令链全绿(type-check / test / build) | ✅ 2026-09-14 |
| 1 | **数据层与密钥**:crypto(HKDF 派生 / HMAC-SHA256 / AES-256-GCM)· 时间工具(+08:00 序列化、UTC 秒转换)· 建表 | 单测:哈希确定性 + 加解密往返;`db:push` 后四张表在库 | ✅ 2026-09-14(15 项单测绿;四表在库——`api_keys` 17 列 / `usage_logs` 23 列 / 索引 8 个) |
| 2 | **上游客户端 + key 管理**(`GET` / `POST` / `PATCH` 三端点)· 客户端接口化(§6) | curl 实测:登记 → 清单 → 停用/启用(**注入样例实现**:登记成功路径用样例响应驱动);三类错误码分支正确(`key_rejected` / `already_registered` / `upstream_unavailable`) | ☐ |
| 3 | **管理面其余**:purchases 五端点 + qpu 一端点 | curl 实测:CRUD 全流程 + 观测聚合(`matched_purchase_id` 判定正确) | ☐ |
| 4 | **同步引擎**:tick(5 分钟 + 防重入 + 启动即跑)· 日志整批事务入库 · 额度采集 · QPU 采样 · 401 状态机(N=3)· **seed 脚本**(§6) | 库中出现数据(seed 或真实采集);`[sync]` 日志符合引擎设计;401 状态机单测(连续 401 → 摘除且 `disabled_source = auto_401`) | ☐ |
| 5 | **查看面数据面**:`logs/query` + `summary` + 折算链(按购买期间分段取尺) | curl 实测:字段齐全、`meta.data_until` 取最慢、金额按购买分段累加;**无归属购买的行 `amount_rmb = null` 且计入 `unpriced_count`**(2026-09-14 定) | ☐ |
| 6 | **前端 `/usage-log`**:筛选区(key 标签 + datetimerange + 快捷项)· 卡片 · 表格 + 展开行 · 分页器 · 六种状态模式 | 浏览器实测:seed 数据走一遍;六态逐一可见(加载 / 初始 / 未收录 / 无记录 / 同步中 / 失败);金额为 `null` 显示「—」 | ☐ |
| 7 | **前端管理面**:口令门帘 + 侧边栏 + 三页(key 管理 / 购买记录含核对提示与补录 / 换算参数) | 浏览器实测:登记、启停、CRUD、补录、时间线全操作 | ☐ |
| 8 | **收尾**:部署文档(`docs/delivery/`)· 端到端走查 · 文档状态更新(设计文档 → 已实现) | 一条命令链可部署(前后端独立 + 后端单实例注记);走查无阻 | ☐ |
## 4. 待定问题
| # | 问题 | 状态 |
|---|------|------|
| 1 | `amount_rmb` 在「日志无归属购买记录」时取什么值 | **已决(2026-09-14)**:取 `null`(展示「—」),汇总增 `unpriced_count`——见 API 契约 §2.1 |
| 2 | 充值(`wallet`)路径的折算近似(设计文档标「后议」) | 先按「未折算」处理(`null` / 计入 `unpriced_count`);池子加权平均待后议 |
| 3 | `dedupe_key` 内容指纹的拼接细节 | **已决**:见「实现假设」#5(表设计 §3.1 已给字段清单) |
| 4 | 上游对同账号请求的态度(限流 / 并发被拒时的表现) | 遇错即按「其他故障」处理(不计数、不摘除),如实记录日志 |
## 5. 风险
- 真实上游联调依赖网络可达(已实测 200);真实 key 未开——以样例数据替代(§6),真实链路标「待作者补验」
- 折算链(阶段 5)依赖已登记的购买记录——无记录时的行为已定(`null` + `unpriced_count`)
- **后端必须单实例**(同步引擎前提):部署文档(阶段 8)必须写明;本地开发也不要起两个后端
## 6. 离线开发与样例数据(2026-09-14 定)
**结论:不需要真实 key 也能跑完全部阶段。** 仓库根目录有三个**实测响应快照**——未入库(`.gitignore` 用 `/api*.json` 排除,**绝不提交**),供开发与验证使用:
| 文件 | 内容 | 用途 |
|------|------|------|
| `api.log.token.json` | `/api/log/token` 的 1000 条响应快照(≈3.2 天、含 `type=2` 与 `type=5`) | 引擎解析 / 去重 / 入库的 fixture;**seed 数据源** |
| `api.log.token.other.json` | 单条日志 `other` 字段样本(26 键:`subscription_*` 八件套、`cache_tokens`、倍率等) | 解析与展示字段核对 |
| `api.status.json` | `/api/status` 响应(含 `quota_per_unit = 500000` / `quota_display_type = USD` / `usd_exchange_rate = 6.8`) | QPU 采样逻辑的 fixture |
**开发策略**:
1. **上游客户端接口化(假设 #10)**:定义接口(如 `UpstreamClient`:`fetchTokenUsage` / `fetchLogs` / `fetchStatus`),两套实现——真实 HTTP 实现 + **样例文件实现**(从上述 JSON 读响应);引擎与路由依赖接口,联调时替换
2. **seed 脚本**(阶段 4 落地,`apps/server/scripts/seed.ts`):从样例灌入——一个占位 `api_keys` 行(**假 key 常量**,其哈希/密文走真实 crypto 逻辑,便于用固定测试 key 查询命中)+ 1000 条 `usage_logs` + `qpu_history` 基线 + 一条示例 `purchases`(用于验证折算链)——使阶段 5/6/7 的开发与验证有真实形状的数据
3. **真实链路验证清单**(标「待作者补验」):登记成功路径(真实 `/api/usage/token` 验证)、真实采集轮次、真实 401 摘除——不阻塞其他阶段,做完在报告中列出待验项