docs(plans): K1 回执(客户端详情+播放+续看+收藏,含真数据/假数据清单)

This commit is contained in:
jianghaifeng committed 2026-10-16 17:39:36 +08:00
1 parent 59d064f8c6
commit 40e2812677
3 files changed
+241 -1

No files matched your search

+92 -1
View File
@@ -19,7 +19,7 @@
---
## [待处理] 2026-10-16 19:40 | K1 客户端对接:详情 + 播放 + 进度续看 + 收藏(看剧闭环)+ UI 调整
## [已处理·待用户验收] 2026-10-16 19:40 | K1 客户端对接:详情 + 播放 + 进度续看 + 收藏(看剧闭环)+ UI 调整
**用户指令(2026-10-16 19:30)**:「现在你要做的事对接客户端接口及 UI 调整,仍然严格按照 specs 执行」→ 转到 **客户端 `ReelShort`(Flutter)**,按 specs 阶段 A 推进。
@@ -364,3 +364,94 @@ ReelShort(Flutter)逐文件清单(**只有这些,没夹带别的**)
**禅道**:J1 在禅道里同样没有对应任务(项目 2 下仍是 S0/S1/S0-16、A1–A6、B1/B2、C1–C3),未动任何既有任务状态。
**服务现状**:8080/8081 是我刚重建起回的新 jar(自测时在跑);:3005 的 dev server 也在。若被环境回收,按上面「准备」起回来即可。
---
### 回执(Codex,2026-10-16):K1 完成,停在干净交接点
**提交**:`ReelShort d7eff4c`(develop,已推;`main` 未动)。留痕:`docs/plans/PROGRESS.md`;计划:`docs/plans/2026-10-16-K1.md`。
**门禁**:`dart analyze lib test` → No issues found!;`flutter test` → **All tests passed!(188 条,存量 27 个测试文件未跑红)**;真实后端联调(8081)见下方原始输出。
**先纠正条目里的一处勘察**:客户端**详情接口早已接上**(`CatalogApi.dramaDetail` + `CatalogController.requireDetail`,详情页在用),
票据调用 `CatalogApi.playTicket` 也存在但**没有任何页面调用**。所以本轮真正做的是:把票据接进播放链路、进度从本地换服务端、
收藏换服务端、补三态 UI。
**改了什么(ReelShort,只列这一批)**
新增:`remote/playback_api.dart`、`remote/watch_api.dart`、`remote/library_api.dart`、`models/watch_progress.dart`、
`repositories/playback_repository.dart`、`repositories/favorite_repository.dart`、`services/favorite_controller.dart`、
`formatting/error_text.dart`。
改:`app.dart`(注入三个仓库 + 收藏控制器 + 登录/登出联动)、`remote/api_client.dart`(补 `delete`)、
`remote/catalog_api.dart` 与 `repositories/catalog_repository.dart`、`services/catalog_controller.dart`(票据职责搬走)、
`models/play_ticket.dart`(补 dramaId/number/title/previewSeconds)、`repositories/watch_history_repository.dart`(换服务端模型)、
`pages/{discover,drama_detail,mine,player}/*`、`l10n/app_{en,zh,id}.arb` + 4 个生成文件。
测试:新增 5 个(含 live 联调),改 8 个(含 fake 与 widget_test 的离线仓库注入)。
**怎么改的(要点)**
1. **起播前取票**:`PlaybackRepository.ticketFor()` 带 TTL 缓存(默认 10 分钟 < 后端 15 分钟签名),过期或 `forceRefresh` 时重取;
回前台发现票据过期会重新取。**签名地址原样交给播放器**(`AppConfig.mediaUrl` 只补 host,path 与 `?e=&s=` 不动,有测试钉住)。
2. **accessType 驱动 UI**:`COIN_REQUIRED/VIP_REQUIRED` → 锁定提示 + "金币解锁即将开放";`NO_MEDIA` → 暂无片源;
前端不推断能不能播(契约 §1.4 的硬要求)。付费解锁仍属 B2。
3. **进度**:服务端每剧一条记录,`POST /app/watch/progress` 节流从 5 秒改成**10 秒**,切集/退出/切后台各补一次,播完带 `finished`;
续看位置只在"记录里的那一集"生效(其它集从头播);`demoProfileId` 与本地 SharedPreferences 实现**已删除**。
4. **收藏**:`FavoriteController` 以服务端返回为准,乐观更新 + 失败回滚 + 失败提示;退出登录清空(不跨账号串联)。
5. **三态**:详情页(加载 / 失败可重试 / 空剧集);播放页(票据加载中 / 失败可重试 / 后端判定不可播);
我的页(续看读取失败可重试 / 收藏列表加载中·失败·空 / 最近观看加载中·失败·空)。断网与超时文案走 l10n(en/zh/id 各 8 个新键)。
**原始联调输出(8081)**
```
① /app/home/feed → dramaId=5
② /app/dramas/5 → episodes=3;第一集 id='7'
playUrl=/files/episode/202610/cec39d8e….mp4?e=1792144372&s=8791826a…
③ /app/episodes/7/play → episodeId='7' dramaId='5' accessType=FREE accessible=True
④ POST /app/watch/progress (42s) → positionMs="42000" durationMs="228000" playCount=1
⑤ GET /app/watch/progress?dramaId=5 → episodeId='7' positionMs='42000' ← 续看位置读得回来
⑥ GET /app/watch/recent?limit=20 → 条数=1(dramaId='5' episodeId='7')
⑦ POST /app/favorites/toggle → true;GET /app/favorites → 1 条;DELETE → 0 条
$ flutter test test/integration/live_watch_flow_test.dart → All tests passed!
```
自测数据已清干净:`app_user` 只剩 PM 那条(id=28)、`watch_progress` 0、`user_favorite` 0。
**哪些页面现在走真数据、哪些还是假的**
| 页面/能力 | 数据来源 | 状态 |
| --- | --- | --- |
| 首页 / 发现页 | `/app/home/feed`、`/app/discover/feed` | ✅ 真数据(本批未动) |
| 登录 / 注册 / 刷新 / 登出 | `/app/auth/*` | ✅ 真数据(本批未动) |
| **详情页(剧集列表、可播地址)** | `/app/dramas/{id}` | ✅ 真数据 |
| **详情页"继续观看"与剧集进度条** | `/app/watch/progress?dramaId=` | ✅ 本批接上(原来读本机) |
| **播放页(票据/签名地址/起播判定)** | `/app/episodes/{id}/play` + accessType | ✅ 本批接上 |
| **播放页进度上报(10s 节流)** | `POST /app/watch/progress` | ✅ 本批接上(原来存本机) |
| **我的页:收藏列表 + 收藏按钮** | `/app/favorites*` | ✅ 本批接上(原来存本机) |
| **我的页:最近观看** | `/app/watch/recent` | ✅ 接上;**只渲染能在已加载目录里按 id 匹配到的剧**,匹配不到先不显示(见待确认 2) |
| 我的页:点赞 | 本地 `EngagementController` | ❌ 仍是本地假状态(属 **K3** 互动) |
| 播放页:评论 | 本地 `DramaCommentsSheet` | ❌ 仍是本地(属 **K3**) |
| 播放页:解锁付费集 | — | ❌ 本批不做(属 **B2**,票据已按 `accessType` 提示) |
| 记录页 / 收藏列表页 / 分类搜索 / 下载 | — | ❌ 属 **K2** |
**用户可照做的验证步骤(每步预期结果)**
准备:后端两个 jar 起着(`admin=200`、`app=200`);`cd ReelShort && flutter run`(模拟器用
`--dart-define=API_BASE_URL=http://10.0.2.2:8081`;iOS/桌面用默认 127.0.0.1)。
1. **匿名可看**:不登录进首页 → 点任一剧 → 详情正常出剧集列表;点某集能起播(免费集)。
预期:起播前有一瞬"加载票据",无白屏;付费集显示"第 N 集已锁定 + 金币解锁即将开放"。
2. **登录后进度**:用「我的 → 登录」注册/登录一个邮箱账号 → 进一部剧 → 看 20~30 秒 → 返回详情。
预期:详情页的"继续观看"按钮下方出现进度条与"续看至 mm:ss / mm:ss",剧集行上也有一条进度条。
3. **跨进程续看(真数据验证)**:杀进程重开 → 进同一部剧 → 直接点"继续观看"。
预期:从上次的位置继续播(不是从头)。也可以直接查库:
`psql -c "select drama_id, episode_id, position_ms from watch_progress order by updated_at desc limit 3;"` 预期有该剧记录。
4. **收藏**:播放页右侧点收藏(或详情页右上角)→ 打开「我的 → Watchlist」。
预期:该剧出现在收藏列表;再点一次取消 → 从列表消失。断网时点收藏 → 提示"收藏操作失败,请重试"且图标回到原状态。
5. **异常路径**:把后端停掉(`lsof -tiTCP:8081 -sTCP:LSLISTEN | xargs kill`)后进详情/播放。
预期:详情页给"网络异常,请检查网络后重试"+ 重试按钮;播放页给"此视频暂时无法播放"+ 重试;
我的页收藏区给失败文案 + 重试;**都不白屏、也不假装成功**。恢复后端后点重试即可继续。
6. **一套命令验证(不用模拟器)**:
`cd ReelShort && flutter test test/integration/live_watch_flow_test.dart` → 预期 All tests passed(服务没起会 skip)。
**待确认 / 如实说明**
1. **票据过期用的是客户端 TTL(10 分钟)**,因为契约里 `play` 响应没有 `expiresAt` 字段。后端若补上过期时间,改成按字段判断即可(一行)。
2. **`/app/watch/recent` 只返回 dramaId/episodeId/位置,没有剧名封面**,所以"我的页最近观看"目前按 id 去已加载的目录卡片里匹配;
匹配不到的条目先不显示。**K2 的记录页**建议要么后端补卡片字段,要么做成独立请求(已在计划里记为风险 4)。
3. **`WatchHistoryRepository` 的接口签名被改了**(服务端是"每剧一条续看记录",没有 profile 概念),连带改了 4 个 logic 与 6 个测试文件——
这是接口模型决定的,不是顺手重构;`clampResumePosition` 等本地工具函数保留。
4. **未登录时**进度与收藏接口一律不发(匿名浏览/试看不受影响),收藏按钮沿用"先引导登录"。
5. 禅道里仍**没有 K1 任务**(项目 2 下只有 S0/S1、A1–A6、B1/B2、C1–C3),未动任何既有任务状态。