Commit f19672ffd021fdb60a285d59e72221379d1bd6c1
1 parent
38245bd4
Plan fourth-phase tasks: credits, Stripe topup, paid garment upload (T23-T30)
Breaks down dressup-dance-room.md 9节第4条 (credit ledger, Stripe topup, mode 2 paid garment upload — explicitly not mode 3, which is period 5) into T23-T30 with acceptance criteria, estimates, and a dependency graph, matching dev-plan.md/dev-plan-2.md/dev-plan-3.md's format. Key decisions: - credit_ledger joins users/sessions in the same SQLite db.js (T18) — architecture.md 1.4 flagged this atomicity requirement explicitly, this is the other half of why SQLite got picked in period 3. pricing_config stays a JSON file (store.js) since it's not on that hot atomic path. - Flags a real doc inconsistency: architecture.md's diagram labels fal.ai integration "period 5", but dressup-dance-room.md §8 item 10 and §9 item 4 both say mode 2's fal.ai path belongs in period 4. Resolved in favor of the more specific/recent requirements doc. - Flags an uncovered gap: §5 references an image-moderation vendor "TBD, see §8" but §8 has no matching item for it — added as an open question here (§1.2). - Three genuinely external blockers (image moderation vendor, vision model vendor for auto-tagging, real Stripe API keys) are each designed around with a pluggable interface (same pattern as T20's sendVerificationEmail) so building isn't blocked on vendor picks — except the Stripe key itself, which only the user can supply. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Deddn3sM3oQnmvqWSMb4y
Showing
1 changed file
with
146 additions
and
0 deletions
docs/todo/dev-plan-4.md
0 → 100644
| 1 | +# 换装跳舞直播间 — 第四期开发任务规划 | |
| 2 | + | |
| 3 | +需求见 `docs/todo/dressup-dance-room.md`(下称"需求文档")第 3.2 节模式二、 | |
| 4 | +第 5 节内容审核、第 7 节积分与充值、第 9 节第 4 条;设计见 | |
| 5 | +`docs/todo/dressup-dance-room-mvcp.md`(下称"MVCP")M.2/M.4/M.9/M.12; | |
| 6 | +架构见 `docs/structure/architecture.md`(下称"架构文档")1.4/2.3 节。第一 | |
| 7 | +(T1-T10)、第二(T11-T17)、第三期(T18-T22)均已完成并提交。任务编号 | |
| 8 | +接着往下编。 | |
| 9 | + | |
| 10 | +工时是**粗估**,单位人天,用来排先后不用来考核。 | |
| 11 | + | |
| 12 | +--- | |
| 13 | + | |
| 14 | +## 0. 第四期范围(需求文档 9 节第 4 条) | |
| 15 | + | |
| 16 | +**做**:积分体系(余额/流水/配置化扣费)+ Stripe 充值接入,上线**免费 | |
| 17 | +换衣服(已有)+ 付费传衣服(模式二)**,验证"扣费→生成→失败退还→ | |
| 18 | +审核通过后沉淀进公共库"这条完整链路。 | |
| 19 | + | |
| 20 | +**明确不做**(留到第五期,需求文档 9 节第 5 条): | |
| 21 | + | |
| 22 | +- 模式三(付费传人物)——包括它的核心业务规则"付费新人物插队播放, | |
| 23 | + 不参与投票"(架构文档 2.6 节 `priority: paid_boost`),跟模式二完全 | |
| 24 | + 不同,本期不碰排播优先级逻辑。**模式二内容仍然走 v1 现有投票机制 | |
| 25 | + 排序**,需求文档 3 节"共同后置流程"里写得很明确:只有模式三跳过 | |
| 26 | + 投票,模式二不跳。 | |
| 27 | +- 人脸识别黑名单审核(模式三专用,需求文档 5 节)。 | |
| 28 | +- `blacklist`(MVCP M.11)完整的运营手工拉黑管理页面——本期只做 | |
| 29 | + `moderation_log`(M.9)记录自动审核结果,不做"管理员随手拉黑一个 | |
| 30 | + 用户/资产"这套后台 CRUD。 | |
| 31 | +- "我的作品"个人中心页面(需求文档 7.4 节)。 | |
| 32 | +- 支付宝/微信支付等 Stripe 之外的渠道(7.3 节已说"需要时再加一个入口 | |
| 33 | + 即可",本期不预先做)。 | |
| 34 | + | |
| 35 | +--- | |
| 36 | + | |
| 37 | +## 1. 已确认的技术决策 + 需要你决定的开放问题 | |
| 38 | + | |
| 39 | +### 1.1 已确认 | |
| 40 | + | |
| 41 | +- **`credit_ledger` 落 SQLite,跟 `users`/`sessions` 同一个 `data.sqlite`** | |
| 42 | + (复用 T18 的 `db.js`)——架构文档 1.4 节点名了"扣积分 + 创建生成 | |
| 43 | + 任务 + 扣减片段配额"这几步操作需要原子性,JSON 全量重写文件在并发下 | |
| 44 | + 会丢更新,这正是当初选 SQLite 的另一半理由(T18 只先用上了"密码存储" | |
| 45 | + 那一半)。`node:sqlite` 的 `DatabaseSync` 支持事务 | |
| 46 | + (`db.exec("BEGIN")...COMMIT/ROLLBACK`),扣积分和退还都要包在事务里。 | |
| 47 | +- **`pricing_config`(M.12)走 JSON 文件**,不进 SQLite——它是运营偶尔 | |
| 48 | + 手改的配置,不在"扣积分"这条热路径的原子性要求里,复用 `store.js` | |
| 49 | + 的模式(类似 `characters.json`)比再建一张表更省事,跟"能用现成的 | |
| 50 | + 就别加新东西"的项目风格一致。 | |
| 51 | +- **`garments`/`characters` 的 `source`/`ownerUserId`/`moderationStatus` | |
| 52 | + 字段第一期就已经建好**(T1,见 MVCP M.3/M.4),本期不需要动 | |
| 53 | + schema,只是第一次真正用上 `user_paid`/`pending` 这些取值。 | |
| 54 | +- **文档发现一处不一致,按需求文档为准**:架构文档 2.2 节的流程图把 | |
| 55 | + fal.ai 接入标成"第五期",但需求文档 §8 第 10 条明确写"到**第四期** | |
| 56 | + 模式二付费实时路径上线时再解决",且本文档 §9 第 4 条本来就是"上线 | |
| 57 | + …付费传衣服(模式二)"。按需求文档更新、更具体的说法为准:**fal.ai | |
| 58 | + 接入是本期(第四期)的任务**,架构文档那张图的标注是旧的,没跟上 | |
| 59 | + 后来的分期方案,此处不改架构文档原文,只在这里记一笔。 | |
| 60 | + | |
| 61 | +### 1.2 需要你决定,但不阻塞开工(每条都设计成可插拔接口,供应商定了随时接进来) | |
| 62 | + | |
| 63 | +| # | 开放问题 | 现在怎么办 | | |
| 64 | +|---|---|---| | |
| 65 | +| 1 | **图片内容审核供应商**(需求文档 5 节提到"需要图片内容审核能力(供应商待定)",但第 8 节居然没有专门列这一条——本文档补上这个缺口) | T26 先接一个 `moderateGarmentImage()` 可插拔函数,未配置真实供应商时走最基础的规则版兜底(文件类型/尺寸已在 T2 做过,这里只做"占位通过"),同 T20 `sendVerificationEmail()` 的套路 | | |
| 66 | +| 2 | **视觉模型供应商**(需求文档 §8 第 8 条,给用户上传的衣服图自动生成 `garmentDescriptor`,因为模式二没有管理员手写这一步) | T25 同样做成可插拔的 `describeGarmentImage()`,未配置真实供应商时退化成一句机械占位描述(如"用户上传的服装"),生成流程照常走,只是效果不如手写描述精细——记在验收标准里,不当阻塞项 | | |
| 67 | +| 3 | **积分具体数值**(需求文档 §8 第 5 条:充值档位、每次传衣服扣多少积分) | `pricing_config` 里先填占位数值(如 `spend_garment: 10` 积分),字段/机制先打好,数值上线前再改,MVCP M.12 原话就是这个意思 | | |
| 68 | +| 4 | **Stripe 真实账号的 API key**(`STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET`) | 我这边没有你的 Stripe 账号,没法自己申请测试模式 key。T24 的代码可以先写完,但**真正跑通一次真实 Checkout 需要你提供测试模式的 key** 放进 `.env`,这条我做不到自己解决,标在这里等你 | | |
| 69 | + | |
| 70 | +--- | |
| 71 | + | |
| 72 | +## 2. 依赖关系 | |
| 73 | + | |
| 74 | +```mermaid | |
| 75 | +flowchart TD | |
| 76 | + T23[T23 credit_ledger + pricing_config] --> T24[T24 Stripe 充值] | |
| 77 | + T23 --> T27[T27 模式二后端] | |
| 78 | + T25[T25 衣服图片自动打标] --> T27 | |
| 79 | + T26[T26 衣服图片内容审核] --> T27 | |
| 80 | + T27 --> T28[T28 传衣服前端接线] | |
| 81 | + T24 --> T29[T29 积分余额/充值 UI] | |
| 82 | + T23 --> T30[T30 admin 积分调整 + admin 鉴权迁移] | |
| 83 | + | |
| 84 | + classDef ready fill:#d4edda,stroke:#28a745 | |
| 85 | + class T23,T25,T26,T30 ready | |
| 86 | +``` | |
| 87 | + | |
| 88 | +T23/T25/T26/T30 互不依赖,可以并行开工;T27 是模式二的核心,要等 | |
| 89 | +T23(能扣积分)、T25(能打标)、T26(能审核)都好了才能真正端到端跑通 | |
| 90 | +(三者的桩/占位实现都能先联调,不必等真实供应商);T28 是前端接线, | |
| 91 | +需要 T27 的接口稳定;T29 只依赖 T24。 | |
| 92 | + | |
| 93 | +--- | |
| 94 | + | |
| 95 | +## 3. 任务表 | |
| 96 | + | |
| 97 | +| # | 任务 | 依赖 | 验收标准 | 工时 | | |
| 98 | +|---|---|---|---|---| | |
| 99 | +| T23 | **`credit_ledger` + `pricing_config` 落地**:`db.js` 加 `credit_ledger` 表(M.2)和一对事务性的 `spend`/`refund`/`topup` 函数(扣减/退还/充值时同步更新 `users.creditBalance`,`balanceAfter` 写入时算好,整个操作包一个 SQLite 事务);新建 `pricing.json`(M.12,走 `store.js`),填 `spend_garment` 等占位扣费额 | T18(已完成,`users`/`sessions` 表已建) | 并发发起两次扣积分请求,`creditBalance` 不会因为读-改-写竞态而算错(写个并发测试);余额不够时 `spend` 拒绝而不是扣成负数;`refund` 能把一笔 `spend_garment` 的流水完整冲正,`balanceAfter` 连续可算 | 1.5 | | |
| 100 | +| T24 | **Stripe 充值**:`POST /api/credits/checkout` 创建 Stripe Checkout Session(金额/档位读 `pricing_config` 的 `topup_tier` 系列),`POST /api/credits/webhook` 接 Stripe 的支付成功回调,验证签名后写一笔 `credit_ledger(reason=topup, paymentRef=<Stripe 流水号>)` 并加余额(复用 T23 的 `topup` 事务函数) | T23 | webhook 签名校验失败的请求直接拒绝(不能任何人 POST 一个假的"支付成功"就加积分);同一个 Stripe 事件 ID 重复投递不会重复加两次积分(Stripe 官方保证 at-least-once,端点必须自己去重);**真实验证需要你提供 Stripe 测试模式 API key**,见 §1.2 第 4 条 | 1.5 | | |
| 101 | +| T25 | **衣服图片自动打标**:`describeGarmentImage(imagePath)` 可插拔函数(同 T20 `sendVerificationEmail` 的接口设计思路),未配置视觉模型供应商时返回一句机械占位描述,供应商定了直接换实现,调用方(T27)不用改 | 无 | 传一张图能拿到非空的 `garmentDescriptor` 字符串(哪怕是占位文案),拼进 T7 的 Stage2 prompt 模板不会报错/漏字段 | 1 | | |
| 102 | +| T26 | **衣服图片内容审核**:`moderateGarmentImage(imagePath)` 可插拔函数,未配置真实审核供应商时走最基础的规则版(复用 T2 已有的文件类型/大小校验,额外加一步占位"通过"),返回 `approved`/`rejected` 落一条 `moderation_log`(M.9) | 无 | 每次调用都在 `moderation_log` 落一条记录(`assetType: garment_upload`, `verdict`, `reviewedBy: "system"`);未配置供应商时默认放行,不阻塞模式二的其余部分先跑通 | 1 | | |
| 103 | +| T27 | **模式二后端**:`POST /api/dance/upload-garment`(需登录 + `emailVerified`,M.10 鉴权规则)——图片走 T2 的上传+EXIF清理 → T26 审核 → 不通过直接 400 且不扣积分 → 通过则 T23 扣积分(失败则整单回滚,不留半扣的积分)→ T25 自动打标 → `garments.create({source:"user_paid", ownerUserId, moderationStatus, appearanceDescriptor})` → 复用 T8/T9 现成的 `submitGeneration({characterId: 默认人物, garmentId})` 排入生成;生成失败要退还已扣积分(`refund_stage2_fail`,需求文档 3 节"共同后置流程"的底线) | T23, T25, T26 | 提交一张图能走完整条链路,产出一条视频且带 `characterId`/`garmentId` 溯源;故意让生成失败(mock H3 报错)能验证到积分被退回、流水里有 `refund_stage2_fail` 记录;审核不通过的图片不创建 `garments` 记录、不扣分;`fal.ai` 路径:图片先转发到 fal 可达的存储(同 T2 `forwardToH3Assets` 的思路,写一个 `forwardToFalAssets`,解决需求文档 §8 第 10 条"fal 够不着我们内网 uploads/"的问题)再提交生成 | 1.5 | | |
| 104 | +| T28 | **`dance.html`"传衣服" tab 接线**(T13 当前占位"开发中敬请期待"):未登录/邮箱未验证先引导去登录(复用 T22 的登录模态框);选图 → 展示 `pricing_config` 里当前扣费额 → 二次确认 → 调 T27 → "生成中"状态展示(复用衣柜 tab 现有的排队 UI 思路,不重新设计) | T27 | 未登录点"传衣服"会先弹登录,不会静默失败;确认框里显示的扣费额跟 `pricing_config` 一致,不是前端写死的数字;提交后能看到生成中状态,完成后自动进播放队列 | 1.5 | | |
| 105 | +| T29 | **积分余额 + 充值入口 UI**:登录后 T22 的账号按钮旁/下拉里展示 `creditBalance`;点"充值"跳转 T24 的 Checkout;充值成功跳回后余额刷新(轮询 `/api/auth/me` 或专门加一个 `/api/credits/balance`) | T24 | 充值成功后不用手动刷新页面就能看到余额变化;余额显示跟 `credit_ledger` 汇总值一致 | 0.5 | | |
| 106 | +| T30 | **管理员积分调整 + admin 鉴权迁移**:`POST /api/admin/credit/adjust {userId, delta, reason}`(M.2 `admin_adjust`,写流水+改余额,一样走 T23 的事务函数);顺带把 `/api/admin/*` 的鉴权从 T6 的单管理员口令切到 `users.role==='admin'` 的 `user_session`——**T6 的 `ADMIN_PASSWORD` 环境变量登录保留作为后备通道**(不删,避免 `role='admin'` 账号还没手动建出来之前把自己锁在后台外面),两条鉴权路径任一通过即可 | T23 | 现有管理员账号能通过 `ADMIN_PASSWORD` 照常登录(后备通道没坏);给某个 `users` 行手动置 `role='admin'` 后,用那个账号的 `user_session` 也能访问 `/api/admin/*`;`credit/adjust` 调整后 `moderation_log`/`credit_ledger` 都能查到是哪个管理员做的 | 1 | | |
| 107 | + | |
| 108 | +**总工时约 8 人天**。T23/T25/T26/T30 可以完全并行(约 4.5 人天),关键 | |
| 109 | +路径是 T23→T27→T28,约 4 人天。 | |
| 110 | + | |
| 111 | +--- | |
| 112 | + | |
| 113 | +## 4. 第四期出口 | |
| 114 | + | |
| 115 | +- 免费模式一(衣柜选衣服)行为不受任何影响; | |
| 116 | +- 已登录 + 邮箱已验证的用户能走通一次真实 Stripe 充值(测试模式), | |
| 117 | + 余额正确入账、页面上能看到; | |
| 118 | +- 上传一张衣服照片能走完整条链路:内容审核 → 扣积分 → 自动打标 → | |
| 119 | + 生成 → 排入播放队列 → 播放,且落库时带 `source: user_paid`/ | |
| 120 | + `ownerUserId` 溯源; | |
| 121 | +- 审核通过的衣服默认沉淀进公共衣柜库,免费用户在模式一也能选到它; | |
| 122 | +- 生成失败或审核不通过,已扣积分(如有)必须退还,流水可查; | |
| 123 | +- 管理员能通过 `POST /api/admin/credit/adjust` 手工调整用户积分,且 | |
| 124 | + 这类操作走 `users.role='admin'` 鉴权,不再是"谁知道那个共享口令谁 | |
| 125 | + 就能做任何事"; | |
| 126 | +- 全程只接 Stripe 一个支付渠道,不做多渠道。 | |
| 127 | + | |
| 128 | +达成后才进第五期(模式三付费传人物 + 人脸黑名单 + 排播优先级改造 + | |
| 129 | +fal.ai 剩余用途 + "我的作品"页面)。 | |
| 130 | + | |
| 131 | +--- | |
| 132 | + | |
| 133 | +## 5. 明确不在第四期范围 | |
| 134 | + | |
| 135 | +避免执行时范围蔓延: | |
| 136 | + | |
| 137 | +- 模式三(付费传人物)的任何后端/前端部分,及其"插队播放不参投票"的 | |
| 138 | + 排播优先级改造(第五期,需要动 `ensureSchedule` 选片算法) | |
| 139 | +- 人脸识别黑名单审核(第五期) | |
| 140 | +- `blacklist`(M.11)运营手工拉黑管理页面(第五期;本期的图片审核走 | |
| 141 | + `moderation_log` 记录结果,不做"管理员随手拉黑"这套 CRUD) | |
| 142 | +- "我的作品"个人中心页面(第五期,需求文档 7.4 节) | |
| 143 | +- Stripe 之外的支付渠道(支付宝/微信支付等,需要时再加) | |
| 144 | +- 人物的"剩余可播 10 秒片段数"配额机制(`characters.totalSegments`/ | |
| 145 | + `remainingSegments`,MVCP M.3)——这是模式三专用的字段,第五期跟 | |
| 146 | + 模式三一起做 | ... | ... |