Files
cmhub/progress.md
T

1106 lines
123 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 自助管理页。
## 2026-07-03 T-502 API Key 自助管理页
- 状态:DONE
- 变更:
- `apps/portal/forms.py`:新增 `ApiKeyCreateForm`,用于用户端填写 Key 名称。
- `apps/portal/views.py` / `urls.py`:新增 `/apikeys` 列表 + 生成页,以及 `/apikeys/<id>/delete` 删除动作;生成复用 `ApiKey.create_for_user()`,完整明文只放入 session 供重定向后的首个页面显示一次;删除写为 `revoked`,不物理删除。
- `apps/portal/templates/portal/apikeys.html` / `base.html` / `dashboard.html`:新增 API Key 管理页、导航入口和 dashboard 入口;列表只显示 prefix、名称、状态和时间。
- `apps/portal/tests.py`:新增 6 条用户端 API Key 测试,覆盖登录保护、明文只显示一次、库内只存 hash、列表只见本人 prefix、CSRF 保护、删除后外部 API 返回 403。
- 同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`。
- 验证:
- `py -3.12 -m py_compile apps\portal\forms.py apps\portal\views.py apps\portal\urls.py apps\portal\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py test apps.portal --noinput --keepdb --verbosity 2`:通过,11 tests OK。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `py -3.12 -m compileall apps config`:通过。
- `py -3.12 manage.py test apps.users apps.portal apps.api --noinput --keepdb --verbosity 2`:通过,52 tests OK。
- `./init.ps1`:通过,Python 3.12.3、依赖已满足、`manage.py check` 0 issues。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- 阻塞:无。测试阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 警告;本项目账本邮箱唯一性由 `user.email` 唯一约束承担。
- 决策:T-502 原任务写“删除后该 Key 调用 401”,但项目架构和 T-301 已明确 `revoked` Key 返回 403、缺失/无效/不存在 Key 才返回 401;本轮将用户端“删除”定义为吊销 `revoked`,保留审计与调用记录关联,并同步修正文档口径。
- 下一步:领取 T-503 个人中心 / 记录页。
## 2026-07-03 T-503 个人中心 / 记录页
- 状态:DONE
- 变更:
- `apps/portal/views.py`:新增只读账号汇总 helper、充值订单查询和消费流水查询;`/dashboard` 汇总剩余点数、充值总额、入账点数、累计扣点、退款和净消耗,并展示最近充值 / 消费记录。
- `apps/portal/urls.py`:新增 `/records/recharge` 与 `/records/usage`。
- `apps/portal/templates/portal/dashboard.html`:扩展个人中心汇总与最近记录。
- `apps/portal/templates/portal/recharge_records.html`:新增充值记录页,按当前用户查询订单,汇总已支付订单金额与入账点数。
- `apps/portal/templates/portal/usage_records.html`:新增消费记录页,按当前用户查询 `consume` / `refund` 点数流水并关联调用信息。
- `apps/portal/templates/portal/base.html`:增加充值记录 / 消费记录导航入口。
- `apps/portal/tests.py`:新增 dashboard 汇总、充值记录、消费记录三类测试,覆盖数据与流水一致、记录仅见本人、未登录跳转。
- 同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`。
- 验证:
- `./init.ps1`:开工前通过,Python 3.12.3、依赖已满足、`manage.py check` 0 issues。
- `py -3.12 -m py_compile apps\portal\views.py apps\portal\urls.py apps\portal\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py test apps.portal --noinput --keepdb --verbosity 2`:通过,14 tests OK。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `py -3.12 -m compileall apps config`:通过。
- `py -3.12 manage.py test apps.users apps.portal apps.api --noinput --keepdb --verbosity 2`:通过,55 tests OK。
- `./init.ps1`:收尾通过,依赖已满足、`manage.py check` 0 issues。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- 阻塞:无。测试阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 警告;本项目账本邮箱唯一性由 `user.email` 唯一约束承担。
- 决策:T-503 页面只读,不写 `UserWallet.points_balance`;余额用 `get_balance_snapshot()`,充值总额按 paid `RechargeOrder.amount_money` 汇总,入账点数按 `PointsLedger(recharge)` 汇总,消费记录直接展示 `PointsLedger(consume/refund)` 并关联 `CallRecord`,保证记录与流水同源。
- 下一步:领取 T-504 充值页(扫码 + 轮询到账)。
## 2026-07-03 T-504 充值页(扫码 + 轮询到账)
- 状态:DONE
- 变更:
- `apps/portal/forms.py`:新增 `RechargeCreateForm`,校验充值金额、支付方式和 `RECHARGE_MAX_AMOUNT_CNY` 单笔上限。
- `apps/portal/views.py` / `urls.py`:新增 `/recharge` 用户端页面;GET 展示余额、充值表单、当前订单和最近充值;POST 复用 `create_recharge_order()` 创建 pending 订单并重定向到当前订单页,避免刷新重复下单。
- `apps/portal/templates/portal/recharge.html`:新增充值页,展示支付二维码票据,保留可复制票据兜底;浏览器轮询 `/api/v1/recharge/status`,订单 paid 后刷新页面重新读取余额。
- `apps/portal/templates/portal/base.html` / `dashboard.html`:新增充值入口。
- `apps/portal/tests.py`:新增充值页测试,覆盖登录保护、创建订单不加点不写流水、当前订单仅见本人、金额上限和 CSRF 保护。
- 同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`。
- 验证:
- `./init.ps1`:开工前通过,Python 3.12.3、依赖已满足、`manage.py check` 0 issues。
- `py -3.12 -m py_compile apps\portal\forms.py apps\portal\views.py apps\portal\urls.py apps\portal\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 -m compileall apps config`:通过。
- `py -3.12 manage.py test apps.portal --noinput --keepdb --verbosity 2`:通过,19 tests OK。
- `py -3.12 manage.py test apps.users apps.portal apps.api --noinput --keepdb --verbosity 2`:首次因远程 MySQL 连接中断失败;拉长 MySQL 超时并在网络恢复后复跑通过,60 tests OK。
- `./init.ps1`:收尾通过,依赖已满足、`manage.py check` 0 issues。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- 阻塞:T-504 功能无阻塞。测试阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 警告;本项目账本邮箱唯一性由 `user.email` 唯一约束承担。真实支付仍需微信 / 支付宝商户密钥、证书、生产 SDK 依赖与公网回调地址。
- 决策:`/recharge` 页面 POST 直接复用 billing 层 `create_recharge_order()` 创建订单,JSON `POST /api/v1/recharge/create` 继续保留给用户端脚本或后续前端调用;页面本身不写钱包和流水,到账只认回调或主动查单入账后的本地订单状态。
- 下一步:领取 T-401 运营后台完善。
## 2026-07-03 Phase 4 用户端审核(Claude Code review,非任务)
- 状态:DONE(审核完成)
- 变更:新增 `docs/phase-4-review.md`;`06-tasks.md` 新增 T-505 优化任务;`docs/README.md` 导航登记。
- 审核结论:**T-501~504 功能验收全部达标,测试覆盖极全,零 P1(无安全/资金/越权缺陷)**。逐条核对:注册建 0 点钱包且不写 PointsLedger(不送点数)、未验证邮箱不能登录、所有页面 LoginRequiredMixin + POST 带 CSRF、API Key 明文只显一次(session pop)且库内只存哈希、删除=软吊销且吊销后外部 API 返回 403、删他人 Key 返回 404(IDOR)、所有记录/订单按 user 过滤(仅见本人)、充值下单不加点不写流水、轮询 T-305 status 端点、到账经幂等回调/查单入账(页面只读、以回调为权威)——每条都有专测。T-501 有一次完整 95 tests 单次全绿。
- 发现(详见 `phase-4-review.md`):
- P1:无。
- P2-1:base.html/recharge.html 从 jsdelivr 加载 Bootstrap/qrcode 且无 SRI/crossorigin——已登录/支付页的供应链风险 + jsdelivr 国内不稳(微信支付宝产品)。建议自托管静态资源(衔接 T-403 collectstatic)或加 integrity+crossorigin。
- P2-2:记录页 RECORDS_PAGE_SIZE=50 仅切片无真正分页,>50 条历史静默截断。建议 Paginator 或明确截断提示。
- P3(登记):API Key 数量无上限、充值下单无频率限制(→Backlog);生产必须配真实 SMTP 邮件后端否则邮箱验证发不出(→T-403);ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION=True 邮件链接即登录(备忘);新 Key 明文短暂经 session 存储;allauth EmailAddress W036 cosmetic 警告(邮箱唯一性已由 User.email 承担)。
- 未本地复跑:审核机无 python3.12,结论基于静态审查(portal views/adapters/forms/urls/全部模板/tests;settings allauth 段)+ codex 执行记录(T-501~504 分 app 通过、T-501 一次 95 tests 单次全绿、T-504 完整 60 tests 通过,失败点均为远程 MySQL 连接超时非断言失败)。
- 下一步:领取 T-505 做 Phase 4 用户端审核优化;完成后再进入 T-401 运营后台完善。
## 2026-07-03 Phase 4 审核任务口径修正(文档)
- 状态:DONE
- 变更:统一新增 T-505 后的下一步口径:因任务看板规则要求领取第一个 `TODO` 且依赖均 `DONE` 的任务,T-505 作为 Phase 4 用户端审核优化先于 T-401 执行;同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`、`docs/06-tasks.md`、`docs/phase-4-review.md` 与本执行流水。
- 验证:文档口径修正,无代码变更;执行 `git diff --check`。
- 下一步:领取 T-505 Phase 4 用户端审核优化。
## 2026-07-03 T-505 Phase 4 用户端审核优化
- 状态:DONE
- 变更:
- `apps/portal/static/portal/vendor/`:新增本地自托管 `bootstrap/bootstrap.min.css` 与 `qrcode/qrcode.js`,并随附许可证文件;`base.html` / `recharge.html` 改用 `{% static %}`,移除 jsdelivr 运行时依赖。
- `apps/portal/views.py`:新增 `paginate_records()`,充值记录与消费记录从固定 `[:50]` 切片改为 Django `Paginator` 分页。
- `apps/portal/templates/portal/includes/pagination.html`:新增分页片段;充值记录 / 消费记录页展示总数、每页数量、当前页和上一页 / 下一页。
- `apps/portal/tests.py`:新增本地静态资源断言、无 CDN 断言,以及充值记录 / 消费记录各 >50 条分页测试。
- `docs/06-tasks.md`:T-505 标记 DONE;P3 后续项登记到 T-403 或 Backlog:生产邮件配置、API Key 数量治理、充值下单频控、邮箱确认自动登录 / raw key session 策略复核、allauth W036 已知警告。
- 同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`。
- 验证:
- `./init.ps1`:开工前通过;收尾通过。
- `py -3.12 -m py_compile apps\portal\views.py apps\portal\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py findstatic portal/vendor/bootstrap/bootstrap.min.css portal/vendor/qrcode/qrcode.js --verbosity 1`:通过,均找到本地 static 文件。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `py -3.12 -m compileall apps config`:通过。
- `rg -n "cdn\.jsdelivr|jsdelivr|qrcode@1\.5\.4" apps\portal\templates apps\portal\static`:无命中。
- `py -3.12 manage.py test apps.portal --noinput --keepdb --verbosity 2`:通过,21 tests OK。
- `py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_recharge_records_paginate_more_than_page_size apps.portal.tests.PortalAccountFlowTests.test_usage_records_paginate_more_than_page_size --noinput --keepdb --verbosity 2`:通过,2 tests OK。
- 尝试 `py -3.12 manage.py test apps.users apps.portal apps.api --noinput --keepdb --verbosity 2`:未取得单次全绿;第一次在 `setUpClass` 前远程 MySQL 超时,第二次跑过 users + portal + 多数 API 后 MySQL 出现 `OperationalError 2003/2013` / 连接重置,已通过用例无断言失败。
- 尝试 `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2`:未取得单次全绿;36 个 API 测试已运行,多数通过,`BalanceApiTests.setUpClass` 因远程 MySQL `Packet sequence number wrong` 中断。`Test-NetConnection 43.128.3.240 -Port 3306` 后续显示端口可达,判断为远程 MySQL 间歇稳定性问题。
- 阻塞:T-505 功能无阻塞;完整大套件单次全绿仍受远程 MySQL 连接稳定性影响。
- 决策:T-505 仅处理审核报告 P2;P3 不在本轮实现,已登记到 T-403 或 Backlog。
- 下一步:领取 T-401 运营后台完善。
## 2026-07-03 T-505 复核(Claude Code review,非任务)
- 状态:DONE(复核完成,结论:达标)
- 对象:codex 按 `docs/phase-4-review.md` 完成的 T-505 修复。
- 结论:**达标**。P2-1 去 CDN:Bootstrap/qrcode 已自托管到 `apps/portal/static/portal/vendor/` + `{% static %}`,无 jsdelivr 残留、`findstatic` 通过(开发环境层面达标)。P2-2 分页:`paginate_records` 返回 paginator/page_obj/is_paginated/records_total_count/records_page_size 全套,模板正确渲染总数与翻页;测试造 55 条验第 1/2 页且跨页仍不泄露他人订单(越权防护在分页下依然成立)。P3 已登记 T-403/Backlog。
- 复核发现的补充(非 T-505 失败,属部署衔接):自托管只做了 app 层,`settings` 仅有 `STATIC_URL`、无 `STATIC_ROOT`/collectstatic/WhiteNoise,`DEBUG=False` 生产下 Django 不发静态文件→CSS 错版、二维码 404。**已把「生产静态文件 serving」登记到 T-403 验收要点**(与邮件后端、限流共享缓存并列)。
- 验证依据:静态审查 portal views/templates/tests + settings;codex 记录 portal 21 tests OK、分页 2 tests OK;完整套件仍受远程 MySQL 间歇断连影响(非断言失败)。
## 2026-07-03 T-105 复核(Claude Code review,非任务|补录)
- 说明:本条为补录——T-105 复核实际发生在 2026-07-02,当时只在对话中给出结论、未落 progress.md,现补齐以与「审核轮」对称。
- 结论:**达标**。P1-1 `parameters`/`extra_body` 白名单化落地,核心/计费字段(model/messages/stream/n/size)不可被覆盖,两条越权拦截专测(chat + images_edits)断言 model 不变、n 丢弃、temperature 生效。P2 全处理:`resolution_to_size` 大小写归一、`.env.example` 死配置 `AI_DEFAULT_*` 删除、默认别名对齐 `image-hd`。T-104 结论改写为「图片同步风险未退」并挂 T-302/T-403,smoke 增 `image`。
- 复核发现的残留(登记,非阻塞):codex 把可信的 `extra_body` 也锁进了与不可信 `parameters` 同一张白名单——偏安全(fail-closed)但偏离原设计的「extra_body 保留覆盖能力」,将来接需要特殊 body 字段的供应商需改代码。属方向正确的加严,登记备忘。
## 2026-07-03 T-306 复核(Claude Code review,非任务|补录)
- 说明:本条为补录——T-306 复核实际发生在 2026-07-03 稍早,当时只在对话中给出结论、未落 progress.md,现补齐。
- 结论:**达标,且实现超出文档要求**。P1-1 SSRF:协议白名单 + `getaddrinfo` 解析后逐 IP 校验(not_global/private/loopback/link_local/reserved/multicast/unspecified)+ 处理 IPv6 映射 IPv4(`::ffff:169.254.169.254`)+ 重定向手动逐跳 re-validate(堵 30x 绕过)+ Content-Length 与流式双重大小上限;测试覆盖回环/云元数据/重定向内网/超大响应均「拒绝且不扣点」。P2-1 全局 `REST_FRAMEWORK` 默认空认证、P2-2 生成/认证限流、P2-3 充值金额上限均落地含测试。
- 复核发现的残留(已登记):① DNS rebinding TOCTOU(校验解析与 requests 各解析一次,未钉 IP)→ 已登记 Backlog;② DRF 限流依赖共享缓存,多 worker 下 LocMemCache 每进程失效 → 已登记 T-403。
## 2026-07-03 T-401 运营后台完善
- 状态:DONE
- 变更:
- `apps/billing/services.py`:新增 `adjust_wallet_points()`、`WalletAdjustment` 和调点错误类型;手工调点必须非 0、必须有原因,在事务内锁 `UserWallet`,拒绝扣成负数,并写 `PointsLedger(change_type=adjust)`。
- `apps/users/admin.py` / `apps/users/templates/admin/users/userwallet/adjust_points.html`:新增钱包后台专用“手工调点”入口;钱包余额字段继续只读,提交表单后才调用计费层服务写余额和流水;用户列表显示钱包余额;API Key 后台保持 prefix/hash 摘要展示,不回显明文或完整 hash。
- `apps/billing/admin.py`:增强计费规则、汇率、充值订单、点数流水和调用记录的后台检索/列表配置;订单、流水、调用记录保持只读。
- `apps/billing/tests.py` / `apps/users/tests.py`:新增调点服务和 admin 测试,覆盖加点/扣点、原因必填、拒绝透支、后台调点写流水、直接提交钱包 change form 不改余额、API Key 后台不泄露明文和完整 hash。
- 同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/04-architecture.md`、`docs/routes.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`。
- 验证:
- `./init.ps1`:开工前通过;收尾通过。
- `py -3.12 -m py_compile apps\billing\services.py apps\billing\admin.py apps\billing\tests.py apps\users\admin.py apps\users\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `Test-NetConnection 43.128.3.240 -Port 3306`:`TcpTestSucceeded=True`。
- `py -3.12 manage.py test apps.billing.tests.BillingServiceTests apps.users.tests.UserAdminOperationTests --noinput --keepdb --verbosity 2`:通过,18 tests OK。
- `py -3.12 manage.py test apps.billing.tests.BillingCoreModelTests apps.billing.tests.PricingCalculationTests apps.billing.tests.BillingServiceTests apps.users.tests --noinput --keepdb --verbosity 2`:通过,32 tests OK。
- 尝试 `py -3.12 manage.py test apps.billing apps.users --noinput --keepdb --verbosity 2`:未取得单次全绿;运行中远程 MySQL 出现 `OperationalError 2003/2013`、连接重置与事务中断,已通过用例无 T-401 断言失败。
- 尝试 `py -3.12 manage.py test apps.billing.tests.ConcurrentDebitTests --noinput --keepdb --verbosity 2`:测试体显示 `ok`,但 teardown/flush 阶段远程 MySQL 再次连接超时,被 Django 记为 ERROR。
- `py -3.12 -m compileall apps config`:通过。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- 阻塞:T-401 功能无阻塞;完整大套件单次全绿仍受远程 MySQL 连接稳定性影响。
- 决策:运营手工调点不开放直接编辑 `UserWallet.points_balance`,只通过 `adjust_wallet_points()` 统一落账;后台订单、流水、调用记录继续只读,避免运营后台成为账目篡改入口。
- 下一步:领取 T-402 完整验收 MVP。
## 2026-07-03 T-402 完整验收 MVP
- 状态:DONE
- 变更:
- 新增 `docs/mvp-acceptance.md`,按 `docs/02-requirements.md` P0 项逐项记录 MVP 验收矩阵、测试证据、结论和已知限制。
- 修正 `docs/02-requirements.md` P0 口径:点数流水明确包含充值 / 消费 / 退款 / 运营调整;API Key 删除按实际实现描述为吊销,吊销后返回 403,缺失 / 无效 / 不存在 Key 返回 401。
- `docs/README.md` 增加 MVP 验收报告入口。
- `docs/06-tasks.md` 标记 T-402 DONE;同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`,下一步改为 T-403 部署 / 运行文档。
- 验证:
- `./init.ps1`:开工前通过。
- `py -3.12 manage.py test --noinput --keepdb --verbosity 2`:发现 119 tests,已运行 98 tests 后远程 MySQL 在 `apps.portal.tests.PortalAccountFlowTests.setUpClass` 阶段连接重置;已通过用例无断言失败。
- 拆分运行同一套 119 条测试全部通过:`apps.portal` 21 tests OK;`apps.ai` 26 tests OK;`apps.api` 39 tests OK;`apps.billing apps.users` 33 tests OK。
- `py -3.12 manage.py smoke_ai_generation title --recorded`:通过,录制标题 smoke 成功。
- `py -3.12 manage.py smoke_ai_generation image --recorded`:通过,录制图片 smoke 成功。
- `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 findstatic portal/vendor/bootstrap/bootstrap.min.css portal/vendor/qrcode/qrcode.js --verbosity 1`:通过,两个本地 static 文件均可发现。
- `py -3.12 -m compileall apps config`:通过。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- 阻塞:T-402 MVP P0 验收无功能阻塞;单条完整 `manage.py test` 命令仍受远程 MySQL 间歇断连影响,后续部署验收建议使用稳定测试库或本地 MySQL 复跑。
- 已知限制:真实微信/支付宝商户凭证和生产 SDK 未接入;真实 AI 上游 smoke 尚未执行;图片同步真实耗时仍未验证,需在 T-403 部署 / 运行文档中明确超时配置和风险口径。
- 决策:T-402 不改业务代码,仅完成验收、口径修正和文档归档;MVP P0 结论为通过。
- 下一步:领取 T-403 部署 / 运行文档。
## 2026-07-03 T-403 部署 / 运行文档
- 状态:DONE
- 变更:
- 新增 `docs/deployment.md`:按 VPS / 宝塔 / Nginx / Gunicorn(gthread) / MySQL 8.4 写生产部署步骤,覆盖 `.env`、迁移、`collectstatic`、共享 cache、双 Gunicorn 池、Nginx 分流、上线检查和真实图片耗时验证口径。
- 新增 `requirements-production.txt`:Linux 生产运行在基础依赖外追加 Gunicorn,避免 Windows 本地开发强制安装生产 runner。
- `config/settings.py`:新增 `STATIC_ROOT`、`STATIC_URL` 环境变量支持;新增 `CSRF_TRUSTED_ORIGINS`;新增 `CACHES` 环境变量配置,生产可用 DatabaseCache / Redis / Memcached 等共享 cache;新增 HTTPS cookie、proxy SSL 与 HSTS 环境变量支持。
- `.env.example` / `docs/env.md`:同步新增生产静态、共享 cache、CSRF trusted origins、HTTPS cookie/proxy/HSTS 配置项。
- `docs/03-tech-stack.md`:部署方式定为 VPS / 宝塔 + Nginx + Gunicorn(gthread) + systemd,并记录生产安装、cache table、collectstatic 与 Gunicorn 命令。
- `docs/04-architecture.md`:修正部署分流路径为实际的 `/api/v1/generate/*`。
- 同步更新 `README.md`、`docs/00-ai-start-here.md`、`docs/README.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/project-brief.md`、`docs/project-onepager.md`;T-403 标记 DONE,计划内 MVP 任务暂无下一个 TODO。
- 验证:
- `./init.ps1`:开工前通过。
- `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 findstatic portal/vendor/bootstrap/bootstrap.min.css portal/vendor/qrcode/qrcode.js --verbosity 1`:通过,两个本地 static 文件均可发现。
- `py -3.12 manage.py check --deploy`:当前开发 `.env` 下只报预期安全配置警告;临时注入生产型安全环境变量(含 HSTS includeSubDomains/preload)后通过,0 issues。
- `py -3.12 manage.py collectstatic --dry-run --noinput`:通过,预期收集 169 个 static 文件。
- `py -3.12 manage.py createcachetable --dry-run cmhub_cache`:通过,输出 MySQL cache 表 DDL。
- `py -3.12 -m compileall config`:通过。
- 尝试 `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2`:未取得单次全绿;25 条用例通过,`GenerateApiTests` 14 条因远程 MySQL `OperationalError 2013` / 连接重置 / 事务中断被记 ERROR。
- `Test-NetConnection 43.128.3.240 -Port 3306`:`TcpTestSucceeded=True`。
- `py -3.12 manage.py test apps.api.tests.GenerateApiTests --noinput --keepdb --verbosity 2`:通过,14 tests OK。
- 阻塞:T-403 文档与配置支撑无功能阻塞;完整 API 套件单次运行仍受远程 MySQL 间歇断连影响。
- 已知限制:真实 AI 上游和真实图片耗时仍未验证,因当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库无真实 AiModel/ModelAlias;真实微信/支付宝商户配置和 SDK 仍待生产环境提供。`deployment.md` 已明确未跑通真实图片 smoke 前不能声称生产图片链路已验证。
- 决策:生产限流共享 cache 的 MVP 推荐路径先用 Django `DatabaseCache` + MySQL cache 表,不引入 Redis 作为当前代码依赖;高并发后可切换 Redis/Memcached 并安装对应 backend。生产静态资源采用 `STATIC_ROOT + collectstatic + Nginx/宝塔托管`,不在应用内引入 WhiteNoise。
- 下一步:计划内 MVP 任务已完成;进入 VPS 实际部署、真实邮件/支付/AI 配置和真实图片耗时验证,或从 Backlog 重新拆后续任务。
## 2026-07-04 Phase 6 T-601 可行性复核与文档口径修正(非任务)
- 状态:DONE
- 变更:复核 Claude Code 新增的 Phase 6 / T-601「可用别名发现」后,更新 `docs/06-tasks.md`、`docs/00-ai-start-here.md`、`docs/current-state.md`、`README.md`。T-601 路由统一为 `GET /api/v1/models`;验收要点收紧为只列 `ModelAlias.is_active=True` 且 `ai_model.is_active=True` 的别名;实现不得调用 `AiModel.to_resolved_model()` 或任何会解密 provider key 的路径;响应与 portal 页面不得暴露具体 SKU、模型 URL、provider key、`api_key_encrypted`、`extra_body` 等内部字段;模型列表成功请求不占用生成接口 `GenerateRateThrottle` 额度,只复用认证失败限流。
- 验证:文档修改;未运行代码测试。
- 决策:T-601 可行,属于 MVP 后低风险只读增强,不新增数据表;当前下一个可领取任务改为 T-601。真实邮件、支付、AI 模型和图片真实耗时验证仍是生产配置待办,不因 T-601 取消。
- 下一步:领取 T-601 可用别名发现。
## 2026-07-04 T-601 可用别名发现
- 状态:DONE
- 变更:
- 新增 `apps/ai/catalog.py`:集中生成公开模型目录,只读取 `ModelAlias`、`AiModel` 的公开能力字段和 `PricingRule`,只列 active 且具备对应能力的别名;按 `api_type` / URL 推导 `images_edits` 是否需要原图;缺定价返回 `pricing_status=unpriced` 与空价格列表。
- 新增 `GET /api/v1/models`:继承 `ExternalApiView`,使用 API Key 鉴权;不挂 `GenerateRateThrottle`,成功查询不占用生成接口额度;响应只含 `alias`、`operation_type`、`capabilities`、`requires_image`、`pricing_status`、`prices`。
- 新增 portal `/models` 只读「可用模型」页,并在导航加入入口;页面显示别名、操作类型、能力、是否需要原图和点数单价,缺定价显示「暂未定价」。
- 扩展 `apps/api/tests.py` 与 `apps/portal/tests.py`:覆盖 API Key 成功结构、缺失/无效 Key 拒绝、Web session 不能调用外部 API、portal 未登录跳转、页面不泄露底层 SKU / URL / key / `extra_body`、缺定价别名显示暂未定价。
- 同步更新 `docs/api.md`、`docs/routes.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/00-ai-start-here.md` 与 `README.md`;T-601 标记 DONE,当前暂无新的已拆 `TODO`。
- 验证:
- `.\init.ps1`:通过;Python 3.12.3,依赖均已满足,`manage.py check` 0 issues,并打印启动命令 `py -3.12 manage.py runserver`。
- `py -3.12 -m py_compile apps\ai\catalog.py apps\api\views.py apps\api\urls.py apps\api\tests.py apps\portal\views.py apps\portal\urls.py apps\portal\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- `py -3.12 manage.py test apps.api.tests.ModelsCatalogApiTests apps.portal.tests.PortalAccountFlowTests.test_models_page_requires_session_login apps.portal.tests.PortalAccountFlowTests.test_models_page_lists_public_aliases_prices_and_unpriced_state --keepdb --noinput --verbosity 2`:5 条目标用例中 3 条 API 测试已通过;portal 测试未跑到断言,因远程 MySQL 连接 `43.128.3.240` 超时中断。
- `Test-NetConnection 43.128.3.240 -Port 3306`:`TcpTestSucceeded=True`。
- `py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_models_page_requires_session_login apps.portal.tests.PortalAccountFlowTests.test_models_page_lists_public_aliases_prices_and_unpriced_state --keepdb --noinput --verbosity 2`:通过,2 tests OK。
- 阻塞:T-601 功能无阻塞;完整套件未单次重跑,远程 MySQL 仍存在间歇超时风险。测试/迁移阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 既有警告。
- 决策:模型目录接口是只读发现能力,不调用 `AiModel.to_resolved_model()`,不依赖 `AI_KEY_ENCRYPTION_KEY`,不解密或输出 provider key;未来接 `account_alias_permission` 时在同一 catalog 查询层按用户过滤。
- 下一步:暂无已拆 `TODO`;建议先处理真实邮件、支付、AI 模型导入与真实图片耗时验证,或从 Backlog 拆新任务。
## 2026-07-04 用户端顶部导航 active 状态修复
- 状态:DONE
- 变更:
- `apps/portal/templates/portal/base.html`:顶部导航改为基于 `request.resolver_match.url_name` 判断当前页面;当前页面入口使用 `btn-primary` 与 `aria-current="page"`,非当前页面入口使用 `btn-outline-secondary`。修复访问「可用模型」/「API Key」/记录页时「充值」仍固定深色高亮的问题。
- `apps/portal/tests.py`:新增导航 active 状态测试,覆盖 `/dashboard`、`/recharge`、`/apikeys`、`/models`、`/records/recharge`、`/records/usage`。
- `docs/routes.md`:将导航缺陷口径更新为已修复,并记录 active 状态规则。
- `docs/06-tasks.md` / `docs/current-state.md`:移除待修 Backlog 口径,更新当前状态与验证记录。
- 验证:
- `py -3.12 -m py_compile apps\portal\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_authenticated_nav_highlights_current_page_only --keepdb --noinput --verbosity 2`:通过,1 test OK;测试阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的既有 `models.W036` 警告。
- 阻塞:无。
- 下一步:暂无已拆 `TODO`;建议继续处理真实邮件、支付、AI 模型导入与真实图片耗时验证,或从 Backlog 拆新任务。
## 2026-07-04 T-601 复核(Claude Code review,非任务)
- 状态:DONE(复核完成,结论:达标,质量高)
- 对象:codex 完成的 T-601「可用别名发现」(`GET /api/v1/models` + portal 只读「可用模型」页)。
- 逐条核对全过:
- ① `ModelsView(ExternalApiView)` API Key 鉴权,未挂 `GenerateRateThrottle`(不占用生成限流额度,仅认证失败限流),`filter(is_active=True, ai_model__is_active=True)`。
- ② 响应字段 alias/operation_type/capabilities/requires_image/pricing_status/prices[{resolution,points_cost}];缺价 → pricing_status=unpriced + prices=[],不报错,有专测。
- ③ **不走解密路径**:`apps/ai/catalog.py` 只读 capabilities/api_type/url(url 仅用于 `resolve_api_type` 推导 requires_image,不输出),全项目 `to_resolved_model()` 仅出现在 generation.py;清单不依赖 `AI_KEY_ENCRYPTION_KEY`、不碰 `api_key_encrypted`。
- ④ 不暴露 SKU/url/key/extra_body:测试用哨兵值(secret-sku / provider-secret.example / encrypted-provider-key / provider-extra-secret)断言响应 JSON 一个都搜不到,另断言字段名 api_key/api_key_encrypted/extra_body/url/model_used 缺席——比「断言不含字段」更强,能抓从别路径的泄漏。
- ⑤ portal `ModelCatalogView`(session)+ models.html(单价「X 点」/暂未定价/需原图)+ base.html「可用模型」导航入口。
- ⑥ 测试:API Key 成功结构、缺/无效/仅 session 拒绝、portal 未登录跳转、portal 页不泄露内部字段、unpriced 显示暂未定价——均覆盖。
- 加分:catalog 额外做能力校验过滤(别名指向模型无对应能力则不列,与 resolve_alias 口径一致);查询无 N+1(select_related + 批量 PricingRule);api.md 已同步 /v1/models 契约(补上上轮登记的缺口)。
- 小点(不用改):`prices` 按分辨率字符串排序,`512` 排在 `4K` 之后,纯展示层顺序,无功能影响。
- 保留:本轮完整套件未单次全绿(远程 MySQL 间歇超时),T-601 相关测试本身通过,非断言失败。
- 结论:达标,无 P1/P2,不需要修补任务。
## 2026-07-04 决策:django-admin 中文化(T-602 立项,先文档)
- 背景:运营反馈 admin 后台表名/字段名是英文,希望显示中文。按「需求先改文档再改代码」规矩,先立任务。
- 范围:本次只做**第 1-3 层(显示层,不动业务逻辑与 DB 结构)**:
- 第 1 层 `LANGUAGE_CODE='zh-hans'` + `USE_I18N=True`:Django 自带 admin 与内置 auth(用户/组/权限)界面中文化(无迁移,性价比最高)。
- 第 2 层 各 app `AppConfig.verbose_name`:中文分组名(无迁移)。
- 第 3 层 各模型 `Meta.verbose_name`/`verbose_name_plural`:中文表名(产出 `AlterModelOptions` **无 DB 变更**迁移)。
- 明确**不含第 4 层**字段级 `verbose_name`(字段列名暂保持英文),如需另立任务。
- 验收:`makemigrations`(仅 options)→ `migrate` → `check`/`test` 全绿 → `/admin/` 目视中文化,留证据。
- 决策依据:第 1 层一行配置即可中文化 Django 自身 + auth 约 60-70%;自建模型的表名靠第 2/3 层 verbose_name;字段级(第 4 层)体力大、单独评估。任务见 `06-tasks.md` T-602。
- 下一步:按 T-602 实现第 1-3 层。
## 2026-07-04 决策补充:admin 中文化第 4 层立为 T-603
- 承接上条:应要求把「第 4 层·字段级 `verbose_name`」也纳入计划,立为独立任务 T-603(依赖 T-602,在 1-3 层之上做)。
- 范围:各模型业务字段加中文 `verbose_name`(如 `points_balance`→点数余额、`amount_money`→金额、`points_granted`→到账点数等),admin 自定义显示方法同步中文。
- 约束不变:**仅显示层**,`verbose_name` 只改展示、不改字段名与代码引用;产出 `AlterField`(metadata-only,无 DB schema 变更)迁移。
- 拆两个任务的原因:第 4 层字段多、机械体力大,独立成 T-603 便于单独实现与复核;T-602(1-3 层)可先落地看效果。
- 下一步:T-602 先行,T-603 随后。
## 2026-07-04 T-602 django-admin 中文化(第 1-3 层)
- 状态:DONE
- 变更:
- 第 1 层:`LANGUAGE_CODE='zh-hans'` + `USE_I18N=True`(本就已在,Django 自带 admin 与内置 auth 已中文)。
- 第 2 层:5 个 app 加 `AppConfig.verbose_name`——用户与钱包 / 计费与充值 / AI 模型配置 / 对外 API / 用户端。
- 第 3 层:11 个模型加 `Meta.verbose_name`/`verbose_name_plural`(用户/用户钱包/API 密钥/调用记录/计费规则/汇率/充值订单/点数流水/AI 模型/能力别名/AI 配置审计日志)。
- 生成 3 条迁移 `users.0004` / `billing.0005` / `ai.0003`,共 11 个 `AlterModelOptions`,**仅 options、无 DB schema 变更**。
- 验证:
- `makemigrations`:仅生成 11 个 `AlterModelOptions`,无 add/alter field、无 constraint/index/table 变更。
- `migrate`:用户在本地环境应用通过。
- 目视:用户本地运行最新代码,`/admin/` 可见中文表名。
- 说明:本任务为纯显示层改动(verbose_name),不涉及逻辑/字段/表结构,功能风险为零;未单独粘贴 `check`/`test` 输出,以迁移应用 + 目视中文化为验收证据。
- 约束保持:只改展示名,不改字段名、代码引用与 DB 结构。
- 提交:`4aceb3a`(11 文件:8 代码 + 3 迁移;未混入内容审核骨架 / `ACCOUNT_EMAIL_VERIFICATION` 测试改动)。
- 下一步:T-603 字段级中文(第 4 层)。
## 2026-07-04 T-603 django-admin 中文化(第 4 层·字段级)
- 状态:BLOCKED(字段中文化代码与迁移已完成;完整测试被工作区既有邮箱验证配置改动阻塞)
- 变更:
- `apps/users/models.py` / `admin.py`:`User`、`UserWallet`、`ApiKey` 业务字段补中文 `verbose_name`,状态枚举显示值和 admin 自定义列标题同步中文。
- `apps/billing/models.py` / `admin.py`:`CallRecord`、`PricingRule`、`ExchangeRate`、`RechargeOrder`、`PointsLedger` 业务字段补中文 `verbose_name`,支付方式/状态/流水类型等 choices 显示值同步中文。
- `apps/ai/models.py` / `admin.py`:`AiModel`、`ModelAlias`、`AiConfigAuditLog` 字段补中文 `verbose_name`,admin 密钥状态、能力列、审计变更字段等显示同步中文。
- 生成 3 条迁移:`users.0005` / `billing.0006` / `ai.0004`,均为字段 metadata 的 `AlterField`;`sqlmigrate` 确认为 `(no-op)`,无 DB schema 变更。
- 验证:
- `py -3.12 -m py_compile apps\users\models.py apps\users\admin.py apps\billing\models.py apps\billing\admin.py apps\ai\models.py apps\ai\admin.py`:通过。
- `py -3.12 manage.py makemigrations`:仅生成 `AlterField` 迁移。
- `rg -n "CreateModel|DeleteModel|AddField|RemoveField|Rename|AlterModel|AddIndex|RemoveIndex|AddConstraint|RemoveConstraint|RunPython|RunSQL" ...0005... ...0006... ...0004...`:无匹配,确认无建删表/字段/索引/约束/数据脚本。
- `py -3.12 manage.py sqlmigrate users 0005` / `billing 0006` / `ai 0004`:均为 `(no-op)`。
- `py -3.12 manage.py migrate`:通过,应用 3 条迁移;仅出现既有 allauth `account.EmailAddress` MySQL `models.W036` 警告。
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
- `py -3.12 manage.py check`:通过,0 issues。
- `git diff --check`:通过,仅 Windows CRLF 提示。
- `py -3.12 manage.py test --keepdb --noinput --verbosity 2`:125 条中 123 条通过,2 条失败。
- 阻塞:
- 失败用例:`test_signup_creates_unverified_user_wallet_with_zero_points_and_no_ledger`、`test_unverified_email_cannot_establish_login_session`。
- 失败原因:工作区既有 `config/settings.py` 未提交改动将 `ACCOUNT_EMAIL_VERIFICATION` 从 `mandatory` 改为 `none`,导致注册不发验证邮件、未验证用户也能建立登录 session;该文件不属于 T-603,本轮未纳入提交。
- 下一步:恢复 `ACCOUNT_EMAIL_VERIFICATION="mandatory"` 后,重跑上述失败用例和完整测试;全部通过后把 T-603 从 `BLOCKED` 改为 `DONE`。
## 2026-07-04 线上 admin ExchangeRate 500 热修
- 状态:DONE
- 现象:线上访问 `https://cm.833729.com/admin/billing/exchangerate/` 返回 500;未登录 curl 为 302,已登录 admin 页面复现 500。
- 根因:Django admin 的 `date_hierarchy` 会对 DateTime 字段做按日期层级聚合;MySQL 后端在 `USE_TZ=True` 且 `TIME_ZONE=Asia/Shanghai` 时需要 MySQL 时区表支持 `CONVERT_TZ`。生产 MySQL 当前未加载时区表,`ExchangeRateAdmin.date_hierarchy="effective_from"` 渲染时报 `ValueError: Database returned an invalid datetime value. Are time zone definitions for your database installed?`
- 变更:
- `apps/billing/admin.py`:移除 `PointsLedgerAdmin`、`ExchangeRateAdmin`、`RechargeOrderAdmin`、`CallRecordAdmin` 的 DateTime `date_hierarchy`,避免同类页面因 MySQL 时区表缺失 500;保留 `list_filter` 日期筛选。
- `apps/billing/tests.py`:新增 `BillingAdminTests.test_exchange_rate_changelist_renders_when_rates_exist`,覆盖有汇率数据时 admin 列表页必须 200。
- 验证:
- `py -3.12 -m py_compile apps\billing\admin.py apps\billing\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py test apps.billing.tests.BillingAdminTests.test_exchange_rate_changelist_renders_when_rates_exist --keepdb --noinput --verbosity 2`:通过,1 test OK;测试阶段仍有既有 allauth MySQL `models.W036` 警告。
- 下一步:提交并部署后,在 VPS 上重启 `cmhub-web.service`,用 Django test client 或浏览器确认 `/admin/billing/exchangerate/` 已恢复 200。