337 lines
28 KiB
Markdown
337 lines
28 KiB
Markdown
# 帆幕(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 服务已停止,复测前需按第一条命令重启。
|