# 换装跳舞直播间 — 第五期开发任务规划 需求见 `docs/todo/dressup-dance-room.md`(下称"需求文档")第 3.3 节模式 三、第 5 节内容审核、第 7.4 节"我的作品"、第 9 节第 5 条;设计见 `docs/todo/dressup-dance-room-mvcp.md`(下称 MVCP)M.3/M.7/M.8/M.9/M.11/ M.13;架构见 `docs/structure/architecture.md`(下称架构文档)2.3/2.6/2.7 节。第一(T1-T10)、第二(T11-T17)、第三(T18-T22)、第四期(T23-T30) 均已完成并提交。任务编号接着往下编。 工时是**粗估**,单位人天,用来排先后不用来考核。 --- ## 0. 第五期范围(需求文档 9 节第 5 条) **做**:付费传人物(模式三)+ 自动人脸黑名单审核 + 人物生命周期(购买 N 个 10 秒片段 / 播完自动下线 / 记录保留可续播)+ 排播优先级改造(付费 新人物插队播放,不参与投票)+ 个人中心"我的作品"页面 + 接入 fal.ai 实时算力路径(4.3 节双算力策略,**这一条同时补上第四期 T27 遗留的一个 缺口**——见下方 §0.1)+ 后台播放管理增强(插播/置顶/跳过/暂停填充/ 拉黑)。 ### 0.1 一个需要先说清楚的事:T27(模式二)从未真正接入 fal.ai `dev-plan-4.md` 当时的 T27 验收标准里写了"fal.ai 路径:图片先转发到 fal 可达的存储…再提交生成",但实际完成的 T27 直接复用了 `submitGeneration` ——**这条路径始终只走 H3 Console,没有走 fal.ai**。也就是说现在付费 传衣服(模式二)用户跟免费衣柜用户排的是同一条约 700s 的生成队列, 没有拿到需求文档 4.3 节承诺的"付费=fal.ai 实时速度(约 4s 级别)"。 原因:`.env` 里从未配置过 `FAL_API_KEY`(fal API key 目前仍只在 `newtask.html` 前端手填,架构文档 1.2 节早就点名这是技术债),没有凭证 就没法真正调用/验证 fal.ai。这不是"忘了写",是"当时没有可用凭证,选 了功能完整但慢的路径先跑通"——跟 T24 处理 Stripe 缺 key 时的思路一致: 先用可工作的路径把主链路跑通,快慢是后续优化。 本期把这条债务一起还上(T33),而不是留到"第六期"——因为模式三本身 也需要走 fal.ai(同一份需求 4.3 节),两条付费路径的实时算力接入天然 应该一起做,不该再拖一次。 **同样的约束仍然存在**:`.env` 里没有 `FAL_API_KEY`。T33 会把 `forwardToFalAssets`/`submitGeneration` 的 fal.ai 分支代码写完,但**真正 验证需要你提供 fal.ai 的 API key**(跟 T24 等 Stripe key 是同一种情况) ——没有 key 之前,T27/T35(模式二/三)会继续退化到走 H3 Console(能跑, 只是慢),不阻塞开发。 **明确不做**(留到之后再评估,不是本期范围): - 支付渠道从 Stripe 拓展到支付宝/微信支付(需求 7.3 节:"需要时再加一 个入口即可",本期不预先做)。 - "我的作品"页面的第三方实名认证(需求 §8 第 7 条,明确说"看实际运营 风险再评估,不预设一定要做")。 - 参考跳舞视频/配乐正式版权素材替换(需求 §8 第 3 条,运营层面的素材 采购,不是开发任务)。 --- ## 1. 已确认的技术决策 + 需要你决定的开放问题 ### 1.1 已确认 - **人物生命周期字段 schema 第一期就建好了**(T1,characters 表的 `totalSegments`/`remainingSegments`/`status`,见 MVCP M.3),本期不需要 改 schema,只是第一次真正用上非 null 的取值。 - **排播优先级走 M.8 定义的 `priorityInbox` 通道**:`ensureDanceSchedule` 每个 tick 优先消费 `priorityInbox`(`paid_boost` 产物),消费完/为空 才回落到现有"`danceQueue` 里挑最高票 → 随机去重填充"逻辑(架构文档 2.6 节已确认,本文档只是落地)。**这是对现有选片算法的新增分支,不 是重写**——`pickBestIndexByStatus` 那条投票排序路径完全不动,模式一 /二的免费内容继续走它。 - **`blacklist`(M.11)和"人脸黑名单"是两张概念上独立的东西**:前者是 运营手工拉黑的本站实体(用户/资产/IP),后者是模型侧对上传照片本身 的自动识别过滤(moderateFaceBlacklist,T32)。两者都要做,但互不 替代——见 MVCP M.11 的警告。 - **`credit_ledger`/`pricing_config` 复用第四期的机制不改**:模式三的 `spend_character_per_segment` 走 `pricing.json` 里 M.12 早就定义好的 `segmentsPerUnit` 字段(扣了多少积分换算成几个 10 秒片段),落 `credit_ledger` 的方式跟模式二完全一样,T23 的 `spend`/`refund`/ `topup`/`adjust` 四个函数不需要再加新方法。 - **fal.ai 分支是"有 key 就用,没 key 就退化",不是新建一套并行系统**: `submitGeneration` 加一个内部路由:付费请求(模式二/三)且 `FAL_API_KEY` 已配置 → 走 fal;其余情况(免费模式一、或付费但没配 key)→ 走现有 H3 Console 路径。调用方(T27 现有代码、T35 新代码)不 用感知这条路由逻辑,同 T20/T24 的可插拔函数设计一脉相承。 ### 1.2 需要你决定,但不阻塞开工 | # | 开放问题 | 现在怎么办 | |---|---|---| | 1 | **人脸识别黑名单供应商**(需求文档 §8 第 4 条,"用什么模型/服务做涉政等敏感人物识别过滤,需要调研候选方案"——目前完全没有候选) | **这条我想先跟你确认默认策略,而不是直接照抄 T26 的"无供应商时默认放行"**:T26 审核的是衣服图片(低敏感度素材,误放行的后果很小);这里审核的是真人人脸+"涉政人物"过滤,误放行的后果是被过滤对象的形象直接进入生成并可能公开播放。默认放行 vs 默认更保守(比如未配置供应商时直接拒绝所有人物上传,模式三先不接受新用户上传,只能等供应商定了再放开)是两种完全不同的产品/风险取舍,我不该替你做这个决定——**见下面的问题**。等你确认后 T32 的可插拔函数按你选的默认值实现 | | 2 | **fal.ai API key**(`FAL_API_KEY`) | 同 T24 处理 Stripe key 的方式:T33 把 fal.ai 集成代码写完(`forwardToFalAssets`、`submitGeneration` 的 fal 分支),**真正调用一次 fal.ai 验证需要你提供 key**;没有 key 时模式二/三继续退化到 H3 Console(能跑,只是慢,不阻塞其余任务) | | 3 | **一步法 prompt 在 fal 模型上是否同样成立**(需求文档 §8 第 1 条历史遗留问题:"S1 的一步法/`attribute_transfer` 写法只在 H3 Console 上验证过,接入 fal 时需要先复核") | 这条**必须**拿到 #2 的 key 后才能真的验证(需要真实调用 fal 的 `minimax/h3*` 端点跑一次换装跳舞视频,对比 H3 Console 的产出)——T33 会把这条验证步骤写进验收标准,但没有 key 就没法执行,不是代码能自己解决的 | | 4 | **模式三的具体积分数值**(充值多少积分对应几个 10 秒片段,`pricing.json` 的 `segmentsPerUnit`) | 跟 T23 处理 `spend_garment` 一样:先填占位数值(比如 `segmentsPerUnit: 1` 表示每份 `spend_character_per_segment` 积分买一个片段),机制打好,数值上线前再改 | --- ## 2. 依赖关系 ```mermaid flowchart TD T31[T31 人物生命周期字段落地] --> T36[T36 播放消耗配额+到期下线] T32[T32 人脸黑名单审核] --> T35[T35 模式三后端] T33[T33 fal.ai 集成] --> T35 T33 --> T27fix[回填 T27: 模式二走 fal 分支] T34[T34 排播优先级 paid_boost/priorityInbox] --> T35 T31 --> T35 T35 --> T36 T35 --> T37[T37 dance.html 传人 tab 接线] T35 --> T38[T38 我的作品页面] T39[T39 播放管理增强 + 后台拉黑] classDef ready fill:#d4edda,stroke:#28a745 class T31,T32,T33,T34,T39 ready ``` T31/T32/T33/T34/T39 互不依赖,可以完全并行开工;T35 是模式三的核心, 依赖前四者都好(桩/占位实现可以先联调,不必等真实供应商/key);T36 依赖 T31+T35;T37/T38 依赖 T35 的接口稳定。 --- ## 3. 任务表 | # | 任务 | 依赖 | 验收标准 | 工时 | |---|---|---|---|---| | T31 | **人物生命周期字段落地**:`characters` 表 `totalSegments`/`remainingSegments`/`status` 从"建好没用"变成"真的读写"——admin.html 人物库列表新增这三列的只读展示(运营排查用);`store.js` 层不需要改,字段已经在 | 无 | 现有 `admin_seed` 人物这三个字段继续是 `null`/`active`(不受影响);能手动构造一条 `totalSegments: 3, remainingSegments: 3, status: "active"` 的人物记录并在 admin.html 正确显示 | 0.5 | | T32 | **人脸黑名单审核**:`moderateFaceBlacklist(imagePath)` 可插拔函数(同 T26 `moderateGarmentImage` 的接口形状),默认策略按你在 §1.2 问题 1 的回答实现;每次调用写一条 `moderation_log`(`assetType: character_upload`) | 你对 §1.2 问题 1 的回答 | 每次调用都落 `moderation_log`;未配置供应商时的行为跟你选定的默认策略一致(不是我自己假设的"默认放行") | 1 | | T33 | **fal.ai 集成**:`forwardToFalAssets(absPath)`(同 T2 `forwardToH3Assets` 的思路,但目标是 fal 自己的存储/签名机制——需要先读 fal.ai 文档确认它接受本地文件转发的具体方式,不能假设跟 H3 Console 的 `/v1/assets` 一样);`FAL_API_KEY` 从 `newtask.html` 前端手填迁移到服务端 `.env`(架构文档 1.2 节技术债);`submitGeneration` 加路由分支:付费请求 + key 已配置 → fal,否则 → 现有 H3 Console 路径 | 无(但真实验证依赖 §1.2 问题 2 的 key) | 无 key 时:所有生成请求行为跟现在完全一致(H3 Console,回归测试见 T27/dance-channel 的既有测试不能红);有 key 时(**需要你提供后才能跑这一条**):一次真实 fal 调用产出的换装跳舞视频人脸/换装/动作三项跟 H3 Console 产出比对合格(§1.2 问题 3),且落库 `backend: "fal"` | 2 | | T34 | **排播优先级改造**:`dance-state.json` 加 `priorityInbox`(M.8),`ensureDanceSchedule` 每个 tick 优先消费它(不经过 `pickBestIndexByStatus` 投票排序),消费完/为空才回落现有逻辑;`generated_videos`(library.json clip)加 `priority: normal \| paid_boost` 字段 | 无 | 写一条 `priorityInbox` 记录,下一个 tick 必须先播它,即使 `danceQueue` 里有票数更高的免费内容在排队;现有模式一/二的投票排序行为不受影响(回归:既有 dance-channel.test.js 不能红) | 1 | | T35 | **模式三后端**:`POST /api/dance/upload-character`(需登录+`emailVerified`,同 T27 鉴权规则)——人物照走 T2 上传+EXIF清理 → T32 人脸黑名单审核 → 不通过直接 400 且不扣积分 → 通过则按 `pricing_config` 的 `spend_character_per_segment`×`segmentsPerUnit` 换算 `totalSegments`(需求 3.3 第 3 点,充多少积分买几个片段)→ T23 扣积分 → 自动打标(复用 T25 `describeGarmentImage` 的接口模式,新增 `describeCharacterImage`)→ `characters.create({source:"user_paid", ownerUserId, totalSegments, remainingSegments: totalSegments, status:"active", ...})` → 衣服可选(body 带 `garmentId` 就用它,否则随机挑一件衣柜库服装,需求 3.3 第 4 点)→ 参考跳舞视频固定随机(3.3 第 5 点,复用现有 `pickRefDanceVideo`)→ 提交生成(T33 fal 优先,回退 H3)→ 产物 `priority: "paid_boost"` 直接进 T34 的 `priorityInbox`,不进投票队列 → 生成失败退还积分(同 T27 的 `refund_stage2_fail` 套路) | T31, T32, T33, T34 | 提交一张人脸照能走完整条链路,产出的人物 `totalSegments` 跟花的积分对得上(改改 `pricing.json` 数值验证换算逻辑不是写死的);人脸黑名单拒绝的照片不创建 `characters` 记录、不扣积分;生成完成后的产物直接进下一个 tick 播放(插队,不用等投票);故意让生成失败能验证到积分退回 | 2 | | T36 | **播放配额消耗 + 到期下线**:`ensureDanceSchedule`(或 `markPlayed`)播放一条 `characterId` 对应 `source:"user_paid"` 人物的 clip 时,该人物 `remainingSegments -= 1`;归零时 `status` 置 `"expired"` 并从排播候选池移除(`pickNextDanceClip`/去重缓存查询都要跳过 `expired` 人物),但**记录/已生成视频不删除**(需求 3.3 第 7 点,架构文档 2.7 节) | T31, T35 | 播放 N 次后(N = 该人物 `totalSegments`)人物自动从排播池消失,后续免费/其他付费请求不会再抽中它;`characters.json` 里这条记录和它名下的 `generated_videos` 全部还在,只是 `status: expired`;再充值续播(T38 之外的后续能力,这里只要求"已生成视频还能被找到复用,不需要重新生成"这条底线成立) | 1 | | T37 | **`dance.html`"传人" tab 接线**(当前占位"开发中敬请期待",同 T28 套路):未登录/未验证先引导登录;选图 + 可选衣服 + 展示"充值 N 积分 = M 个 10 秒片段"换算提示(读 T33 前实现的 `/api/pricing`)→ 二次确认 → 调 T35 → "生成中"状态展示 | T35 | 未登录点"传人"先弹登录;确认框显示的换算跟 `pricing_config` 一致;提交后能看到生成中状态,完成后**直接插播**而不是进普通排队列表(跟模式一/二的"进队列"视觉上要有区分,用户能感知到自己是付费插队) | 1.5 | | T38 | **"我的作品"个人中心页面**(需求 7.4 节):新页面(如 `my-works.html`)展示当前登录用户名下的 `user_paid` 人物/衣服及其生成视频,`status: expired` 的人物依然可见可查(不是软删除后不可访问,需求 7.4 节明确要求) | T35 | 登录用户能看到自己上传的人物/衣服列表;某个人物片段耗尽变 `expired` 后,仍能在这个页面看到它和它的历史视频;未登录访问跳去登录 | 1.5 | | T39 | **播放管理增强 + 后台拉黑**(需求 6.5 节):`admin.html`/`dance.html` 后台加"插播/置顶下一条""手动跳过当前""暂停自动填充"控制;新增 `POST /api/admin/blacklist`(M.11,`targetType: user\|character\|garment\|generated_video\|ip`),`/api/dance/pick`、两个 upload 接口入口先查这张表命中即拒绝,已在库资产被拉黑后从排播候选/衣柜列表过滤(不删除记录) | 无 | 管理员能把某条排队内容置顶为下一条播放;能跳过当前播放;能暂停"票数不够时随机填充"这条兜底逻辑;拉黑一个 `garmentId` 后它立刻从 `/api/dance/wardrobe` 消失、`/api/dance/pick` 对它返回拒绝 | 1.5 | **总工时约 10.5 人天**。T31/T32/T33/T34/T39 可以完全并行(约 6 人天), 关键路径是 T32+T33+T34→T35→T36/T37/T38,约 6.5 人天。 --- ## 4. 第五期出口 - 第一~四期全部行为不受影响(免费模式一、付费模式二、账号、积分/ 充值继续照常工作); - 上传一张人脸照片能走完整条模式三链路:人脸黑名单审核 → 按积分换算 片段数扣费 → 自动打标 → 生成 → **插队播放**(不经过投票); - 人物播完购买的片段数后自动下线,记录/视频保留,用户能在"我的作品" 页面继续查看; - fal.ai 真实凭证到位后(你提供 key),模式二/三的付费请求实测走 fal.ai 实时路径,等待时间从 H3 Console 的约 700s 降到 fal 的约 4s 级别(这条是"到位后验证",key 不到位不算出口条件未达成——跟 T24 处理 Stripe 一样,功能链路完整跑通即算完成,真实供应商验证是独立 的收尾步骤); - 管理员能手工拉黑用户/资产/IP,命中的请求在提交入口直接拒绝;能插播 /跳过/暂停自动填充。 达成后第五期即完成需求文档 9 节列出的全部五期范围;后续(人脸识别真实 供应商选型落地、参考视频版权素材替换、支付渠道拓展、"我的作品"实名 认证)都是运营/采购/风险评估层面的收尾,不再是本项目待开发的功能缺口。 --- ## 5. 明确不在第五期范围 - 支付宝/微信支付等 Stripe 之外的支付渠道; - "我的作品"页面的第三方实名认证; - 参考跳舞视频/配乐正式版权素材采购与替换(运营任务,后台字段已预留); - 多人物同时设为"直播默认人物"(第一期已确认的"全库最多一条"限制不 在本期重新评估)。