# 系统架构文档 覆盖现状(v1,已上线)与规划中的「换装跳舞直播间」(v2,见 `docs/todo/dressup-dance-room.md`)。需求范围见对应文档,本文档只谈 组件划分、数据流、集成方式和存储演进。 ## 1. 现状架构(v1) ### 1.1 组件 ```mermaid flowchart LR subgraph Browser live[live.html
直播间/浏览] admin_ui[admin.html
素材后台] end subgraph Node["server.js (单文件 Node http server, PORT 8934)"] api[路由处理
/api/*, /lib/video/*] sched[schedule + promptQueue
2s tick] ingest[ingest.js
syncLibrary 每5分钟] end libjson[(library.json)] statejson[(live-state.json)] h3[(H3 Console
内部 MiniMax H3 集群
/v1/videos)] fal[(fal.ai queue API
/fal/* 反代)] live -- 轮询 /api/live/state, /api/chat --> api live -- POST /api/chat, /api/prompt, /api/queue/vote --> api admin_ui -- CRUD /api/admin/library --> api api -- 读写 --> libjson sched -- 落盘 --> statejson api --> sched sched -- POST /v1/videos, 轮询状态 --> h3 api -- GET /v1/videos/:id/file 代理 --> h3 ingest --> h3 ingest -- 重建 --> libjson legacy[index.html/app.js/newtask.html
独立的 fal.ai 任务提交小工具
与直播间无数据关联] -.-> fal ``` ### 1.2 关键设计点 - **单进程、无框架、无数据库**:`server.js` 用 Node 原生 `http` 模块手写 路由,持久化就是两个 JSON 文件(`library.json` 素材库、`live-state.json` 直播状态),全量读写、无锁、无并发写保护——量级小的场景下够用,见 1.4 存储演进建议里什么时候该换。 - **服务端权威时钟**:直播"当前播放的是哪条"由服务端 `schedule` 数组 决定,浏览器只是被动轮询同步,杜绝"每个观众各自播各自的"——这是 "直播感"的核心,v2 任何改造都不能破坏这个前提。 - **H3 Console 是外部服务,本项目只是它的一个消费方**:内部 MiniMax H3 视频生成集群,独立部署、独立鉴权(`H3_CONSOLE_TOKEN`),契约见 `H3-CONSOLE.md`。`server.js` 里所有跟"生成视频"相关的逻辑,都是在编排 这个外部 API(提交任务 → 轮询 → 落库),本身不做任何 GPU 推理。 - **`/fal/*` 反代通道**:转发到 `queue.fal.run`(fal.ai 官方队列 API), 目前只被仓库里独立的 `index.html`/`app.js`/`newtask.html`/`taskdetail.html` 这套"手动提交 H3/H3-Max 生成任务"小工具使用,跟直播间(`live.html`) 没有数据关联。**v2 的付费实时生成路径会正式启用这条通道**,理由见 2.3。 - **无鉴权边界**:管理后台 (`admin.html`) 和所有写接口都没有应用层鉴权, 依赖网络可达性(内网/反代层面控制)。v2 引入用户账号体系后,管理后台 这块的"零鉴权"现状需要重新评估(至少后台管理接口应该要求管理员身份, 这不是本次需求主线但是个不能一直拖的技术债)。 ### 1.3 直播时刻表状态机(供 v2 复用/扩展参考) ```mermaid sequenceDiagram participant V as 观众浏览器 participant S as server.js participant H3 as H3 Console V->>S: POST /api/chat {text} S->>S: moderateText() 关键词审核 alt 命中 library.json S-->>V: {status: matched} else 未命中 S->>H3: POST /v1/videos {prompt} S-->>V: {status: generating} loop 轮询直到 done/error S->>H3: GET /v1/videos/:id end S->>S: 写入 library.json, 状态置 ready end Note over S: ensureSchedule() 每 2s tick
按投票数从 ready/matched 里选片
不够则从素材库随机填充 loop 每 4s V->>S: GET /api/live/state S-->>V: {current, upNext, queue} end Note over V: 前端用 endAt 精确定时切片
轮询只是纠偏/兜底 ``` ### 1.4 存储演进建议 JSON 文件方案在 v1(单一素材类型、无账号、无并发写压力)是合适的 "够用就好"选择。v2 一旦引入下列任一项,建议评估迁移到嵌入式数据库 (如 SQLite,单文件、无需额外部署,跟现有"零运维依赖"的风格最接近): - 用户账号 + 密码/凭证存储(JSON 明文文件不适合放凭证,即便是 hash 后 的也建议至少上 SQLite)。 - 积分流水 + 人物"剩余可播 10 秒片段数"这类计数器(需要"扣积分 + 创建 生成任务 + 扣减片段配额"这几步操作的原子性,全量重写 JSON 文件在 并发下有丢更新风险)。 - 资产种类变多(人物/衣服/参考视频/合成图/生成视频五张表,互相有外键 引用关系,继续用一个大 JSON 数组的心智负担会明显上升)。 不建议现在就迁移——v2 第一期(`docs/todo` 第 9 节分期建议)如果只是先 用内部 H3 Console 跑冷启动种子内容、不涉及账号和并发写,继续加 JSON 文件(比如新增 `characters.json`/`garments.json`/`dance-refs.json`, 沿用 `library.json` 的读写模式)完全够用;到第三/四期引入账号+积分时 再迁移,避免过早优化。 ## 2. 规划架构(v2:换装跳舞直播间) ### 2.1 两个独立的直播间页面 `live.html`(文字点播)与 `dance.html`(换装点播)是**两条并行的产品 线**,不是同一个页面的两个 tab: - 各自维护独立的 `schedule`/`promptQueue`(各自的播出时刻表和排队), 互不影响对方的播放进度。 - 共用同一套底层基础设施:素材库读写模式(`library.json` 风格)、 H3 Console/fal.ai 生成编排、观众身份(cookie/账号)、投票机制、 在线人数统计。 - `dance.html` 复用 `live.html` 的视觉语言(同一套 CSS 风格、播放器、 排队列表 UI),保留在线人数/点赞/排队展示,**唯一的差别是左侧点播 面板**:`live.html` 是文字输入框,`dance.html` 是"衣柜选择 / 传衣服 / 传人"操作面板,没有文字输入。 ### 2.2 新增组件 ```mermaid flowchart TB subgraph Browser dance[dance.html
衣柜/传衣服/传人,无文字输入] admin2[admin.html
+ 人物/衣服/参考视频/用户 管理页] end subgraph Node["server.js(扩展)"] api2[路由扩展
/api/dressup/*, /api/admin/characters, garments, dance-refs, users] pipeline[生成流水线编排
Stage1 → Stage2
按请求类型路由到不同算力] moderation[审核
模式二:图片内容审核
模式三:人脸黑名单过滤] credit[积分校验/扣减/退还
+ 人物剩余片段配额] end charjson[(characters.json)] garmjson[(garments.json)] dancejson[(dance-refs.json)] libjson2[(library.json
+ characterId/garmentId/refDanceVideoId 溯源字段)] userdb[(users + credits
建议 SQLite,见 1.4)] fal2[(fal.ai
Stage1 换装图合成
+ 付费实时 Stage2 视频生成)] h32[(内部 H3 Console
冷启动/后台种子内容
批量 Stage2 视频生成)] dance -- 免费:选衣柜 --> api2 dance -- 付费:传衣服/传人 --> api2 api2 --> moderation api2 --> credit moderation -- 通过 --> pipeline credit -- 扣减通过 --> pipeline pipeline -- Stage1 --> fal2 fal2 -- 换装参考图 --> pipeline pipeline -- Stage2 冷启动/种子 --> h32 pipeline -- Stage2 付费实时 --> fal2 h32 -- 跳舞视频 --> pipeline fal2 -- 跳舞视频 --> pipeline pipeline -- 落库 --> libjson2 admin2 --> charjson admin2 --> garmjson admin2 --> dancejson admin2 --> userdb ``` ### 2.3 双算力策略:冷启动 vs 付费实时(已确认,见 `docs/todo` 4.3) Stage2(视频生成)按"是否是付费实时请求"路由到两条不同算力: - **冷启动/后台种子内容**(admin 批量预生成,铺满衣柜库/默认人物库) → 走内部 H3 Console(免费,但单条约 200s,见 `H3-CONSOLE.md`)。这条 路径不面向单个观众的实时请求,批量跑不受时间压力。 - **观众付费实时请求**(模式二付费传衣服、模式三付费传人物)→ 走 fal.ai 云端(`minimax/h3*` 系列端点,约 4s 级别,见 `H3-CONSOLE.md` 的实测对比),成本由观众支付的积分覆盖。 Stage1(换装图合成)两条路径下都走 fal.ai——H3 Console 本身是纯视频 生成 API,不产图,见 `H3-CONSOLE.md` 明确说明。这也是 `/fal/*` 反代 通道(1.2 节)真正被直播间业务启用的地方:现状它只服务于仓库里跟 直播间无关的旧工具,v2 里会成为 Stage1 全量、以及付费 Stage2 的核心 依赖。fal API Key 现状是前端 `newtask.html` 里手填(见 1.2 节代码原样), v2 必须挪到服务端 `.env`,不能再让 key 走浏览器。 `reference_video_urls` 这个字段目前只是从 H3 Console 控制台 UI tab 推断存在、未经本项目实测调用过(`H3-CONSOLE.md` 原话),v2 落地 Stage2 前第一步就是用真实调用验证这条路径,而不是假设它一定按预期工作 (`docs/todo` 第 8.2 条)。 ### 2.4 视频规格:默认竖屏,移动端优先 `dance.html` 的生成请求默认用 **480×832**(早期横屏默认 832×480 的 宽高对调),总像素量与现状完全一致,理论上不会拖慢 H3 Console/fal.ai 的生成耗时(`docs/todo` 4.4 节)。两个直播间的规格是各自独立的 `H3_GEN_PARAMS` 配置,互不共享——`live.html` 一侧后来也独立改成了 竖屏默认(`docs/done` 2.2 节),只是刚好两边选型一致,不是同一份配置 联动的结果。播放器 UI 上,`dance.html` 需要一套按 9:16 设计的播放器 容器,不能直接复用 `live.html` 现有播放器的宽高比假设。 ### 2.5 与现状的集成点 - **浏览三 tab 不动**:v2 生成的视频最终落地也是 `library.json` 风格的 一条 clip(多了几个溯源字段),复用现状浏览逻辑,`dance.html` 只是 拥有自己独立的一份 schedule 实例(2.1 节)。 - **"生成中/排队中"展示复用**:换装流水线本质也是"提交 → 后台异步生成 → 完成后进队列",跟现有 `generateForPrompt` 的状态机(generating → ready/failed,任务 id 落盘可断点续传)同构,Stage2 直接复用这套逻辑, Stage1 失败则更早终止。 - **`ensureSchedule` 选片逻辑需要实质性扩展**,不是"复用不动"——见 2.6。 ### 2.6 排播优先级 + 去重缓存(已确认,对 v1 排片算法的改动) `dance.html` 的 `promptQueue`/`schedule` 不能直接照抄 v1 的"纯投票排序" 逻辑,需要两处新行为(`docs/todo` 3 节"共同后置流程"、4.6 节): - **免费模式一走"先查缓存再生成"**:提交时先用 `(characterId, garmentId)` 查是否已有产物,命中直接排入队列(跟 v1 `findLibraryMatch` 是同一个套路,只是匹配维度从文本相似度换成资产 id 组合),未命中才真正走 Stage1/Stage2。免费内容排入队列后仍按 v1 现有投票机制排序。 - **付费模式三新人物内容插队播放**:Stage2 完成后**不进入投票排序**, 而是直接插入 `schedule`(紧跟在当前播放的片段之后,或队首),跳过 `pickBestIndexByStatus` 这条"按票数选"的路径。`ensureSchedule` 需要 一个新的"优先插入"通道,跟现有"从 promptQueue 里挑最高票"逻辑并行 存在,而不是复用同一个排序比较器。 - 这两点意味着 `dance.html` 的排片模块虽然复用 v1 的整体结构(数据 持久化到 `live-state.json` 风格的文件、`ensureSchedule` 定时 tick 的 思路),但选片函数本身要重写,不是简单的参数化复用。 ### 2.7 用户账号、积分与人物生命周期 - 现有 `viewer_uid`/`viewer_nickname` cookie 身份保留作为过渡态;免费 模式一(选衣柜)不强制登录。上传新资产/花积分(模式二、三)才要求 邮箱登录(`docs/todo` 7.1)。注册后把 cookie `uid` 绑定到账号 id, 历史投票/点播记录不丢。 - 积分校验点:提交付费流水线(Stage1 触发前)先校验+预扣积分,Stage1 或 Stage2 失败要退还——这个扣减+退还路径要求原子性,是 1.4 节里 "该上 SQLite 了"的触发点之一。 - **人物生命周期**(`docs/todo` 3.3 节第 7 点、9.5 期):付费生成的新 人物带一个"剩余可播 10 秒片段数"配额,每播放一段消耗一份;配额耗尽 自动从排播池移除(但人物、换装图、已生成视频全部保留,不删除), 用户再充值可以直接续播,无需重新走 Stage1/Stage2——这意味着"人物" 和"该人物名下的已生成视频"要能独立于"当前是否在直播池中"被检索到, 数据模型上是一个显式的 `status: active|expired` + 关联的积分流水, 不是简单的软删除。 ## 3. 待验证事项汇总(技术验证,非产品决策分歧) 避免重复散落在需求文档各处,详见 `docs/todo` 第 8 节: | 待验证项 | 影响范围 | |---|---| | Stage1 换装模型在 fal.ai 上的具体选型 | 换装一致性、速度、成本 | | `reference_video_urls` 在 H3 `ref2va` 下的实测效果 | Stage2 冷启动路径是否可行 | | fal.ai 端 `minimax/h3*` 端点是否支持同等的多参考图生视频 | Stage2 付费实时路径是否可行 | | 人脸识别黑名单模型/服务选型 | 模式三自动审核能力 | | 图片内容审核供应商/接口 | 模式二审核能力 | | JSON 文件 → SQLite 的迁移时机 | 存储层改造范围,见本文档 1.4 | | 支付网关选型(初步倾向 Stripe,需确认是否要叠加支付宝/微信支付) | 充值流程开发方向,见 `docs/todo` 7.3 |