dev-plan-3.md 10.1 KB

换装跳舞直播间 — 第三期开发任务规划

需求见 docs/todo/dressup-dance-room.md(下称"需求文档")第 7.1 节、 第 9 节第 3 条;设计见 docs/todo/dressup-dance-room-mvcp.md(下称 "MVCP")M.1/M.10;架构见 docs/structure/architecture.md(下称"架构 文档")1.4/2.7 节。第一期(dev-plan.md,T1-T10)、第二期 (dev-plan-2.md,T11-T17)均已完成并提交。任务编号接着往下编。

工时是粗估,单位人天,用来排先后不用来考核。


0. 第三期范围(需求文档 9 节第 3 条)

做:用户注册体系上线(邮箱 + 密码登录),把现有匿名 viewer_uid/viewer_nickname cookie 身份绑定到账号——账号是第四期 付费功能的前提,必须先落地,但第三期本身不做任何付费/积分/上传功能。

明确不做(留到后续分期):

  • 积分体系(余额/流水/扣费/退还,M.2 credit_ledger)——第四期。
  • Stripe 充值——第四期。
  • 模式二/三(付费传衣服/传人)——第四期依赖账号,但功能本身也是第四期。
  • Google/Apple/GitHub 三方登录——M.1 oauthProviders 字段预留,第三期 留空数组,不接入任何 OAuth provider。
  • 密码找回邮件的完整产品化 UI(M.10 password_reset 令牌类型的表结构 留出来,但"忘记密码"这个前端入口不是本期出口条件,有余力可顺手做)。
  • 把现有 /api/admin/* 的单管理员口令(T6,ADMIN_SESSION_COOKIE) 迁移成 users.role: admin——T6 现状继续保留不动,避免为了"架构 更统一"牵动已经跑通的后台鉴权;等第四期 POST /api/admin/credit/adjust 这类真正需要按管理员身份审计的接口出现时,再一并把 admin 鉴权切到 users/sessions 表(MVCP M.1 role 字段本来就是为那时候准备的)。
  • admin-users.html(用户/积分管理后台页面,MVCP V.表)——挂在积分 管理上,第四期跟积分体系一起做,第三期没有积分可管。

1. 已确认的技术决策

  • 存储:users/sessions 两张表落 SQLite,不用 JSON 文件——架构 文档 1.4 节明确把"账号 + 密码/凭证存储"列为 JSON 明文文件不适合的 触发条件之一(哪怕 hash 过也建议上 SQLite),现在正是这个触发点。 characters/garments/dance-refs/library 等既有 JSON 存储不 受影响,不做全站数据库迁移。
  • 零新增 npm 依赖:Node 24(当前运行版本已确认)自带 node:sqlite(DatabaseSync,实验性但可直接用)和 node:crypto 的 scryptSync——密码哈希不需要 bcrypt/argon2 这类第三方包, SQLite 不需要 better-sqlite3。这个项目至今没有 package.json, 保持这个状态。
  • 两套 session 互不干扰:现有 admin_session cookie(T6,纯内存 Map,重启即丢)继续只管后台;本期新增的用户登录态用独立的 user_session cookie + sessions 表(M.10),语义、有效期 (30 天)、校验逻辑都是新的一套,不会互相污染。
  • 邮箱验证发信渠道未定:需求文档第 8 节没有把这条列进"待验证 事项",是本文档新发现的缺口——发验证邮件需要一个真实的发信渠道 (SMTP 或 Resend/SendGrid 这类走 HTTP API 的服务,后者不需要额外 依赖,fetch 直接调),这个供应商选型需要产品方拍板,不阻塞 T18-T19 开工(下面 T20 会说明怎么绕开这个阻塞点先把其余部分做完)。

2. 依赖关系

flowchart TD
  T18[T18 SQLite users/sessions 建表] --> T19[T19 注册/登录/退出后端]
  T19 --> T20[T20 邮箱验证]
  T19 --> T21[T21 匿名身份绑定]
  T19 --> T22[T22 登录/注册前端 UI]
  T20 --> T22

  classDef ready fill:#d4edda,stroke:#28a745
  class T18 ready

T18 是唯一的地基任务,其余都要等它先建好表。T19 完成后,T20(邮箱 验证)、T21(绑定匿名身份)、T22(前端 UI)可以并行——T22 前期能先 拿假数据/mock 接口联调(同第二期 T16 的做法),不用死等 T20 邮件通道 真正打通。


3. 任务表

# 任务 依赖 验收标准 工时
T18 SQLite 建表 + 最小查询封装:新建 db.js,用 node:sqlite 的 DatabaseSync 打开 data.sqlite(新文件,不复用任何现有 JSON),按 MVCP M.1/M.10 建 users/sessions 两张表(sessions.kind 先只用到 session/email_verify,password_reset 建表但本期不产生这类行);封装跟 store.js 类似的最小 CRUD 接口(create/get/findByEmail 等),不引入 ORM 无 进程重启后表结构和数据都还在(文件持久化,不是内存 :memory:);email 唯一约束生效(重复注册同邮箱返回冲突而不是插入两行);node --test 能起一个指向临时文件的 DatabaseSync 实例做集成测试,不依赖真实文件系统路径写脏 1
T19 注册 / 登录 / 退出:POST /api/auth/register {email, password}(scryptSync 加盐哈希,email 格式做最基础校验)、POST /api/auth/login、POST /api/auth/logout;登录成功签发 user_session cookie(HttpOnly、SameSite=Lax、30 天,同 T6 ADMIN_SESSION_TTL_MS 的量级但独立常量);提供一个 requireUserSession(req) 中间件式辅助函数供后续接口复用 T18 密码错误/邮箱不存在返回 401 且不泄露"是邮箱错还是密码错"这种细节;重复邮箱注册返回 409;拿到 cookie 后能访问一个临时的 GET /api/auth/me 调试接口拿回 {id, email, emailVerified, nickname};退出后该 cookie 立即失效 1.5
T20 邮箱验证:注册成功后创建一条 sessions(kind='email_verify') 记录(24 小时过期),调用 sendVerificationEmail(email, token)——这个函数本期只实现可插拔的接口:生产模式下若没配置真实发信渠道的环境变量(供应商待第 1 节的开放问题定了再填),直接把邮件内容记日志,同时 NODE_ENV !== 'production' 时把验证链接原样放进 POST /api/auth/register 的响应体,方便本地/测试环境不等真邮件就能跑通全流程;GET /api/auth/verify?token= 校验后把 users.emailVerified 置 true T19 未配置真实发信渠道时,注册接口的开发态响应里能拿到可用的验证链接,访问后 emailVerified 变 true;过期/已用过的 token 访问返回明确错误而不是静默成功;真实邮件发送这部分标注为"待供应商选型解锁",不算进本期验收范围 1
T21 匿名身份绑定:注册/登录成功时,若请求带着现有的 viewer_uid/viewer_nickname cookie(live.html/dance.html 现有机制,纯前端 JS 设置,未加密),把 viewer_uid 写进新建/命中的 users.legacyUid,nickname 字段初始值取自 viewer_nickname(用户后续可改);不做历史投票记录的批量重写——promptQueue/schedule 里的旧 votes 数组本来就是短生命周期数据(条目播完即从队列移除,见架构文档 1.3 节),"绑定"只保证从这一刻起同一个 uid 继续被同一账号复用,不回填过去 T19 带着已有 viewer_uid cookie 的浏览器完成注册后,users.legacyUid 等于 cookie 里的 uid;同一浏览器换个新 cookie 再登录同一账号,投票用的仍是账号绑定的那个 uid(不会每次登录都生成新 uid 导致投票配额被绕过);nickname 从 cookie 迁移过来,不用重新填一遍 0.5
T22 登录/注册前端 UI:复用 live.html/dance.html 现有昵称弹窗的交互套路(同一套按钮/输入框视觉风格),加一个登录/注册的模态框和一个"已登录"状态展示(右上角显示邮箱前缀或 nickname + 退出按钮);未登录时的行为跟现在完全一样(免登录用 viewer_uid 逛/投票),这个模态框本期唯一触发方式是页面上新增的一个"登录"入口按钮,不强制弹出、不阻塞免费模式一的任何现有流程 T19(可先用 mock 数据联调), T20 未登录状态下页面行为和现在(第二期验收时)完全一致,没有引入任何强制登录点;点"登录"能完成注册/登录/退出的完整闭环;刷新页面登录态保持(cookie 生效) 1

总工时约 5 人天。T20/T21/T22 在 T19 完成后可以并行,实际墙钟时间 取决于并行度,关键路径是 T18→T19→T22,约 3.5 人天。


4. 第三期出口

  • 能用邮箱+密码完成注册、登录、退出,users/sessions 落 SQLite;
  • 登录态用独立的 user_session cookie,跟 T6 的 admin_session 互不 干扰,30 天有效期,重启进程/重新登录都不丢;
  • 邮箱验证链路走通(开发态不依赖真实发信渠道也能跑完整闭环);
  • 已有 viewer_uid cookie 的浏览器注册/登录后自动绑定,不丢失现有 的匿名身份延续性;
  • live.html/dance.html 免费模式一的现有体验(第一、二期验收过的 部分)不受任何影响,登录始终是可选项,不是前置门槛;
  • characters/garments/dance-refs/library 等既有 JSON 存储不受 影响,没有做不必要的全站数据库迁移。

达成后才进第四期(积分体系 + Stripe 充值 + 模式二付费传衣服)。


5. 明确不在第三期范围

避免执行时范围蔓延:

  • 积分/充值/退款(M.2 credit_ledger,第四期)
  • 模式二/三的任何后端或前端部分(第四/五期)
  • Google/Apple/GitHub 三方登录的实际接入(字段预留,不实现)
  • 密码找回的前端产品化流程(表结构留出来,UI 可选顺手做)
  • 把 /api/admin/* 迁移到 users.role='admin'(T6 现状保留,第四期 真正需要按管理员身份审计时再切)
  • admin-users.html(第四期,跟积分管理一起做)
  • 真实邮件发信渠道的选型/接入(T20 标注为"待供应商选型解锁",不阻塞 本期其余任务,但也不算完整完成邮件发送这件事本身)