dev-plan-4.md
13.1 KB
换装跳舞直播间 — 第四期开发任务规划
需求见 docs/todo/dressup-dance-room.md(下称"需求文档")第 3.2 节模式二、
第 5 节内容审核、第 7 节积分与充值、第 9 节第 4 条;设计见
docs/todo/dressup-dance-room-mvcp.md(下称"MVCP")M.2/M.4/M.9/M.12;
架构见 docs/structure/architecture.md(下称"架构文档")1.4/2.3 节。第一
(T1-T10)、第二(T11-T17)、第三期(T18-T22)均已完成并提交。任务编号
接着往下编。
工时是粗估,单位人天,用来排先后不用来考核。
0. 第四期范围(需求文档 9 节第 4 条)
做:积分体系(余额/流水/配置化扣费)+ Stripe 充值接入,上线免费 换衣服(已有)+ 付费传衣服(模式二),验证"扣费→生成→失败退还→ 审核通过后沉淀进公共库"这条完整链路。
明确不做(留到第五期,需求文档 9 节第 5 条):
- 模式三(付费传人物)——包括它的核心业务规则"付费新人物插队播放,
不参与投票"(架构文档 2.6 节
priority: paid_boost),跟模式二完全 不同,本期不碰排播优先级逻辑。模式二内容仍然走 v1 现有投票机制 排序,需求文档 3 节"共同后置流程"里写得很明确:只有模式三跳过 投票,模式二不跳。 - 人脸识别黑名单审核(模式三专用,需求文档 5 节)。
blacklist(MVCP M.11)完整的运营手工拉黑管理页面——本期只做moderation_log(M.9)记录自动审核结果,不做"管理员随手拉黑一个 用户/资产"这套后台 CRUD。- "我的作品"个人中心页面(需求文档 7.4 节)。
- 支付宝/微信支付等 Stripe 之外的渠道(7.3 节已说"需要时再加一个入口 即可",本期不预先做)。
1. 已确认的技术决策 + 需要你决定的开放问题
1.1 已确认
credit_ledger落 SQLite,跟users/sessions同一个data.sqlite(复用 T18 的db.js)——架构文档 1.4 节点名了"扣积分 + 创建生成 任务 + 扣减片段配额"这几步操作需要原子性,JSON 全量重写文件在并发下 会丢更新,这正是当初选 SQLite 的另一半理由(T18 只先用上了"密码存储" 那一半)。node:sqlite的DatabaseSync支持事务 (db.exec("BEGIN")...COMMIT/ROLLBACK),扣积分和退还都要包在事务里。pricing_config(M.12)走 JSON 文件,不进 SQLite——它是运营偶尔 手改的配置,不在"扣积分"这条热路径的原子性要求里,复用store.js的模式(类似characters.json)比再建一张表更省事,跟"能用现成的 就别加新东西"的项目风格一致。garments/characters的source/ownerUserId/moderationStatus字段第一期就已经建好(T1,见 MVCP M.3/M.4),本期不需要动 schema,只是第一次真正用上user_paid/pending这些取值。- 文档发现一处不一致,按需求文档为准:架构文档 2.2 节的流程图把 fal.ai 接入标成"第五期",但需求文档 §8 第 10 条明确写"到第四期 模式二付费实时路径上线时再解决",且本文档 §9 第 4 条本来就是"上线 …付费传衣服(模式二)"。按需求文档更新、更具体的说法为准:fal.ai 接入是本期(第四期)的任务,架构文档那张图的标注是旧的,没跟上 后来的分期方案,此处不改架构文档原文,只在这里记一笔。
1.2 需要你决定,但不阻塞开工(每条都设计成可插拔接口,供应商定了随时接进来)
| # | 开放问题 | 现在怎么办 |
|---|---|---|
| 1 | 图片内容审核供应商(需求文档 5 节提到"需要图片内容审核能力(供应商待定)",但第 8 节居然没有专门列这一条——本文档补上这个缺口) | T26 先接一个 moderateGarmentImage() 可插拔函数,未配置真实供应商时走最基础的规则版兜底(文件类型/尺寸已在 T2 做过,这里只做"占位通过"),同 T20 sendVerificationEmail() 的套路 |
| 2 | 视觉模型供应商(需求文档 §8 第 8 条,给用户上传的衣服图自动生成 garmentDescriptor,因为模式二没有管理员手写这一步) |
T25 同样做成可插拔的 describeGarmentImage(),未配置真实供应商时退化成一句机械占位描述(如"用户上传的服装"),生成流程照常走,只是效果不如手写描述精细——记在验收标准里,不当阻塞项 |
| 3 | 积分具体数值(需求文档 §8 第 5 条:充值档位、每次传衣服扣多少积分) | pricing_config 里先填占位数值(如 spend_garment: 10 积分),字段/机制先打好,数值上线前再改,MVCP M.12 原话就是这个意思 |
| 4 | Stripe 真实账号的 API key(STRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRET) |
我这边没有你的 Stripe 账号,没法自己申请测试模式 key。T24 的代码可以先写完,但真正跑通一次真实 Checkout 需要你提供测试模式的 key 放进 .env,这条我做不到自己解决,标在这里等你 |
2. 依赖关系
flowchart TD
T23[T23 credit_ledger + pricing_config] --> T24[T24 Stripe 充值]
T23 --> T27[T27 模式二后端]
T25[T25 衣服图片自动打标] --> T27
T26[T26 衣服图片内容审核] --> T27
T27 --> T28[T28 传衣服前端接线]
T24 --> T29[T29 积分余额/充值 UI]
T23 --> T30[T30 admin 积分调整 + admin 鉴权迁移]
classDef ready fill:#d4edda,stroke:#28a745
class T23,T25,T26,T30 ready
T23/T25/T26/T30 互不依赖,可以并行开工;T27 是模式二的核心,要等 T23(能扣积分)、T25(能打标)、T26(能审核)都好了才能真正端到端跑通 (三者的桩/占位实现都能先联调,不必等真实供应商);T28 是前端接线, 需要 T27 的接口稳定;T29 只依赖 T24。
3. 任务表
| # | 任务 | 依赖 | 验收标准 | 工时 |
|---|---|---|---|---|
| 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 |
| 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 |
| T25 | 衣服图片自动打标:describeGarmentImage(imagePath) 可插拔函数(同 T20 sendVerificationEmail 的接口设计思路),未配置视觉模型供应商时返回一句机械占位描述,供应商定了直接换实现,调用方(T27)不用改 |
无 | 传一张图能拿到非空的 garmentDescriptor 字符串(哪怕是占位文案),拼进 T7 的 Stage2 prompt 模板不会报错/漏字段 |
1 |
| T26 | 衣服图片内容审核:moderateGarmentImage(imagePath) 可插拔函数,未配置真实审核供应商时走最基础的规则版(复用 T2 已有的文件类型/大小校验,额外加一步占位"通过"),返回 approved/rejected 落一条 moderation_log(M.9) |
无 | 每次调用都在 moderation_log 落一条记录(assetType: garment_upload, verdict, reviewedBy: "system");未配置供应商时默认放行,不阻塞模式二的其余部分先跑通 |
1 |
| 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 |
| T28 | dance.html"传衣服" tab 接线(T13 当前占位"开发中敬请期待"):未登录/邮箱未验证先引导去登录(复用 T22 的登录模态框);选图 → 展示 pricing_config 里当前扣费额 → 二次确认 → 调 T27 → "生成中"状态展示(复用衣柜 tab 现有的排队 UI 思路,不重新设计) |
T27 | 未登录点"传衣服"会先弹登录,不会静默失败;确认框里显示的扣费额跟 pricing_config 一致,不是前端写死的数字;提交后能看到生成中状态,完成后自动进播放队列 |
1.5 |
| T29 | 积分余额 + 充值入口 UI:登录后 T22 的账号按钮旁/下拉里展示 creditBalance;点"充值"跳转 T24 的 Checkout;充值成功跳回后余额刷新(轮询 /api/auth/me 或专门加一个 /api/credits/balance) |
T24 | 充值成功后不用手动刷新页面就能看到余额变化;余额显示跟 credit_ledger 汇总值一致 |
0.5 |
| 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 |
总工时约 8 人天。T23/T25/T26/T30 可以完全并行(约 4.5 人天),关键 路径是 T23→T27→T28,约 4 人天。
4. 第四期出口
- 免费模式一(衣柜选衣服)行为不受任何影响;
- 已登录 + 邮箱已验证的用户能走通一次真实 Stripe 充值(测试模式), 余额正确入账、页面上能看到;
- 上传一张衣服照片能走完整条链路:内容审核 → 扣积分 → 自动打标 →
生成 → 排入播放队列 → 播放,且落库时带
source: user_paid/ownerUserId溯源; - 审核通过的衣服默认沉淀进公共衣柜库,免费用户在模式一也能选到它;
- 生成失败或审核不通过,已扣积分(如有)必须退还,流水可查;
- 管理员能通过
POST /api/admin/credit/adjust手工调整用户积分,且 这类操作走users.role='admin'鉴权,不再是"谁知道那个共享口令谁 就能做任何事"; - 全程只接 Stripe 一个支付渠道,不做多渠道。
达成后才进第五期(模式三付费传人物 + 人脸黑名单 + 排播优先级改造 + fal.ai 剩余用途 + "我的作品"页面)。
5. 明确不在第四期范围
避免执行时范围蔓延:
- 模式三(付费传人物)的任何后端/前端部分,及其"插队播放不参投票"的
排播优先级改造(第五期,需要动
ensureSchedule选片算法) - 人脸识别黑名单审核(第五期)
blacklist(M.11)运营手工拉黑管理页面(第五期;本期的图片审核走moderation_log记录结果,不做"管理员随手拉黑"这套 CRUD)- "我的作品"个人中心页面(第五期,需求文档 7.4 节)
- Stripe 之外的支付渠道(支付宝/微信支付等,需要时再加)
- 人物的"剩余可播 10 秒片段数"配额机制(
characters.totalSegments/remainingSegments,MVCP M.3)——这是模式三专用的字段,第五期跟 模式三一起做