2026-07-01 17:42:10 +08:00
# 执行进度记录
> 本文件是只追加的历史流水,用来记录任务执行过程、验证命令、阻塞点和关键决策。
> 当前目录、当前命令、下一个可领取任务等可覆盖快照,写入 [`docs/current-state.md`](docs/current-state.md)。
## 职责边界
- `docs/06-tasks.md` :任务看板,维护任务状态、依赖和验收要点。
- `progress.md` :历史流水,只追加记录每轮执行发生了什么。
- `docs/current-state.md` :当前快照,可覆盖更新仓库现实、可运行命令和下一步。
不要在本文重复维护当前目录结构、当前运行命令或下一个任务;这些信息以 `docs/current-state.md` 为准。
## 记录格式
每完成或中断一轮任务,在文件末尾追加一条记录:
```markdown
## 【YYYY-MM-DD】T-【编号】 【任务名】
- 状态:【DONE / BLOCKED / PARTIAL】
- 变更:【修改了哪些文件或模块】
- 验证:【运行的真实命令和结果】
- 阻塞:【如有,写明原因和需要谁决策】
- 决策:【如有,记录本轮确定的关键取舍】
- 下一步:【建议下一个任务 ID 或待确认事项】
```
## 执行记录
## 2026-06-29 文档初始化(非任务)
- 状态:DONE
- 变更:基于 `D:\github\harness_coding_docs` 模板,结合需求讨论生成 `cmhub` 全套 harness 文档(AGENTS/CLAUDE、docs/00– 06、api、routes、current-state、README、progress、init 脚本);通用流程文档(adoption/clean-state/method-map/evaluator/quality)从模板复制。
- 验证:仅文档,无代码可跑。
- 决策:技术栈 Django+DRF+django-admin;预付费点数模型(充值按汇率转点存本地,调用扣本地点数,不实时查支付系统);生成接口同步返回;充值由支付系统服务端回调入账(验签+幂等)。
- 下一步:领取 T-001 初始化 Django + DRF 骨架。支付系统接口文档待提供(T-304 前需要)。
## 2026-06-29 设计优化:可插拔供应商(别名 + 适配器)(非任务)
- 状态:DONE
- 变更:把「两个接口 + 后台配模型」的设计从简化版抬到可治理版,更新到多个文档:
- `04-architecture.md` : AI 调用层改为 Provider 适配器架构;新增 ModelAlias 表与 AiModel 的 capabilities/加密 key; PricingRule 改为按别名定价;CallRecord 增 alias + model_used;计费时序加入别名解析与能力校验;难点表补充抽象泄漏/供应商耦合/配置热生效/审计;项目结构加 `apps/ai/providers/` 。
- `api.md` :对外 `model` 字段明确为能力别名;新增 `parameters` 透传;响应加 `model_used` ;图片默认返回 URL;AI 模块合约改为别名解析 + Provider 适配器接口。
- `03-tech-stack.md` / `02-requirements.md` / `05-coding-rules.md` / `routes.md` / `06-tasks.md` :同步别名机制、密钥加密、配置审计、对象存储等决策与任务。
- 验证:仅文档;自检文档间链接与任务依赖一致(T-302 依赖更新为 T-104)。
- 决策:① 对外绑能力别名而非具体模型 SKU;② `api_type` 升级为显式 Provider 适配器层 + capabilities 声明 + parameters 透传;③ 供应商密钥加密存储 + 配置热生效 + 后台变更审计。按账号授权别名、按比例分流/故障转移列入 Backlog(接口预留,MVP 不实现)。
- 下一步:领取 T-001 初始化 Django + DRF 骨架(Phase 1 任务已重排为 T-101 适配器层 / T-102 别名解析 / T-103 审计 / T-104 跑通)。
## 2026-07-01 设计决策:桌面端全同步接入 + V2 异步预研(非任务)
- 状态:DONE
- 变更:把「桌面端不改、全同步接入 cmhub」的可用性结论与前提写入两个每轮必读文档:
- `03-tech-stack.md` 第二节「同步生成而非任务队列」决策下补:桌面端可不改交互骨架、只换 service 层 URL/密钥即可接入;同步可用的三前提;何时转 V2 异步的触发信号。
- `04-architecture.md` 第五节新增 `5.1 同步方案可用性结论` :超时链路层层放大对齐(点名 `timeout_seconds:0` 、Gunicorn 30s、Nginx 60s 三个默认值雷,建议统一 300s)、worker 数按峰值总并发预留、适用边界与 V2 触发条件。
- 验证:仅文档;`ai_models.json` 现存 `timeout_seconds: 0` ,迁入服务端时须改有限值(关联约束已写入 04 第五节)。
- 决策:① MVP 桌面端不改、`桌面端 → cmhub → 中转站` 全同步,多一跳不影响可用性,点数一致性更简单(一次请求闭环:预扣→同步调→成功/失败退点);② 同步可用的硬前提是「超时链路 + worker 容量」配对,否则「小量正常、上量假死」;③ 适用边界为单接入方小并发批量,V2 异步(队列)延后,但适配器接口与 `call_record` 三态需为异步预留口子。
- 安全提醒:`ai_models.json` 内三把 `sk-` 为明文真实密钥,视为已泄露,迁入时须加密存储(Fernet/KMS)并轮换;密钥不进桌面端。
- 下一步:不改变任务看板顺序,仍从 T-001 起步;V2 异步化触发条件见 03/04,暂不排期。
## 2026-07-01 设计决策:版本锁定 + 数据库选型(MySQL 8.4 独立实例)(非任务)
- 状态:DONE
- 变更:结合实际部署环境(一台 VPS,实测 15G 内存 / available 7.4G / 无 swap,已装 MySQL 5.7 供其他服务用),定稿版本与数据库选型,更新 `03-tech-stack.md` (技术栈表语言/框架/数据库三行 + 决策记录三条)、`04-architecture.md` (数据库条目 + 第五节并发扣点难点补 CHECK 版本注意)、`README.md` 技术栈行。
- 验证:仅文档。
- 决策:
- ① 框架/语言锁定 **Django 5.2 LTS + Python 3.12** ( `requires-python ">=3.12,<3.14"` );禁用已 EOL 的 Django 4.0/4.1;理由是安全/维护窗口,非性能(性能瓶颈在等上游+worker,见 5.1)。
- ② 数据库定 **MySQL 8.4 LTS, cmhub 专用独立实例** 。放弃复用 VPS 已有的 MySQL 5.7: 5.7 跑不了 Django 5.2(需 ≥8.0.11)、已 EOL、不支持 CHECK 约束;曾评估「坚持 5.7」会连锁把 Django 拖回 4.0(EOL)+Python≤3.10+cmbot 兼容风险,被否。内存宽裕(7.4G),单开独立实例与已有 5.7 隔离、互不影响。
- ③ 硬性约束:InnoDB + utf8mb4; CHECK 需 MySQL ≥8.0.16 才生效,扣点主防线是 `select_for_update` / `UPDATE ... WHERE balance>=N` ,不能只靠 CHECK; MySQL 默认隔离级别 REPEATABLE READ,计费按此语义验证;开发亦用 MySQL,不用 SQLite(会忽略 FOR UPDATE,测不出并发扣点)。
- 待办提醒:T-001 骨架落地时须选 MySQL 驱动(`mysqlclient` 或 `PyMySQL` )、`DATABASES` 配 `charset=utf8mb4` 、连接指向独立实例端口;机器建议补 2–4G swap;`03` 部署维度仍为「待定」,部署基线待后续定稿。
- 下一步:不改变任务看板顺序,仍从 T-001 起步(Django+DRF 骨架,按上述版本/数据库落地)。
## 2026-07-01 定位扩展:新增自助用户端(B2B → B2B+B2C)(非任务)
- 状态:DONE
- 变更:按用户新增需求(终端用户自助注册/扫码充值/API Key 管理/查记录),把项目从纯 B2B API 网关扩展为「自助用户端 + 计费 API + 运营后台」三合一,系统性更新文档:
- 定位/需求:`01-vision` 、`00-ai-start-here` 、`02-requirements` 、`project-brief` 、`project-onepager` (用户角色加注册用户;MVP 加用户端;移除"自助注册"非目标;注册不送点数)。
- 架构:`04-architecture` (系统结构加用户端;数据模型 Account→`User` +`UserWallet` +`ApiKey` ;两套认证;4.2 加自助扫码下单时序;难点加 API Key 哈希/注册滥用/Web-API worker 隔离/充错账户;项目结构 apps;架构纪律)。
- 合约/路由/规则:`api.md` (认证主体 User、`recharge/create` 转正扫码、API 只认 Key)、`routes.md` (用户端页面路由、admin 改 User/Wallet/ApiKey)、`03-tech-stack` (用户端形态 Django SSR+Bootstrap+allauth、鉴权、部署 worker 隔离)、`05-coding-rules` (范围、API Key 哈希、API 只认 Key、锁 wallet)。
- 任务:`06-tasks` (T-002 首迁移前定自定义 User; T-201 改 User/Wallet/ApiKey; T-203 锁 wallet; T-305 扫码下单;新增 Phase 4 用户端 T-501~504;里程碑加 M4 用户端)。
- `README` 、`current-state` 同步。
- 验证:仅文档。
- 决策:
- ① 前端形态选 **Django 模板 SSR 单体** ( +Bootstrap/allauth/crispy),不引前后端分离框架。
- ② 用户模型:`User` (auth 登录态) / `UserWallet` (点数余额,扣点锁 wallet、与 auth 解耦) / `ApiKey` (User 1:N, sha256 哈希存储、明文只显示一次)。
- ③ 认证分两套认同一 User:用户端 session+CSRF,对外 API 只挂 API Key(不挂 Session,防绕过计费)。
- ④ 充值:`recharge/create` 转正,用户端自助扫码下单 + 回调入账,订单绑定 user 防充错账户,金额 Decimal 向下取整。
- ⑤ **注册不送免费点数** (必须充值才有点数,降低薅羊毛);注册须邮箱验证、生成接口须限流。
- ⑥ 单体部署按路径把图片 API 与用户端页面分流到不同 worker 池。
- 待办提醒:**支付系统扫码下单 + 回调接口文档仍未提供**,是 T-304/T-305 充值的硬阻塞;自定义 User 必须在首次 migrate 前定义(Django 硬约束)。
- 下一步:任务仍从 T-001 起步;用户端任务见 Phase 4( T-501~504)。
## 2026-07-01 补全支付协议(参考同系统 PHP 实现,解除充值 blocker)(非任务)
- 状态:DONE
- 变更:从 Obsidian 笔记(虎观虾皮一键采购 `扫码支付购买流程-技术文档` + `扫码支付迁移到Django-实施指南` )提取同一支付系统的扫码支付协议,补进当前 Django 仓库文档:
- `api.md` :充值段重写为微信 V3 native + 支付宝当面付双回调(`/recharge/callback/wechat` 、`/alipay` ) + `create` ( pay_method、code_url/qr_code) + `status` 轮询;金额单位、验签、幂等、应答格式明确。
- `04-architecture.md` 4.2:补通道协议、库、金额单位、回调应答、`@csrf_exempt` 、主动查单兜底。
- `03-tech-stack.md` :充值对接行加库 `wechatpayv3` /`python-alipay-sdk` 。
- `routes.md` :拆微信/支付宝回调端点 + status。
- `06-tasks.md` :T-304 拆双回调+主动查单,T-305 加 weixin/alipay+轮询。
- `05-coding-rules.md` :回调 `@csrf_exempt` + 主动查单兜底。
- `current-state.md` : blocker 降级。
- 验证:仅文档。
- 决策:
- ① 路线确认:**继续当前 Django 仓库**, Obsidian 的 Go(Gin+GoAdmin) cmhub 方案仅作支付逻辑参考(用户拍板)。
- ② 支付通道:微信 V3 native(`wechatpayv3` ,金额**分**,回调 SDK 验签解密、`TRANSACTION.SUCCESS` 、应答 `{code:SUCCESS}` );支付宝当面付 `trade.precreate` ( `python-alipay-sdk` ,金额**元**, `verify` 、`TRADE_SUCCESS/FINISHED` 、应答 `success` )。
- ③ 二维码不含业务数据,靠 `out_trade_no` (=order_no) 在回调关联;回调 `@csrf_exempt` ;幂等 `select_for_update` +`status!=pending` ;须主动查单兜底。
- ④ 只取支付层,不引入 Obsidian 源里的套餐/会员有效期/邀请返佣业务(那是虎观助手专有);充值入账走本项目 `UserWallet` +`points_ledger` +`ExchangeRate` 。
- 待办提醒:**blocker 从「协议缺失」降级为「仅缺商户密钥/证书真实值」**,不阻塞开发,可先 mock。原 PHP 遗留坑(测试后门号、H5 金额写死、支付宝 debug 沙箱、明文密钥)不迁入。
- 下一步:任务仍从 T-001 起步;充值见 T-304/T-305(可 mock 先行)。
## 2026-07-01 吸收 AI 模型调用机制(来自 Go 方案 §16.6)(非任务)
- 状态:DONE
- 变更:从 Obsidian `Golang-Gin-GoAdmin-技术方案` §16.6 提取三个上游模型的真实调用机制(业务规则/上游契约,非技术栈),补进 `04-architecture.md` ( 3.1 + AI 层职责)、`api.md` (待确认 + image 改图必传)、`03-tech-stack.md` 、`06-tasks.md` ( T-101 验收)。
- 验证:仅文档。
- 决策:
- ① GPT-5.5( `chat` )标准 chat/completions; Nano Banana 2( `api_type=auto` )走 chat/completions 多模态返图、需自定义解析、可文/图生图;GPT Image 2( `images_edits` )走 images/edits 改图、原图必传。
- ② 两个图片模型**非标准** `images/generations` ,每模型独立 url+key;图片返回结构(URL/base64/位置)**首次对接抓真实响应再定解析**。
- ③ 只吸收 AI 模型机制,**不吸收** Go 方案的会员套餐/续期/折扣/邀请返佣/多端 JWT(属虎观采购业务或 Go 栈,非 cmhub 定位);鉴权继续用 API Key(比桌面端 JWT 更安全,不在桌面端存密码)。
- 下一步:T-101/T-102 实现适配器时按此机制,首次对接 `api.vectorengine.ai` 抓真实响应确认图片返回结构。
<!-- 后续从这里向下继续追加记录。 -->
## 2026-07-01 文档优化:定位一致性 + 充值 schema + 调用状态 + 环境配置(非任务)
- 状态:DONE
- 变更:按全栈落地前优先级修正文档:
- `docs/README.md` / `project-brief.md` / `project-onepager.md` :定位同步为「自助用户端 + 计费型 AI 能力网关 + 运营后台」三合一,并加入 `env.md` 导航。
- `docs/04-architecture.md` / `docs/api.md` / `docs/06-tasks.md` :充值订单改为下单时锁定 `exchange_rate` 与 `points_granted` ,回调入账使用订单值并校验金额;`call_record.status` 明确为 `pending -> success / failed` ,失败退点通过 `points_ledger(refund, ref_call_id)` 关联。
- `docs/env.md` :新增环境变量与配置清单,覆盖 Django、MySQL、AI 密钥加密、微信/支付宝、对象存储与上线检查。
- `docs/03-tech-stack.md` / `docs/current-state.md` / `docs/00-ai-start-here.md` / `docs/02-requirements.md` / 汇报文档:同步环境配置入口、当前快照与支付 blocker 降级口径。
- 验证:文档修改;用 `rg` 定位相关段落,未运行代码测试(仓库当前尚无代码)。
- 阻塞:无。
- 决策:充值汇率采用「下单锁定」而非「回调时读取当前汇率」,便于页面展示预计到账点数,也避免用户扫码后后台改价导致入账变化;调用记录不增加 `refunded` 状态,账务冲正由点数流水表达。
- 下一步:仍从 T-001 初始化 Django + DRF 骨架开始。
## 2026-07-01 文档优化:里程碑口径同步(非任务)
- 状态:DONE
- 变更:同步汇报文档里程碑到 `06-tasks.md` 的 M1-M5 口径:`project-brief.md` 增加 M4 用户端可用、M5 验收上线;`project-onepager.md` 进度行同步 M4/M5。
- 验证:文档修改;用 `rg` 定位里程碑旧口径。
- 阻塞:无。
- 决策:以 `06-tasks.md` 的里程碑为权威,汇报文档只做同口径摘要。
- 下一步:仍从 T-001 初始化 Django + DRF 骨架开始。
## 2026-07-01 文档优化:Phase 顺序口径同步(非任务)
- 状态:DONE
- 变更:更新 `docs/00-ai-start-here.md` 的优先路径,Phase 3/4/5 与 `docs/06-tasks.md` 对齐:Phase 3 为对外 API 与充值,Phase 4 为用户端,Phase 5 为后台与发布。
- 验证:文档修改;用 `rg` 对照 `00-ai-start-here.md` 与 `06-tasks.md` 的 Phase 标题。
- 阻塞:无。
- 决策:任务阶段顺序以 `06-tasks.md` 为权威,入口文档只做一致的导航摘要。
- 下一步:仍从 T-001 初始化 Django + DRF 骨架开始。
2026-07-01 18:01:23 +08:00
## 2026-07-01 T-001 初始化 Django + DRF 项目骨架
- 状态:DONE
- 变更:
- 新增 `manage.py` 、`config/` Django 工程骨架。
- 新增 `requirements.txt` ,锁定 Django 5.2 系列与 DRF 3.16 系列。
- `config/settings.py` 加入 `rest_framework` , `SECRET_KEY` / `DEBUG` / `ALLOWED_HOSTS` 支持环境变量,默认仅用于本地开发。
- `init.ps1` / `init.sh` 替换为真实命令;按用户要求使用系统 Python 3.12,不使用虚拟环境。Windows 用 `py -3.12` , Unix/WSL 用 `python3.12` 。
- 同步 `docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/current-state.md` 、`docs/06-tasks.md` 、`README.md` 。
- 验证:
- `py -3.12 -m pip install -r requirements.txt` : Django 5.2.15 / DRF 3.16.1 已安装。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py test` :通过,当前 0 tests。
- `./init.ps1` :通过,完成依赖检查、`manage.py check` ,打印启动命令。
- `C:\Python312\python.exe manage.py runserver 127.0.0.1:8765 --noreload` :端口 smoke 通过(TCP 8765 可连接),随后已停止进程。
- `bash -n init.sh` :当前机器无可用 bash/WSL,无法本机验证 Unix 脚本。
- 阻塞:无。
- 决策:T-001 不接入 MySQL、不创建自定义 User、不创建 apps 目录;这些按任务边界留给 T-002。T-001 阶段暂保留 Django 默认 SQLite 配置用于框架启动检查,T-002 必须在首次迁移前改为自定义 User + MySQL 8.4 配置。
- 下一步:领取 T-002 建立 apps 目录、自定义 User 与配置。
2026-07-02 09:07:15 +08:00
## 2026-07-02 T-002 建立 apps 目录、自定义 User 与配置
- 状态:DONE
- 变更:
- 创建 `apps/` 包与 `apps/users|portal|billing|ai|api` 五个 Django app。
- `apps/users.models.User` 继承 `AbstractUser` ,新增 `payment_user_id` 、`status` 、`created_at` ,邮箱改为必填,`db_table="user"` ; `settings.AUTH_USER_MODEL="users.User"` 已设置。
- `settings.py` 增加根目录 `.env` 读取,数据库从 SQLite 切换到 MySQL,配置 `utf8mb4` 与严格 SQL 模式。
- 采用 `PyMySQL` 作为 MySQL 驱动,更新 `requirements.txt` 与 `config/__init__.py` 。
- 生成 `apps/users/migrations/0001_initial.py` 。
- 验证:
- `py -3.12 -m pip install -r requirements.txt` :通过,安装 PyMySQL 1.1.3。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations users` :通过,生成 `0001_initial.py` 。
- `py -3.12 manage.py migrate` :首次失败,MySQL 返回 `ALTER command denied to user 'test'@'61.141.174.182' for table 'django_content_type'` ;用户在宝塔/MySQL 授权后,清理失败迁移留下的空表并重新执行,最终通过。
- `SHOW GRANTS FOR CURRENT_USER()` :授权后显示 `GRANT ALL PRIVILEGES ON cmhub.* TO test@%` 。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py test` :通过,当前 0 tests。
- MySQL 验证:存在自定义 `user` 表,含 `payment_user_id` / `status` / `created_at` ;未创建默认 `auth_user` 表;`django_migrations` 共 19 条。
- 阻塞:已解除。曾因 MySQL 用户缺少 `ALTER/INDEX/DROP` 等迁移权限受阻。
- 决策:使用 PyMySQL 作为 MySQL 驱动;T-002 只落 apps、自定义 User、settings 与 MySQL 初始迁移,不提前实现 UserWallet/ApiKey 等 T-201 内容。
- 下一步:领取 T-003 接通 django-admin 与最小测试。
2026-07-02 09:46:32 +08:00
## 2026-07-02 T-003 接通 django-admin 与最小测试
- 状态:PARTIAL
- 变更:
- `apps/users/admin.py` :用 `DjangoUserAdmin` 注册自定义 `User` ,后台列表展示 `username/email/status/is_staff/is_active/created_at` ,支持按 `status` 过滤、按 `username/email/payment_user_id` 搜索,`created_at` 只读。
- `apps/users/tests.py` :新增最小 admin smoke test,覆盖自定义 User 已注册到 admin、superuser 可登录 `/admin/` 。
- `config/settings.py` :本地默认 `ALLOWED_HOSTS` 增加 `testserver` ,匹配 Django test client。
- `docs/06-tasks.md` / `docs/current-state.md` :记录 T-003 当前权限阻塞。
- 验证:
- `py -3.12 manage.py check` :通过,0 issues。
- 手工创建临时 superuser 并用 Django test client 登录 `/admin/` :登录成功,`GET /admin/` 返回 200;临时用户已清理。
- `py -3.12 manage.py test` :未通过,Django 在创建 `test_cmhub` 测试库时报 `(1044, "Access denied for user 'test'@'%' to database 'test_cmhub'")` 。
- 进一步验证 `CREATE DATABASE IF NOT EXISTS test_cmhub ...` 同样返回 1044;当前授权仅覆盖 `cmhub.*` 。
- 阻塞:需要给 MySQL 用户增加测试库权限,建议优先只授权测试库范围:
- `GRANT ALL PRIVILEGES ON test_cmhub.* TO 'test'@'%';`
- `FLUSH PRIVILEGES;`
- 决策:不把 Django 测试改跑到业务库 `cmhub` ,避免测试清表/迁移影响真实开发数据;T-003 在标准 `manage.py test` 通过前不标 `DONE` 。
- 下一步:完成 MySQL 授权后重跑 `py -3.12 manage.py test` ,再跑 `py -3.12 manage.py check` / `./init.ps1` ,通过后把 T-003 标记 DONE。
## 2026-07-02 T-003 接通 django-admin 与最小测试(完成验收)
- 状态:DONE
- 变更:
- `requirements.txt` :新增 `cryptography>=42,<47` ,用于支持 PyMySQL 连接 MySQL 8 默认 `caching_sha2_password` 认证。
- `docs/06-tasks.md` : T-003 从 BLOCKED 改为 DONE。
- `docs/current-state.md` / `docs/03-tech-stack.md` :同步当前依赖、验证结果与下一步任务。
- 验证:
- `py -3.12 -m pip install -r requirements.txt` :通过,安装 `cryptography 46.0.7` 。
- `py -3.12 manage.py test` :通过,2 tests OK,测试库 `test_cmhub` 创建与销毁正常。
- `py -3.12 manage.py check` :通过,0 issues。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- 阻塞:无。
- 决策:保留独立 `test_cmhub` 测试库路径,标准测试不使用业务库 `cmhub` 。
- 下一步:领取 T-101 Provider 适配器层 + 移植 cmbot 调用。
2026-07-02 10:17:10 +08:00
## 2026-07-02 Phase 0 骨架审核(Claude Code review,非任务)
- 状态:DONE(审核完成)
- 变更:新增 `docs/phase-0-review.md` ; `06-tasks.md` 新增 T-004 修补任务;`docs/README.md` 导航登记;`current-state.md` 下一步改 T-004。
- 审核结论:**T-001~003 验收全部达标,质量高于骨架平均水准**。自定义 User 先于首次迁移、`AUTH_USER_MODEL` 已设、`PyMySQL install_as_MySQLdb()` 胶水到位、密钥全走 env 且 `.env` /`ai_models.json` 未被 git 跟踪、独立 `test_cmhub` 测试库——关键硬约束全对。
- 发现(详见 `phase-0-review.md` ):
- P1(建议现在改):`User.email` 未 `unique` ; `requires-python` 未落地、init 脚本无解释器版本断言。
- P2(规范性):`INSTALLED_APPS` 顺序(本地 app 排在 contrib 前,模板查找隐患);缺 `.env.example` 。
- P3(后续任务处理):REST_FRAMEWORK 认证配置(T-301)、`sql_mode` 完整性、生产 SECRET_KEY 保护(T-403)。
- 未本地复跑:审核机无 `python3.12` ,结论基于静态审查 + codex 验证记录(记录真实,含 MySQL 授权 / caching_sha2 修复过程)。
- 下一步:codex 领 T-004,按 `phase-0-review.md` 修 P1/P2,全绿留证后再进 T-101。
## 2026-07-02 T-004 Phase 0 骨架审核修补
- 状态:DONE
- 变更:
- 文档先行:更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/current-state.md` 、`docs/env.md` 、`docs/06-tasks.md` ,把 T-004、邮箱唯一约束、Python 版本断言、`.env.example` 和下一步 T-101 口径同步。
- `apps/users.models.User.email` 增加 `unique=True` ,并生成/应用 `apps/users/migrations/0002_alter_user_email.py` 。
- `config/settings.py` 调整 `INSTALLED_APPS` 顺序为 `django.contrib.*` → `rest_framework` → 本地 apps。
- `init.ps1` / `init.sh` 在安装依赖前校验 Python 版本必须满足 `>=3.12,<3.14` 。
- 新增 `.env.example` (仅占位符,无真实密钥)与 `pyproject.toml` (落地 `requires-python` )。
- 验证:
- 重复邮箱预检:业务库非空 email 无重复。
- `py -3.12 manage.py makemigrations users` :生成 `0002_alter_user_email.py` 。
- `py -3.12 manage.py migrate` :通过,应用 `users.0002_alter_user_email` 。
- 数据库索引验证:`user.email` 存在唯一索引 `user_email_54dc62b2_uniq` 。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py test` :通过,2 tests OK。
- `./init.ps1` :通过,输出 Python 3.12.3 版本断言、依赖同步与基础检查。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `git diff --check` :通过,仅有 CRLF 提示。
- `bash -n init.sh` :当前 Windows 未安装 WSL/bash,无法本机验证 Unix 脚本语法。
- 阻塞:无。
- 决策:保留 `requirements.txt` 作为运行依赖来源,`pyproject.toml` 只承载 Python 版本元数据;`.env.example` 使用占位符,不提交 `.env` 。
- 下一步:领取 T-101 Provider 适配器层 + 移植 cmbot 调用。
2026-07-02 10:33:15 +08:00
## 2026-07-02 T-101 Provider 适配器层 + 移植 cmbot 调用
- 状态:DONE
- 变更:
- 新增 `apps/ai/providers/` : `ResolvedModel` 、`Provider` 协议、结果对象、错误类型、注册表、`chat` /`gemini` /`images` /`images_edits` 适配器。
- 从 `D:\chengma\cmbot\src\services\ai_text_service.py` / `ai_image_service.py` 移植纯 HTTP 与响应解析逻辑:URL 归一化、`api_type=auto` 识别、分辨率超时、chat/gemini payload、images/edits multipart、标题清洗、图片 data URL/base64/URL 解析。
- 服务端接口改为 bytes 输入,不依赖桌面端本地路径、GUI、线程或 Qt;HTTP session 可注入,便于测试。
- `requirements.txt` 新增 `requests>=2.32,<3` 。
- `apps/ai/tests.py` 新增 5 条 mock 单测,覆盖 provider 选择、chat 文本请求构造和标题解析、chat 多模态返图解析、images/edits multipart 请求构造和 base64 图片解析、能力不支持错误。
- 同步 `docs/api.md` 、`docs/03-tech-stack.md` 、`docs/current-state.md` 、`docs/06-tasks.md` 、`README.md` 、`docs/00-ai-start-here.md` 。
- 验证:
- 脱敏读取 `D:\chengma\cmbot\config\ai_models.json` :顶层为 `models` 列表,共 3 个模型;仅打印非密钥字段,未暴露真实 key。
- `py -3.12 -m pip install -r requirements.txt` :通过,安装 `requests 2.34.2` 及依赖。
- `py -3.12 manage.py test apps.ai` :通过,5 tests OK。
- `py -3.12 manage.py test` :通过,7 tests OK。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps` :通过。
- `./init.ps1` :通过,依赖同步含 `requests` ,基础检查正常。
- 阻塞:无。
- 决策:T-101 不创建 AiModel/ModelAlias 数据表、不做别名解析数据库读取、不接计费;先用 `ResolvedModel` dataclass 承接后续 T-102 的数据库模型。
- 下一步:领取 T-102 AiModel + ModelAlias 模型 + 别名解析。
2026-07-02 11:07:44 +08:00
## 2026-07-02 T-102 AiModel + ModelAlias 模型 + 别名解析
- 状态:DONE
- 变更:
- 新增 `AiModel` / `ModelAlias` 数据模型与 `apps/ai/migrations/0001_initial.py` : `AiModel` 保存上游 url/model/api_type/capabilities/timeout/extra_body/is_active 与 Fernet 密文 `api_key_encrypted` ; `ModelAlias` 用 `operation_type + alias` 映射到具体模型,支持默认别名与启停。
- 新增 `apps/ai/security.py` : `AI_KEY_ENCRYPTION_KEY` 驱动 Fernet 加解密,密文带 `fernet:` 前缀;拒绝明文 fallback。
- 新增 `apps/ai/aliases.py` : `resolve_alias(operation_type, alias=None)` 每次查当前 active 配置,标题要求 `text` 能力,图片要求 `image` 能力,返回 provider 可用的 `ResolvedModel` 。
- 新增 `apps/ai/importers.py` 与 `import_ai_models` 管理命令:支持导入 `cmbot` 形状的 `ai_models.json` ,导入时加密 key、推断 capabilities,可选创建 `title-standard` / `image-standard` 默认别名。
- 完善 `apps/ai/admin.py` : admin 提供写入型 `api_key` 字段,密钥脱敏显示且不回显;缺少 Fernet 主密钥时以表单错误提示;`api_key_encrypted` 不暴露。
- 扩展 `apps/ai/tests.py` :覆盖密钥加密/解密、别名解析、能力不匹配、inactive alias、配置导入、admin 脱敏与缺主密钥错误;同步更新 README、入口文档、技术栈、架构、API、环境配置、任务看板和当前状态。
- 验证:
- `py -3.12 manage.py migrate` :通过,`ai.0001_initial` 已应用;`py -3.12 manage.py showmigrations ai` 显示 `[X] 0001_initial` 。
- `py -3.12 manage.py test apps.ai --noinput` :通过,16 tests OK。
- `py -3.12 manage.py test --noinput` :通过,18 tests OK。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps` / `py -3.12 -m compileall apps\ai` :通过。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- 验证期间远程 MySQL `43.128.3.240:3306` 多次短暂超时,`Test-NetConnection 43.128.3.240 -Port 3306` 恢复后重试通过;失败点均为建库/事务前连接超时,不是断言失败。
- 阻塞:无。
- 决策:
- 供应商密钥存储采用 `cryptography` Fernet 应用层加密,主密钥只走 `AI_KEY_ENCRYPTION_KEY` ;不实现明文兼容。
- T-102 不自动导入真实 `D:\chengma\cmbot\config\ai_models.json` ,避免真实上游 key 被误暴露;后续由管理员在确认环境密钥后手动执行导入命令。
- 默认别名唯一性先由 model validation、admin 与导入器保证;MySQL partial unique 约束留待审计/后台完善阶段评估。
- 下一步:领取 T-103 配置变更审计。
2026-07-02 11:42:39 +08:00
## 2026-07-02 T-103 配置变更审计
- 状态:DONE
- 变更:
- 新增 `AiConfigAuditLog` 审计模型与 `apps/ai/migrations/0002_aiconfigauditlog.py` ,字段包括 `actor` 、`action` 、`target_type` 、`target_id` 、`target_repr` 、`changed_fields` 、`changes` 、`created_at` 。
- 新增 `apps/ai/audit.py` :对 `AiModel` / `ModelAlias` 做变更快照与字段 diff, `api_key_encrypted` 对外记录为 `api_key` ,只保留 `empty` / `set` 状态,不记录明文或 Fernet 密文。
- 更新 `apps/ai/admin.py` : `AiModelAdmin` / `ModelAliasAdmin` 的保存和删除 hook 自动写审计日志;新增 `AiConfigAuditLogAdmin` ,仅允许只读查看,不允许新增、修改、删除审计日志。
- 扩展 `apps/ai/tests.py` :新增 5 条审计测试,覆盖 AiModel 创建、密钥更新、ModelAlias 映射变更、删除审计、审计 admin 只读。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/04-architecture.md` 、`docs/06-tasks.md` 、`docs/api.md` 、`docs/routes.md` 、`docs/current-state.md` 、汇报文档。
- 验证:
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py migrate` :通过,应用 `ai.0002_aiconfigauditlog` 。
- `py -3.12 manage.py showmigrations ai` :通过,`0001_initial` / `0002_aiconfigauditlog` 均为 `[X]` 。
- `py -3.12 manage.py test apps.ai.tests.AiConfigAuditAdminTests --noinput` :通过,5 tests OK。
- `py -3.12 manage.py test apps.ai --noinput` :通过,21 tests OK。
- `py -3.12 manage.py test --noinput --keepdb` :通过,23 tests OK。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps` :通过。
- `git diff --check` :通过,仅有 CRLF 提示。
- 标准 `py -3.12 manage.py test --noinput` 多次在远程 MySQL `43.128.3.240:3306` 创建/销毁测试库或测试中途重连时超时;失败点为 MySQL 连接/建库,不是断言失败。为降低远程建库压力,使用 `--keepdb` 完整测试已通过。
- 阻塞:无代码阻塞;远程 MySQL 偶发连接超时会影响标准测试建库稳定性。
- 决策:T-103 使用自建 `AiConfigAuditLog` 专表,不只依赖 django-admin `LogEntry` ;密钥审计只记录状态变化,不记录明文或密文。
- 下一步:领取 T-104 跑通一次真实/录制的标题或图片生成。
2026-07-02 13:58:34 +08:00
## 2026-07-02 T-104 跑通一次真实/录制的标题或图片生成
- 状态:DONE
- 变更:
- 新增 `smoke_ai_generation` 管理命令:支持 `title --recorded` ,在事务内临时创建 fake `AiModel` / `ModelAlias` ,通过 `resolve_alias()` 解析别名,再用录制的 chat/completions 响应走 `ChatCompletionsProvider.generate_text()` ,最后回滚临时配置,不写真实 key、不污染业务库。
- 扩展 `apps/ai/tests.py` :新增录制 smoke 命令测试,确认输出不包含 placeholder key / Bearer,且临时 `AiModel` / `ModelAlias` 不持久化。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 、汇报文档。
- 验证:
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py showmigrations ai` :通过,`0001_initial` / `0002_aiconfigauditlog` 均为 `[X]` 。
- `py -3.12 manage.py shell -c "...AI_KEY_ENCRYPTION_KEY..."` :当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 。
- `py -3.12 manage.py shell -c "...AiModel/ModelAlias count..."` :业务库 `AiModel count=0` 、`ModelAlias count=0` 。
- `py -3.12 manage.py smoke_ai_generation title --recorded --prompt "为测试商品生成3个中文标题" --resolution 1K` :通过;`alias=t-104-recorded-title-c054d919` , `model_used=recorded-title-model` , `elapsed_ms=149` , `title_count=3` 。
- smoke 后再次查询业务库:`AiModel count=0` 、`ModelAlias count=0` ,确认临时配置已回滚。
- `py -3.12 manage.py test apps.ai.tests.AiGenerationSmokeCommandTests --noinput --keepdb` :通过,1 test OK。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps` :通过。
- `py -3.12 manage.py test apps.ai --noinput --keepdb` :多次在远程 MySQL `43.128.3.240:3306` 连接/测试库初始化阶段超时;失败点为数据库连接,不是断言失败。
- 阻塞:真实上游 smoke 未执行,因为当前环境未配置 `AI_KEY_ENCRYPTION_KEY` ,且业务库没有 AiModel/ModelAlias。配置 Fernet 主密钥并导入模型后,可去掉 `--recorded` 用同一命令跑真实标题 smoke。
- 决策:T-104 采用“录制标题生成”作为验收路径,验证别名解析、Provider 选择、响应解析和同步耗时记录;真实图片同步耗时仍需在后续配置真实模型后补测。
- 下一步:领取 T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型。
2026-07-02 14:47:22 +08:00
## 2026-07-02 Phase 1 审核(Claude Code review,非任务)
- 状态:DONE(审核完成)
- 变更:新增 `docs/phase-1-review.md` ; `06-tasks.md` 新增 T-105 修补任务、T-104 状态标注「图片同步风险未退」;`docs/README.md` 导航登记;`current-state.md` 阶段/下一步改 T-105。
- 审核结论:**T-101~104 验收达标,代码质量高**。三模型机制忠实落地(chat / auto→chat 多模态返图 nano-banana2 / images_edits 改图 gpt-image-2);`timeout_seconds=0→按分辨率有限超时` 修对了桌面端「0=无限」的坑;Fernet 加密 + 拒绝明文 fallback + 缺主密钥 fail-closed + admin 写入型脱敏字段 + 审计只记 empty/set,密钥零泄露闭环;审计 admin 真只读、delete_queryset 也留痕;smoke 用 override_settings+atomic+rollback 不落库、不打印 Bearer/key。
- 发现(详见 `phase-1-review.md` ):
- P1-1(进 T-302 前必修,资金安全):`apps/ai/providers/openai_compatible.py:apply_extra_body` 无差别 `payload.update(parameters)` ,调用方透传可覆盖 `model` /`n` /`size` /`resolution` 等核心/计费字段,击穿「按别名计费」契约。须白名单化、核心字段服务端固定不可覆盖。
- P1-2(最高风险未退):T-104 只跑了 recorded **标题** ( `elapsed_ms=149` 无网络意义),Phase 1 存在的唯一理由「图片同步在 Gunicorn/nginx 下不超时」**未验证**。结论应改为「管路已验、图片同步风险未退」,并在依赖同步部署前(最迟 T-403)跑一次真实图片生成记录真实耗时。
- P2: `.env.example` 的 `AI_DEFAULT_CONNECT/READ_TIMEOUT_SECONDS` 代码未读取(死配置,接上或删除);`resolution_to_size` 未大小写归一(`"1k"` 漏命中);导入器默认别名 `image-standard` 与文档示例 `image-hd` 口径不一致。
- P3(登记):`Provider.capabilities()` 无调用点(半冗余);图生图 vs 改图都归 `operation_type=IMAGE` ,T-302 需把「缺原图」翻成 400 而非 500; CallRecord 写入禁 dump `result.raw` (含 base64 大图/敏感);provider 密钥无轮换(单 Fernet key)。
- 未本地复跑:审核机无 `python3.12` ,结论基于静态审查(providers/models/security/aliases/audit/importers/admin/tests/迁移/settings) + codex 验证记录(7→18→23 tests 递增、含真实 MySQL 建库超时的诚实记录,可信度高)。T-104 的 149ms 不作为图片同步可行性证据。
- 下一步:codex 领 T-105,按 `phase-1-review.md` 修 P1/P2; P1-1 须先于 T-302,全绿留证。
## 2026-07-02 T-105 Phase 1 AI 层审核修补
- 状态:DONE
- 变更:
- `apps/ai/providers/openai_compatible.py` : `parameters` / `extra_body` 改为白名单安全透传,`model` 、`messages` 、`n` 、`size` 、`stream` 等核心/计费字段不可被覆盖;`generationConfig` 仅允许安全子字段合并。
- `apps/ai/providers/utils.py` : `resolution_to_size()` 增加大小写归一,`"1k"` / `"512px"` 可正确映射。
- `apps/ai/importers.py` 与 `import_ai_models` 帮助文案:默认图片别名从 `image-standard` 对齐为 `image-hd` 。
- `smoke_ai_generation` 支持 `image --recorded` ,录制图片路径走别名解析 + Provider 解析 + 事务回滚,不打印 key/Bearer、不持久化临时配置;保留真实 image smoke 入口和 `--image-file` 。
- 扩展 `apps/ai/tests.py` :覆盖 provider 参数越权拦截、分辨率归一、默认别名、录制 image smoke。
- `.env.example` / `docs/env.md` 删除未被代码读取的 `AI_DEFAULT_CONNECT_TIMEOUT_SECONDS` / `AI_DEFAULT_READ_TIMEOUT_SECONDS` ,改为说明超时来自 `AiModel` 字段与 Provider 分辨率默认值。
- 同步更新 `docs/06-tasks.md` 、`docs/api.md` 、`docs/04-architecture.md` 、`docs/03-tech-stack.md` 、`README.md` 、`docs/00-ai-start-here.md` 、`docs/current-state.md` : T-105 完成,P1/P2 已修;P3 已挂到 T-201/T-302/T-403 或 Backlog。
- 验证:
- `py -3.12 -m py_compile apps\ai\providers\openai_compatible.py apps\ai\providers\utils.py apps\ai\importers.py apps\ai\management\commands\smoke_ai_generation.py apps\ai\tests.py` :通过。
- `py -3.12 manage.py test apps.ai.tests.ProviderUtilsTests apps.ai.tests.ChatCompletionsProviderTests apps.ai.tests.ImagesEditsProviderTests apps.ai.tests.AiModelsImportTests apps.ai.tests.AiGenerationSmokeCommandTests --noinput --keepdb` :通过,10 tests OK。
- `py -3.12 manage.py smoke_ai_generation image --recorded --prompt "生成测试图片" --resolution 1k` :通过;`alias=t-105-recorded-image-27c25b3a` , `model_used=recorded-image-model` , `elapsed_ms=71` , `image_bytes=20` 。
- `py -3.12 manage.py smoke_ai_generation title --recorded --prompt "为测试商品生成3个中文标题" --resolution 1K` :通过;`alias=t-104-recorded-title-ceec8dab` , `model_used=recorded-title-model` , `elapsed_ms=92` , `title_count=3` 。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py test apps.ai --noinput --keepdb` :两次未作为绿灯;一次在连接远程 MySQL 测试库前超时,一次跑到 23 tests 后于 `AiModelAdminTests.setUpClass` 事务连接阶段超时。失败点均为远程 MySQL `43.128.3.240:3306` 连接/重连,不是断言失败。
- 阻塞:无代码阻塞。真实图片同步耗时仍未验证,已登记到 T-302/T-403;需要真实 `AI_KEY_ENCRYPTION_KEY` 、AiModel/ModelAlias 与上游 key 后执行。
- 决策:对外 `parameters` 不是任意直通上游,而是 provider 白名单安全参数;服务端固定字段优先,计费仍以能力别名/分辨率为准。未生效的全局 AI timeout 环境变量删除,避免运维误以为可通过 env 调整。
- 下一步:领取 T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型。
2026-07-02 15:06:00 +08:00
## 2026-07-02 T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型
- 状态:DONE
- 变更:
- `apps/users/models.py` :新增 `UserWallet` (一用户一钱包,`points_balance >= 0` DB 约束)与 `ApiKey` ( `key_hash` SHA-256、`key_prefix` 、active/revoked 状态、`create_for_user()` 只在创建时返回明文)。
- `apps/billing/models.py` :新增 `CallRecord` ( user/api_key/operation/alias/model_used/result_ref/result_summary/status/latency,不含 provider raw/base64 字段)与 `PointsLedger` ( recharge/consume/adjust/refund,非零 delta、`balance_after >= 0` ,手工调整需 reason)。
- 新增迁移:`users.0003_apikey_userwallet` 、`billing.0001_initial` ,并已应用到当前 MySQL。
- `apps/users/admin.py` :注册 `UserWallet` / `ApiKey` ,钱包余额只读,API Key 只显示 prefix 和 hash 摘要,不回显明文。
- `apps/billing/admin.py` :注册 `PointsLedger` / `CallRecord` 只读 admin,用于检索对账。
- `apps/billing/tests.py` :覆盖钱包非负约束、API Key 哈希与匹配、调用记录不含 raw 字段、流水约束与 admin 注册。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/04-architecture.md` 、`docs/06-tasks.md` 、`docs/api.md` 、`docs/routes.md` 、`docs/current-state.md` 、`docs/project-brief.md` 。
- 验证:
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py test apps.billing --noinput --keepdb` :通过,6 tests OK。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py migrate` :通过,应用 `users.0003_apikey_userwallet` / `billing.0001_initial` 。
- `py -3.12 manage.py test apps.users apps.billing --noinput --keepdb` :通过,8 tests OK。
- `py -3.12 manage.py showmigrations users billing` :通过,`users.0001/0002/0003` 与 `billing.0001` 均为 `[X]` 。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py test --noinput --keepdb` :通过,34 tests OK。
- `py -3.12 -m compileall apps` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- 阻塞:无。
- 决策:
- `UserWallet` / `ApiKey` 放在 `apps.users` , `PointsLedger` / `CallRecord` 放在 `apps.billing` ;T-201 只建数据结构,不实现扣点服务、计费规则、充值订单或 API 鉴权。
- `PointsLedger.ref_order_id` 在 RechargeOrder 模型落地前先保留为索引化数值引用,充值任务落地时再正式关联或补迁移。
- 下一步:领取 T-202 PricingRule / ExchangeRate 模型 + 计费计算。
2026-07-02 15:28:19 +08:00
## 2026-07-02 T-202 PricingRule / ExchangeRate 模型 + 计费计算
- 状态:DONE
- 变更:
- `apps/billing/models.py` :新增 `PricingRule` (按 `operation_type + alias + resolution` 唯一,`points_cost > 0` , active 开关)与 `ExchangeRate` ( `currency + effective_from` 当前汇率,`points_per_unit > 0` ),并归一化分辨率/币种。
- `apps/billing/pricing.py` :新增计费计算服务;定价优先精确分辨率,再回退空 `resolution` 默认价;缺规则抛 `NoPricingRuleError(code="no_pricing_rule")` ;充值点数按当前 active 汇率 `floor(amount * points_per_unit)` 计算。
- `apps/billing/admin.py` :注册 `PricingRule` / `ExchangeRate` ,运营可在 admin 配规则与汇率。
- `apps/billing/tests.py` :覆盖分辨率精确价优先、默认价回退、换底层 `ModelAlias.ai_model` 不影响别名价格、缺规则错误码、当前汇率选择与点数向下取整。
- 新增迁移 `billing.0002_exchangerate_pricingrule` ,已应用到当前 MySQL。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 、`docs/project-brief.md` 、`docs/project-onepager.md` 。
- 验证:
- `py -3.12 -m py_compile apps\billing\models.py apps\billing\pricing.py apps\billing\admin.py apps\billing\tests.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py migrate` :通过,应用 `billing.0002_exchangerate_pricingrule` 。
- `py -3.12 manage.py showmigrations billing` :通过,`billing.0001_initial` / `billing.0002_exchangerate_pricingrule` 均为 `[X]` 。
- `Test-NetConnection 43.128.3.240 -Port 3306` :通过,`TcpTestSucceeded=True` 。
- `py -3.12 manage.py test apps.billing --noinput --keepdb` :通过,10 tests OK。
- `py -3.12 manage.py test apps.users --noinput --keepdb` :通过,2 tests OK。
- `py -3.12 -m compileall apps` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py test --noinput --keepdb` :两次未作为绿灯;一次跑到 31 tests 后在 `apps.ai` 测试类建事务时远程 MySQL 连接超时,一次在测试库连接阶段被远程主机重置。`py -3.12 manage.py test apps.ai --noinput --keepdb` 也在远程连接重建时超时;已跑过测试无断言失败。失败点均为远程 MySQL `43.128.3.240:3306` 连接问题。
- 阻塞:无代码阻塞。远程 MySQL 对全量测试仍有间歇连接超时/重置;T-203 做并发扣点时需要继续使用 `--keepdb` 并必要时分 app 重试。
- 决策:
- 定价规则绑定能力别名字符串,不外键到具体 `AiModel` ;后台切换 `ModelAlias` 指向不改变价格。
- T-202 不实现扣点、退点、充值订单或 API 编排;这些留给 T-203/T-304/T-305/T-302。
- 下一步:领取 T-203 并发安全扣点 / 退点(billing 层)。
2026-07-02 16:33:31 +08:00
## 2026-07-02 T-203 并发安全扣点 / 退点(billing 层)
- 状态:DONE
- 变更:
- 新增 `apps/billing/services.py` : `precharge_call()` 在事务内 `select_for_update()` 锁 `UserWallet` 行,余额足够才预扣,写 `CallRecord(status=pending)` 与 `PointsLedger(consume, -N)` ;余额不足抛 `InsufficientPointsError(code="insufficient_points")` ,不写调用/流水。
- 新增 `mark_call_success()` :上游成功后只更新调用记录为 `success` 与结果摘要/耗时,不再改余额。
- 新增 `refund_call_points()` :失败退点锁 `CallRecord` ,同一调用只写一条 `refund` 流水并恢复余额;重复调用幂等,不重复加点;成功调用不能走失败退点。
- 扩展 `apps/billing/tests.py` :覆盖预扣写账、余额不足不写账、失败退点幂等、成功后拒绝失败退点、2 线程并发扣同一钱包不超扣不为负。测试数据改成唯一用户名/别名,避免远程 `test_cmhub` 断线残留造成重复键假失败。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 、`docs/project-brief.md` 、`docs/project-onepager.md` : Phase 2 完成,下一步 T-301。
- 验证:
- `py -3.12 -m py_compile apps\billing\services.py apps\billing\tests.py` :通过。
- `py -3.12 manage.py test apps.billing.tests.BillingServiceTests --noinput --keepdb` :通过,4 tests OK。
- `py -3.12 manage.py test apps.billing.tests.ConcurrentDebitTests --noinput --keepdb` :通过,1 test OK。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py test --noinput --keepdb` :未作为绿灯;发现 43 tests,跑到 42 tests 后远程 MySQL 连接超时,导致并发测试线程拿不到连接,测试库 flush/teardown 也断线。
- `py -3.12 manage.py test apps.billing --noinput --keepdb` / 不带 `--keepdb` :未作为绿灯;远程 MySQL 在 `PricingCalculationTests` 或测试库创建/销毁阶段超时/断线。T-203 新增的服务测试与并发测试已单独通过。
- 阻塞:无代码阻塞。远程 MySQL `43.128.3.240:3306` 对完整测试仍间歇超时/重置,并且失败后可能保留脏 `test_cmhub` ;后续建议优先用 `--keepdb` + 唯一测试数据分 app 验证,必要时清理测试库后再跑全量。
- 决策:
- T-203 只做 billing 层扣点/退点服务,不做 DRF 认证、生成接口编排、充值订单或支付回调。
- 失败退点以 `CallRecord` 行锁 + 已有 refund 流水检查实现幂等,不给 `CallRecord` 增加 refunded 状态。
- 下一步:领取 T-301 API Key 鉴权(DRF Authentication)。
## 2026-07-02 Phase 2 计费核心审核(Claude Code review,非任务)
- 状态:DONE(审核完成)
- 变更:新增 `docs/phase-2-review.md` ; `06-tasks.md` 新增 T-204 加固任务;`docs/README.md` 导航登记。
- 审核结论:**T-201~203 验收全部达标,质量为三阶段最高,零 P1**。§8 资金安全硬约束逐条核对通过——先扣后调/失败必退(precharge/mark_success/refund 三原语对齐 4.1 时序)、并发安全(事务 + `select_for_update` 锁 wallet + DB 级 `points_balance>=0` )、退点幂等(锁 CallRecord 行 + 查 REFUND 流水去重)、全程留痕(ledger/call_record admin 只读=只追加)、wallet 余额 admin 只读(后台无法绕过账本)、ApiKey sha256+hmac.compare_digest+prefix+hash 脱敏、计费按别名解耦模型 + 分辨率 wildcard、Decimal floor 计费、CallRecord 无 raw 字段、错误码 api.md 对齐(insufficient_points→402/no_pricing_rule→400)。
- 关键证据:`ConcurrentDebitTests` 用真线程 + Barrier 强制并发扣同一钱包,**在 MySQL `test_cmhub` 上单独跑过(1 test OK)**——`select_for_update` 真生效,不是 SQLite 假绿。DB 级 CHECK/Unique 约束全部落到迁移文件。
- 发现(详见 `phase-2-review.md` ):
- P1:无。
- P2-1:退点幂等缺 DB 级兜底(无「每 ref_call 最多一条 REFUND」约束,锁内安全但无 backstop);T-204 应加 MySQL 可落地的 `ref_call + change_type` 复合唯一约束 + 直写 IntegrityError 测试,不使用 MySQL 不支持的 partial unique / 条件唯一约束。
- P2-2:完整测试套件从未单次全绿(远程 MySQL 间歇超时/重置),靠分 app 子集通过佐证;建议在稳定 MySQL 上跑一次完整全绿留证,并标注「并发测试须在 MySQL 上跑,SQLite 会假绿」。
- P3(登记):手工调点服务未实现(模型层 reason 守卫已备好、wallet admin 已锁死=安全中间态)→ T-401;扣点热路径 wallet get_or_create 掩盖钱包缺失 → T-501;锁顺序约定;只追加靠 admin+无 update 保证(DB 无触发器);OperationType 双份枚举 DRY。
- 未本地复跑:审核机无 python3.12,结论基于静态审查(users/billing models、services、pricing、两 app admin、tests、全部迁移、settings) + codex 执行记录(T-201 34 tests / T-202 billing 10 tests / T-203 BillingServiceTests 4 + ConcurrentDebitTests 1 均单独在 MySQL 通过;完整套件因连接问题未单次全绿,失败点均为连接/建库超时非断言失败)。
- 下一步:codex 可领 T-204 做 P2 加固(无 P1,不阻塞 T-301)。
2026-07-02 16:59:02 +08:00
## 2026-07-02 T-204 Phase 2 计费核心审核加固
- 状态:BLOCKED( P2-1 已完成;P2-2 完整 `manage.py test` 单次全绿被远程 MySQL 连接稳定性阻塞)
- 变更:
- `apps/billing/models.py` :给 `PointsLedger` 增加 MySQL 可落地的 `UniqueConstraint(fields=("ref_call", "change_type"), name="unique_ledger_change_type_per_call")` ,作为同一调用重复写同类型流水的 DB 级兜底;没有使用 MySQL 不支持的 partial unique / 条件唯一约束。
- 新增迁移 `billing.0003_pointsledger_unique_ledger_change_type_per_call` ,已应用到当前 MySQL。
- `apps/billing/tests.py` :新增直写测试,确认同一 `CallRecord` 的一条 `CONSUME` + 一条 `REFUND` 可共存,第二条 `REFUND` 抛 `IntegrityError` 。
- `docs/03-tech-stack.md` :测试说明补充「涉及 `select_for_update` 的并发扣点测试必须在 MySQL 上跑,SQLite 会忽略行锁导致假绿」。
- `docs/06-tasks.md` / `docs/current-state.md` :记录 T-204 的 P2-1 完成、P2-2 因远程 MySQL 全量测试未单次全绿而阻塞;T-204 无 P1,不阻塞 T-301。
- 验证:
- `py -3.12 -m py_compile apps\billing\models.py apps\billing\tests.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py migrate` :通过,应用 `billing.0003_pointsledger_unique_ledger_change_type_per_call` 。
- `py -3.12 manage.py showmigrations billing` :通过,`billing.0001/0002/0003` 均为 `[X]` 。
- `py -3.12 manage.py test apps.billing.tests.BillingCoreModelTests.test_points_ledger_allows_consume_and_refund_but_rejects_duplicate_refund --noinput --keepdb` :通过,1 test OK。
- `py -3.12 manage.py test apps.billing.tests.BillingServiceTests apps.billing.tests.ConcurrentDebitTests --noinput --keepdb` :通过,5 tests OK。
- `py -3.12 manage.py test apps.billing --noinput --keepdb` :通过,16 tests OK。
- `Test-NetConnection 43.128.3.240 -Port 3306` :通过,`TcpTestSucceeded=True` 。
- `py -3.12 -m compileall apps` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- `py -3.12 manage.py test --noinput --keepdb` :两次未作为绿灯;一次发现 44 tests,跑到 42 tests 后 `apps.ai.tests.AiModelEncryptionTests` 建连接超时;一次跑到 34 tests 后 `apps.ai` 多个 TestCase 建连接超时。失败点均为远程 MySQL `43.128.3.240:3306` 连接超时。
- `py -3.12 manage.py test apps.users apps.billing --noinput --keepdb` :未作为绿灯;跑到 17/18 后 `ConcurrentDebitTests.setUp` 建连接超时。
- 阻塞:当前没有代码阻塞;P2-1 已完成且相关测试通过。P2-2 需要稳定 MySQL 环境完成一次完整全量测试单次绿灯,当前远程实例仍在多连接/长测试期间间歇超时。
- 下一步:若先处理 T-204 blocker,需要提供/切换稳定 MySQL 8.4 测试环境后重跑 `py -3.12 manage.py test --noinput` ;若接受 T-204 无 P1 且不阻塞后续,可进入 T-301 API Key 鉴权。
2026-07-02 17:29:26 +08:00
## 2026-07-02 T-204 blocker 处理:MySQL 测试稳定性与完整测试补跑
- 状态:DONE
- 变更:
- `config/settings.py` : MySQL `OPTIONS` 增加 `MYSQL_CONNECT_TIMEOUT` / `MYSQL_READ_TIMEOUT` / `MYSQL_WRITE_TIMEOUT` ,默认分别为 30 / 120 / 120 秒,避免远程测试库使用 PyMySQL 默认值导致连接稳定性误判。
- `.env.example` / `docs/env.md` / `docs/03-tech-stack.md` :同步登记 MySQL 客户端超时配置。
- `apps/billing/tests.py` : `ConcurrentDebitTests` 增加 `connections.close_all` cleanup,确保并发测试结束后 Django teardown 使用新连接,减少远程连接失效对 flush/post_migrate 的影响。
- `docs/06-tasks.md` / `docs/current-state.md` : T-204 从 `BLOCKED` 更新为 `DONE` ,下一步回到 T-301。
- 验证:
- `Test-NetConnection 43.128.3.240 -Port 3306` :通过,`TcpTestSucceeded=True` 。
- `py -3.12 manage.py shell -c "select version(), @@wait_timeout, @@interactive_timeout, @@connect_timeout, @@max_connections"` :通过;MySQL `8.4.8` , `max_connections=500` 。
- `py -3.12 -m py_compile config\settings.py apps\billing\tests.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py test apps.users apps.billing --noinput --keepdb --verbosity 2` :通过,18 tests OK。
- `py -3.12 manage.py test apps.ai --noinput --keepdb --verbosity 2` :通过,26 tests OK。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2` :首次在测试库 setup/post_migrate 阶段遇到一次远程 MySQL read timeout,确认无残留 processlist 后立即重跑;第二次通过,44 tests OK,耗时 157.827s。
- `py -3.12 -m compileall apps config` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `./init.ps1` :通过,依赖同步与基础检查正常。
- 阻塞:无。远程 MySQL 仍可能偶发链路超时;后续测试不要并行跑共享同一 `test_cmhub` 的数据库测试,必要时用 `--keepdb` 串行重跑。
- 决策:T-204 的 P2-2 已补齐单次完整测试全绿证据;并发扣点测试仍必须在 MySQL 上跑,SQLite 不可作为验收依据。
- 下一步:领取 T-301 API Key 鉴权(DRF Authentication)。
2026-07-02 17:40:26 +08:00
## 2026-07-02 T-301 API Key 鉴权(DRF Authentication)
- 状态:DONE
- 变更:
- 新增 `apps/api/authentication.py` :实现 `ApiKeyAuthentication` ,只解析 `Authorization: Bearer <API_KEY>` ;对明文 Key 做 SHA-256 hash 后查 `ApiKey` ,成功后返回 `(user, api_key)` ,并更新 `last_used_at` 。
- 新增 `apps/api/errors.py` :提供统一错误响应结构 `{"error":{"code":...,"message":...}}` 。
- 更新 `apps/api/views.py` :新增 `ExternalApiView` ,外部 API 只挂 `ApiKeyAuthentication + IsAuthenticated` ,缺失/无效 Key 返回 `401 unauthorized` ,不接受 Web session。
- 更新 `apps/api/tests.py` :覆盖有效 Bearer Key、缺失 Key、无效 Key、错误鉴权 scheme、Key revoked、User disabled、Web session 不被外部 API 接受。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/06-tasks.md` 、`docs/current-state.md` : T-301 完成,下一步 T-302。
- 验证:
- `./init.ps1` :开工前通过;完成后再次通过。
- `py -3.12 -m py_compile apps\api\authentication.py apps\api\errors.py apps\api\views.py apps\api\tests.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2` :通过,8 tests OK。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps config` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2` :通过,52 tests OK,耗时 54.905s。
- 阻塞:无。
- 决策:生成/余额等对外 API 后续统一继承 `ExternalApiView` ;充值下单/状态查询仍按用户端 session 流程另行实现,不使用该外部 API 基类。
- 下一步:领取 T-302 生成标题 / 图片接口。
2026-07-02 22:41:37 +08:00
## 2026-07-02 T-302 生成标题 / 图片接口
- 状态:DONE
- 变更:
- 新增 `apps/api/generation.py` :编排「别名解析 -> Provider 能力二次校验 -> 计费 -> 预扣 -> 调 Provider -> 成功确认 / 失败退点」,统一翻译 `insufficient_points` 、`no_pricing_rule` 、`model_not_allowed` 、`bad_request` 、`upstream_error` 。
- 新增 `apps/api/serializers.py` :校验 `POST /api/v1/generate/title` 与 `POST /api/v1/generate/image` 请求参数,仅把 `parameters` 字段传给 Provider,不把整包请求透传。
- 新增 `apps/api/storage.py` :图片结果用 Django `default_storage` 存到 `MEDIA_ROOT/generated/images/...` ,响应返回 `image_url` ,调用记录只写 URL / 摘要,不写 provider raw 或 base64。
- 新增 `apps/api/urls.py` 并接入 `config/urls.py` :开放 `/api/v1/generate/title` 与 `/api/v1/generate/image` 。
- `config/settings.py` :补 `MEDIA_ROOT` / `MEDIA_URL` 。
- `apps/ai/aliases.py` :新增 `resolve_model_alias()` ,用于默认别名场景拿到真实 alias 参与计费和调用记录;原 `resolve_alias()` 保持兼容。
- 扩展 `apps/api/tests.py` :覆盖标题成功扣点、图片存储返回 URL、余额不足不调上游、缺计费规则不扣点、别名能力不匹配、Provider 能力二次校验、上游失败退点、`AiCapabilityError` 返回 400 且退点。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 。
- 验证:
- `./init.ps1` :开工前通过。
- `py -3.12 -m py_compile apps\api\generation.py apps\api\serializers.py apps\api\storage.py apps\api\urls.py apps\api\views.py apps\api\tests.py apps\ai\aliases.py config\settings.py config\urls.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2` :通过,16 tests OK。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2` :通过,60 tests OK,耗时 133.953s。
- 阻塞:无。真实上游标题/图片接口未跑,原因是当前环境未配置可用 `AI_KEY_ENCRYPTION_KEY` + AiModel/ModelAlias + 真实上游 key;图片同步真实耗时风险仍保留到 T-403 部署前校准。
- 决策:MVP 图片结果先用本地 media 存储返回 URL,不引入 S3 依赖;后续生产对象存储可替换 `default_storage` 。
- 下一步:领取 T-303 余额查询接口。
2026-07-03 08:36:30 +08:00
## 2026-07-02 T-303 余额查询接口
- 状态:DONE
- 变更:
- `apps/billing/services.py` :新增 `BalanceSnapshot` 与 `get_balance_snapshot(user)` ,只读返回 `UserWallet.points_balance` 与 `PointsLedger.points_delta` 累加值,供余额接口和后续对账复用。
- `apps/api/views.py` :新增 `BalanceView` ,继承 `ExternalApiView` ,只接受 API Key 鉴权,返回当前用户名与 `points_balance` 。
- `apps/api/urls.py` :新增 `GET /api/v1/balance` 路由。
- `apps/api/tests.py` :新增余额接口测试,覆盖余额响应与流水累加一致、缺失钱包返回 0 且不创建钱包、Web session 不能调用外部余额接口。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/06-tasks.md` 、`docs/current-state.md` : T-303 完成,下一步 T-304。
- 验证:
- `./init.ps1` :开工前通过,依赖同步与 `manage.py check` 正常。
- `py -3.12 -m py_compile apps\billing\services.py apps\api\views.py apps\api\urls.py apps\api\tests.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2` :通过,19 tests OK。
- `py -3.12 manage.py test apps.ai.tests.AiModelEncryptionTests --noinput --keepdb --verbosity 2` (设置 `MYSQL_CONNECT_TIMEOUT=90` 补跑失败类):通过,2 tests OK。
- `py -3.12 -m compileall apps config` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `Test-NetConnection 43.128.3.240 -Port 3306` :一次超时,后续通过,`TcpTestSucceeded=True` 。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2` :多次未作为绿灯;一次工具超时未取得明细,后续运行分别在 `apps.users.tests.AdminSmokeTests.setUpClass` / `apps.ai.tests.AiModelEncryptionTests.setUpClass` / `apps.billing.tests.ConcurrentDebitTests.setUp` 或测试库 setup 阶段遇到远程 MySQL 连接超时或 reset;已跑过的测试均无断言失败。
- 阻塞:T-303 功能与相关测试无阻塞;完整测试当前被远程 MySQL `43.128.3.240:3306` 连接稳定性阻塞,后续建议数据库连通稳定后重跑一次 `py -3.12 manage.py test --noinput --keepdb --verbosity 2` 。
- 决策:余额接口返回 `UserWallet.points_balance` 作为对外余额;测试用流水累加值校验账务场景一致性。接口只读,不在 API 层创建或修改钱包。
- 下一步:领取 T-304 充值回调(微信/支付宝验签 + 幂等入账)。
2026-07-03 09:07:21 +08:00
## 2026-07-03 T-304 充值回调(微信/支付宝验签 + 幂等入账)
- 状态:DONE
- 变更:
- `apps/billing/models.py` :新增 `RechargeOrder` ,保存 `order_no` 、用户、金额、通道、下单锁定汇率、预计/实际入账点数、订单状态、支付流水号与支付时间;新增 `points_ledger(ref_order_id, change_type)` 复合唯一约束,作为同一订单重复充值流水的 DB 兜底。
- `apps/billing/services.py` :新增 `RechargePayment` / `RechargeResult` 、`apply_recharge_payment()` 与 `query_and_apply_recharge_payment()` ;入账时锁订单,校验 pending、金额、通道,锁 `UserWallet` 加点,写 `PointsLedger(recharge)` ,重复回调幂等返回不重复加点。
- `apps/billing/payment_gateways.py` :新增微信 / 支付宝回调 verifier;`PAYMENT_CALLBACK_MODE=mock` 用 HMAC 模拟验签,`sdk` 模式走 `wechatpayv3` / `python-alipay-sdk` 入口,缺 SDK 或配置时失败不入账。
- `apps/api/views.py` / `apps/api/urls.py` :新增 `POST /api/v1/recharge/callback/wechat` 与 `/alipay` ,两个端点均无登录态、显式 `@csrf_exempt` ;微信成功返回 `{"code":"SUCCESS","message":"成功"}` ,支付宝成功返回纯文本 `success` 。
- `apps/billing/admin.py` :注册只读 `RechargeOrderAdmin` 。
- `apps/billing/tests.py` :覆盖订单 admin 注册、同一订单重复充值流水 DB 兜底、成功入账、重复回调幂等、金额不一致不入账、主动查单复用同一入账路径。
- `apps/api/tests.py` :覆盖微信/支付宝回调成功与重复回调幂等、mock 验签失败不入账、金额不一致不入账、开启 CSRF 检查时回调端点仍可被支付网关调用。
- 新增迁移 `apps/billing/migrations/0004_rechargeorder_and_more.py` ,已应用到当前 MySQL。
- 同步更新 `.env.example` 、`README.md` 、`docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/env.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 。
- 验证:
- `./init.ps1` :开工前通过,依赖同步与 `manage.py check` 正常。
- `py -3.12 manage.py makemigrations billing` :生成 `billing.0004_rechargeorder_and_more` 。
- `py -3.12 -m py_compile apps\billing\models.py apps\billing\admin.py apps\billing\services.py apps\billing\payment_gateways.py apps\billing\tests.py apps\api\views.py apps\api\urls.py apps\api\tests.py config\settings.py` :通过。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 manage.py migrate` :通过,应用 `billing.0004_rechargeorder_and_more` 。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py showmigrations billing` :通过,`billing.0001` ~ `0004` 均为 `[X]` 。
- `py -3.12 manage.py test apps.billing apps.api --noinput --keepdb --verbosity 2` :通过,45 tests OK。
- `py -3.12 -m compileall apps config` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2` :未作为绿灯;本轮跑到 65/73 后,远程 MySQL 连接超时导致 `BillingCoreModelTests.setUpClass` 失败,并引发并发测试 barrier 失败。
- `$env:MYSQL_CONNECT_TIMEOUT='90'; py -3.12 manage.py test apps.billing.tests.BillingCoreModelTests apps.billing.tests.ConcurrentDebitTests --noinput --keepdb --verbosity 2` :业务用例均跑过并显示 ok;最终测试库 flush 阶段仍遇到远程 MySQL 连接超时,因此命令整体未作为绿灯。
- 阻塞:T-304 功能与相关测试无阻塞;完整测试仍被远程 MySQL `43.128.3.240:3306` 连接稳定性阻塞。生产真实支付还需要安装/配置微信、支付宝 SDK 与商户密钥/证书;当前 mock 仅用于本地/测试联调。
- 决策:T-304 只做回调验签与幂等入账;扫码下单、订单状态查询、前端轮询留给 T-305。`PointsLedger.ref_order_id` 本轮仍保持数值引用,配合复合唯一约束兜底防重复充值流水。
- 下一步:领取 T-305 扫码充值下单 + 轮询(create/status)。
2026-07-03 09:34:07 +08:00
## 2026-07-03 T-305 扫码充值下单 + 轮询(create/status)
- 状态:DONE
- 变更:
- `apps/billing/services.py` :新增 `create_recharge_order()` ,创建 pending 订单并绑定发起用户;下单时锁定当前 `ExchangeRate.points_per_unit` 与 `points_granted` ,再调用支付网关下单取二维码票据;支付平台下单失败时订单标记 `failed` 。
- `apps/billing/payment_gateways.py` :新增 `PaymentOrderCode` 、`create_payment_order()` 、微信/支付宝 mock 二维码生成、微信金额分转换、支付宝金额元格式化,以及 SDK 模式下单/查单入口;mock 查单明确返回不可用,等待回调或测试注入主动查单结果。
- `apps/api/views.py` / `apps/api/serializers.py` / `apps/api/urls.py` :新增 `POST /api/v1/recharge/create` 与 `GET /api/v1/recharge/status` ;两个端点走 `SessionAuthentication + IsAuthenticated` ,不继承外部 API Key 视图;create 受 CSRF 保护,status 只允许订单所属用户访问,并在 pending 时尝试主动查单补入账。
- `config/settings.py` / `.env.example` / `docs/env.md` :新增 `PAYMENT_QR_EXPIRES_MINUTES` ,仅作为二维码本地有效期提示,不自动把 pending 订单改 expired。
- `apps/billing/tests.py` :覆盖创建充值订单锁定汇率/点数/二维码票据、网关下单失败时订单标记 failed。
- `apps/api/tests.py` :覆盖 session 下单、API Key 不能调用充值下单、真实 session 客户端无 CSRF 被拒、支付宝 mock 二维码、状态查询本人可见/他人 404、主动查单补入账且不重复入账。
- 同步更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/03-tech-stack.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 。
- 验证:
- `./init.ps1` :开工前通过,依赖同步与基础检查正常。
- `py -3.12 -m py_compile apps\billing\payment_gateways.py apps\billing\services.py apps\billing\tests.py apps\api\serializers.py apps\api\views.py apps\api\urls.py apps\api\tests.py config\settings.py` :通过。
- `py -3.12 manage.py test apps.billing apps.api --noinput --keepdb --verbosity 2` :通过,53 tests OK。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check` :通过,No changes detected。
- `py -3.12 -m compileall apps config` :通过。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2` :未作为绿灯;发现 81 tests,跑过 71 tests,已运行用例均无断言失败;远程 MySQL `43.128.3.240:3306` 连接超时导致 `BillingCoreModelTests.setUpClass` 与 `AdminSmokeTests.setUpClass` 失败。
- `$env:MYSQL_CONNECT_TIMEOUT='90'; py -3.12 manage.py test apps.billing.tests.BillingCoreModelTests apps.users.tests.AdminSmokeTests --noinput --keepdb --verbosity 2` :通过,10 tests OK。
- 补微信主动查单 SDK 入口后,`py -3.12 -m py_compile apps\billing\payment_gateways.py apps\billing\services.py apps\api\views.py apps\api\tests.py` :通过;`py -3.12 manage.py check` :通过。
- `py -3.12 manage.py test apps.api.tests.RechargeCreateStatusApiTests apps.billing.tests.BillingServiceTests.test_create_recharge_order_locks_exchange_rate_points_and_qr_code apps.billing.tests.BillingServiceTests.test_create_recharge_order_marks_order_failed_when_gateway_fails --noinput --keepdb --verbosity 2` :未作为绿灯;API 新增 6 tests OK 后,进入 billing 测试类时远程 MySQL 连接超时。
- `$env:MYSQL_CONNECT_TIMEOUT='90'; py -3.12 manage.py test apps.billing.tests.BillingServiceTests.test_create_recharge_order_locks_exchange_rate_points_and_qr_code apps.billing.tests.BillingServiceTests.test_create_recharge_order_marks_order_failed_when_gateway_fails --noinput --keepdb --verbosity 2` :通过,2 tests OK。
- 阻塞:T-305 功能与相关测试无阻塞;完整测试仍被远程 MySQL 连接稳定性阻塞。真实支付生产下单 / 查单仍需要安装 SDK 并提供微信、支付宝商户密钥/证书与公网 notify_url;当前 mock 用于本地/测试联调。
- 决策:状态轮询不因本地 `expires_at` 自动把 pending 订单改 expired,避免误挡延迟到达的真实支付回调;二维码过期只作为前端提示,支付结果仍以回调或主动查单入账为准。T-305 只提供服务端 create/status,实际用户端充值页面留给 T-504。
- 下一步:领取 T-501 注册 / 登录(allauth)。
2026-07-03 09:53:23 +08:00
## 2026-07-03 Phase 3 对外 API 与充值审核(Claude Code review,非任务)
- 状态:DONE(审核完成)
- 变更:新增 `docs/phase-3-review.md` ; `06-tasks.md` 新增 T-306 安全加固任务;`docs/README.md` 导航登记。
- 审核结论:**T-301~305 功能验收全部达标,测试覆盖为四阶段最全**。§8/4.2 逐条核对通过——对外只认 Key 不认 Session(含专测)、回调 @csrf_exempt + 验签前置(mock 用 HMAC-SHA256+compare_digest 真验签、mock/真实 SDK 双路径由 PAYMENT_CALLBACK_MODE 切换、SDK 惰性导入)、入账锁订单行幂等 + 金额校验 + 用订单锁定 points_granted + 锁钱包写 RECHARGE 流水、主动查单兜底复用同一入账、生成编排 4.1 全分支(余额不足不调上游/能力不符不扣点/上游失败先退点)均有测试;Portal 会话接口自动强制 CSRF(含专测);resolve_alias 保留向后兼容未破 smoke。
- 发现(详见 `phase-3-review.md` ):
- P1-1(上线前必修,安全):`download_image_input` 拉取调用方任意 `image_url` = SSRF;且在 precharge **之前**执行,0 点数用户即可无成本触发;无内网/回环/link-local 拦截、无大小上限、无重定向限制(可 30x/DNS rebinding 绕过)。须加协议白名单 + 网段拦截(含重定向后地址)+ 大小上限 + 测试。
- P2-1: settings 无全局 REST_FRAMEWORK 默认,DRF 默认认证是 Session+Basic;外部视图都显式覆盖为 ApiKey(当前安全),但将来新增视图忘设置会默默继承默认——建议设 DEFAULT_AUTHENTICATION_CLASSES=[] 强制显式声明。
- P2-2:生成/认证端点无限流(刷点/放大上游成本/Key 枚举风险),建议 ScopedRateThrottle。
- P2-3:充值金额只有 min 无 max,建议加单笔上限。
- P3(登记):充值流水 DB 兜底已存在(T-304 已加 `unique(ref_order_id, change_type)` ,并有重复充值流水 `IntegrityError` 测试),非待办;生成图片 /media 公网 URL 无 per-user 访问控制;T-305 后完整套件未再单次全绿(远程 MySQL 不稳,T-302 时有 60 tests 全绿);过期订单不自动置 expired(有意设计)。
- 未本地复跑:审核机无 python3.12,结论基于静态审查(api authentication/generation/views/serializers/storage/urls; billing services/payment_gateways/models/migrations; settings;两 app tests) + codex 执行记录(T-301 52 / T-302 60 tests 单次全绿;T-303~305 分 app 子集通过,完整套件受远程 MySQL 连接稳定性阻塞,失败点为连接超时非断言失败)。
- 口径校正:后续核对发现报告 P3 把充值流水 DB 兜底误列为待办;T-304 已在 `billing.0004_rechargeorder_and_more` 落地 `unique(ref_order_id, change_type)` ,并有重复充值流水 `IntegrityError` 测试。后续文档已改为“DB 兜底已存在,非待办”。
- 下一步:codex 领 T-306 做 P1 SSRF 修复 + P2 加固(P1 属上线前必须)。
## 2026-07-03 Phase 3 审核报告口径修正(文档)
- 状态:DONE
- 变更:
- 修正 `docs/phase-3-review.md` :P3 中充值流水 DB 兜底口径改为“已存在,非待办”,明确 T-306 不应重复增加同类迁移。
- 修正 `progress.md` 中同一处错误口径。
- 更新 `README.md` 、`docs/00-ai-start-here.md` 、`docs/current-state.md` :下一步从 T-501 改为 T-306。
- 更新 `docs/06-tasks.md` :T-306 验收口径明确 P3 只登记媒体访问控制、完整测试补跑和过期订单口径。
- 验证:文档口径修正,无代码变更;执行 `git diff --check` 作为格式检查。
- 下一步:领取 T-306 Phase 3 对外 API 安全加固。
2026-07-03 10:34:37 +08:00
## 2026-07-03 T-306 Phase 3 对外 API 安全加固
- 状态:DONE
- 变更:
- `apps/api/generation.py` :重写 `download_image_input()` 安全边界;`image_url` 只允许 `http` / `https` ,请求前解析域名并拒绝私有、回环、链路本地、保留、组播、未指定地址;手动跟随重定向并逐跳重校验;响应按 `IMAGE_URL_MAX_BYTES` 流式限长读取,超限 / 非图片 / 空内容均返回 `bad_request` ,且发生在预扣点前。
- `apps/api/throttles.py` / `apps/api/exceptions.py` :新增生成接口按 API Key / 用户限流、认证失败按 IP 限流;DRF 429 统一返回 `rate_limited` 错误结构。
- `apps/api/authentication.py` / `apps/api/views.py` :无效 / 畸形 / 缺失 API Key 进入认证失败限流;生成标题、生成图片视图挂生成限流。
- `config/settings.py` :新增全局 `REST_FRAMEWORK` 安全默认(默认空认证 + 默认 `IsAuthenticated` ,强制视图显式 opt-in)、限流 / `image_url` 下载 / 充值金额上限配置。
- `apps/api/serializers.py` : `RechargeCreateRequestSerializer` 增加 `RECHARGE_MAX_AMOUNT_CNY` 单笔金额上限校验。
- `apps/api/tests.py` :新增裸 `APIView` 不接受 Web session 默认认证、无效 Key 限流、生成接口限流、`image_url` 回环 / 云元数据 / 重定向到内网 / 超大响应拒绝且不扣点、合法公网图片下载、充值金额上限测试。
- 同步更新 `.env.example` 、`README.md` 、`docs/00-ai-start-here.md` 、`docs/04-architecture.md` 、`docs/api.md` 、`docs/env.md` 、`docs/06-tasks.md` 、`docs/current-state.md` 、`docs/project-brief.md` 、`docs/project-onepager.md` 。
- 验证:
- `py -3.12 -m py_compile config/settings.py apps/api/exceptions.py apps/api/generation.py apps/api/authentication.py apps/api/views.py apps/api/serializers.py apps/api/throttles.py apps/api/tests.py` :通过。
- `py -3.12 manage.py check` :通过,0 issues。
- `py -3.12 manage.py makemigrations --check --dry-run` :通过,No changes detected。
- `py -3.12 manage.py test apps.api.tests.ApiKeyAuthenticationTests.test_invalid_api_key_failures_are_throttled_by_ip apps.api.tests.GenerateApiTests.test_generate_endpoint_is_throttled_by_api_key_without_extra_charge --noinput --keepdb --verbosity 2` :通过,2 tests OK。
- `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2` :通过,39 tests OK。
- `py -3.12 -m compileall apps config` :通过。
- `py -3.12 manage.py test apps.billing apps.api --noinput --keepdb --verbosity 2` :未作为绿灯;62 tests 跑到末尾,功能用例输出均为 ok,最后远程 MySQL 在并发扣点测试的 flush/重连阶段 reset 连接,报 `OperationalError (2013 Lost connection to MySQL server during query)` 。
- `$env:MYSQL_CONNECT_TIMEOUT='90'; py -3.12 manage.py test apps.billing.tests.ConcurrentDebitTests --noinput --keepdb --verbosity 2` :通过,1 test OK。
- `./init.ps1` :通过,Python 3.12.3、依赖已满足、`manage.py check` 0 issues。
- `git diff --check` :通过,仅 Windows CRLF 提示。
- 阻塞:T-306 功能无阻塞;完整大套件仍受远程 MySQL 连接稳定性影响。生产真实支付仍需微信 / 支付宝商户密钥、证书与 SDK 配置;真实 AI 上游 smoke 仍需配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。
- 下一步:领取 T-501 注册 / 登录(allauth)。