Files
new-api-key-usage/docs/design/实施计划.md
T
skycurtain f906289f3a docs: 实施计划——阶段 1 完成(进度表 + 实现假设更新)
- 阶段 1 标记完成(15 项单测绿 + 四表在库:api_keys 17 列 / usage_logs 23 列 / 索引 8 个)
- 实现假设 #2 更新为落地形态(load-env.ts 显式加载根 .env);新增 #9(drizzle-kit 用 postgres 驱动)
- 前提补「联调用的测试 key」准备说明(建 key + 发一次成功/一次失败请求)
2026-09-15 00:18:44 +08:00

7.2 KiB
Raw Blame History

实施计划

开发过程的推进方案: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

2. 前提(2026-09-14 定)

  • 开发库:Docker 本地 PostgreSQL(docker-compose.yml 已落地;连接串走 .env,不入库)——四张表已建
  • 上游联调:真实 sk-key(作者已填 .env)——NEWAPI_BASE 实测可达:HTTP 200 / 5.2KB / 2.0s;quota_per_unit = 500000、quota_display_type = USD、usd_exchange_rate = 6.8,与文档一致
  • 联调用的测试 key:作者新建专用测试 key(用完可删),并用它发一次成功调用(产生 type=2 日志)与一次故意失败调用(产生 type=5 日志)——阶段 4/5 的验证依赖这些日志
  • 提交: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 三端点) curl 实测:登记 → 清单 → 停用/启用;三类错误码分支正确(key_rejected / already_registered / upstream_unavailable) ☐
3 管理面其余:purchases 五端点 + qpu 一端点 curl 实测:CRUD 全流程 + 观测聚合(matched_purchase_id 判定正确) ☐
4 同步引擎:tick(5 分钟 + 防重入 + 启动即跑)· 日志整批事务入库 · 额度采集 · QPU 采样 · 401 状态机(N=3) 库中出现真实数据;[sync] 日志符合引擎设计;连续 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 + 快捷项)· 卡片 · 表格 + 展开行 · 分页器 · 六种状态模式 浏览器实测:真实数据走一遍;六态逐一可见(加载 / 初始 / 未收录 / 无记录 / 同步中 / 失败);金额为 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);不可用时临时以假上游过渡(不改变验收口径)
  • 折算链(阶段 5)依赖已登记的购买记录——无记录时的行为已定(null + unpriced_count)
  • 后端必须单实例(同步引擎前提):部署文档(阶段 8)必须写明;本地开发也不要起两个后端