142 KiB
142 KiB
执行进度记录
本文件是只追加的历史流水,用来记录任务执行过程、验证命令、阻塞点和关键决策。 当前目录、当前命令、下一个可领取任务等可覆盖快照,写入
docs/current-state.md。
职责边界
docs/06-tasks.md:任务看板,维护任务状态、依赖和验收要点。progress.md:历史流水,只追加记录每轮执行发生了什么。docs/current-state.md:当前快照,可覆盖更新仓库现实、可运行命令和下一步。
不要在本文重复维护当前目录结构、当前运行命令或下一个任务;这些信息以 docs/current-state.md 为准。
记录格式
每完成或中断一轮任务,在文件末尾追加一条记录:
## 【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,测不出并发扣点)。
- ① 框架/语言锁定 Django 5.2 LTS + Python 3.12(
- 待办提醒: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.md4.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 更安全,不在桌面端存密码)。
- ① GPT-5.5(
- 下一步: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)。
- P1(建议现在改):
- 未本地复跑:审核机无
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 数据表、不做别名解析数据库读取、不接计费;先用
ResolvedModeldataclass 承接后续 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恢复后重试通过;失败点均为建库/事务前连接超时,不是断言失败。
- 阻塞:无。
- 决策:
- 供应商密钥存储采用
cryptographyFernet 应用层加密,主密钥只走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多次在远程 MySQL43.128.3.240:3306创建/销毁测试库或测试中途重连时超时;失败点为 MySQL 连接/建库,不是断言失败。为降低远程建库压力,使用--keepdb完整测试已通过。
- 阻塞:无代码阻塞;远程 MySQL 偶发连接超时会影响标准测试建库稳定性。
- 决策:T-103 使用自建
AiConfigAuditLog专表,不只依赖 django-adminLogEntry;密钥审计只记录状态变化,不记录明文或密文。 - 下一步:领取 T-104 跑通一次真实/录制的标题或图片生成。
2026-07-02 T-104 跑通一次真实/录制的标题或图片生成
- 状态:DONE
- 变更:
- 新增
smoke_ai_generation管理命令:支持title --recorded,在事务内临时创建 fakeAiModel/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:多次在远程 MySQL43.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 写入禁 dumpresult.raw(含 base64 大图/敏感);provider 密钥无轮换(单 Fernet key)。
- P1-1(进 T-302 前必修,资金安全):
- 未本地复跑:审核机无
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事务连接阶段超时。失败点均为远程 MySQL43.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 >= 0DB 约束)与ApiKey(key_hashSHA-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也在远程连接重建时超时;已跑过测试无断言失败。失败点均为远程 MySQL43.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 强制并发扣同一钱包,在 MySQLtest_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 建连接超时。失败点均为远程 MySQL43.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:MySQLOPTIONS增加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_allcleanup,确保并发测试结束后 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":通过;MySQL8.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:图片结果用 Djangodefault_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,已运行用例均无断言失败;远程 MySQL43.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(有意设计)。
- P1-1(上线前必修,安全):
- 未本地复跑:审核机无 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 check0 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 对 allauthaccount.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 check0 issues。git diff --check:通过,仅 Windows CRLF 提示。
- 阻塞:无。测试阶段仍有 allauth
account.EmailAddress条件唯一约束在 MySQL 上不可创建的models.W036警告;本项目账本邮箱唯一性由user.email唯一约束承担。 - 决策:T-502 原任务写“删除后该 Key 调用 401”,但项目架构和 T-301 已明确
revokedKey 返回 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 check0 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 check0 issues。git diff --check:通过,仅 Windows CRLF 提示。
- 阻塞:无。测试阶段仍有 allauth
account.EmailAddress条件唯一约束在 MySQL 上不可创建的models.W036警告;本项目账本邮箱唯一性由user.email唯一约束承担。 - 决策:T-503 页面只读,不写
UserWallet.points_balance;余额用get_balance_snapshot(),充值总额按 paidRechargeOrder.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 check0 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 check0 issues。git diff --check:通过,仅 Windows CRLF 提示。
- 阻塞:T-504 功能无阻塞。测试阶段仍有 allauth
account.EmailAddress条件唯一约束在 MySQL 上不可创建的models.W036警告;本项目账本邮箱唯一性由user.email唯一约束承担。真实支付仍需微信 / 支付宝商户密钥、证书、生产 SDK 依赖与公网回调地址。 - 决策:
/recharge页面 POST 直接复用 billing 层create_recharge_order()创建订单,JSONPOST /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]切片改为 DjangoPaginator分页。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因远程 MySQLPacket 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.mdP0 项逐项记录 MVP 验收矩阵、测试证据、结论和已知限制。 - 修正
docs/02-requirements.mdP0 口径:点数流水明确包含充值 / 消费 / 退款 / 运营调整;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.portal21 tests OK;apps.ai26 tests OK;apps.api39 tests OK;apps.billing apps.users33 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 条用例通过,GenerateApiTests14 条因远程 MySQLOperationalError 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 check0 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;测试阶段仍有 allauthaccount.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 变更迁移)。
- 第 1 层
- 明确不含第 4 层字段级
verbose_name(字段列名暂保持英文),如需另立任务。 - 验收:
makemigrations(仅 options)→migrate→check/test全绿 →/admin/目视中文化,留证据。 - 决策依据:第 1 层一行配置即可中文化 Django 自身 + auth 约 60-70%;自建模型的表名靠第 2/3 层 verbose_name;字段级(第 4 层)体力大、单独评估。任务见
06-tasks.mdT-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 变更。
- 第 1 层:
- 验证:
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 条迁移;仅出现既有 allauthaccount.EmailAddressMySQLmodels.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的 DateTimedate_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 MySQLmodels.W036警告。
- 下一步:提交并部署后,在 VPS 上重启
cmhub-web.service,用 Django test client 或浏览器确认/admin/billing/exchangerate/已恢复 200。
2026-07-04 Mock 支付二维码提示
- 状态:DONE
- 背景:线上
PAYMENT_CALLBACK_MODE=mock时生成的微信二维码内容为weixin://wxpay/cmhub-mock...,该票据只用于联调,不会进入微信真实支付页;用户用微信扫码可能看到空白,容易误认为生产支付异常。 - 文档先行:
docs/api.md:补充 mock 模式返回的是测试二维码票据,不能跳转真实微信/支付宝收银台;用户端充值页应展示醒目 mock 提示。docs/04-architecture.md:补充/recharge页面在PAYMENT_CALLBACK_MODE=mock时必须提示测试票据不可真实支付。
- 代码:
apps/portal/views.py:向充值页模板传入payment_callback_mode与is_mock_payment_mode。apps/portal/templates/portal/recharge.html:待支付订单区域在 mock 模式下显示 warning 提示。apps/portal/tests.py:覆盖 mock 模式显示提示、sdk 模式不显示提示。
- 验证:
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 test apps.portal.tests.PortalAccountFlowTests.test_recharge_page_post_creates_pending_order_without_crediting_wallet_or_ledger apps.portal.tests.PortalAccountFlowTests.test_recharge_page_does_not_show_mock_notice_in_sdk_mode --keepdb --noinput --verbosity 2:通过,2 tests OK;测试阶段仍有既有 allauth MySQLmodels.W036警告。
2026-07-04 临时隐藏用户端支付宝充值入口
- 状态:DONE
- 背景:线上支付宝
trade.precreate返回isv.illegal-client-ip/当前调用IP不在可信名单中,VPS 出口 IP 已确认为43.128.3.240;在支付宝开放平台可信 IP 未配置前,用户端展示支付宝会持续创建失败订单。 - 变更:
apps/portal/forms.py:RechargeCreateForm.pay_method暂时只保留微信选项,页面不再渲染支付宝 radio,手工提交pay_method=alipay也会被表单拒绝。apps/portal/tests.py:覆盖充值页不显示支付宝、超过上限仍按微信校验、隐藏支付宝提交不创建订单。docs/current-state.md:记录支付宝临时下线原因和恢复条件;底层 API / 回调 / SDK 路径保留。
- 恢复条件:支付宝开放平台把
43.128.3.240加入可信 IP 白名单并通过真实预下单 smoke 后,再把RechargeCreateForm的支付宝 choice 加回。
2026-07-06 支付正式模式热修收敛
- 状态:DONE
- 背景:生产已切到
PAYMENT_CALLBACK_MODE=sdk并配置微信 / 支付宝真实参数后,微信 1 元下单先后暴露两个 SDK 兼容问题;支付宝预下单返回业务错误isv.illegal-client-ip。 - 变更:
requirements.txt:新增wechatpayv3/python-alipay-sdk,并把cryptography上限放宽到<50,对齐真实支付 SDK 依赖。.gitignore:忽略.secrets//secrets/,避免支付证书和私钥误入库。apps/billing/payment_gateways.py:微信 Native 下单显式传WeChatPayType.NATIVE;兼容wechatpayv3返回(status_code, json_string)的 tuple 响应,统一解析code_url与查单结果。apps/api/views.py:pending 充值订单主动查单遇到支付网关运行时异常时不再让状态接口 500,而是记录 warning 并保持当前订单状态返回 200。apps/portal/forms.py:支付宝可信 IP 未配置前,用户端充值表单只保留微信;手工提交pay_method=alipay会被表单拒绝。docs/api.md/docs/04-architecture.md/docs/current-state.md:同步 mock 提示、微信正式下单热修、支付宝临时下线和恢复条件。
- 验证:
py -3.12 -m py_compile apps\billing\payment_gateways.py apps\api\views.py apps\billing\tests.py apps\api\tests.py:通过。py -3.12 manage.py check:通过,0 issues。py -3.12 manage.py test apps.billing.tests.BillingServiceTests.test_wechat_sdk_native_order_passes_explicit_pay_type apps.billing.tests.BillingServiceTests.test_wechat_sdk_query_parses_tuple_json_response apps.api.tests.RechargeCreateStatusApiTests.test_recharge_status_keeps_pending_when_active_query_raises_unexpected_error --keepdb --noinput --verbosity 2:通过,3 tests OK。py -3.12 -m py_compile apps\portal\forms.py apps\portal\tests.py:通过。py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_recharge_page_requires_login_and_shows_form apps.portal.tests.PortalAccountFlowTests.test_recharge_page_rejects_amount_above_configured_maximum apps.portal.tests.PortalAccountFlowTests.test_recharge_page_rejects_hidden_alipay_submit --keepdb --noinput --verbosity 2:通过,3 tests OK。- 线上:同步热修后
python3.12 manage.py check通过,cmhub-web/cmhub-generate重启后均为active;修复后的微信 1 分诊断预支付请求返回weixin://wxpay/bizpayurl票据;用户端表单 choices 仅剩微信,支付宝提交无效。
- 后续:微信下单已通,但线上微信回调曾出现
PaymentVerificationError,仍需修复并做真实支付到账闭环;支付宝需在开放平台把 VPS 出口 IP43.128.3.240加入可信 IP 后再恢复页面选项。
2026-07-06 需求+方案:中文敏感词本地过滤(T-604,ahocorapy)
- 需求:对中文 prompt 做本地敏感词过滤,命中即在预扣点之前拦截(不扣点、不调上游、返回 400 content_blocked)。
- 定位(重要):本地词表 = 免费快筛 + 运营自定义黑名单,不替代合规内容审核。国内 AIGC 上线通常仍需有资质的云内容安全服务;云 API 作为后续可插拔槽位,不在 T-604 范围。
- 选型:匹配引擎用
ahocorapy(纯 Python Aho-Corasick,免编译,VPS 上 pip 一定装得上),放弃需编译的pyahocorasick。繁简归一化opencc可选。 - 方案要点:
- 接现有
apps/moderation/骨架(provider 接口 +moderate_input/moderate_output_*钩子 + fail-closed),本任务把骨架一并转正提交;MODERATION_ENABLED默认 False → no-op,现有测试不受影响。 KeywordModerationProvider:先归一化再匹配(全半角/去空白分隔标点/零宽/大小写/可选繁简)以抗低级绕过;ahocorapy 做子串匹配。SensitiveWord模型(word/category/action/is_active)+ admin 维护 + 迁移。- 缓存 matcher:启动/词库变更时构建自动机缓存内存,信号或版本号失效重建,绝不每请求重建;多 worker 各建各的(只读)。
- 命中默认 block(脱敏/flag 后续可配);留痕只记分类/词 id,不落违规原文。
- 可选
seed_sensitive_words命令导开源词库打底,真实词库数据不进仓库。
- 接现有
- 依赖:现有
apps/moderation/骨架(当前未提交),本任务转正;requirements.txt加ahocorapy。 - 待确认(已给默认):本地是「补充」非「替代」云 API(推荐);命中动作默认 block;词库 admin 维护 + 可选 seed。
- 任务:
06-tasks.mdT-604。下一步:实现 T-604(并处理 T-603 的ACCOUNT_EMAIL_VERIFICATION阻塞)。
2026-07-06 决策:永久免邮箱验证(ACCOUNT_EMAIL_VERIFICATION="none")
- 决策:用户端注册不做邮箱验证,
ACCOUNT_EMAIL_VERIFICATION="none"为长期既定策略(不再考虑改回 mandatory)。注册填邮箱 + 密码即注册成功、可直接登录使用。 - 影响:
- 邮箱仍必填且唯一(
User.emailunique 约束不变),只是不验证。 - 注册流程不再发验证邮件——注册本身不再依赖 SMTP/邮件服务(找回密码等其它邮件功能如启用才需要)。
- 原假设 mandatory 的 2 条 portal 测试(
test_signup_creates_unverified_user_wallet_...、test_unverified_email_cannot_establish_login_session)需按新策略更新,不是恢复 mandatory。 settings.py现为"none"#"mandatory"的临时写法,需清理成干净的"none"。- 防刷改由「注册限流 / 图形验证码」补位(邮箱验证不再承担这个作用)。
- 邮箱仍必填且唯一(
- 本决策为全项目权威口径:
02-requirements/05-coding-rules/api/04-architecture/03-tech-stack/routes/env/deployment/00-ai-start-here中所有「邮箱验证」表述以本决策为准,统一改为「免邮箱验证,邮箱仍唯一」,由 T-605 落实。 - T-603 解封路径随之改变:不再「恢复 mandatory」,而是等 T-605 更新测试后重跑标 DONE。
2026-07-06 发现+决策:用户端与后台共用会话 → 账号分离(方案 A)
- 发现:portal(
/)、/admin/、/api/挂同一域名/同一 Django 工程,共用同一个sessionidcookie;/admin/不是另一套登录,只是在同一登录态上加is_staff=True门槛。所以 staff/superuser 账号登了 portal,去/admin/也是登录态(观察到的「用户端登录后台跟着登」根因)。属单体复用 Django auth 的必然结果,非 bug。 - 澄清:普通用户
is_staff=False登 portal 进不了后台,无越权、无泄露。 - 决策:采用方案 A·账号分离(零代码)——终端用户一律
is_staff=False;运营用专用 admin superuser 只登/admin/,不与 portal 账号混用。不采用「同账号双会话」(需 admin 独立子域名/独立 cookie,成本高、MVP 不值)。 - 加固(上线前,deployment):
/admin/加访问保护(改路径 / IP 白名单 / 反代 basic auth / 2FA),永不给终端用户is_staff。 - 文档落点:
04-architecture.md(设计事实 + 方案 A)、05-coding-rules.md(「用户端与后台账号分离」规则)。属运营/编码约定,无代码改动。
2026-07-06 T-604 文档方案收紧:本地 prompt 敏感词过滤
- 状态:DONE(文档先行,未改代码)。
- 背景:复核 Claude Code 提出的“提示词敏感词检查”方案后,结论为方向合理,但原方案把本地关键词、云内容安全、输出审核和图片审核混在一起,且现有
generation.py接入顺序存在“先下载 image_url 再审 prompt”的实现风险。 - 变更:
- 新增
docs/moderation.md,作为 T-604 权威设计:T-604 只做输入 prompt 本地敏感词快筛;命中必须在扣点和调上游前拦截;云内容安全、输出审核、图片审核后续另做。 - 更新
docs/06-tasks.md:T-604 范围收紧为 keyword provider +SensitiveWord+ matcher 缓存 + 共享 cache 版本号失效;明确ahocorapy只是候选依赖,编码前必须验证 PyPI 可用性和 API 形状。 - 更新
docs/04-architecture.md:新增内容安全层职责、SensitiveWord配置数据、prompt 先审再下载图片/计费的时序,以及多 worker 下不能只依赖 Django signal。 - 更新
docs/api.md:补content_blocked错误码,明确命中不扣点、不写调用/流水、不调上游,也不得先下载image_url。 - 更新
docs/env.md:补MODERATION_ENABLED、MODERATION_PROVIDER、MODERATION_FAIL_CLOSED、MODERATION_BLOCK_ON_REVIEW、MODERATION_CACHE_VERSION_KEY等配置,并说明生产依赖共享 cache。 - 更新
docs/05-coding-rules.md与docs/README.md:加入内容安全顺序规则和文档导航。
- 新增
- 决策:
- T-604 不启用输出审核;
MODERATION_REFUND_ON_OUTPUT_BLOCK不作为 T-604 交付项,避免配置存在但行为未实现。 SensitiveWord.actionMVP 只支持block,后续再扩展review/flag。- 词库变更后用共享 cache 版本号让各 worker 懒重建 matcher,不能只靠
post_save/post_deletesignal。
- T-604 不启用输出审核;
- 验证:文档修改;未运行代码测试。
- 下一步:实现 T-604 时先清理
apps/moderation/__pycache__,把apps.moderation接入INSTALLED_APPS,再按docs/moderation.md落模型、admin、provider、缓存和测试。
2026-07-06 复核:codex 更新的 T-604 敏感词方案(Claude Code review,非任务)
- 对象:codex 审核并扩写的中文 prompt 敏感词方案(新增
docs/moderation.md,更新04-architecture/api/05-coding-rules/06-tasks/env/README)。 - 结论:合理,且是明显改进,可放行。
- codex 改对/改好:
- 范围收紧为「只审 prompt 文本」,砍掉 skeleton 里对输入图片做关键词匹配(无意义)。
- 请求时序:审 prompt 在下载 image 之前,命中即不下载图片/不别名/不计费/不调上游。
- 多 worker 缓存失效用共享 cache 版本号(
moderation:sensitive_words:version),点破 post_save signal 只在当前进程生效——与 T-403 限流共享 cache 口径一致。 - 「ahocorapy 编码前必须验证 PyPI 可用性和 API 形状」——先核实真实 API 的纪律。
- 诚实标注归一化误伤风险并要求正常文本测试;不暴露未实现的输出审核配置。
- 实现时须盯的一点(skeleton 与新规格冲突,非文档错):现有未提交的
generation.pyskeleton 是「先 load_image_input 再 moderate_input(含 image)」+ 有 output 钩子;T-604 实现必须按新规格翻正——① prompt 审核挪到图片下载之前;② moderate_input 改纯 prompt;③ 去掉/休眠 output 钩子与未实现的退点配置。 - 小注意(codex 已自标、可接受):归一化误伤需正常文本测试兜底;每请求一次共享 cache 版本检查(DatabaseCache 为一次 DB 读)。
2026-07-06 T-605:落实免邮箱验证策略
- 状态:DONE。
- 目标:把
ACCOUNT_EMAIL_VERIFICATION="none"作为长期既定策略清理落地,注册即可用、不发验证邮件,邮箱仍必填且唯一。 - 代码变更:
config/settings.py:ACCOUNT_EMAIL_VERIFICATION改为干净的"none";ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION=False,避免免验证策略下保留“点确认邮件自动登录”的误导配置。apps/portal/tests.py:更新注册 / 登录测试口径。注册测试改为断言不发验证邮件、创建 0 点钱包、不写赠点流水且注册后已登录;未验证邮箱用户可直接登录;普通登录用例名称去掉verified语义。
- 文档变更:
README.md、docs/00-ai-start-here.md、docs/03-tech-stack.md、docs/06-tasks.md、docs/current-state.md、docs/deployment.md、docs/env.md、docs/mvp-acceptance.md、docs/project-brief.md:同步“免邮箱验证、注册即可用,邮箱仍唯一”口径。- 生产部署文档不再把真实邮件服务作为注册登录前置条件;邮件服务仅用于后续密码找回、通知或恢复邮箱验证等邮件能力。
- T-603 从“邮箱验证口径阻塞”解封,状态改为
TODO:字段中文化代码/迁移已完成,待稳定 MySQL 测试库补跑完整验证后再标DONE。
- 验证:
py -3.12 -m py_compile config\settings.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 manage.py test apps.portal.tests.PortalAccountFlowTests.test_signup_creates_user_wallet_with_zero_points_no_ledger_and_can_login apps.portal.tests.PortalAccountFlowTests.test_user_can_login_without_verified_email apps.portal.tests.PortalAccountFlowTests.test_user_can_login_view_dashboard_and_logout --keepdb --noinput --verbosity 2:通过,3 tests OK。py -3.12 manage.py test apps.portal --keepdb --noinput --verbosity 2:通过,26 tests OK。git diff --check:通过,仅 Windows CRLF 提示。
- 全量测试说明:
py -3.12 manage.py test --keepdb --noinput --verbosity 2:已尝试,发现 131 tests,跑到 95 tests 后远程 MySQL43.128.3.240连接超时 / WinError 10051,最终 22 errors。Test-NetConnection 43.128.3.240 -Port 3306:随后显示TcpTestSucceeded=True,说明端口间歇可达。- 单独复跑失败起点
apps.billing.tests.BillingServiceTests apps.billing.tests.BillingAdminTests apps.billing.tests.ConcurrentDebitTests:17 条业务用例 OK;并发扣点用例本体打印ok后在测试库 flush 阶段再次因 MySQL 连接超时 / WinError 10060 记 ERROR。 - 结论:T-605 相关测试已通过,全量单次绿受远程 MySQL 长跑稳定性阻塞,不是本任务断言失败。
- 下一步:回到 T-603,在更稳定的 MySQL 测试库或网络条件下补跑完整验证,再决定是否标
DONE。
2026-07-06 T-604:中文敏感词本地过滤
- 状态:DONE。
- 目标:按
docs/moderation.md落地本地 keyword provider,只审核输入 prompt;命中必须在下载image_url、解析别名、计费、预扣点和上游调用前返回400 content_blocked。 - 依赖确认:编码前已验证
ahocorapy==1.6.2可安装,API 使用KeywordTree.add()/finalize()/search_all(),search_all()返回(matched_keyword, index)。 - 代码变更:
config/settings.py/.env.example/requirements.txt:接入apps.moderation、MODERATION_*配置和ahocorapy依赖。apps/moderation:新增SensitiveWord模型/admin/迁移、归一化管线、共享 cache 版本号、signal 失效、keyword provider 与 prompt 审核 service;清理早期 Tencent stub 和输出审核骨架。apps/api/generation.py:生成 title/image 的入口改为 serializer 后先moderate_prompt();命中直接content_blocked,未命中才读取图片并继续别名、计费、扣点、上游调用。apps/api/tests.py/apps/moderation/tests.py:覆盖 no-op、归一化、防绕过、词库版本重建、命中不下载图片/不扣点/不建记录/不调上游。
- 文档变更:
docs/moderation.md:从设计口径更新为实现态,明确 T-604 已清理输出审核和云厂商 stub。docs/06-tasks.md:T-604 标为 DONE。docs/current-state.md:同步依赖、当前状态、配置基线、验证证据和任务看板。
- 验证:
py -3.12 -m py_compile config\settings.py apps\api\generation.py apps\api\tests.py apps\moderation\apps.py apps\moderation\models.py apps\moderation\normalization.py apps\moderation\policy.py apps\moderation\services.py apps\moderation\signals.py apps\moderation\versioning.py apps\moderation\providers\base.py apps\moderation\providers\keyword.py apps\moderation\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.moderation --keepdb --noinput --verbosity 2:通过,7 tests OK。py -3.12 manage.py test apps.api.tests.GenerateApiTests.test_blocked_prompt_returns_content_blocked_before_image_download_or_charge apps.api.tests.GenerateApiTests.test_disabled_moderation_does_not_block_matching_prompt --keepdb --noinput --verbosity 2:通过,2 tests OK。py -3.12 manage.py test apps.api.tests.GenerateApiTests --keepdb --noinput --verbosity 2:通过,16 tests OK。- 测试期仅保留 allauth 在 MySQL 条件唯一约束上的既有
models.W036警告,本项目邮箱唯一性由user.email承担。
- 注意:
- 真实敏感词库数据不入仓库,需要运营在 admin 中维护或后续另做导入命令。
- 生产多 worker 必须使用共享 cache(DatabaseCache/Redis/Memcached)承载
MODERATION_CACHE_VERSION_KEY,不能依赖LocMemCache做跨进程刷新。 - 下一个看板任务仍是 T-603:在稳定 MySQL 测试库上补跑完整验证并收尾。