feat(app): 收藏与观看历史支持分页

This commit is contained in:
Zhou Weidong committed 2026-10-22 12:23:43 +08:00
1 parent ec15950851
commit 71b0c7315a
11 files changed
+672 -37

No files matched your search

+83 -2
View File
@@ -290,16 +290,97 @@ app.register.max-per-device-per-day
| --- | --- | --- |
| POST | `/app/watch/progress` | 上报进度(建议每 10s 一次,切集时补一次) |
| GET | `/app/watch/progress?dramaId=` | 某剧续看位置 |
| GET | `/app/watch/recent?limit=20` | 最近观看 |
| GET | `/app/watch/recent` | 最近观看分页;`pageIndex` 默认 1,`pageSize` 默认 20、上限 100 |
| GET | `/app/watch/recent?limit=20` | 旧版最近观看 List 响应,仅保留一版兼容 |
| DELETE | `/app/watch/progress/{dramaId}` | 删除某剧记录 |
| DELETE | `/app/watch/progress` | 清空记录 |
| POST | `/app/favorites/toggle` | 收藏/取消收藏,返回切换后状态 |
| GET | `/app/favorites` | 收藏列表(卡片结构与首页一致) |
| GET | `/app/favorites` | 收藏分页;`pageIndex` 默认 1,`pageSize` 默认 20、上限 100 |
| DELETE | `/app/favorites/{dramaId}` | 取消收藏 |
进度合并规则:同一集内只前进不后退;换集直接采纳新集;`finished` 只在接近片尾或客户端显式上报时置位。
这样用户换设备不会看到进度倒退。
#### 收藏分页
`GET /app/favorites?pageIndex=1&pageSize=20`
请求参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `pageIndex` | int | 否 | 1 | 页码,小于 1 按 1 处理 |
| `pageSize` | int | 否 | 20 | 每页数量,上限 100,小于 1 按 1 处理 |
响应 `data` 为 `PageResult<AppDramaCardVO>`:
```json
{
"total": "23",
"list": [
{
"id": "12",
"title": "替嫁新娘:总裁的掌心宠",
"cover": "/files/drama_cover/12.jpg",
"preview": {}
}
]
}
```
排序固定为收藏时间倒序 `created_at DESC`,收藏时间相同时按 `drama_id DESC`。
仅统计和返回仍处于已上线状态的短剧。
#### 最近观看分页
`GET /app/watch/recent?pageIndex=1&pageSize=20`
请求参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `pageIndex` | int | 否 | 1 | 页码,小于 1 按 1 处理 |
| `pageSize` | int | 否 | 20 | 每页数量,上限 100,小于 1 按 1 处理 |
| `limit` | int | 否 | 无 | 兼容旧版;只要传该参数就返回 List,不再返回 PageResult |
分页响应 `data` 为 `PageResult<ProgressRespVO>`:
```json
{
"total": "18",
"list": [
{
"dramaId": "12",
"episodeId": "88",
"dramaTitle": "替嫁新娘:总裁的掌心宠",
"dramaCover": "/files/drama_cover/12.jpg",
"episodeNumber": 6,
"totalEpisodes": 68,
"positionMs": "80000",
"durationMs": "120000",
"finished": false,
"playCount": 3,
"updatedAt": "2026-10-22 10:00:00"
}
]
}
```
排序固定为 `updated_at DESC, drama_id DESC`。每页记录通过批量 IN 查询装配短剧和分集,
不逐条访问数据库。
`ProgressRespVO` 展示字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `dramaTitle` | string / null | 短剧标题 |
| `dramaCover` | string / null | 短剧封面 |
| `episodeNumber` | int / null | 当前续看集号 `ep_no` |
| `totalEpisodes` | int / null | 短剧总集数 |
旧兼容请求 `GET /app/watch/recent?limit=20` 的 `data` 仍为 `List<ProgressRespVO>`,
行为在本版本保留;新客户端应使用分页响应。
### 1.6 离线下载(已实现)
服务端只发**授权**与**短时效地址**,分片由客户端直接向 CDN 拉取 ——
@@ -0,0 +1,24 @@
# 客户端收藏与观看历史分页
## 目标
- `/app/favorites` 改为 `PageResult<AppDramaCardVO>`,默认第 1 页、每页 20,最大 100。
- `/app/watch/recent` 默认返回 `PageResult<ProgressRespVO>`;请求带 `limit` 时保留一版旧
`List<ProgressRespVO>` 响应。
- 观看历史补充短剧标题、封面、集号和总集数,剧/集均批量查询。
## 实现步骤
1. 收藏关系使用 `selectPage`,按 `created_at DESC, drama_id DESC` 稳定排序;
只统计已上线短剧收藏,再按当前页 ID 批量加载短剧。
2. 观看进度使用 `selectPage`,按 `updated_at DESC, drama_id DESC` 稳定排序。
3. 当前页的 dramaId / episodeId 去重排序后,分别执行一次 IN 查询并装配展示字段。
4. Controller 归一化 pageIndex/pageSize;观看历史根据是否传 `limit` 分流新旧响应。
5. 更新 API 契约 §1.5,补 service/controller 测试。
## 风险与约束
- `limit` 分支仅用于旧客户端兼容,新分页响应与旧 List 响应不能混用。
- 已下线短剧不计入收藏 total,也不出现在收藏页。
- 观看历史展示字段找不到对应剧或集时保持 null,不影响历史记录返回。
- 不修改客户端、不改表结构、不启停服务、不操作真机。
+70
View File
@@ -25135,3 +25135,73 @@ $ mvn test
解读:14 个 reactor 模块全部通过;worker 真实 ffmpeg smoke 产出 HLS、封面、
VTT 与 sprite,缩略图单格宽高断言为 160x90。
## 2026-10-22 客户端收藏与观看历史分页
- 分支:`feature/app-library-paging`
- 基线:`develop` / `origin/develop` = `ec15950`
- 隔离工作树:`/private/tmp/fruitserver-app-library-paging`
- 计划:`docs/plans/2026-10-22-app-library-paging.md`
### 实现
- `/app/favorites` 增加 `pageIndex`(默认 1)和 `pageSize`(默认 20、上限 100),
返回 `PageResult<AppDramaCardVO>`。
- 收藏关系使用 MyBatis-Plus `selectPage`,按 `created_at DESC, drama_id DESC` 排序;
使用已上线 drama 的 `IN` 子查询过滤后,再对当前页短剧做一次批量装配。
- `/app/watch/recent` 默认返回 `PageResult<ProgressRespVO>`,`pageIndex/pageSize`
默认 1/20、上限 100;传 `limit` 时保留旧版 `List<ProgressRespVO>` 响应。
- 观看进度使用 `selectPage`,按 `updated_at DESC, drama_id DESC` 排序。
- `ProgressRespVO` 增加 `dramaTitle`、`dramaCover`、`episodeNumber`、`totalEpisodes`;
当前页 dramaId / episodeId 去重排序后分别执行一次批量查询,无逐条 N+1。
- `docs/api-phase1.md §1.5` 已同步分页参数、默认值、上限、稳定排序、响应样例及
`limit` 兼容策略。
### 编译
```text
$ mvn -pl fruitshorts-app-api -am -DskipTests compile
[INFO] fruitshorts-module-playback ......................... SUCCESS
[INFO] fruitshorts-app-api .................................. SUCCESS
[INFO] BUILD SUCCESS
[INFO] Total time: 9.888 s
```
解读:`IPage/Page`、新 VO 字段和 Controller 返回类型均通过编译。
### 测试
首次全量测试的两个失败均为测试直接展开 LambdaWrapper SQL 时缺少 MyBatis-Plus
实体缓存,不是生产逻辑失败:
```text
MybatisPlusException: can not find lambda cache for this entity
[UserFavorite]
[WatchProgress]
```
按项目现有测试模式增加 `TableInfoHelper.initTableInfo(...)` 后,定向测试先通过:
```text
$ mvn -pl fruitshorts-app-api -am -Dtest=FavoriteServiceTest,WatchProgressServiceTest,AppLibraryPagingContractTest -Dsurefire.failIfNoSpecifiedTests=false test
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
```
最终 app-api 及改动依赖模块全量门禁:
```text
$ mvn -pl fruitshorts-app-api -am test
[INFO] Tests run: 109, Failures: 0, Errors: 0, Skipped: 0
[INFO] Reactor Summary for fruitshorts 1.0.0-SNAPSHOT:
[INFO] fruitshorts-module-content ......................... SUCCESS
[INFO] fruitshorts-module-playback ........................ SUCCESS
[INFO] fruitshorts-app-api ................................ SUCCESS
[INFO] BUILD SUCCESS
[INFO] Total time: 10.918 s
```
解读:收藏默认 pageSize=20、第二页不重叠、total 正确;观看历史分页、
dramaTitle/episodeNumber/totalEpisodes 非空、越界页空 list 且 total 保持、
`limit` 旧 List 兼容均通过;服务层断言 `selectPage`、稳定排序和两批 IN 查询。
未启停服务、未操作真机、未修改客户端。