docs: add signup bonus requirement
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
`cmhub` 是「**自助用户端 + 计费 API + 运营后台**」三合一服务:终端用户在用户端自助注册、扫码充值、管理 API Key,用 API Key 调用把 `cmbot` 的「生成标题」「生成图片」能力封装成的 HTTP 接口,按**点数计费**;运营用 django-admin 管理用户、点数与记录。
|
||||
|
||||
第一版 MVP 做:**用户自助注册登录 → 扫码充值转点数 → 自助生成/删除 API Key → 带 Key 调用按规则算点数 → 点数足够则调上游生成并扣点 / 不足则报错 → 写调用与消费记录 → django-admin 运营后台**。**注册不送免费点数,必须充值才有点数。**
|
||||
第一版 MVP 做:**用户自助注册登录 → 注册成功赠送 100 点试用点数 → 扫码充值转点数 → 自助生成/删除 API Key → 带 Key 调用按规则算点数 → 点数足够则调上游生成并扣点 / 不足则报错 → 写调用与消费记录 → django-admin 运营后台**。注册赠点也是账本资产,必须经计费层入账并写点数流水。
|
||||
|
||||
## 必读顺序
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
## 当前阶段
|
||||
|
||||
当前项目处于:**Phase 6 增强任务推进期**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调、T-305 扫码充值下单 + 轮询与 T-306 对外 API 安全加固;Phase 4 已完成 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化;Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;Phase 6 已完成 T-601「可用别名发现」、T-602「django-admin 中文化第 1-3 层」、T-603「django-admin 字段级中文化」、T-604「中文敏感词本地过滤」、T-605「免邮箱验证策略落地」、T-606「公开首页 + 客户端下载入口」与 T-607「桌面端最新版本检查接口」。生产侧仍需补真实支付回调到账闭环、配置并发布客户端下载包和图片生成真实耗时验证;邮件服务仅用于后续密码找回/通知等邮件能力,不阻塞注册登录。
|
||||
当前项目处于:**Phase 6 增强任务推进期**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调、T-305 扫码充值下单 + 轮询与 T-306 对外 API 安全加固;Phase 4 已完成 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化;Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;Phase 6 已完成 T-601「可用别名发现」、T-602「django-admin 中文化第 1-3 层」、T-603「django-admin 字段级中文化」、T-604「中文敏感词本地过滤」、T-605「免邮箱验证策略落地」、T-606「公开首页 + 客户端下载入口」与 T-607「桌面端最新版本检查接口」;T-608「新用户注册赠送 100 点试用点数」已登记为下一项 TODO。生产侧仍需补真实支付回调到账闭环、配置并发布客户端下载包和图片生成真实耗时验证;邮件服务仅用于后续密码找回/通知等邮件能力,不阻塞注册登录。
|
||||
|
||||
优先路径:
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
4. Phase 3:对外 API 与充值 —— T-301 Key 鉴权、T-302 生成接口、T-303 余额查询、T-304 充值回调、T-305 扫码下单与轮询、T-306 安全加固已完成。
|
||||
5. Phase 4:用户端(Django 模板 SSR)—— T-501 注册登录、T-502 API Key 管理、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化已完成。
|
||||
6. Phase 5:后台与发布 —— T-401 运营后台完善、T-402 完整验收 MVP、T-403 部署 / 运行文档已完成;计划内 MVP 任务已收尾。
|
||||
7. Phase 6:增强(MVP 后)—— T-601 可用别名发现已完成,实现 `/api/v1/models` 与 portal 只读「可用模型」页;T-602 已完成 django-admin 分组/表名中文化;T-603 已完成字段级中文标签代码与 no-op 迁移并人工确认 admin 字段中文化;T-604 已完成中文敏感词本地过滤;T-605 已完成免邮箱验证策略落地;T-606 已完成公开首页 + 客户端下载入口;T-607 已完成桌面端最新版本检查接口。下一步需先确认业务优先级或拆新任务。
|
||||
7. Phase 6:增强(MVP 后)—— T-601 可用别名发现已完成,实现 `/api/v1/models` 与 portal 只读「可用模型」页;T-602 已完成 django-admin 分组/表名中文化;T-603 已完成字段级中文标签代码与 no-op 迁移并人工确认 admin 字段中文化;T-604 已完成中文敏感词本地过滤;T-605 已完成免邮箱验证策略落地;T-606 已完成公开首页 + 客户端下载入口;T-607 已完成桌面端最新版本检查接口;T-608 已拆为「新用户注册赠送 100 点试用点数」,下一步可领取实现。
|
||||
|
||||
## 领取任务规则
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
MVP 只做:
|
||||
|
||||
- **用户端(Django 模板 SSR + Bootstrap)**:自助注册/登录、扫码充值、查看充值记录/充值总额/剩余点数/消费记录、自助生成与删除 API Key。
|
||||
- **用户端(Django 模板 SSR + Bootstrap)**:自助注册/登录、注册成功一次性赠送 100 点试用点数、扫码充值、查看充值记录/充值总额/剩余点数/消费记录、自助生成与删除 API Key。
|
||||
- 对外两个生成接口:`POST /api/v1/generate/title`、`POST /api/v1/generate/image`(同步返回结果)。
|
||||
- API Key 鉴权,识别所属注册用户(API 只认 Key,不认 Web session)。
|
||||
- 点数计费:按「操作类型 + 能力别名(+ 可选分辨率)」查计费规则得点数;本地余额原子扣减;不足报错。
|
||||
@@ -81,7 +81,7 @@ MVP 不做:
|
||||
- 异步任务队列(图片生成第一版走**同步等待**返回,不引入 Celery)。
|
||||
- 自建支付/收银台(充值走外部支付系统,本服务发起扫码下单并接收回调,不碰资金清算)。
|
||||
- 面向终端消费者的「内容生成产品页」(用户端只做账户自助,生成结果仍只经 API 输出)。
|
||||
- 注册赠送免费点数(必须充值才有点数)。
|
||||
- 复杂活动赠点、邀请奖励或历史用户自动补发;除 T-608 明确定义的新用户一次性 100 点注册试用额度外,不做其他免费点数活动。
|
||||
- 多币种、分账、自动退款对账。
|
||||
|
||||
## 事实来源
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@
|
||||
- **核心流程优先**:先把「鉴权 → 计费 → 生成 → 扣点 → 记录」这条主链路做顺。
|
||||
- **真实数据优先**:点数、金额、扣费、充值不造假;不用演示逻辑冒充计费逻辑。
|
||||
- **账目必须对得上**:每一笔点数变动都有流水、可追溯到充值订单或调用记录。
|
||||
- **试用额度可控**:新用户注册送 100 点试用点数降低首次接入门槛,但必须按账本发放、可追溯、可防重复。
|
||||
- **小步交付**:每一步都能运行、能验证、能回退。
|
||||
- **少即是稳**:MVP 不追求完整,只追求最小闭环可靠;先同步、先单机、先后台手工开通。
|
||||
- **可维护**:复用 `cmbot` 现有 AI 调用代码,不另起一套。
|
||||
@@ -39,7 +40,7 @@
|
||||
- 不做自建支付/收银台,付款在外部支付系统完成,本服务发起扫码下单并接收回调,不碰资金清算。
|
||||
- 不做异步任务队列(第一版图片生成走同步等待)。
|
||||
- 不做面向终端消费者的「内容生成产品页」;用户端只做账户自助(注册/充值/API Key 管理/记录查询)。
|
||||
- **注册不赠送免费点数**:必须充值才有点数,降低薅羊毛/滥用风险。
|
||||
- 不做复杂活动赠点、邀请奖励或历史用户自动补发;当前只做新用户注册一次性 100 点试用额度,且必须配套防刷、幂等和流水留痕。
|
||||
- 不做多币种、分账、自动退款对账等容易膨胀、不属于 MVP 的能力。
|
||||
|
||||
> MVP 的具体功能范围与验收标准,见 [需求](02-requirements.md)。
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
| 功能 | 用户能做什么 | 优先级 |
|
||||
| --- | --- | --- |
|
||||
| 用户注册/登录 | 终端用户在用户端自助注册、登录(**免邮箱验证、注册即可用;邮箱仍必填且唯一**);**注册不送免费点数** | P0 |
|
||||
| 用户注册/登录 | 终端用户在用户端自助注册、登录(**免邮箱验证、注册即可用;邮箱仍必填且唯一**);注册成功一次性赠送 **100 点**试用点数 | P0 |
|
||||
| API Key 自助管理 | 登录后自助生成/删除 API Key;Key 只在生成时显示一次(库内哈希存储) | P0 |
|
||||
| 扫码充值 | 用户端发起充值→展示支付二维码→扫码付款→回调到账;金额按汇率转点数 | P0 |
|
||||
| 个人中心 | 查看剩余点数、充值总额、充值记录、点数使用(消费)记录 | P0 |
|
||||
@@ -45,13 +45,13 @@
|
||||
| --- | --- | --- |
|
||||
| 异步生成 | 图片生成改任务队列 + 轮询/回调,提升并发 | V2 |
|
||||
| 用量统计报表 | 按用户/时间/模型聚合消耗与成本 | V2 |
|
||||
| 注册赠点 / 试用额度 | 若开放,需配套邮箱/图形验证码 + 限流防薅羊毛 | V2 |
|
||||
| 复杂活动赠点 / 邀请奖励 | 注册 100 点之外的活动额度、邀请返利、历史用户补发等,需单独风控与审批 | V2 |
|
||||
| API Key 轮换 / 授权 | Key 到期轮换、按 Key 限授权别名 | V2 |
|
||||
| 退款对账自动化 | 外部退款联动本地点数冲正 | V3 |
|
||||
|
||||
## 四、核心用户故事(MVP)
|
||||
|
||||
1. 作为新用户,我在用户端自助注册登录,扫码充值一笔钱,看到对应点数按汇率到账。
|
||||
1. 作为新用户,我在用户端自助注册登录后先获得 100 点试用点数;后续扫码充值一笔钱,看到对应点数按汇率到账。
|
||||
2. 作为已登录用户,我自助生成一把 API Key(明文只显示一次),也能删除不用的 Key。
|
||||
3. 作为接入方,我带着 API Key 调用生成标题接口,传入 prompt,能拿到生成的标题。
|
||||
4. 作为接入方,我调用生成图片接口,等待若干秒后在同一个响应里拿到图片结果。
|
||||
@@ -68,8 +68,8 @@
|
||||
- **点数计费与扣减**:调用前按规则算出点数 N;余额 ≥ N 才放行并精确扣 N;余额 < N 返回点数不足错误码且不调上游。并发同时多次调用,最终扣点总额正确,**不出现超扣或扣成负数**。
|
||||
- **余额查询 API**:返回的余额等于该账号点数流水累加结果。
|
||||
- **充值转点数**:模拟一次支付系统回调(含订单号、金额、签名),验签通过后按汇率加点;**同一订单号重复回调只入账一次**(幂等);验签失败不入账。
|
||||
- **调用记录 / 点数流水**:每次成功/失败调用、每次充值/退款/调整都各生成一条记录,字段完整、可在后台检索。
|
||||
- **用户注册/登录**:新用户能自助注册(**免邮箱验证、注册即可用;邮箱仍必填且唯一**)、登录;注册后点数为 0(不送免费点数)。
|
||||
- **调用记录 / 点数流水**:每次成功/失败调用、每次充值/退款/调整/注册赠点都各生成一条记录,字段完整、可在后台检索。
|
||||
- **用户注册/登录**:新用户能自助注册(**免邮箱验证、注册即可用;邮箱仍必填且唯一**)、登录;注册成功后点数为 100,且有一条对应的注册赠点流水。重复提交或服务重试不得重复赠送。
|
||||
- **API Key 自助管理**:登录用户能生成 API Key(明文只显示一次,库内存哈希)、能删除;删除落库为吊销,吊销后该 Key 调用返回 403,缺失/无效/不存在 Key 返回 401。
|
||||
- **扫码充值**:用户端发起充值→展示二维码→(mock 或真实)支付成功回调后点数按汇率到账,充值记录与流水各一条;同一订单号重复回调只入账一次。
|
||||
- **个人中心**:剩余点数 = 点数流水累加;充值总额、充值记录、消费记录与实际一致。
|
||||
@@ -87,14 +87,15 @@
|
||||
| 生成返回方式 | **同步**等待返回结果(图片接口需把网关/服务超时调到 ≥ 300s) |
|
||||
| 模型选择方式 | 对外用**能力别名**(如 `image-hd`),不暴露具体模型名;后台配置别名→模型映射 |
|
||||
| 充值入口 | 用户端自助发起扫码充值(本服务向支付系统下单取二维码),付款成功由支付系统**服务端回调**入账 |
|
||||
| 注册是否送点数 | 否。注册不送免费点数,必须充值才有点数 |
|
||||
| 暂不支持 | 异步队列、注册赠点、多币种、分账、自动退款对账 |
|
||||
| 注册是否送点数 | 是。新用户注册成功一次性赠送 100 点试用点数;只对功能上线后的新注册用户自动发放,历史用户是否补发需另行审批和单独任务 |
|
||||
| 暂不支持 | 异步队列、复杂活动赠点 / 邀请奖励、多币种、分账、自动退款对账 |
|
||||
|
||||
## 七、待确认 / 风险点
|
||||
|
||||
- **支付商户配置**:协议字段、签名/验签方式、成功应答已明确;上线前仍需提供微信/支付宝真实商户密钥、证书、公网 `notify_url`。配置到位前,先以 mock 支付客户端开发和测试。
|
||||
- **汇率与点数单价**:1 元 = 多少点?标题/图片/各模型各分辨率分别多少点?由运营/产品确认,配置在后台。
|
||||
- **资金风险**:充值入账必须幂等防重复加点;扣点必须并发安全防超扣;上游失败必须不扣点或冲正。规则由产品确认,实现按 `05-coding-rules.md` 第 8 节。
|
||||
- **注册赠点风险**:注册送点虽然不来自支付回调,但仍是可消费点数,必须防重复发放、防并发重试重复入账,并配套注册限流 / 图形验证码等防刷手段。
|
||||
- **凭证风险**:上游 AI 的 api_key、支付系统密钥属敏感信息,只能放环境变量/后台配置,不入代码与文档样例。
|
||||
- **第三方平台风险**:上游 vectorengine 可能限流、超时、变更;需处理超时、错误透传与失败不扣点。
|
||||
- **耗时风险**:图片同步生成耗时长,需确认网关/反向代理/客户端超时上限,避免连接中断导致已扣点但结果丢失。
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
- **同步可用的前提是「超时链路 + worker 容量」配对**,否则会「小量正常、上量假死」:① 每模型 `timeout_seconds` 设有限值(`ai_models.json` 现为 `0`,迁入须改);② Gunicorn `--timeout` 与网关 `proxy_read_timeout` 按最慢图片放大(别用默认 30s / 60s);③ worker/线程数按**峰值总并发**预留。详见 [架构设计](04-architecture.md) 第五节结论。
|
||||
- **何时转 V2 异步**:worker 被长连接占满拖慢快接口/后台、接入方总并发明显上涨、或需要「关窗重连/任务持久化」体验——在此之前保持同步。
|
||||
- **数据库:MySQL 8.4 LTS,cmhub 专用独立实例**:① 部署环境的 VPS 已装 MySQL 5.7 供其他服务用,但 5.7 跑不了 Django 5.2(需 ≥8.0.11)、已 EOL、且不支持 CHECK 约束,故**不复用**它;② 机器内存宽裕(`available` 7.4G),给 cmhub **单开一个 MySQL 8.4 LTS 实例**(独立端口/容器),与已有 5.7 完全隔离、互不影响;③ 选 8.4 LTS 取长维护窗口 + 完整 CHECK 约束(CHECK 需 MySQL ≥8.0.16 才真正生效);④ 强制 InnoDB + utf8mb4(5.7/老配置默认非 utf8mb4,prompt 的 emoji/生僻字会写失败);⑤ MySQL 默认隔离级别 REPEATABLE READ(不同于 PostgreSQL 的 READ COMMITTED),`select_for_update` 扣点仍安全,但计费实现按此语义验证;⑥ 开发环境同用 MySQL,不要用 SQLite——SQLite 会静默忽略 `FOR UPDATE`,并发扣点逻辑测不出来;⑦ 不在代码里写死只适配某一种库的 SQL。
|
||||
- **用户端用 Django 模板 SSR 单体,不引前端框架**:需求含终端用户自助(注册/充值/API Key/记录),选 Django 模板 + Bootstrap + allauth 与后端同工程单体部署,复用 Django auth/session,开发部署最快、最契合单机 MVP;代价是交互不如 SPA,可后续加 HTMX。T-501 已接入 django-allauth 65.18.0,使用 session 与 CSRF;T-605 起注册策略固定为 `ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;注册成功只创建 0 点 `UserWallet`,不写赠点流水。T-502 已用 Django Form + Bootstrap 模板落地 `/apikeys`,生成 Key 后明文只显示一次,删除写为 `revoked`。T-503 已用只读 Django TemplateView 落地 dashboard 汇总、充值记录和消费记录。T-504 已用 Django Form + Bootstrap 模板落地 `/recharge`:页面 POST 创建 pending 充值订单,展示支付二维码票据,并用浏览器轮询 `/api/v1/recharge/status`,订单 paid 后刷新余额。T-505 已把 Bootstrap 5 CSS 与 qrcode.js vendoring 到 `apps/portal/static/portal/vendor/`,页面不再依赖 jsdelivr,充值/消费记录页改为 Django `Paginator` 分页。放弃 Vue/React 前后端分离(两套项目/部署,与单体 MVP 调性冲突)。用户模型:`User`(auth) 持登录态、`UserWallet` 持点数(扣点锁 wallet、与 auth 解耦)、`ApiKey`(User 1:N,哈希存储)。注册不送免费点数。
|
||||
- **用户端用 Django 模板 SSR 单体,不引前端框架**:需求含终端用户自助(注册/充值/API Key/记录),选 Django 模板 + Bootstrap + allauth 与后端同工程单体部署,复用 Django auth/session,开发部署最快、最契合单机 MVP;代价是交互不如 SPA,可后续加 HTMX。T-501 已接入 django-allauth 65.18.0,使用 session 与 CSRF;T-605 起注册策略固定为 `ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;T-608 已登记把注册成功后的初始化改为经 `apps.billing` 发放 100 点试用点数并写 `signup_bonus` 流水。T-502 已用 Django Form + Bootstrap 模板落地 `/apikeys`,生成 Key 后明文只显示一次,删除写为 `revoked`。T-503 已用只读 Django TemplateView 落地 dashboard 汇总、充值记录和消费记录。T-504 已用 Django Form + Bootstrap 模板落地 `/recharge`:页面 POST 创建 pending 充值订单,展示支付二维码票据,并用浏览器轮询 `/api/v1/recharge/status`,订单 paid 后刷新余额。T-505 已把 Bootstrap 5 CSS 与 qrcode.js vendoring 到 `apps/portal/static/portal/vendor/`,页面不再依赖 jsdelivr,充值/消费记录页改为 Django `Paginator` 分页。放弃 Vue/React 前后端分离(两套项目/部署,与单体 MVP 调性冲突)。用户模型:`User`(auth) 持登录态、`UserWallet` 持点数(扣点锁 wallet、与 auth 解耦)、`ApiKey`(User 1:N,哈希存储)。新注册用户一次性赠送 100 点,必须走计费层和点数流水。
|
||||
|
||||
## 三、构建与运行命令
|
||||
|
||||
|
||||
+35
-6
@@ -18,7 +18,7 @@
|
||||
|
||||
组件落位:
|
||||
|
||||
- **用户端层(Django 模板 SSR)**:公开首页、注册/登录(Django auth / allauth)、个人中心(余额/充值总额/充值记录/消费记录)、API Key 自助管理、发起扫码充值、模型目录与客户端下载入口。入口 `apps/portal/`,公开首页匿名可访问,其他自助页面用 session 鉴权。T-501 已落地 `/signup`、`/login`、`/logout` 与最小 `/dashboard`;注册成功通过 allauth adapter 创建 0 点 `UserWallet`,不写赠点流水。T-502 已落地 `/apikeys`,用户可自助生成和删除(吊销)自己的 API Key,明文只显示一次,列表只显示 prefix。T-503 已扩展 `/dashboard` 并新增 `/records/recharge`、`/records/usage`,只读展示当前用户余额、充值订单与消费流水。T-504 已落地 `/recharge`,用户可创建 pending 充值订单、查看二维码票据,并轮询订单状态;到账仍以服务端回调或主动查单入账后的本地订单状态为准。T-505 已把 Bootstrap/qrcode.js 改成本地 static 自托管,并把充值/消费记录页从固定切片改为分页。T-606 已把 `/` 改为公开首页,并新增 `DownloadRelease` 下载版本配置用于展示 Windows 客户端版本、下载地址、SHA256 与发布说明;T-607 已新增公开 JSON 版本检查接口给桌面端自动更新使用。
|
||||
- **用户端层(Django 模板 SSR)**:公开首页、注册/登录(Django auth / allauth)、个人中心(余额/充值总额/充值记录/消费记录)、API Key 自助管理、发起扫码充值、模型目录与客户端下载入口。入口 `apps/portal/`,公开首页匿名可访问,其他自助页面用 session 鉴权。T-501 已落地 `/signup`、`/login`、`/logout` 与最小 `/dashboard`;T-608 需把注册成功后的钱包初始化改为经计费层一次性发放 100 点试用点数,并写 `signup_bonus` 点数流水。T-502 已落地 `/apikeys`,用户可自助生成和删除(吊销)自己的 API Key,明文只显示一次,列表只显示 prefix。T-503 已扩展 `/dashboard` 并新增 `/records/recharge`、`/records/usage`,只读展示当前用户余额、充值订单与消费流水。T-504 已落地 `/recharge`,用户可创建 pending 充值订单、查看二维码票据,并轮询订单状态;到账仍以服务端回调或主动查单入账后的本地订单状态为准。T-505 已把 Bootstrap/qrcode.js 改成本地 static 自托管,并把充值/消费记录页从固定切片改为分页。T-606 已把 `/` 改为公开首页,并新增 `DownloadRelease` 下载版本配置用于展示 Windows 客户端版本、下载地址、SHA256 与发布说明;T-607 已新增公开 JSON 版本检查接口给桌面端自动更新使用。
|
||||
- **用户与账号层**:注册用户 `User`、点数钱包 `UserWallet`、`ApiKey`(一用户多把、哈希存储)。入口 `apps/users/`。
|
||||
- **API 层(DRF)**:对外生成接口、余额查询、支付回调接收、扫码下单、公开版本检查接口。入口 `apps/api/`。生成/余额/模型目录这类对外业务 API **只认 API Key,不接受 Web session**;充值下单/状态查询属于用户端流程,走 Web session + CSRF;支付回调走平台验签;T-607 的客户端下载版本检查接口为公开只读例外,不需要 API Key,不读取用户、不扣点。
|
||||
- **计费层**:点数计算、原子扣减(锁 `UserWallet` 行)、退点、充值入账、流水记账。入口 `apps/billing/`。
|
||||
@@ -56,6 +56,7 @@ T-306 已实现对外 API 安全加固:`download_image_input()` 在请求前
|
||||
- 计费规则查询:按「操作类型 + 能力别名(+ 可选分辨率)」算出本次点数 N。**按别名定价,不按具体供应商 SKU 定价**,这样后台换底层模型时计费不变。
|
||||
- 点数原子扣减与退回:数据库事务 + 行锁,保证并发不超扣、不为负。
|
||||
- 充值入账:接收已验签的支付回调数据,按汇率换算点数,幂等入账,写流水。
|
||||
- 注册赠点:新用户注册成功后一次性发放 100 点试用点数,必须幂等、防并发重复,并写注册赠点流水。
|
||||
- 手工调点:运营后台只收集点数变动和原因,必须调用计费层 `adjust_wallet_points()`;服务在事务内锁 `UserWallet`,拒绝扣成负数,并写 `PointsLedger(change_type=adjust)`。
|
||||
- 点数流水记账:所有点数变动(充值/消费/调整/冲正)都生成一条流水。
|
||||
- 是点数余额的唯一写入方。
|
||||
@@ -212,7 +213,7 @@ T-102 已实现 `AiModel` / `ModelAlias` 的 Django models、admin、迁移与
|
||||
CREATE TABLE "user" (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE, -- 注册邮箱,需验证且唯一
|
||||
email TEXT NOT NULL UNIQUE, -- 注册邮箱,必填且唯一;当前免邮箱验证
|
||||
password TEXT NOT NULL, -- Django 哈希存储
|
||||
payment_user_id TEXT, -- 对应外部支付系统的用户标识
|
||||
status TEXT NOT NULL DEFAULT 'active', -- active / disabled
|
||||
@@ -228,6 +229,14 @@ CREATE TABLE user_wallet (
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 注册赠点幂等标记(每个用户最多一条;MySQL 兼容,不使用条件唯一约束)
|
||||
CREATE TABLE signup_bonus_grant (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER UNIQUE NOT NULL REFERENCES "user"(id),
|
||||
points_granted BIGINT NOT NULL DEFAULT 100,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- API Key(一个用户可有多把;哈希存储,明文只在创建时显示一次)
|
||||
CREATE TABLE api_key (
|
||||
id INTEGER PRIMARY KEY,
|
||||
@@ -262,7 +271,7 @@ CREATE TABLE recharge_order (
|
||||
CREATE TABLE points_ledger (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES "user"(id),
|
||||
change_type TEXT NOT NULL, -- recharge / consume / adjust / refund
|
||||
change_type TEXT NOT NULL, -- recharge / consume / adjust / refund / signup_bonus
|
||||
points_delta BIGINT NOT NULL, -- 正为加、负为减
|
||||
balance_after BIGINT NOT NULL, -- 变动后余额,便于对账
|
||||
ref_order_id INTEGER, -- 数值引用 RechargeOrder.id;T-304 已加 ref_order_id+change_type 唯一兜底
|
||||
@@ -295,13 +304,13 @@ CREATE TABLE call_record (
|
||||
需要说明:
|
||||
|
||||
- 主键自增;`api_key.key_hash`、`pricing_rule(operation_type, alias, resolution)`、`recharge_order.order_no`、`user.username` 唯一。
|
||||
- 重要索引:`call_record(user_id, created_at)`、`points_ledger(user_id, created_at)`、`recharge_order(order_no)`、`api_key(key_hash)`。
|
||||
- 重要索引:`call_record(user_id, created_at)`、`points_ledger(user_id, created_at)`、`recharge_order(order_no)`、`api_key(key_hash)`、`signup_bonus_grant(user_id)`。
|
||||
- 不软删除业务流水;用户/账号可标记 `disabled` 而非物理删;API Key 用 `revoked` 状态而非物理删。
|
||||
- 服务端生成字段:`api_key.key_hash`/`key_prefix`、`points_balance`、`balance_after`、各 `created_at` / `updated_at`。
|
||||
- `payment_user_id`、`payment_txn_no` 为对账预留,字段先建。
|
||||
- `recharge_order.exchange_rate` 与 `points_granted` 在下单时写入,状态为 `pending` 时也必须有值;支付回调金额必须与订单金额一致,入账时不得按新的汇率重算。
|
||||
- `call_record.status` 状态机为 `pending -> success / failed`。上游失败退点后仍保持 `failed`,退款流水通过 `points_ledger(change_type=refund, ref_call_id=call_record.id)` 关联,不单独增加 `refunded` 状态,避免调用结果与账务动作混在一个字段里。
|
||||
- T-201 已落地 `UserWallet` / `ApiKey` 于 `apps.users`,`PointsLedger` / `CallRecord` 于 `apps.billing`;T-203 已落地扣点/退点服务;T-304 已落地 `RechargeOrder`、回调幂等入账服务和 `points_ledger(ref_order_id, change_type)` 复合唯一约束,`ref_order_id` 当前仍为数值引用 `RechargeOrder.id`;T-305 已落地 `create_recharge_order()`,负责创建 pending 订单、锁定汇率/点数并回填二维码票据;T-401 已落地 `adjust_wallet_points()`,手工调整点数必须带原因并写 `adjust` 流水,后台钱包余额字段只读;T-501 已保证 allauth 自助注册路径创建 `UserWallet(points_balance=0)`,且不写 `PointsLedger`,避免把注册初始化误记为赠点或充值;T-502 已把 API Key 自助管理接到 `ApiKey.create_for_user()`,删除动作写为 `revoked` 状态而非物理删除;T-503 已把个人中心和记录页接到只读查询,余额用 `get_balance_snapshot()`,充值总额按 paid `RechargeOrder` 汇总,入账 / 消费 / 退款按 `PointsLedger` 汇总;T-504 已把 `/recharge` 页面接到 `create_recharge_order()` 与 `/api/v1/recharge/status`。
|
||||
- T-201 已落地 `UserWallet` / `ApiKey` 于 `apps.users`,`PointsLedger` / `CallRecord` 于 `apps.billing`;T-203 已落地扣点/退点服务;T-304 已落地 `RechargeOrder`、回调幂等入账服务和 `points_ledger(ref_order_id, change_type)` 复合唯一约束,`ref_order_id` 当前仍为数值引用 `RechargeOrder.id`;T-305 已落地 `create_recharge_order()`,负责创建 pending 订单、锁定汇率/点数并回填二维码票据;T-401 已落地 `adjust_wallet_points()`,手工调整点数必须带原因并写 `adjust` 流水,后台钱包余额字段只读;T-608 需新增 `signup_bonus` 流水类型和 MySQL 兼容的注册赠点幂等标记(如 `SignupBonusGrant(user UNIQUE)`),并把 allauth 自助注册路径改为调用 `grant_signup_bonus()` 发放 100 点;T-502 已把 API Key 自助管理接到 `ApiKey.create_for_user()`,删除动作写为 `revoked` 状态而非物理删除;T-503 已把个人中心和记录页接到只读查询,余额用 `get_balance_snapshot()`,充值总额按 paid `RechargeOrder` 汇总,入账 / 消费 / 退款 / 注册赠点按 `PointsLedger` 汇总;T-504 已把 `/recharge` 页面接到 `create_recharge_order()` 与 `/api/v1/recharge/status`。
|
||||
|
||||
## 四、计费时序(核心,务必照此实现)
|
||||
|
||||
@@ -357,6 +366,26 @@ CREATE TABLE call_record (
|
||||
- **兜底**:回调可能丢失,须提供按 `order_no` 主动查单补入账;前端每 ~1s 轮询订单状态仅作 UX。
|
||||
- 仅商户密钥/证书为真实值待提供(微信 appid/mchid/apiv3_key/证书、支付宝 appid/公私钥/RSA2、notify_url 域名),协议已明确,可先 mock。
|
||||
|
||||
### 4.3 注册赠点(新用户 100 点试用额度)
|
||||
|
||||
```text
|
||||
1. allauth 注册成功并保存 User(邮箱必填且唯一,当前免邮箱验证)
|
||||
2. 调用 billing.grant_signup_bonus(user, points=100)
|
||||
3. 事务:创建或锁定该用户的 UserWallet;创建 SignupBonusGrant(user UNIQUE) 幂等标记
|
||||
若标记已存在 → 直接返回已发放,不改余额、不写新流水
|
||||
否则 points_balance += 100
|
||||
写 points_ledger(signup_bonus, +100, balance_after, reason="new_user_registration")
|
||||
4. 注册后 dashboard / balance API 读取到包含 100 点赠点后的余额
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- 注册赠点不是充值收入,但属于可消费点数,必须纳入点数账本和后台审计。
|
||||
- 不允许在 adapter / view / signal 中直接 `points_balance=100`;所有余额变动必须走计费层。
|
||||
- 幂等要有数据库级兜底。MySQL 不支持条件唯一约束,不要用 partial unique;也不要用 `PointsLedger(user, change_type)` 这类会误伤其他流水类型的全局唯一约束。推荐独立 `SignupBonusGrant(user UNIQUE)` 标记表。
|
||||
- 只对 T-608 上线后的新注册用户自动发放。历史用户是否补发不在本流程内,需单独审批、单独任务、单独批处理流水。
|
||||
- 由于当前注册免邮箱验证,注册送点会提高刷号动机。T-608 实施时至少要有基础注册限流;图形验证码 / 人机验证可作为上线前风控项继续增强。
|
||||
|
||||
## 五、关键技术难点
|
||||
|
||||
| 难点 | 说明 | 应对 |
|
||||
@@ -372,7 +401,7 @@ CREATE TABLE call_record (
|
||||
| 配置热生效 | 后台改模型/密钥后运行时仍用旧值 | 不在进程内长缓存;每次查库或保存时失效缓存 |
|
||||
| 配置变更审计 | 改密钥/模型/别名映射无痕 | `AiConfigAuditLog` 自动记录后台 create/update/delete;密钥只记录 empty/set 变化,日志只读 |
|
||||
| API Key 泄露 | 明文存库一旦泄露全泄 | Key **哈希存储**(sha256),明文只在创建时显示一次,库内存 `key_hash`+`key_prefix`,鉴权做哈希比对 |
|
||||
| 注册滥用 | 自助注册被批量刷 | **免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`),防刷改由注册限流 / 图形验证码补位 + 生成接口限流(DRF throttle);**注册不送点数**,无免费额度可薅 |
|
||||
| 注册滥用 | 自助注册被批量刷以获取 100 点试用额度 | **免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`)下必须补注册限流 / 图形验证码 / 人机验证等防刷;注册赠点走 `SignupBonusGrant(user UNIQUE)` 幂等标记 + `PointsLedger(signup_bonus)` 留痕,防重复发放 |
|
||||
| Web/API 抢 worker | 图片同步长请求占满 worker、拖慢用户端页面 | 单体下按路径把 `/api/v1/generate/*` 与用户端页面**分流到不同 gunicorn/worker 池**(见 5.1) |
|
||||
| 充错账户 | 扫码订单未绑定发起用户 | 订单创建即绑定 `user`;回调按 `order_no` 定位订单→其 user 入账 |
|
||||
| SSRF / 任意 URL 下载 | `image_url` 让服务端请求调用方指定地址 | 仅允许公网 http/https;请求前解析 IP 并拒绝内网/回环/链路本地/保留地址;重定向逐跳校验;流式读取并限制大小 |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
## 0. 黄金法则
|
||||
|
||||
1. **不臆造**:数据字段、文件、接口、依赖、支付回调字段,不确定就查证或询问。
|
||||
2. **守范围**:只做当前任务要求的事,不顺手加后续功能(如异步队列、报表、注册赠点)。
|
||||
2. **守范围**:只做当前任务要求的事,不顺手加后续功能(如异步队列、报表、复杂活动赠点)。
|
||||
3. **照架构**:使用既定技术栈(Django+DRF+admin)和模块边界,不擅自引入新框架。
|
||||
4. **小步改**:一次只解决一个问题,不夹带无关重构。
|
||||
5. **可验证**:改完必须能迁移、能测试、对得上验收标准。
|
||||
@@ -28,7 +28,7 @@
|
||||
## 3. 范围纪律
|
||||
|
||||
- MVP 只做 `02-requirements.md` 中列为 P0 的功能。
|
||||
- V2 / V3 功能(异步队列、报表、注册赠点、自动退款对账)只记录,不实现。(自助注册/扫码充值/API Key 管理已纳入 MVP)
|
||||
- V2 / V3 功能(异步队列、报表、复杂活动赠点 / 邀请奖励、自动退款对账)只记录,不实现。(自助注册/扫码充值/API Key 管理已纳入 MVP;新用户一次性注册送 100 点已拆为 T-608)
|
||||
- 需求明确排除的非目标不得实现(自建收银台、多币种、分账等)。
|
||||
- 不为“将来可能用到”提前抽象。
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
- 技术栈以 `03-tech-stack.md` 为准;新增依赖前先说明理由,未经确认不引入重量级依赖(Celery、Redis 等)。
|
||||
- 模块职责以 `04-architecture.md` 为准,不跨层偷写逻辑。
|
||||
- **点数余额只能经 `apps/billing` 修改**:余额存 `UserWallet`(与 auth `User` 分离),扣点锁 wallet 行;API 层/Admin/用户端都不得直接写 `points_balance`。
|
||||
- **注册赠点也只能经 `apps/billing` 修改**:T-608 的 100 点试用额度必须走 `grant_signup_bonus()` 这类计费层服务,写 `PointsLedger(signup_bonus)`,不得在 allauth adapter、view 或 signal 里直接把钱包余额设为 100。
|
||||
- **API Key 哈希存储**(sha256),明文只在生成时返回一次;不存明文、任何页面不回显。
|
||||
- **对外 API 只挂 API Key 认证,不挂 SessionAuthentication**;用户端页面用 session + CSRF;自助注册**免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)、生成接口须限流。
|
||||
- **用户端与后台账号分离**:portal 与 `/admin/` 共用同一个 Django 会话(同一 `sessionid`),后台访问只多一道 `is_staff` 门槛。因此 **终端用户一律 `is_staff=False`**(普通用户登 portal 进不了后台,无泄露);**运营用专用 admin superuser 登录 `/admin/`,不要拿这个账号登 portal**——账号分离(方案 A)就是「解耦入口」的正解,不要为同账号双会话去拆 cookie/子域名。详见 `04-architecture.md`。
|
||||
@@ -90,6 +91,7 @@ python manage.py runserver
|
||||
- **回调防护与兜底**:支付回调端点必须 `@csrf_exempt`(网关调用无 CSRF token);回调可能丢失,须提供按 `order_no` 主动查单兜底,避免已付款却一直 pending。
|
||||
- **失败必退**:调用上游失败时,已预扣的点数必须冲正退回。
|
||||
- **全程留痕**:每笔点数变动写 `points_ledger`,每次调用写 `call_record`,运营手工调整必须填原因。
|
||||
- **注册赠点幂等**:新用户 100 点试用额度必须有数据库级幂等保护,同一用户无论注册回调、重试或并发触发多少次,都最多发放一次;不得用 MySQL 不支持的条件唯一约束。
|
||||
- **敏感信息**:密钥/凭证只用环境变量或后台配置,不写真实值进代码与文档。
|
||||
- 资金相关改动必须有对应测试(并发扣点、重复回调、上游失败退点至少各一条)。
|
||||
- 不绕过支付系统的鉴权、验签或风控。
|
||||
|
||||
+3
-1
@@ -86,6 +86,7 @@
|
||||
| T-604 | 中文敏感词本地过滤(本地 keyword provider) | T-302, T-401 | 按 [`moderation.md`](moderation.md) 实施。**范围收紧**:T-604 只做输入 prompt 的本地敏感词快筛,不做云内容安全、不做输出审核、不做图片审核。**关键时序**:serializer 后先审 prompt,命中即 `400 content_blocked`;不得先下载 `image_url`,不得预扣点,不写 `CallRecord` / `PointsLedger`,不调上游。**核心实现**:新增 `apps.moderation`、`SensitiveWord` 模型/admin/迁移、keyword provider、归一化管线、Aho-Corasick matcher;`ahocorapy` 作为候选依赖,编码前必须验证 PyPI 可用性和 API 形状。**缓存**:matcher 进程内缓存,词库变更用共享 cache 版本号失效,不能只靠 `post_save` signal;生产依赖共享 cache。**配置**:`MODERATION_ENABLED=false` 默认 no-op;启用时 `MODERATION_PROVIDER=keyword`;MVP `SensitiveWord.action` 只支持 `block`。**验收**:`check`/`test` 全绿,覆盖 no-op、命中拦截且不扣点/不建记录/不调上游/不下载图片、归一化防绕过、词库变更后 matcher 重建;真实词库数据不进仓库,`__pycache__` 不进 Git | DONE |
|
||||
| T-606 | 公开首页 + 客户端下载入口 | T-501 | 给网站补「前门」并提供桌面端下载。**路由改造**:`/` 从「重定向到 `/dashboard`」改为**公开首页**(匿名可访问、不跳登录);已登录用户显示「进入控制台」,匿名显示「注册/登录 + 下载客户端」。**首页内容**(SSR 模板):项目一句话介绍(生成标题/图片、按点数计费)+ 三步上手(注册→充值→建 API Key→桌面端填 Key)+ 下载入口 + 文档链接。**`DownloadRelease` 模型**:`platform`(如 windows)、`version`、`file`(FileField,**前期存 `MEDIA_ROOT`、服务器托管**) + `external_url`(URLField 可选,**后续切对象存储/CDN 用,有则优先**)、`sha256`、`is_current`(每平台仅一个当前版本)、`release_notes`、时间戳;admin 可上传安装包并标记当前版本;迁移。**下载区块**:展示当前 release 的版本、下载按钮、**SHA256 校验值**、可选 release notes;无 current release 时优雅提示「暂未发布」。**托管策略**:**前期安装包放本服务器**(生产由 **Nginx 直接服务 media/下载文件、不走 Django**,与 T-403 static/media serving 一致,大文件不占 gunicorn worker);`external_url` 预留,后续切对象存储只改后台链接不改代码。**安全**:下载走 HTTPS,页面展示 SHA256 供校验;**代码签名**作为决策登记——未签名 Windows 安装包会被 SmartScreen 拦「未知发布者」、macOS 被 Gatekeeper 拦,首页先给「如何忽略警告」说明,正式签名后续补(挂 Backlog / deployment)。**含测试**:`/` 匿名 200 不跳登录、下载区展示当前 release、无 current release 优雅处理、已登录用户显示「进入控制台」。**视觉原型(已定 v1)**:按 `prototypes/cmhub-homepage-v1.svg` 落地——「生成台」方向:靛蓝=生成 / 琥珀=点数;Hero 为「商品图 + 一句话 → 吸睛标题 + 生成主图 + 点数计量」转化图;四步上手 01–04、两张能力卡、深色计费 band、下载区(版本/SHA256/未签名提示);配色、间距、结构照此原型转成 Django 模板(Bootstrap + 本地 static)。**视觉一致性(brand token)**:按 [`brand.md`](brand.md) 抽出共享 CSS 变量(如 `apps/portal/static/portal/brand.css`),**首页与现有 portal 页面(dashboard/记录/充值/API Key)一起套用同一套 token**(把配色/字体变量灌进现有 Bootstrap,各页只引用变量不散写 hex),确保落地页与登录后控制台风格一致——**不是只做漂亮首页**。验收 `check`/`test`/`init` 全绿并在 `../progress.md` 留证据 | DONE |
|
||||
| T-607 | 桌面端最新版本检查接口 | T-606 | 给桌面端自动检查更新提供只读 JSON 合约。**新增路由**:`GET /api/v1/client/releases/latest?platform=windows`,公开匿名可访问,**不需要 API Key、不读取用户、不扣点、不占用生成接口限流**;只返回发布元数据,不返回本地文件系统路径、后台 ID、内部状态或任何用户数据。**请求参数**:`platform` 支持 `windows` / `macos` / `linux`,缺省按 `windows`;非法平台返回 `400 bad_request`。**响应结构**:有当前版本时返回 `{platform, release:{version, download_url, sha256, release_notes, published_at}}`;无当前版本或当前版本没有下载地址时返回 `{platform, release:null, message:"暂未发布"}` 且 HTTP 200,方便客户端安静处理。**数据来源**:复用 `DownloadRelease`,只查 `platform + is_current=True`;`download_url` 继续按 `external_url` 优先,否则由 `file.url` 生成绝对 HTTPS URL;`published_at` 可先使用 `updated_at`。**缓存与安全**:可加短 TTL 公共缓存(如 60 秒);生产下载仍走 HTTPS + SHA256 校验;不得把 `MEDIA_ROOT` 或服务器路径暴露给客户端。**含测试**:匿名无 Key 可访问;当前 release 返回完整结构和绝对下载 URL;无 current release 返回 `release:null`;非法 platform 返回 400;`external_url` 优先于 `file`;响应不含本地路径、模型/密钥/用户字段;`check`/目标测试通过并在 `../progress.md` 留证据 | DONE |
|
||||
| T-608 | 新用户注册赠送 100 点试用点数 | T-501, T-203, T-401 | 把 2026-07-08 的产品口径落地为账本安全实现。**业务口径**:只对 T-608 上线后的新注册用户自动发放 100 点;历史用户是否补发不在本任务内,需单独审批和批处理任务。**数据与服务**:新增 `PointsLedger.ChangeType.SIGNUP_BONUS`(展示为注册赠点)和 MySQL 兼容的数据库级幂等兜底(推荐 `SignupBonusGrant(user UNIQUE)`,不要用 partial unique / 条件唯一约束);新增 `apps.billing.services.grant_signup_bonus(user, points=100)`,在事务内锁/创建钱包、发放点数、写 `PointsLedger(signup_bonus,+100,balance_after,reason)`,重复调用或并发触发不得重复发放。**接入点**:allauth adapter 注册成功后调用 billing 服务,不得在 portal 直接写 `points_balance`;失败要清晰暴露并保持注册 / 账务一致性。**页面与文案**:公开首页、注册页、dashboard/记录页等用户端文案同步「注册送 100 点」;消费记录页能展示注册赠点正向流水;admin 可检索该流水。**防刷**:在免邮箱验证策略下,至少补基础注册限流或明确依赖 allauth/Django 限流配置;图形验证码 / 人机验证若暂不做,必须在文档登记为上线前风控风险。**测试**:注册后余额为 100 且有一条 `signup_bonus` 流水;重复调用服务不重复加点;并发触发不重复发放;现有用户不会自动补发;余额 API / dashboard / 使用记录显示一致;`check`/迁移/目标测试通过并在 `../progress.md` 留证据 | TODO |
|
||||
|
||||
## 里程碑
|
||||
|
||||
@@ -95,13 +96,14 @@
|
||||
- M4:用户端(注册/充值/API Key/记录)可用(T-504)。
|
||||
- M5:运营后台 + MVP 验收 + 可部署(T-403)。
|
||||
- M6:可用别名发现(T-601)。
|
||||
- M7:注册试用额度安全落地(T-608)。
|
||||
|
||||
## 待办池(Backlog)
|
||||
|
||||
- 异步生成(任务队列 + 轮询/回调)。
|
||||
- 按账号授权可用别名(`account_alias_permission`),防止调用未授权/昂贵模型。
|
||||
- 别名按比例分流到多个模型(灰度 / A/B / 故障转移);供应商 A 故障自动切 B。
|
||||
- 注册赠点 / 试用额度(需邮箱/图形验证码 + 限流防薅羊毛)。
|
||||
- 复杂活动赠点、邀请奖励、历史用户批量补发(需单独审批、风控、批处理与流水留痕)。
|
||||
- 用量统计报表。
|
||||
- 退款对账自动化、API Key 轮换、Provider key 轮换(MultiFernet / 双 key 迁移)、限流细化。
|
||||
- 用户端 API Key 数量治理:限制每用户 active Key 数量(如 10 个)、提供轮换与过期策略,避免无限建 Key。
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
- [Phase 1 AI 层审核](phase-1-review.md):T-101~104 代码审核结论与修补清单(P1/P2/P3),对应任务 T-105;重点提示图片同步风险未退与 `parameters` 越权计费隐患。
|
||||
- [Phase 2 计费核心审核](phase-2-review.md):T-201~203 代码审核结论(资金安全逐条核对)与加固清单(**零 P1**,P2/P3),对应任务 T-204。
|
||||
- [Phase 3 对外 API 与充值审核](phase-3-review.md):T-301~305 代码审核结论(鉴权/回调/幂等/入账逐条核对)与优化建议,对应任务 T-306;**P1 提示 `image_url` SSRF 上线前必修**。
|
||||
- [Phase 4 用户端审核](phase-4-review.md):T-501~504 代码审核结论(注册不送点/越权/CSRF/明文只显一次/到账以回调为权威逐条核对)与优化建议(**零 P1**,P2/P3),对应任务 T-505。
|
||||
- [Phase 4 用户端审核](phase-4-review.md):T-501~504 代码审核结论(按当时口径核对注册不送点/越权/CSRF/明文只显一次/到账以回调为权威;2026-07-08 新需求已由 T-608 改为注册送 100 点)与优化建议(**零 P1**,P2/P3),对应任务 T-505。
|
||||
- [MVP 完整验收报告](mvp-acceptance.md):T-402 对 `02-requirements.md` P0 验收项的逐项结论、测试证据和已知限制。
|
||||
- [部署 / 运行文档](deployment.md):T-403 生产部署步骤、宝塔 / Nginx / Gunicorn 配置、静态文件、共享 cache、图片超时与上线检查。
|
||||
- [API 合约](api.md):对外接口、支付回调、AI 调用模块合约、错误码。
|
||||
|
||||
+4
-1
@@ -15,6 +15,7 @@
|
||||
- 例外:客户端下载版本检查接口只返回公开发布元数据,设计为匿名只读接口,不需要 API Key,不读取用户、不扣点。
|
||||
- 图片生成为**同步**接口,可能耗时较长,调用方与网关需设置足够超时(≥ 300s)。
|
||||
- 用户端注册使用同一个 `User` 账本主体;注册邮箱**必填且唯一**(`ACCOUNT_EMAIL_VERIFICATION="none"`,**不做邮箱验证**、注册即可用),唯一约束避免同邮箱对应多个点数账户。
|
||||
- T-608 起新用户注册成功一次性赠送 **100 点**试用点数;赠点必须经计费层写入钱包和 `PointsLedger(change_type=signup_bonus)`,不得直接改余额字段。历史用户是否补发不属于默认注册流程。
|
||||
- API Key 库内只存 `key_hash`(SHA-256)与 `key_prefix`,明文只在创建时返回一次,不在 admin、日志或调用记录中回显。
|
||||
- 每次生成调用写 `CallRecord`;只允许保存 `result_ref` / `result_summary` 这类引用或摘要,不保存 provider `raw`、base64 图片或敏感上游字段。
|
||||
|
||||
@@ -34,7 +35,7 @@ T-306 已实现对外 API 安全加固:`image_url` 下载只允许 `http` / `h
|
||||
|
||||
T-604 目标口径:生成接口在 serializer 基础校验后先执行 prompt 本地敏感词检查;命中返回 `400 content_blocked`,且不得下载 `image_url`、不得预扣点、不得写 `CallRecord` / `PointsLedger`、不得调用上游。T-604 不启用输出审核和图片审核;详细规则见 [`moderation.md`](moderation.md)。
|
||||
|
||||
T-501 已实现用户端注册 / 登录基线:`/signup` `/login` `/logout` 走 django-allauth + Django session + CSRF;**免邮箱验证、注册即可用(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)**,注册成功创建 0 点 `UserWallet`,不创建赠点流水。对外 API 仍只认 API Key,不接受 Web session。(策略见 T-605 与 2026-07-06 决策)
|
||||
T-501 已实现用户端注册 / 登录基线:`/signup` `/login` `/logout` 走 django-allauth + Django session + CSRF;**免邮箱验证、注册即可用(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)**。T-608 需把注册成功后的 0 点钱包初始化改为一次性发放 100 点注册试用额度,并创建 `signup_bonus` 流水。对外 API 仍只认 API Key,不接受 Web session。(免验证策略见 T-605 与 2026-07-06 决策)
|
||||
|
||||
T-502 已实现用户端 API Key 自助管理基线:`/apikeys` 走 Django session + CSRF;登录用户可生成和删除自己的 Key,生成后的明文只在重定向后的首个页面显示一次,库内只保存 `key_hash` 与 `key_prefix`。用户端“删除”落库为 `revoked`,保留历史记录关联;吊销后的 Key 调用生成 / 余额接口返回 `403 account_disabled`,缺失、无效或不存在的 Key 仍返回 `401 unauthorized`。
|
||||
|
||||
@@ -243,6 +244,7 @@ calculate_points_cost(operation_type: str, alias: str, resolution: str | None =
|
||||
quote_recharge_points(amount, currency: str = "CNY", at=None) -> RechargeQuote
|
||||
calculate_points_granted(amount, currency: str = "CNY", at=None) -> int
|
||||
create_recharge_order(..., user, amount, pay_method: str, currency: str = "CNY") -> RechargeOrder
|
||||
grant_signup_bonus(user, points: int = 100) -> SignupBonusGrantResult
|
||||
precharge_call(..., user, points_cost: int, operation_type: str, alias: str, ...) -> CallCharge
|
||||
mark_call_success(call_record: CallRecord, ...) -> CallRecord
|
||||
refund_call_points(call_record: CallRecord, ...) -> RefundResult
|
||||
@@ -257,6 +259,7 @@ query_and_apply_recharge_payment(order_no: str, query_func) -> RechargeResult
|
||||
- 缺计费规则抛 `NoPricingRuleError(code="no_pricing_rule")`,API 层应翻译为上方同名错误码。
|
||||
- `ExchangeRate` 按 `currency + effective_from` 取当前 active 汇率;充值下单时应锁定当时的 `exchange_rate` / `points_granted` 到订单,回调入账不得按新汇率重算。
|
||||
- 金额换点数采用 `floor(amount * points_per_unit)`,点数为整数。
|
||||
- `grant_signup_bonus()` 是注册赠点唯一入口:只对当前用户首次成功发放 100 点,锁定 / 创建 `UserWallet`,写 `PointsLedger(change_type=signup_bonus, points_delta=+100)`,并用 MySQL 兼容的唯一幂等标记防重复发放。
|
||||
- `create_recharge_order()` 创建 `RechargeOrder(status=pending)` 并绑定发起用户;写入订单创建时的金额、币种、汇率、预计点数,再调用支付网关下单取二维码票据并回填 `code_url` / `expires_at`;支付平台下单失败时订单标记 `failed`。
|
||||
- `precharge_call()` 使用事务 + `select_for_update()` 锁 `UserWallet` 行;余额不足抛 `InsufficientPointsError(code="insufficient_points")`,不创建 `CallRecord`、不写 `PointsLedger`、不调上游。
|
||||
- 预扣成功后写 `CallRecord(status=pending)` 与 `PointsLedger(change_type=consume, points_delta=-N)`;上游成功只更新调用记录,余额不再变化。
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ cmhub 有双重身份——**AI 生成** + **按点数计费**。视觉主线
|
||||
## 5. 语气(Voice)
|
||||
|
||||
- **说人话**:按用户看得懂的说,不用系统术语(说「管理 API Key」,不说「凭证配置」)。
|
||||
- **诚实**:如实说「注册不送点数」「失败自动退点」「首次安装点『仍要运行』」——不粉饰。
|
||||
- **诚实**:如实说「注册送 100 点试用点数」「失败自动退点」「首次安装点『仍要运行』」——不粉饰。
|
||||
- **动词优先、句子式大小写**:按钮说清「做完会发生什么」(「下载 .exe」而非「获取」)。
|
||||
- **同一动作全程同名**:按钮「充值」→ 结果提示也用「充值」。
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@
|
||||
|
||||
| P0 验收项 | 结论 | 主要证据 |
|
||||
| --- | --- | --- |
|
||||
| 用户注册 / 登录 | PASS | `apps.portal`:注册创建 0 点钱包且不写赠点流水;T-605 后注册为免邮箱验证、注册后可直接登录,邮箱仍必填且唯一 |
|
||||
| 用户注册 / 登录 | PASS | T-402 历史验收口径为 `apps.portal` 注册创建 0 点钱包且不写赠点流水;T-605 后注册为免邮箱验证、注册后可直接登录,邮箱仍必填且唯一。2026-07-08 新需求已登记为 T-608,后续需改为注册赠送 100 点并写 `signup_bonus` 流水 |
|
||||
| API Key 自助管理 | PASS | `apps.portal`:Key 明文只显示一次、列表仅显示 prefix、删除为吊销、不可删除他人 Key;`apps.api`:缺失/无效 Key 为 401,吊销 Key 为 403 |
|
||||
| 扫码充值 | PASS | `apps.portal` / `apps.api`:创建 pending 订单不直接加点、不写流水;二维码票据展示;状态轮询只允许本人订单;主动查单可补入账 |
|
||||
| 个人中心 / 记录 | PASS | `apps.portal`:余额、充值总额、入账点数、消费/退款记录与实际订单/流水一致;充值记录和消费记录分页且仅见本人 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Phase 4 用户端审核报告(T-501 ~ T-504)
|
||||
|
||||
> 审核人:Claude Code(全栈视角)|日期:2026-07-03|结论:**功能验收全部达标,测试覆盖极全,零 P1(无安全/资金/越权缺陷)**。
|
||||
> Phase 4 是自助用户端(Django 模板 SSR + allauth)。逐条比对验收:注册不送点数、session+CSRF、越权(IDOR)防护、API Key 明文只显一次、吊销后调用 403、记录仅见本人、充值到账以回调为权威——**全部正确落地且有专测**。剩余为前端资源与 UX 优化项(P2/P3)。
|
||||
> Phase 4 是自助用户端(Django 模板 SSR + allauth)。逐条比对当时验收:注册不送点数、session+CSRF、越权(IDOR)防护、API Key 明文只显一次、吊销后调用 403、记录仅见本人、充值到账以回调为权威——**全部正确落地且有专测**。2026-07-08 新需求已由 T-608 改为「新用户注册送 100 点」,本报告保留历史审核口径。剩余为前端资源与 UX 优化项(P2/P3)。
|
||||
> 本文面向 codex 执行:每条给出「症状 / 位置 / 怎么改 / 怎么验证」。修补任务见 [`06-tasks.md`](06-tasks.md) 的 **T-505**;按任务看板领取规则,当前应先完成 T-505,再进入 T-401。
|
||||
|
||||
## 一、验收核对(功能全部达标)
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
**第一版做**:
|
||||
|
||||
- 用户端:自助注册登录、扫码充值、查看点数/充值/消费记录、自助生成与删除 API Key(注册不送免费点数)。
|
||||
- 用户端:自助注册登录、注册成功赠送 100 点试用点数、扫码充值、查看点数/充值/消费记录、自助生成与删除 API Key。
|
||||
- 生成标题、生成图片两个接口(同步返回结果)。
|
||||
- 点数计费、扣点、余额查询。
|
||||
- 充值到账(对接已有支付系统)。
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
**第一版先不做**(留待后续迭代):
|
||||
|
||||
- 高并发异步处理、用量统计报表、自动退款对账、注册赠点等(自助注册/扫码充值/API Key 管理已纳入第一版)。
|
||||
- 高并发异步处理、用量统计报表、自动退款对账、复杂活动赠点 / 邀请奖励等(自助注册/扫码充值/API Key 管理与注册送 100 点已纳入第一版口径)。
|
||||
|
||||
> 取舍原则:先把「充值 → 调用 → 计费 → 记录」最小闭环做稳,再扩展。
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
## 第一版范围
|
||||
|
||||
做:自助用户端(注册/扫码充值/API Key 管理/记录)、两个生成接口、点数计费、充值到账、调用记录、运营后台。
|
||||
先不做:异步高并发、报表、注册赠点(后续迭代;自助注册/充值/API Key 已在第一版)。
|
||||
做:自助用户端(注册注册送 100 点/扫码充值/API Key 管理/记录)、两个生成接口、点数计费、充值到账、调用记录、运营后台。
|
||||
先不做:异步高并发、报表、复杂活动赠点 / 邀请奖励(后续迭代;自助注册/充值/API Key 已在第一版)。
|
||||
|
||||
## 需要拍板
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
| 路由 | 方法 | 职责 | 鉴权 |
|
||||
| --- | --- | --- | --- |
|
||||
| `/` | GET | 公开首页:项目介绍、四步上手、客户端下载入口、API/控制台入口 | 公开 |
|
||||
| `/signup` `/login` `/logout` | GET/POST | 自助注册(**免邮箱验证、注册即可用**)/登录/登出(Django auth/allauth) | 公开 |
|
||||
| `/signup` `/login` `/logout` | GET/POST | 自助注册(**免邮箱验证、注册即可用**;注册成功一次性赠送 100 点试用点数)/登录/登出(Django auth/allauth) | 公开 |
|
||||
| `/dashboard` | GET | 个人中心:剩余点数、充值总额、快捷入口 | session |
|
||||
| `/recharge` | GET/POST | 发起充值:选金额→展示支付二维码→轮询到账 | session |
|
||||
| `/records/recharge` | GET | 充值记录 | session |
|
||||
@@ -16,7 +16,7 @@
|
||||
| `/apikeys` | GET/POST | API Key 管理:列表 / 生成 / 删除(删除即吊销,明文只显示一次) | session |
|
||||
| `/models` | GET | 可用模型:只读展示可调用能力别名、能力、是否需要原图和点数单价 | session |
|
||||
|
||||
T-501 已落地 `/signup`、`/login`、`/logout` 与最小 `/dashboard`。T-502 已落地 `/apikeys`:登录用户只能管理自己的 Key,生成后明文只显示一次,列表只显示 prefix,删除为吊销 `revoked`。T-503/T-505 已扩展 `/dashboard` 为个人中心汇总,并落地 `/records/recharge` 与 `/records/usage`:充值总额按已支付订单统计,入账 / 消费 / 退款点数按 `PointsLedger` 统计,记录页只查询当前登录用户数据并分页展示。T-504/T-505 已落地 `/recharge`:登录用户可选择金额和支付方式创建 pending 充值订单,页面用本地 static 自托管 qrcode.js 展示二维码票据并轮询 `/api/v1/recharge/status`,到账后刷新余额。
|
||||
T-501 已落地 `/signup`、`/login`、`/logout` 与最小 `/dashboard`;T-608 需把注册后 0 点初始化改为经计费层一次性发放 100 点试用点数并写流水。T-502 已落地 `/apikeys`:登录用户只能管理自己的 Key,生成后明文只显示一次,列表只显示 prefix,删除为吊销 `revoked`。T-503/T-505 已扩展 `/dashboard` 为个人中心汇总,并落地 `/records/recharge` 与 `/records/usage`:充值总额按已支付订单统计,入账 / 消费 / 退款点数按 `PointsLedger` 统计,记录页只查询当前登录用户数据并分页展示。T-504/T-505 已落地 `/recharge`:登录用户可选择金额和支付方式创建 pending 充值订单,页面用本地 static 自托管 qrcode.js 展示二维码票据并轮询 `/api/v1/recharge/status`,到账后刷新余额。
|
||||
T-601 已落地 `/models`:登录用户可查看当前公开可调用别名、能力、是否需要原图和点数单价;页面不展示底层 SKU、模型 URL、provider key、`api_key_encrypted` 或 `extra_body`。
|
||||
T-606 已落地 `/` 公开首页:匿名访问返回 200,不再重定向到 `/dashboard`;匿名用户看到注册 / 登录 / 下载入口,登录用户看到「进入控制台」。首页下载区读取 `DownloadRelease(platform=windows, is_current=True)`,优先使用 `external_url`,否则使用后台上传文件的 `file.url`;无当前版本时显示「暂未发布」。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user