Files
cmhub/progress.md
T

756 lines
82 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.
# 执行进度记录
> 本文件是只追加的历史流水,用来记录任务执行过程、验证命令、阻塞点和关键决策。
> 当前目录、当前命令、下一个可领取任务等可覆盖快照,写入 [`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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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-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 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 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 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 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)。
## 2026-07-03 T-501 注册 / 登录(allauth)
- 状态:DONE
- 变更:
- `requirements.txt`:新增 `django-allauth>=65.18,<66`,本机安装到 65.18.0。
- `config/settings.py` / `config/urls.py`:接入 `allauth` / `allauth.account`、allauth authentication backend、`AccountMiddleware`、邮箱验证 mandatory、登录/登出跳转、邮件 backend 配置、项目模板优先级、`accounts/` 路由与 portal 页面路由。
- `apps/portal/`:新增 `CmhubAccountAdapter`,allauth 注册成功后创建 `UserWallet(points_balance=0)`,不写赠点流水;新增 `/signup`、`/login`、`/logout`、`/dashboard` 路由,最小 dashboard 只读显示点数余额;新增 Bootstrap 模板与中文验证邮件模板。
- `apps/portal/tests.py`:覆盖注册创建未验证邮箱与 0 点钱包、无赠点流水、未验证邮箱不能登录、已验证用户登录/dashboard/登出、dashboard session 保护、登录 POST CSRF 保护。
- 同步更新 `.env.example`、`README.md`、`docs/00-ai-start-here.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/env.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`、`docs/phase-2-review.md`。
- 验证:
- `./init.ps1`:开工前通过,依赖同步与 `manage.py check` 正常。
- `py -3.12 -m pip install -r requirements.txt`:通过,安装 `django-allauth 65.18.0`。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 -m py_compile config\settings.py config\urls.py apps\portal\adapters.py apps\portal\urls.py apps\portal\views.py apps\portal\tests.py`:通过。
- `py -3.12 manage.py test apps.portal --noinput --keepdb --verbosity 2`:通过,5 tests OK。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `py -3.12 manage.py migrate --noinput`:通过,应用 `account.0001` ~ `account.0009`;MySQL 对 allauth `account.EmailAddress` 条件唯一约束提示 `models.W036`,第三方表的条件约束不会创建。
- `py -3.12 manage.py test apps.users apps.portal --noinput --keepdb --verbosity 2`:通过,7 tests OK。
- `py -3.12 -m compileall apps config`:通过。
- `py -3.12 manage.py shell -c "from django.test import Client; r=Client().get('/login'); print(r.status_code); print(r.content[:120])"`:通过,`/login` 返回 200;修正模板优先级后确认返回项目内 `portal/base.html` 模板。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2`:首次在 6 分钟工具超时内无结果;随后设置 `$env:MYSQL_CONNECT_TIMEOUT='90'`、`$env:MYSQL_READ_TIMEOUT='180'`、`$env:MYSQL_WRITE_TIMEOUT='180'` 后重跑通过,95 tests OK,234.743s;修正模板优先级后再次完整重跑通过,95 tests OK,301.722s。
- 阻塞:T-501 功能无阻塞。生产邮箱验证需要配置真实邮件服务;当前默认 console backend 只适合本地开发。allauth 的 `EmailAddress` 条件唯一约束在 MySQL 上有第三方模型警告,但本项目账本唯一性由 `user.email` 唯一约束承担。
- 决策:不引入 crispy-forms;T-501 只需要简单 allauth 表单,MVP 先用 Django form + Bootstrap 模板。注册路径只初始化 0 点钱包,不创建 `PointsLedger`,避免被误解为注册赠点。
- 下一步:领取 T-502 API Key 自助管理页。