app-template/全流程链路测试报告.md

337 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 帆幕(Fanmu)产品全流程链路测试报告
- **测试日期**:2026-09-26
- **测试方式**:真实浏览器(Playwright)逐流程操作 + 接口层复现取证
- **测试环境**:
- 主测实例:`http://localhost:4321`(Docker 容器,生产构建 `NODE_ENV=production`)
- 对照实例:`http://127.0.0.1:4327`(本地 `astro dev`,用于定位根因)
- 片源夹具:临时本地 WebDAV(`tmp-e2e-dav/`,2 个 webm 样例视频),测试后已停止
- **测试账号**:`e2e_tester`、`e2e_mail`(主实例)、`dev2`、`dev3`(dev 实例,密码已重置为 `Devpass456`)
- **注意**:主实例中 `e2e_tester` 的挂载已在「删除挂载」测试中删除;媒体库残留 1 条刮削条目(见问题 P2-5)
---
## 一、测试结论摘要
**主链路可以走通**:注册登录 → 挂载添加/测试连接 → 目录浏览 → 扫描入库 → 媒体库(列表/海报墙/匹配刮削)→ 播放(流代理、进度保存)→ 弹幕(导入、渲染、开关/密度/透明度)→ 登出/再登录回跳,均实测通过,且鉴权回跳、多用户隔离、i18n 切换等细节闭环做得不错。
**但存在 3 处闭环断裂(P1)**:
1. 登录后访问 `/dashboard` 必现 500(用户菜单「仪表盘」是死链);
2. **生产模式下找回密码流程无法完成**(无令牌、无邮件通道);
3. **播放页导入的弹幕刷新即丢**(服务端只存了字节数,不存内容)。
另有删除挂载无确认/留孤儿数据、校验错误暴露原始 JSON 等一批 P2 可用性问题。
**第二轮(播放能力专项,见第七章)**:流代理 Range/206/416 全部规范;进度上报与续播精确(首页/媒体库双入口带百分比);多集切换与中间集结束引导完整;弹幕时间轴联动正常。新增发现:**孤儿集条目混入剧集列表、点中即黑屏且无错误提示(P2 升级)**、`/api/stream` 错误码语义问题,以及快捷键 f/m 不可用、末集无结束引导等 P3 项。
---
## 二、全流程闭环矩阵
| # | 流程 | 实测结果 | 判定 |
|---|------|---------|------|
| 1 | 首页 → 各导航入口 | 5 个主入口全部可达,编号导航清晰 | ✅ 闭环 |
| 2 | 游客访问受保护页(/library 等) | 302 → `/auth/login?next=…`,登录后回跳原页(实测回跳 `/mounts` 成功) | ✅ 闭环 |
| 3 | 错误凭据登录 | toast「用户名或密码不正确」 | ✅ 闭环 |
| 4 | 注册(弱密码 123) | 浏览器原生校验拦截(min=8),但表单无前置规则提示 | ⚠️ 可用 |
| 5 | 注册(合法) | 成功 → 首页 → 顶栏显示用户名,自动登录 | ✅ 闭环 |
| 6 | 挂载:非法地址保存 | 400 返回,但 toast 显示**原始 JSON 数组** | ❌ 不友好 |
| 7 | 挂载:空名称点保存 | 无任何提示,仅静默聚焦到名称字段 | ⚠️ 隐晦 |
| 8 | 挂载:保存成功 | 列表即时刷新、计数+1,但**无成功 toast** | ⚠️ 反馈弱 |
| 9 | 挂载:测试连接(坏地址/好地址) | 内联显示「连接失败 / 连接成功」,失败无原因说明 | ⚠️ 可用 |
| 10 | 挂载:编辑(含密码留空语义) | 进入编辑态、卡片标「编辑中」、密码占位「留空保持不变」 | ✅ 闭环 |
| 11 | 文件浏览:目录/面包屑/上一级/根目录 | 全部正常,中文目录名处理正确 | ✅ 闭环 |
| 12 | 文件浏览:目录读取失败 | 显示「目录读取失败」,无重试按钮、无具体原因 | ⚠️ 弱 |
| 13 | 扫描入库 | 「扫描完成:2 个文件」+ CTA「去媒体库整理」 | ✅ 闭环(优秀) |
| 14 | 媒体库:列表视图 | 已扫条目齐全,带「播放」「匹配」操作 | ✅ 闭环 |
| 15 | 媒体库:海报墙(未刮削时) | 空白 + 文案;不提示切列表视图;**搜索无结果复用同一文案** | ⚠️ 误导 |
| 16 | 匹配刮削(Bangumi 搜索→绑定) | 搜索结果正常,绑定后改名、入海报墙分组、匹配按钮消失 | ✅ 闭环(优秀) |
| 17 | 播放(流代理 + Range) | 正常播放,时长正确,进度到 00:04/00:04 | ✅ 闭环 |
| 18 | 播放进度持久化 | 服务端 `positionMs=4000` 每 5s/暂停/离开时上报;看完整片从头播(合理) | ✅ 闭环 |
| 19 | 弹幕导入 XML | 「弹幕 5 条」+ 逐条渲染上屏 | ⚠️ **刷新即丢**(P1) |
| 20 | 弹幕设置(开关/密度/透明度) | 保存 toast「已保存(本地会话)」,播放页即时生效 | ✅ 闭环(本地) |
| 21 | 修改密码(错误/正确当前密码) | 「当前密码不正确」/「密码已更新」,当前会话保留 | ✅ 闭环 |
| 22 | 登出 | 顶栏恢复 登录/注册,数据清空 | ✅ 闭环 |
| 23 | 找回密码(不存在邮箱) | 「若邮箱存在,重置申请已受理」(防枚举正确) | ✅ 闭环 |
| 24 | 找回密码(存在邮箱,**生产实例**) | 响应 `{}`:无令牌、无邮件通道 → **流程无法完成** | ❌ **断裂** |
| 25 | 找回密码(dev 实例) | 令牌 +「使用此令牌重置」→ 重置页令牌预填 → 新密码登录成功 | ✅ 闭环(仅 dev) |
| 26 | 删除挂载 | **无二次确认、无成功提示,直接删除** | ❌ 不安全 |
| 27 | 删除挂载后播放旧条目 | 流 502,播放器只显示「重新连接: 5」,无业务解释 | ❌ 孤儿数据 |
| 28 | 用户菜单「仪表盘」 | `/zh-CN/dashboard` **500**(dev:ResponseSentError) | ❌ **断裂** |
| 29 | 404 页 | 正确 404 状态码 + 说明 + 返回首页 | ✅ 闭环 |
| 30 | 中英文切换 | cookie 写入、路径保留、英文界面翻译完整 | ✅ 闭环 |
| 31 | 多用户数据隔离 | `e2e_mail` 看不到 `e2e_tester` 的挂载 | ✅ 闭环 |
| 32 | 媒体库搜索 | 命中正常;**无结果时文案误导**(见 P3-11) | ⚠️ 弱 |
| 33 | 无挂载空态(浏览页) | 禁用下拉 + 「添加挂载」CTA 跳转正确 | ✅ 闭环(优秀) |
---
## 三、问题清单
### P1 — 功能闭环断裂(必须修复)
**P1-1 登录后访问仪表盘必现 500(用户菜单死链)**
- 复现:登录任意账号 → 顶栏头像菜单 →「仪表盘」→ 页面显示 Internal server error。
- 根因(dev 实例报错页证实):`app/web/src/views/dashboard.astro` 在流式渲染中执行 `return Astro.redirect(…)`,抛出 `ResponseSentError: The response has already been sent`(`astro/render.js:158`)。
- 影响:菜单常驻入口是死链;现有 e2e 未覆盖登录后 dashboard,故未被发现。
- 建议:把重定向移到 `pages/[locale]/dashboard.astro`(页面级、渲染前)或中间件层;或干脆让 `/dashboard` 直接渲染媒体库内容。并补一条登录态 e2e。
**P1-2 生产模式下「找回密码」无法完成**
- 复现(生产实例):注册带邮箱账号 → 申请重置 → 接口 200 但响应 `{}`(`auth.service.ts` 中 `if (IS_PROD) return {}`),页面无令牌、后端无邮件发送实现 → 用户拿不到重置令牌,流程终止。
- dev 实例有「开发环境令牌(生产请接入邮件发送)」兜底,生产没有任何兜底。
- 建议:接入邮件发送(SMTP/API),或至少在生产日志/管理端暴露令牌并给出明确的「请联系管理员」降级提示;上线前把该流程列入验收。
**P1-3 播放页导入弹幕刷新即丢**
- 复现:播放页「导入本地 XML」→ 显示「弹幕 5 条」并渲染 → 刷新/离开页面 → 变回「暂无在线弹幕」。
- 根因:`danmakuService.importMeta` 只校验 `byteSize` 后返回 `{ok:true}`,**XML 内容不落库也不落 localStorage**;内容仅存在 `WatchBody` 组件 state。
- 影响:核心卖点「弹幕观影」的导入闭环断裂,且用户被成功 toast 误导认为已保存。
- 建议:将 XML 存入服务端(按 `mediaItemId` 持久化,或复用 `danmakuCacheDao`),或至少写 localStorage;并把 `danmakuSaveSettings/GetSettings` 的服务端桩实现补真。
### P2 — 可用性与数据安全(应当修复)
**P2-4 删除挂载无确认、无成功反馈**
- 点「删除」立即生效:无确认对话框、无撤销、无 toast,列表直接清空。挂载配置录入成本高,误触即丢。
- 建议:加确认(名称确认或二次弹窗)+ 删除成功 toast。
**P2-5 删除挂载产生孤儿媒体条目**
- 实测:删除挂载后媒体库海报/剧集仍在,点播放 → `/api/stream` 502,播放器仅显示「重新连接: 5」重试耗尽,无「片源已失效/挂载已删除」说明,也无处理入口(删除/重新挂载后重扫)。
- 建议:删除挂载时提示影响范围(N 个条目将失效)并可选级联清理;条目侧检测挂载缺失时给出业务级错误页(带「返回媒体库」)。
**P2-6 校验错误暴露原始 JSON**
- 实测 toast 内容:`[ { "code": "invalid_format", "format": "url", "path": ["baseUrl"], "message": "需要合法 URL" } ]`。
- 建议:客户端统一提取 zod 错误的 `message`(或按字段渲染行内错误),不要把序列化结果直接展示。
**P2-7 非 JSON 错误响应未兜底**
- 实测曾出现一次:`/api/trpc/media.mountCreate` 返回 500 HTML 错误页 → toast 显示 `Unexpected token '<', "<!DOCTYPE "... is not valid JSON`。任何网关/HTML 错误都会以解析异常的形式暴露给用户。
- 建议:tRPC 链路统一包装,非 2xx 且非 JSON 时降级为「网络异常,请稍后重试」。
**P2-8 成功反馈不一致**
- 扫描完成、测试连接、改密码、重置申请都有明确文案;但**挂载保存/保存修改无任何成功提示**(仅靠列表变化感知)。
- 建议:补「挂载已保存」toast,统一反馈策略。
**P2-9 「测试连接」失败无原因**
- 只显示「连接失败」,无法区分 DNS 失败 / 超时 / 认证 401 / 地址非 WebDAV,用户排障只能盲猜。
- 建议:透出服务端归一化的失败原因(`assertSafeWebdavUrl` 已有细分文案,连接测试同样可细分)。
**P2-10 表单即时校验缺位**
- 空名称点「保存」:无提示,仅焦点跳到名称;非法 URL 客户端不拦截,全靠服务端 400。
- 建议:提交时对首个错误字段做行内红字提示(不只靠 focus)。
### P3 — 体验细节(建议优化)
| # | 问题 | 建议 |
|---|------|------|
| P3-11 | 媒体库「搜索无结果」与「尚无刮削作品」共用同一文案,误导 | 无结果时显示「未找到匹配“xxx”」+ 清除搜索按钮 |
| P3-12 | 海报墙/列表视图偏好不持久,每次进入回默认 | localStorage 记住视图 |
| P3-13 | 已登录状态下首页 hero 仍显示「登录 / 注册」CTA | 按登录态换成「继续观看/打开媒体库」 |
| P3-14 | 找回密码页 label 仍是「邮箱(可选,用于找回密码)」 | 该场景改为必填语义「注册邮箱」 |
| P3-15 | 关闭弹幕开关后,已导入 XML 被隐藏但文案仍说「暂无…可导入」 | 提示「弹幕已关闭,去设置开启」 |
| P3-16 | `/zh-CN/403`、`/zh-CN/500` 不存在(落到 404);404 页按 **cookie** 语言渲染,en cookie 下 `/zh-CN/404` 显示英文且「Back home」指向 `/en` | 404/403 视图按 URL locale 渲染;补路由 |
| P3-17 | 密码规则(≥8)只靠浏览器原生气泡,提交前不可见 | 表单下方常显规则文案 + 强度提示 |
| P3-18 | 弹幕设置仅 localStorage(服务端接口为桩),多设备不同步(UI 已诚实标注「本地会话」) | 若要多端一致,补服务端持久化 |
| P3-19 | 播放结束态仅在「有下一集」时出现引导,单集内容无「重播」入口 | 结束后给重播/返回系列按钮 |
### 观察项(非缺陷)
- 登录页密码框缺少 `autocomplete="current-password"`(浏览器 VERBOSE 提示);注册页同理。
- 曾观测到一次 `/api/trpc/*` 全量 500 HTML,随后恢复(疑似容器/构建热切换窗口期),未再复现;P2-7 的客户端兜底可覆盖此类瞬态故障。
---
## 四、操作友好性评价(做得好的地方)
1. **鉴权回跳完整**:受保护页 → 登录 → `next` 回跳实测成功,游客/登录态切换干净。
2. **关键节点反馈清晰**:登录失败、密码错误、重置受理(防枚举)、扫描完成、测试连接成功/失败、弹幕开关保存,都有即时反馈。
3. **CTA 引导链路好**:首页「快速开始」四步 → 扫描完成「去媒体库整理」 → 无挂载空态「添加挂载」,每一步都有下一步入口,是全流程最亮眼的设计。
4. **刮削体验完整**:Bangumi 搜索 → 绑定 → 海报墙分组即时呈现,条目标题同步更新。
5. **细节语义到位**:编辑态「编辑中」徽标、密码「留空保持不变」占位、「凭据仅保存在服务端」说明、「已保存(本地会话)」的诚实措辞。
6. **空态覆盖好**:尚未添加挂载、暂无刮削作品、404 均有说明性文案与出路。
7. **i18n 执行彻底**:切换语言保留当前路径、双语文案完整、cookie 优先级正确。
8. **多用户隔离正确**:跨账号看不到彼此挂载与媒体数据。
9. **进度保存可靠**:定时 + 暂停 + 离开页面(beacon)多时机上报,服务端实测落库。
---
## 五、现有自动化测试盲区
`e2e/` 仅 16 条用例,全部是**游客视角**:首页渲染、404、i18n 跳转、API 冒烟、登录/注册页可达。
完全未覆盖:
- 登录成功后的主链路(挂载 → 扫描 → 媒体库 → 播放 → 弹幕);
- 登录态 dashboard(P1-1 因此漏网);
- 找回密码/重置全链路(P1-2 漏网);
- 破坏性操作(删除挂载)与错误分支(非法输入、失效挂载播放)。
**建议**:补一条 Playwright 登录态冒烟链(约 10 步即可覆盖 P1 全部场景)。
---
## 六、修复优先级建议
1. **先修 P1-1**(死链,改动小:dashboard 重定向位置);
2. **P1-3**(弹幕持久化,核心卖点闭环);
3. **P1-2**(找回密码生产交付通道,上线阻塞项);
4. **P2-4/P2-5**(删除确认 + 孤儿数据,数据安全);
5. **P2-6/P2-7**(错误文案归一化,用户可感知质量提升最明显的一档);
6. 其余 P2/P3 随迭代处理,并同步补登录态 e2e。
---
## 七、播放能力专项测试(第二轮 · 补充)
> 环境:重建 WebDAV 夹具(新增 6s 的 EP02 用于多集测试),重建挂载并重新扫描;接口层用 curl 带会话 Cookie 直测 `/api/stream`;播放器行为用浏览器自动化逐项实测。
### 7.1 流代理 `/api/stream`(接口层)
| 场景 | 结果 | 判定 |
|------|------|------|
| 无 Range GET | 200,`Accept-Ranges: bytes`,`video/webm`,长度正确 | ✅ |
| `Range: bytes=0-99` | 206,`Content-Range: bytes 0-99/112733`,实际 100 字节 | ✅ |
| `Range: bytes=100-199`(seek 分段) | 206,精确 100 字节 | ✅ |
| `Range: bytes=-50`(尾部) | 206,`bytes 112683-112732/112733` | ✅ |
| 越界 Range | 416 + `Content-Range: bytes */total`(规范) | ✅ |
| 分段内容一致性 | 前 100 字节与源文件 `cmp` 一致 | ✅ |
| 并发 5 路 Range | 全部 206 | ✅ |
| 无会话 Cookie | 401 | ✅ |
| 不存在的 mediaId | **502** + 正文「媒体或挂载不存在」 | ⚠️ 应为 404 |
| 挂载已删的孤儿条目 | **502** + 同上文案 | ⚠️ 条目明明在列表中,文案自相矛盾 |
### 7.2 播放器交互
| 能力 | 实测 | 判定 |
|------|------|------|
| 播放 / 暂停(UI 按钮) | 正常,暂停即时上报进度(实测 2.1s → 服务端 2103ms 精确) | ✅ |
| 进度条点击 seek | 点 50% → t=2.0(总长 4s)精确 | ✅ |
| 进度条拖拽 seek | 拖至 75% → t=3.0 精确 | ✅ |
| 倍速(设置面板 0.5–2.0) | 选 2.0 后 `playbackRate=2` 生效 | ✅ |
| 音量(hover 面板 / 图标静音) | 面板可见可调;图标切换 `muted` 生效 | ✅ |
| 全屏按钮 | 点击进入 `fullscreenElement`,Escape 退出 | ✅ |
| 画中画按钮 | 按钮存在,环境 `pictureInPictureEnabled=true` | ✅ |
| 键盘 Space | 播放/暂停切换生效 | ✅ |
| 键盘 ← → | 快退/快进(±5s,越界钳制到 0/duration)生效 | ✅ |
| 键盘 ↑ ↓ | 音量 ± 生效 | ✅ |
| 键盘 f / m | **无效** —— ArtPlayer 默认 hotkey 不注册这两个键(源码确认),UI 按钮可用;属体验建议而非故障 | ⚠️ P3 |
| 弹幕快速开关(播放器内按钮) | 切换生效,`artplayerPluginDanmuku:config` 回写 localStorage 偏好,下次进入继承 | ✅ |
### 7.3 进度与续播体系(全链路)
| 场景 | 实测 | 判定 |
|------|------|------|
| UI 暂停上报 | 位置精确(2103ms) | ✅ |
| 离开页面(pagehide beacon) | 暂停态 seek 1.5s 后离开 → 服务端 1500ms | ✅ |
| 重新进入续播 | 恢复 1.5s / 2.1s / 5.19s 三次均精确,就绪后暂停等用户播放 | ✅ |
| 看完再进 | position ≥ duration 不跳转,从头播 | ✅ 合理 |
| 首页「继续观看」 | 有未完成进度时出现;点击跳 watch 精确续播 | ✅ |
| 媒体库「继续播放 · 87%」 | 带百分比;点击 → t=5.19/6.0 精确 | ✅ |
| 已看完条目 | 正确从「继续观看」中消失(`position < duration` 过滤) | ✅ |
| 程序化 `video.pause()`(系统媒体键等) | **不触发上报**——代码监听的是组件事件 `art.on("pause")`,原生暂停只发 `video:pause` | ⚠️ P3-3 |
| 5s 定时 + 播放/暂停/结束多时机上报 | 均实测生效 | ✅ |
### 7.4 多集系列与结尾引导
| 场景 | 实测 | 判定 |
|------|------|------|
| EP01/EP02 绑定同一 Bangumi | 海报墙聚合「进击的巨人 2 集」 | ✅ |
| 系列详情 | 集列表(EP1/EP2)+ 每集播放/匹配 | ✅ |
| 播放页剧集导航 | 「上一集/下一集」可用性正确(首集禁上、末集禁下),当前集高亮 | ✅ |
| 下一集切换 | 跳 EP02,时长 6s 正确,URL 带 `from=series&g=` | ✅ |
| 中间集播放结束 | 出现「第 2 集 / 播放下一集」引导卡,点击跳转成功 | ✅ |
| 「返回系列」 | 回 `/library?g=55770&...` 系列详情 | ✅ |
| 末集 / 单集播放结束 | **无任何结束引导**(无重播、无返回系列 CTA),隐性入口仅中央播放键 | ⚠️ P3-4 |
| 点「播放下一集」后 URL | 丢失 `g/from` 参数(分享该 URL 丢失上下文;页内返回按钮仍可用) | ⚠️ P3 |
| ⚠️ 剧集列表出现**两个「第 1 集」** | 挂载删除产生的孤儿条目与新条目同 epNumber 并列 | ❌ P2-5 升级 |
| 点中孤儿集(列表第一项) | 流 502 → `MEDIA_ELEMENT_ERROR: Format error` → duration=NaN;重连计数结束后**静默黑屏,无错误文案、无重试、无出路** | ❌ **P2-5 最严重表现** |
### 7.5 弹幕 × 播放
| 场景 | 实测 | 判定 |
|------|------|------|
| 导入后渲染(官方插件 artplayer-plugin-danmuku) | 文本节点实际出现 | ✅ |
| 时间轴同步 | t≈1.5 弹幕在 1.5–2.3s 出现;seek 到 4.3 后 4.6s 弹幕出现 | ✅ |
| seek 后重新定位 | 回拖后对应时刻弹幕重新发出 | ✅ |
| 插件设置面板 | 按类型屏蔽(滚动/顶部/底部)、防止弹幕重叠、同步视频速度、透明度 | ✅ 能力超预期 |
| 弹幕开关联动 | `dm.show()/hide()` + 偏好回写 localStorage | ✅ |
### 7.6 第二轮新增问题清单
| 级别 | 问题 | 建议 |
|------|------|------|
| **P2(升级)** | 孤儿条目混入剧集列表且与正常集重名,点中即黑屏(502→Format error→静默),用户无法分辨也无法自救 | 根治:删除挂载时级联清理/标记失效条目(同 P2-5);播放侧:加载失败时展示业务级错误卡(原因+重试+返回),不要停在 00:00/00:00 |
| **P2** | `/api/stream` 对「不存在」与「挂载失效」统一 502,且正文文案「媒体或挂载不存在」与媒体库列表可见相矛盾 | 分别返回 404 / 410(或 502+结构化 code),UI 消费文案生成对应提示 |
| P3 | 原生 `video.pause()` 不触发进度上报(仅组件事件 `art.on("pause")`) | 追加 `art.on("video:pause")` 上报(主路径已正常,覆盖系统媒体键/锁屏场景) |
| P3 | 末集/单集播放结束无引导 | 结束后展示「重播 / 返回系列」动作卡 |
| P3 | 快捷键 f(全屏)/ m(静音)不可用 | 自定义注册 hotkey,或在提示中标注可用按键 |
| P3 | 「播放下一集」跳转后 URL 丢 `g/from` | openEpisode 保留 series 上下文参数 |
| P3 | 筛选无结果时空态文案复用「暂无媒体,请先在文件浏览中扫描挂载」(第二轮再次复现,叠加挂载 tab 过滤时更易触发) | 区分「筛选无结果」与「确实无数据」两套文案 |
### 7.7 第二轮正面结论
- **流代理的 HTTP 语义是全项目最扎实的部分**:Range/206/416/鉴权/字节一致性全部规范。
- **进度体系闭环质量高**:多时机上报 + 精确恢复 + 双入口带百分比 + 已看完过滤,实测无一次偏差。
- **多集导航与结束引导**(中间集)完整;弹幕与播放的时间轴联动、偏好持久化均正常。
- 主路径(UI 按钮暂停、seek、切集)进度上报全部精确——上报缺口仅限程序化暂停的边缘场景。
---
## 八、优化修复与回归验证(第三轮)
> 修复范围:两轮报告全部 P1/P2/P3 项。验证环境为本地 dev 实例(127.0.0.1:4327,重启后加载新构建);**4321 Docker 实例跑的是旧镜像,需重新构建部署才能获得以下修复**。
### 8.1 修复对照表
| 级别 | 问题 | 修复方式 | 回归结果 |
|------|------|---------|---------|
| P1-1 | /dashboard 登录态 500 | 重定向移到**页面级** `pages/[locale]/dashboard.astro`(渲染前),删除子视图内 redirect 与死代码(views/dashboard、DashboardBody、UserList) | ✅ /dashboard → 302 → /library |
| P1-2 | 生产找回密码无交付通道 | 新增 `deliverResetToken`:配置 `PASSWORD_RESET_WEBHOOK_URL` 时 POST `{email,username,token,resetPath}`;未配置时**写服务日志**(含令牌与链接);UI 提示文案更新;`.env.example` 登记 | ✅ 日志输出 `[password-reset] user=dev3 … token=… link=…`;toast 新文案 |
| P1-3 | 弹幕导入刷新即丢 | 导入 XML 按 `media-danmaku-xml:<id>` 写 localStorage(2MB 上限),进入/切换条目时恢复 | ✅ 导入2条 → 刷新仍「弹幕 2 条」 |
| P2-4 | 删除挂载无确认 | 两步确认(按钮变「确认删除?」+ 影响提示),成功 toast 带级联数量 | ✅ 确认态/「挂载已删除」toast |
| P2-5 | 孤儿条目(黑屏/剧集重复/列表僵尸) | ① `mountDelete` **级联删除**媒体条目+播放进度;② `libraryService` list/groups/get/series 前**惰性清理孤儿**;③ WatchBody 监听 `video:error` 弹出**业务级错误卡**(原因+重试+返回媒体库) | ✅ 删除后条目/进度全清;手工插入孤儿 → 自动清理 + 页面显示「加载失败」卡(不再黑屏);停 WebDAV 播放 → 错误卡 → 恢复后「重试」成功 |
| P2-6 | 校验错误暴露原始 JSON | 新增 `formatTrpcError`(提取 zod 数组消息/HTML 兜底),挂载保存/删除等 onError 全部接入 | ✅ |
| P2-7 | 非 JSON 错误响应未兜底 | `lib/trpc.ts` 自定义 `safeFetch`:非 JSON 错误响应转换为结构化 tRPC 错误 | ✅(typecheck/链路验证) |
| P2-8 | 保存成功无反馈 | 挂载创建/更新/删除 toast 链路核验 | ✅ 「挂载已保存」「挂载已更新」「挂载已删除」 |
| P2-9 | 测试连接失败无原因 | `mountService.test` 归一化错误(认证失败/无法解析地址/连接被拒绝/超时/证书/非 WebDAV) | ✅ 显示「连接被拒绝,请检查地址和端口」 |
| P2-10 | 表单静默校验 | 行内红字(名称必填、http(s) 合法性)+ 焦点定位,非法提交**零网络请求** | ✅ 两条红字 + 请求数 0 |
| P2 | stream 错误码语义 | `StreamSourceError`:媒体不存在 404 / 挂载失效 410 / 上游失败 502;`mountListDir` 失败改 BAD_GATEWAY+具体原因 | ✅ 孤儿条目接口 404 NOT_FOUND |
| P3-11 | 空结果文案误导 | 区分「搜索无结果(可清除搜索)/筛选无结果(可切全部)/真无数据(引导扫描)」 | ✅ 三层文案与恢复按钮 |
| P3-12 | 视图偏好不持久 | `media-library-view` localStorage | ✅ 跨页面刷新保持「列表」 |
| P3-13 | 已登录首页仍显示登录 CTA | `auth.me` 判断登录态,隐藏「登录 / 注册」 | ✅ |
| P3-14 | 找回密码邮箱 label | forgot 模式专用「注册邮箱」 | ✅ |
| P3-15 | 弹幕关闭文案 | 已导入时显示「弹幕已关闭,可在上方按钮或弹幕设置中开启」 | ✅ |
| P3-16 | 404 语言不随路径 | 404 页优先取 URL locale 段 | ✅ en cookie 下 `/zh-CN/*` 显示中文、`/en/*` 英文 |
| P3-17 | 密码规则不可见 | 注册/改密/重置密码框下常显「至少 8 位字符」 | ✅ |
| P3-19 | 末集结束无引导 | 结束卡:「本集已播放完毕」+ 重新播放 + 返回系列/媒体库 | ✅ |
| 播放 | 原生暂停不报进度 | 监听改为 `video:play` / `video:pause`(原生事件转发,覆盖系统媒体键) | ✅ 程序化 pause → positionMs=702 精确上报 |
| 播放 | 快捷键 f/m 缺失 | 构造后注册 `hotkey KeyF/KeyM`(全屏/静音) | ✅ m 静音切换、f 进全屏 |
| 播放 | 目录读取失败无重试 | 浏览页错误行显示服务端原因 + 「重试」按钮 | ✅ |
### 8.2 修复过程中发现并解决的阻塞问题
1. **工作区存在未提交 WIP**(models 新增 `dandanplay_episode_id`/`matched_hash` 列),但**未执行 `yarn db:push`**——导致媒体库查询/写入全部 500、扫描假成功。已执行 `db:push`(schema 变更已应用),媒体库恢复正常。**注意:Docker 实例的数据库需在其环境单独执行迁移。**
2. WIP 代码 `danmaku.service.ts` 存在 `exactOptionalPropertyTypes` 类型错误,已修复。
3. 运行期 Vite 依赖预构建过期(504 Outdated Optimize Dep)导致页面水合失败,重启 dev server 恢复。
### 8.3 最终质量门禁
- `yarn typecheck`:19/19 任务通过
- `yarn lint`:11/11 通过(仅 3 条存量 CSS specificity warning,非本次引入)
- `yarn build:web`:构建成功
- 浏览器回归(dev 实例):8.1 表中全部 ✅ 项逐一手动验证
### 8.4 暂缓项与生效说明
| 项 | 说明 |
|----|------|
| P3-18 弹幕设置服务端持久化 | **暂缓**:需新增数据库表 + 双环境迁移;当前 localStorage 方案已用「本地会话」文案如实标注,单设备闭环完整。建议随下个 schema 变更窗口一并实施 |
| 4321 Docker 实例 | 仍是**旧构建**:本轮修复需重新 `docker build` 并对容器内数据库执行 `db:push` 后生效 |
| 现有 e2e 套件 | 仍指向 4321(旧镜像)且只覆盖游客路径;建议将 baseURL 指向新部署并补充登录态冒烟链(覆盖 dashboard/挂载/播放) |
| m/f 快捷键的全屏退出 | headless 环境下 Escape 未观察到退出全屏(进入正常);真实浏览器行为待人工确认 |
---
## 附录:测试现场说明
- 临时 WebDAV 已停止;如需复测:`"$MIMO_PYTHON" tmp-e2e-dav/dav_server.py "$PWD/tmp-e2e-dav/dav-root" 8099`,挂载地址 `http://192.168.31.125:8099`(localhost 被 SSRF 策略禁止,须用局域网 IP)。
- 样例视频位于 `tmp-e2e-dav/dav-root/片库/`(EP01 4s、EP02 6s、示例电影),弹幕样例 `tmp-e2e-dav/sample-danmaku.xml`。
- 主实例遗留状态:`e2e_tester` 名下 2 条孤儿媒体条目(挂载删除残留;其中孤儿 EP1 与新 EP01 已同绑「进击的巨人」,即 7.4 节剧集列表重复的复现条件)+ 3 条有效条目;新增账号 `e2e_mail`。
- 第二轮测试结束后 WebDAV 服务已停止,复测前需按第一条命令重启。