Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a658fc67e | ||
|
|
698bb78aeb |
No files matched your search
@@ -0,0 +1,170 @@
|
||||
# K2 客户端第二批:分类 + 观看记录 + 收藏列表 + 下载授权管理
|
||||
|
||||
> 依据:`docs/plans/INBOX.md` 2026-10-17 `[待处理] K2` 条目。
|
||||
> 客户端基线:`ReelShort` `develop` / `00280ca`。
|
||||
> 接口唯一权威:`FruitServer/docs/api-phase1.md` §0、§1.3、§1.5、§1.6。
|
||||
> 本批只做 K2,完成后停下写回执,不连做 K3/K4。
|
||||
|
||||
## 一、范围与完成口径
|
||||
|
||||
### K2-A 分类
|
||||
|
||||
- 接 `GET /app/categories`,分类 id 全程按字符串处理。
|
||||
- 发现页分类条改为服务端分类;保留现有分类条样式。
|
||||
- 分类内容只从 `GET /app/discover/feed` 的 `sections[]` 中读取
|
||||
`code == "category-{id}"` 的分区,不在客户端拼装分类请求。
|
||||
- 分类在发现 feed 中没有对应分区时显示空态。
|
||||
- 保留 `hot` / `new` 入口与既有样式;其内容同样只按发现 feed 中对应 `code`
|
||||
分区读取,缺失时显示空态。
|
||||
|
||||
### K2-B 观看记录页
|
||||
|
||||
- 接 `GET /app/watch/recent?limit=20`、`DELETE /app/watch/progress/{dramaId}`、
|
||||
`DELETE /app/watch/progress`。
|
||||
- 最近记录先去重 `dramaId`,再按 id 补 `GET /app/dramas/{id}` 卡片数据。
|
||||
- 详情补取并发上限 4,同页按 `dramaId` 只请求一次。
|
||||
- 单条详情失败只影响该条:显示“加载失败”占位与单项重试,其余记录正常渲染。
|
||||
- 独立“观看记录”页:封面、剧名、集号、进度、更新时间;点击详情并续看;
|
||||
单条删除、清空均有二次确认;删除采用乐观更新,失败回滚并提示。
|
||||
- 加载 / 空 / 失败可重试三态;未登录使用现有登录引导。
|
||||
|
||||
### K2-C 收藏列表页
|
||||
|
||||
- 复用 `GET /app/favorites`、`DELETE /app/favorites/{dramaId}`、
|
||||
`POST /app/favorites/toggle`,不增加分页参数。
|
||||
- 独立“我的收藏”页,复用卡片数据模型与列表卡片视觉;点击进详情。
|
||||
- 取消收藏采用乐观更新,失败回滚并提示。
|
||||
- 加载 / 空 / 失败可重试三态;未登录使用现有登录引导。
|
||||
|
||||
### K2-D 下载授权管理
|
||||
|
||||
- 接契约 §1.6 全部 8 个接口:
|
||||
`GET /app/downloads/policy`、`PUT /app/downloads/settings`、
|
||||
`POST /app/downloads`、`GET /app/downloads`、
|
||||
`GET /app/downloads/{episodeId}/url`、`POST /app/downloads/{episodeId}/progress`、
|
||||
`DELETE /app/downloads/{episodeId}`、`DELETE /app/downloads`。
|
||||
- 详情页与播放页增加下载入口;后端拒绝时直接展示后端 `message`。
|
||||
- 独立“我的下载”页:策略卡、授权列表、单条删除、清空、仅 Wi-Fi、最高清晰度。
|
||||
- 授权状态与任务状态完全使用后端字符串,客户端不新增状态枚举、不自行判断付费/配额。
|
||||
- 加载 / 空 / 失败可重试三态;未登录使用现有登录引导。
|
||||
- 明确不做:文件分片落盘、断点续传、离线播放、`licenseKey` 本地校验。
|
||||
`url` 与 `progress` 本批只提供并测试 API 方法,不驱动实际下载任务。
|
||||
|
||||
## 二、实施顺序与 TDD
|
||||
|
||||
1. 基线:从 `ReelShort/develop` 建 `feature/flutter-k2-library`。
|
||||
2. K2-A:先写分类解析、`category-{id}` 映射、无分区空态和筛选状态测试,
|
||||
确认红灯后实现;再跑该块门禁。
|
||||
3. K2-B:先写控制器测试覆盖详情去重、并发上限 4、单条失败降级、
|
||||
删除/清空乐观更新与回滚;确认红灯后实现 API、repository、controller、页面;
|
||||
再跑门禁。
|
||||
4. K2-C:先写独立收藏页取消收藏失败回滚、三态与登录引导测试,
|
||||
确认红灯后实现页面与导航;再跑门禁。
|
||||
5. K2-D:先写 DTO 字符串 ID/时间解析、策略/列表渲染、授权失败透传、
|
||||
设置更新、删除/清空和未登录测试;确认红灯后实现;
|
||||
`ApiClient` 仅补契约所需的 `PUT` 能力;再跑门禁。
|
||||
6. 三份 ARB 一次补齐并生成 l10n;全量跑
|
||||
`dart analyze lib test`、`flutter test`、`flutter build apk --debug`。
|
||||
7. 门禁全绿后合并回 `develop` 并 push;不动 `main`。
|
||||
8. 在 `PROGRESS.md` 与 INBOX K2 条目追加回执;真机验证交用户执行。
|
||||
|
||||
## 三、预计影响文件
|
||||
|
||||
### 数据层
|
||||
|
||||
- 修改:`lib/core/data/remote/api_client.dart`(补 `put`)
|
||||
- 修改:`lib/core/data/models/watch_progress.dart`(补 `updatedAt`)
|
||||
- 修改:`lib/core/data/remote/watch_api.dart`(补清空)
|
||||
- 修改:`lib/core/data/repositories/watch_history_repository.dart`(补清空)
|
||||
- 新增:`lib/core/data/models/content_category.dart`
|
||||
- 新增:`lib/core/data/remote/category_api.dart`
|
||||
- 新增:`lib/core/data/repositories/category_repository.dart`
|
||||
- 新增:`lib/core/data/services/category_controller.dart`
|
||||
- 新增:`lib/core/data/models/watch_record.dart`
|
||||
- 新增:`lib/core/data/services/watch_records_controller.dart`
|
||||
- 新增:`lib/core/data/models/download_policy.dart`
|
||||
- 新增:`lib/core/data/models/download_license.dart`
|
||||
- 新增:`lib/core/data/remote/download_api.dart`
|
||||
- 新增:`lib/core/data/repositories/download_repository.dart`
|
||||
- 新增:`lib/core/data/services/download_controller.dart`
|
||||
|
||||
### 页面与共享 UI
|
||||
|
||||
- 修改:`lib/app.dart`(新仓库/控制器注入)
|
||||
- 修改:`lib/core/widgets/auth_gate.dart`(页面级登录引导复用)
|
||||
- 修改:`lib/pages/discover/discover_logic.dart`
|
||||
- 修改:`lib/pages/discover/discover_view.dart`
|
||||
- 修改:`lib/pages/home/home_view.dart`(我的页入口接线)
|
||||
- 修改:`lib/pages/mine/mine_view.dart`(观看记录、收藏、下载入口)
|
||||
- 修改:`lib/pages/drama_detail/drama_detail_view.dart`(下载入口)
|
||||
- 修改:`lib/pages/player/player_view.dart`(当前集下载入口)
|
||||
- 新增:`lib/pages/watch_records/watch_records_binding.dart`
|
||||
- 新增:`lib/pages/watch_records/watch_records_view.dart`
|
||||
- 新增:`lib/pages/favorites/favorites_binding.dart`
|
||||
- 新增:`lib/pages/favorites/favorites_view.dart`
|
||||
- 新增:`lib/pages/downloads/downloads_binding.dart`
|
||||
- 新增:`lib/pages/downloads/downloads_view.dart`
|
||||
- 视复用需要新增列表卡片/状态组件,预计位于 `lib/core/widgets/` 或各页面 `widgets/`。
|
||||
|
||||
### 文案
|
||||
|
||||
- 修改:`lib/l10n/app_en.arb`
|
||||
- 修改:`lib/l10n/app_zh.arb`
|
||||
- 修改:`lib/l10n/app_id.arb`
|
||||
- 重新生成:`lib/l10n/app_localizations*.dart`
|
||||
|
||||
### 测试
|
||||
|
||||
- 修改:`test/catalog/catalog_controller_test.dart` 及分类筛选相关测试
|
||||
- 修改:`test/discover/discover_logic_test.dart`
|
||||
- 修改:`test/mine/mine_logic_test.dart`
|
||||
- 修改:`test/library/favorite_controller_test.dart`
|
||||
- 新增:分类 API/控制器与 section 映射测试
|
||||
- 新增:观看记录 API、并发加载、单项降级、删除/清空回滚和页面三态测试
|
||||
- 新增:收藏独立页、取消收藏回滚、三态和登录引导测试
|
||||
- 新增:下载 DTO/API/controller/page 测试
|
||||
- 新增:未登录 `AuthGate` 页面测试
|
||||
|
||||
## 四、契约缺口(本批登记,不改契约/后端)
|
||||
|
||||
1. `GET /app/watch/recent` 未返回剧名与封面,客户端只能先取最近记录,
|
||||
再对去重后的 `dramaId` 调详情补齐卡片;代价是 N+1 请求。
|
||||
建议后续在 `ProgressRespVO` 补卡片字段(需 specs 负责人改契约)。
|
||||
2. `GET /app/favorites` 没有分页参数并返回全量数组,与
|
||||
`TC-RECORD-04` 的“分页正确”不一致。本批按全量列表实现,
|
||||
不自造 `page/size`。
|
||||
3. `api-phase1.md` 没有搜索接口,本批不做搜索,也不以本地模糊匹配冒充搜索接口。
|
||||
|
||||
## 五、风险与控制
|
||||
|
||||
1. 记录页 N+1 详情请求可能放大流量和延迟:严格去重、并发上限 4、页内缓存,
|
||||
单项失败降级,不在失败后自动无限重试。
|
||||
2. 页面销毁时异步详情仍可能返回:controller 的 `notify` 做 disposed 保护,
|
||||
不把迟到结果写回已退出页面。
|
||||
3. `updatedAt` 为后端 `LocalDateTime` 字符串,解析失败只能显示占位,
|
||||
不能让整条记录解析失败。
|
||||
4. 下载契约里授权与任务状态均为字符串,客户端只原样展示/映射现有文案,
|
||||
不根据未知值猜测业务状态。
|
||||
5. 下载地址字段 `playUrl` 后端当前未填充,本批不做实际下载;不能据此宣称
|
||||
“可离线播放”。
|
||||
6. 本机可能缺少 Android SDK,`flutter build apk --debug` 若失败将保留原始输出,
|
||||
回执标记“未验证”,不静默跳过。
|
||||
7. 真机/模拟器可达性由用户验证;开发机 `curl` 不作为设备证据。
|
||||
|
||||
## 六、待确认(不阻塞本批)
|
||||
|
||||
1. 发现页 `hot` / `new` 入口保留,但当前后端 `discoverFeed()` 只生成
|
||||
`category-{id}` 分区。若后续要展示热门/最新内容,需要后端在发现 feed 下发
|
||||
对应分区;在此之前按契约显示空态。
|
||||
2. 收藏分页与观看记录卡片字段待契约负责人后续补足;本批按 INBOX 降级方案执行。
|
||||
3. 下载真实落盘、断点续传与离线播放需要存储、权限、加密播放方案,另立批次。
|
||||
|
||||
## 七、完成定义
|
||||
|
||||
- 四块均有“红 → 绿”测试证据。
|
||||
- `dart analyze lib test` 零告警,`flutter test` 全过。
|
||||
- `flutter build apk --debug` 通过,或保留缺 SDK 的原始失败输出并标记未验证。
|
||||
- 三语言 ARB 齐全且生成物已更新。
|
||||
- 独立记录、收藏、下载页和分类条均接契约接口;未登录入口可完成登录引导。
|
||||
- 不改 `main`、不改 specs、不改 FruitServer 后端、不做搜索。
|
||||
- `PROGRESS.md` 与 INBOX K2 回执包含验证步骤、预期结果、提交号与原始门禁输出。
|
||||
+82
-1
@@ -20,7 +20,7 @@
|
||||
|
||||
---
|
||||
|
||||
## [待处理] 2026-10-17 | K2 客户端第二批:分类 + 观看记录 + 收藏列表 + 下载授权(A3/A5 剩余)+ UI
|
||||
## [已处理·待用户验收] 2026-10-17 | K2 客户端第二批:分类 + 观看记录 + 收藏列表 + 下载授权(A3/A5 剩余)+ UI
|
||||
|
||||
**用户指令(2026-10-17)**:「现在先做客户端 对接相关接口 及ui 严格执行specs 内的要求」。
|
||||
|
||||
@@ -149,6 +149,87 @@
|
||||
|
||||
> **用户裁定(2026-10-17)**:**先按现状契约执行,上述缺口后期再补**。本批不得因缺口停工,也不得临时改后端或自造接口;按第二节的降级方案实现,缺口留到后续批次。
|
||||
|
||||
### 七、Codex 回执(2026-10-17)
|
||||
|
||||
**状态**:K2-A/B/C/D 代码、自动化测试、Android debug 构建与 develop 推送已完成;
|
||||
真机/模拟器侧业务验证**未验证**,按红线交用户执行。用户照做清单与测试跳过口径见
|
||||
`docs/plans/PROGRESS.md`「动作 17:用户真机验收清单」。K3/K4 未开始。
|
||||
|
||||
**提交**
|
||||
|
||||
```
|
||||
feature: cc4d7c5a9dc6a4f6f0565a32503cce5973af703c
|
||||
merge: 45f5f25640a274edb51bd90b3b5e52c43ba9d9cd
|
||||
branch: feature/flutter-k2-library -> develop(--no-ff,已 push,feature 已删除)
|
||||
main: 未改动
|
||||
```
|
||||
|
||||
**已接真实契约数据**
|
||||
|
||||
- 发现页分类:`GET /app/categories` + `GET /app/discover/feed`;
|
||||
服务端分类只匹配 `category-{id}`,无分区显示空态。
|
||||
- 独立观看记录页:`GET /app/watch/recent?limit=20`、按 dramaId 去重并发上限 4 补详情、
|
||||
`DELETE /app/watch/progress/{dramaId}`、`DELETE /app/watch/progress`。
|
||||
- 独立我的收藏页:`GET /app/favorites`、`POST /app/favorites/toggle`、
|
||||
`DELETE /app/favorites/{dramaId}`。
|
||||
- 我的下载页:§1.6 全部 8 个 `/app/downloads*` 接口;详情页/播放页下载入口调用
|
||||
`POST /app/downloads`,拒绝时直接展示后端 `message`。
|
||||
- 三页面均为加载 / 空 / 失败可重试三态;未登录显示登录引导。
|
||||
|
||||
**仍本地或未做**
|
||||
|
||||
- 搜索未做:契约没有搜索接口。
|
||||
- 文件分片落盘、断点续传、离线播放、`licenseKey` 本地校验未做;本批只交付授权与状态管理。
|
||||
- 点赞/评论仍沿用既有本地状态,属 K3,不在本批。
|
||||
- 发现 feed 当前只下发 `category-{id}`;若后端没有 `hot`/`new` 分区,保留的入口显示空态。
|
||||
- 收藏接口仍返回全量数组,本批未发送 `page/size`。
|
||||
|
||||
**门禁原始输出**
|
||||
|
||||
```
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:11 +220 ~1: All tests passed!
|
||||
|
||||
$ flutter build apk --debug
|
||||
Running Gradle task 'assembleDebug'... 2,377ms
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
```
|
||||
|
||||
`~1` 是既有真实后端联调用例因本机 8081 服务未启动而按设计跳过,不是失败。
|
||||
首次冷构建也成功,耗时 `705.7s`;最终修复后缓存构建 `2,377ms`。
|
||||
|
||||
**用户验证步骤与预期**
|
||||
|
||||
1. 在 ReelShort 执行 `git checkout develop && git pull origin develop`,确认 HEAD 为
|
||||
`45f5f25`;后端 8081 已启动,手机与开发机在同一局域网。
|
||||
2. 取局域网地址并运行:`IP=$(ipconfig getifaddr en0)`,
|
||||
`flutter run -d <device-id> --dart-define=API_BASE_URL=http://$IP:8081`。
|
||||
预期:App 启动、首页/发现能加载;地址必须按当前网络重新获取。
|
||||
3. 打开发现页:分类条应出现 `/app/categories` 返回的分类。选择一个有内容分类,
|
||||
预期显示 discover feed 对应 `code=category-{id}` 的剧;选择无分区分类显示空态。
|
||||
4. 播放任意免费集约 10 秒后返回,进入“我的 → 观看记录”。
|
||||
预期:出现该剧封面/剧名/集号/进度/更新时间;即使该剧不在首页分区也能显示。
|
||||
单条删除先立即消失;断网重试删除时,记录应恢复并提示失败。清空应先弹二次确认。
|
||||
5. 在详情或播放页收藏一部剧,进入“我的 → 我的收藏”。
|
||||
预期:卡片来自服务端;点粉色收藏按钮取消后立即消失;断网取消应恢复卡片并提示失败。
|
||||
6. 登录后在详情页/播放页点下载入口。
|
||||
预期:成功时提示“下载授权已就绪”;付费、未解锁或配额满时,直接出现后端 `message`。
|
||||
进入“我的 → 下载”,预期策略卡显示剩余配额/授权天数/`expirePolicyText`;
|
||||
列表显示后端 `status`、`taskStatus`、`progressPercent`、到期时间;
|
||||
仅 Wi-Fi、清晰度、单删、清空可操作。
|
||||
7. 退出登录后分别进入观看记录、我的收藏、我的下载。
|
||||
预期:三页都显示登录引导,不显示网络错误或白屏。
|
||||
|
||||
**证据口径**
|
||||
|
||||
- 以上设备侧步骤尚未执行,因此真机/模拟器验证统一标记「未验证」。
|
||||
- 没有用开发机 `curl` 冒充设备可达证据。
|
||||
- 本批不包含实际文件下载,因此不能据此验收“离线播放”。
|
||||
|
||||
---
|
||||
|
||||
## [已处理] 2026-10-17 | 三库全量拉取 + `main` → `develop` 同步(Trae 执行,无代码改动)
|
||||
|
||||
@@ -489,3 +489,834 @@ $ pgrep -fl "flutter_tools|xcodebuild|k1v_poller"
|
||||
- 动作 3 那次是**真实的设备侧输出且全过**,但随后两次执行失败(一次 VM service 未 attach、一次构建产物缺失),
|
||||
所以在用户重跑之前**不把它当作最终结论**;它作为历史记录保留在这里。
|
||||
- 我**没有**用任何"开发机 curl"作为设备可达证据。
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-17 K2:分类 + 观看记录 + 收藏列表 + 下载授权管理
|
||||
|
||||
### 动作 1:按 INBOX 指定顺序读取规范与契约
|
||||
|
||||
叙述:开工前先完整核对 INBOX K2 条目、项目约束、Git 流程、接口 §0/§1.3/§1.5/§1.6,
|
||||
再读 A3/A5、CONTENT/RECORD 用例与测试计划。当前没有启动客户端构建,也没有改业务代码。
|
||||
|
||||
```
|
||||
$ sed -n '1,260p' docs/plans/INBOX.md
|
||||
# INBOX — 给 Codex 的指令队列(FruitServer 仓库内)
|
||||
## [待处理] 2026-10-17 | K2 客户端第二批:分类 + 观看记录 + 收藏列表 + 下载授权(A3/A5 剩余)+ UI
|
||||
本批只做 K2,做完停下写回执,不要连做 K3/K4。
|
||||
接口只认 FruitServer/docs/api-phase1.md §1.3 / §1.5 / §1.6
|
||||
|
||||
$ sed -n '1,260p' /Users/jianghaifeng/Desktop/ReelShort/AGENTS.md
|
||||
# 水果短剧(FruitShorts)— Codex 项目上下文
|
||||
## 强制约束(不可违反)
|
||||
## 自主执行原则(Autonomy — 最高优先级)
|
||||
|
||||
$ sed -n '1,340p' /Users/jianghaifeng/Desktop/ReelShort/CODING_STANDARDS.md
|
||||
## 3. Dart / Flutter(ReelShort)
|
||||
TDD 强制:先写测试(红灯)→ 实现(绿灯)→ 重构
|
||||
门禁:dart analyze lib test 零告警 + flutter test 全过
|
||||
|
||||
$ sed -n '1,300p' /Users/jianghaifeng/Desktop/ReelShort/GIT_WORKFLOW.md
|
||||
## 一、分支模型(Git Flow)
|
||||
feature/*:具体功能开发,来源 develop
|
||||
合并一律 --no-ff(禁用 fast-forward)
|
||||
|
||||
$ sed -n '1,64p;150,158p;186,222p' docs/api-phase1.md
|
||||
## 0. 通用约定
|
||||
所有 ID 字段(主键与外键)在 JSON 里都是字符串
|
||||
### 1.3 内容浏览(已实现)
|
||||
GET /app/categories | 分类列表
|
||||
### 1.5 观看记录与收藏(已实现)
|
||||
GET /app/watch/recent?limit=20 | 最近观看
|
||||
DELETE /app/watch/progress/{dramaId} | 删除某剧记录
|
||||
DELETE /app/watch/progress | 清空记录
|
||||
### 1.6 离线下载(已实现)
|
||||
GET /app/downloads/policy
|
||||
PUT /app/downloads/settings
|
||||
POST /app/downloads
|
||||
GET /app/downloads
|
||||
GET /app/downloads/{episodeId}/url
|
||||
POST /app/downloads/{episodeId}/progress
|
||||
DELETE /app/downloads/{episodeId}
|
||||
DELETE /app/downloads
|
||||
|
||||
$ sed -n '1,90p' /Users/jianghaifeng/Desktop/ReelShort/docs/plans/codex-研发任务清单.md
|
||||
- A3 内容(03):首页 feed、发现 feed、分类、详情(一次请求渲染详情页)、搜索。
|
||||
- A5 记录/收藏(05):观看记录、最近观看、收藏切换与列表、下载授权。
|
||||
|
||||
$ sed -n '1,135p' /Users/jianghaifeng/Desktop/ReelShort/docs/16-测试用例库.md
|
||||
TC-CONTENT-09 | 分类题材 | 有分类 | 查看分类 | 分类列表正确 | P2
|
||||
TC-RECORD-01 | 观看历史 | 有播放 | 查看历史 | 按时间倒序,可删除 | P0
|
||||
TC-RECORD-03 | 取消收藏 | 已收藏 | 取消 | 状态反转 | P0
|
||||
TC-RECORD-04 | 收藏列表 | 有收藏 | 查看 | 分页正确 | P1
|
||||
TC-RECORD-09 | 历史清空 | 有历史 | 清空 | 全部清空 | P2
|
||||
|
||||
$ sed -n '1,100p' /Users/jianghaifeng/Desktop/ReelShort/docs/09-测试计划.md
|
||||
单元测试 | Dart 核心类 | flutter test | 核心模块 ≥80%
|
||||
UI 测试 | 关键页面 | Flutter widget test | 核心页面
|
||||
```
|
||||
|
||||
解读:K2 范围、TDD 顺序、接口白名单、三语言文案、三态、登录闸、门禁与设备验证边界均已确认。
|
||||
搜索没有契约接口,本批不做;收藏分页和观看记录卡片字段按 INBOX 的降级方案处理。
|
||||
|
||||
### 动作 2:只读核对两个仓库基线与客户端现状
|
||||
|
||||
叙述:确认客户端从干净 `develop` 开工,并核对现有网络层、记录/收藏实现和页面入口,
|
||||
用于计划中的真实文件清单。
|
||||
|
||||
```
|
||||
$ git status --short --branch && git log -5 --oneline --decorate
|
||||
## develop...origin/develop
|
||||
00280ca (HEAD -> develop, origin/develop) merge: 同步 main 的作者标识提交到 develop
|
||||
84a1f58 test(client): 新增设备侧集成测试(真机跑通后端可达性)
|
||||
d7eff4c feat(client): 对接详情/播放/进度续看/收藏(K1 看剧闭环)+ 三态 UI
|
||||
|
||||
$ git status --short --branch && git log -5 --oneline --decorate # FruitServer
|
||||
## develop...origin/develop
|
||||
4118efd (HEAD -> develop, origin/develop) docs(inbox): 按 specs 补全 K2 执行规范与门禁
|
||||
096e90c docs(inbox): 记录用户裁定——K2 先按现状契约执行,契约缺口后期再补
|
||||
582453f docs(inbox): 下发 K2 客户端第二批(分类/记录/收藏/下载授权 + UI)
|
||||
|
||||
$ find lib -maxdepth 4 -type f | sort
|
||||
lib/core/data/remote/catalog_api.dart
|
||||
lib/core/data/remote/library_api.dart
|
||||
lib/core/data/remote/watch_api.dart
|
||||
lib/pages/discover/discover_view.dart
|
||||
lib/pages/mine/mine_view.dart
|
||||
...
|
||||
```
|
||||
|
||||
解读:两仓均无未提交改动。分类 API、下载 API/page/controller 不存在;
|
||||
观看记录删除只有单条,没有清空;收藏数据层已有但无独立页面;
|
||||
`ApiClient` 只有 GET/POST/DELETE,下载设置所需的 PUT 需最小补齐。
|
||||
|
||||
### 动作 3:写 K2 计划
|
||||
|
||||
叙述:先落计划再动业务代码,明确四块范围、实施顺序、影响文件、三个契约缺口、
|
||||
风险和待确认项。
|
||||
|
||||
```
|
||||
文件:docs/plans/2026-10-17-K2.md
|
||||
章节:范围与完成口径 / 实施顺序与 TDD / 预计影响文件 /
|
||||
契约缺口 / 风险与控制 / 待确认 / 完成定义
|
||||
```
|
||||
|
||||
解读:后续实现按 K2-A → K2-B → K2-C → K2-D 推进;每块先红后绿并单独跑门禁。
|
||||
真机与 Android 构建结果按实际证据记录,缺失时明确标“未验证”。
|
||||
|
||||
### 动作 4:创建功能分支并跑存量基线门禁
|
||||
|
||||
叙述:客户端从 `develop` 创建任务指定分支;沙箱不允许写 `.git/index.lock`,
|
||||
Git 与 Flutter 工具链缓存写操作按权限流程在受控环境执行。先确认存量门禁本身是绿的。
|
||||
|
||||
```
|
||||
$ git checkout -b feature/flutter-k2-library develop
|
||||
Switched to a new branch 'feature/flutter-k2-library'
|
||||
## feature/flutter-k2-library
|
||||
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:10 +188: All tests passed!
|
||||
```
|
||||
|
||||
解读:客户端基线 188 个测试全过、静态检查零告警,可以开始 K2-A。
|
||||
|
||||
### 动作 5:K2-A 测试红灯
|
||||
|
||||
叙述:先新增分类模型/API/控制器的契约测试,覆盖雪花 ID 字符串、分类到
|
||||
`category-{id}` 分区映射、缺失分区空态、hot/new 入口、全量去重和失败重试。
|
||||
|
||||
```
|
||||
$ flutter test test/category/category_controller_test.dart
|
||||
test/category/category_controller_test.dart:6:8: Error: Error when reading
|
||||
'lib/core/data/models/content_category.dart': No such file or directory
|
||||
test/category/category_controller_test.dart:10:8: Error: Error when reading
|
||||
'lib/core/data/remote/category_api.dart': No such file or directory
|
||||
test/category/category_controller_test.dart:11:8: Error: Error when reading
|
||||
'lib/core/data/repositories/category_repository.dart': No such file or directory
|
||||
test/category/category_controller_test.dart:12:8: Error: Error when reading
|
||||
'lib/core/data/services/category_controller.dart': No such file or directory
|
||||
00:00 +0 -1: Some tests failed.
|
||||
```
|
||||
|
||||
解读:红灯原因符合预期,目标实现尚不存在。下一步实现分类数据层与控制器。
|
||||
|
||||
### 动作 6:K2-A 实现绿灯与门禁
|
||||
|
||||
叙述:实现 `ContentCategory`、`CategoryApi`、`CategoryRepository`、
|
||||
`CategoryController`,发现页改为读取 `/app/categories` 与 `/app/discover/feed`;
|
||||
保留 For You / Hot / New 样式,服务端分类只匹配 `category-{id}`。
|
||||
|
||||
```
|
||||
$ flutter test test/category/category_controller_test.dart
|
||||
00:00 +5: All tests passed!
|
||||
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:08 +191 ~1: All tests passed!
|
||||
```
|
||||
|
||||
解读:分类块绿。`~1` 是既有真实后端联调用例因本机 8081 未启动而按设计跳过,
|
||||
不是失败。分类 UI 仍保留 hot/new 入口;后端发现 feed 未下发对应分区时显示空态。
|
||||
|
||||
### 动作 7:K2-B 测试红灯
|
||||
|
||||
叙述:先固定观看记录页核心逻辑:`updatedAt` 解析、recent 按 dramaId 去重、
|
||||
详情并发上限 4、单条详情失败降级与单项重试、删除/清空乐观回滚、recent 失败态。
|
||||
|
||||
```
|
||||
$ flutter test test/watch/watch_records_controller_test.dart
|
||||
test/watch/watch_records_controller_test.dart:11:8: Error: Error when reading
|
||||
'lib/core/data/services/watch_records_controller.dart': No such file or directory
|
||||
test/watch/watch_records_controller_test.dart:23:3: Error: No named parameter
|
||||
with the name 'updatedAt'.
|
||||
test/watch/watch_records_controller_test.dart:49:21: Error: The getter
|
||||
'updatedAt' isn't defined for the type 'WatchProgress'.
|
||||
00:00 +0 -1: Some tests failed.
|
||||
```
|
||||
|
||||
解读:红灯符合预期,观看记录控制器与更新时间字段尚不存在。
|
||||
|
||||
### 动作 8:K2-B 实现与门禁
|
||||
|
||||
叙述:增加 `updatedAt`、观看记录清空 API/repository,新增独立
|
||||
`WatchRecordsController` 与 `WatchRecordsPage`。“我的 → 观看记录”已改为打开独立页。
|
||||
详情请求按 dramaId 去重、最多 4 个并发;单条详情失败显示占位与单项重试;
|
||||
单删和清空先更新 UI,失败回滚并提示;清空有二次确认。
|
||||
|
||||
```
|
||||
$ flutter test test/watch/watch_records_controller_test.dart test/watch/watch_records_page_test.dart
|
||||
00:00 +10: All tests passed!
|
||||
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:08 +201 ~1: All tests passed!
|
||||
```
|
||||
|
||||
解读:K2-B 绿。`~1` 仍是既有真实后端联调用例跳过;本轮新增 10 个记录相关测试。
|
||||
|
||||
### 动作 9:K2-C 测试红灯
|
||||
|
||||
叙述:先补收藏取消失败的原位回滚测试,再新增独立收藏页的卡片、取消失败、
|
||||
空态、失败重试与未登录闸测试。
|
||||
|
||||
```
|
||||
$ flutter test test/library/favorite_controller_test.dart test/library/favorites_page_test.dart
|
||||
test/library/favorite_controller_test.dart:80:8: Error: The setter 'failRemove'
|
||||
isn't defined for the type 'InMemoryFavoriteRepository'.
|
||||
test/library/favorites_page_test.dart:13:8: Error: Error when reading
|
||||
'lib/pages/favorites/favorites_page.dart': No such file or directory
|
||||
test/library/favorites_page_test.dart:108:23: Error: The getter 'listCalls'
|
||||
isn't defined for the type 'InMemoryFavoriteRepository'.
|
||||
00:00 +0 -2: Some tests failed.
|
||||
```
|
||||
|
||||
解读:红灯符合预期:独立收藏页不存在,测试仓库也还不能模拟删除失败。
|
||||
|
||||
### 动作 10:K2-C 实现与门禁
|
||||
|
||||
叙述:新增独立 `FavoritesPage` 和复用列表卡片,服务端列表三态齐全;
|
||||
取消收藏采用乐观更新,失败时按原索引回滚;我的页增加“我的收藏”入口。
|
||||
|
||||
```
|
||||
$ flutter test test/library/favorite_controller_test.dart test/library/favorites_page_test.dart
|
||||
00:00 +13: All tests passed!
|
||||
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:10 +207 ~1: All tests passed!
|
||||
```
|
||||
|
||||
解读:K2-C 绿。收藏页按契约全量读取,不发送 `page/size`;真实后端联调用例仍按设计跳过。
|
||||
|
||||
### 动作 11:K2-D 测试红灯
|
||||
|
||||
叙述:先用 API 契约测试钉住 §1.6 全部 8 个下载接口与字符串 ID;
|
||||
再用控制器测试固定策略/授权加载、后端 message 透传、设置回滚、删除和清空回滚。
|
||||
|
||||
```
|
||||
$ flutter test test/download/download_api_test.dart test/download/download_controller_test.dart
|
||||
test/download/download_api_test.dart:7:8: Error: Error when reading
|
||||
'lib/core/data/remote/download_api.dart': No such file or directory
|
||||
test/download/download_controller_test.dart:4:8: Error: Error when reading
|
||||
'lib/core/data/models/download_license.dart': No such file or directory
|
||||
test/download/download_controller_test.dart:9:31: Error: Type
|
||||
'DownloadController' not found.
|
||||
00:00 +0 -2: Some tests failed.
|
||||
```
|
||||
|
||||
解读:红灯符合预期,下载模型、API、仓库与控制器尚未实现。
|
||||
|
||||
### 动作 12:K2-D 数据层与页面实现
|
||||
|
||||
叙述:实现下载策略/授权模型、8 个接口的 `DownloadApi`、仓库与控制器;
|
||||
`ApiClient` 仅补契约需要的 `PUT`。新增“我的下载”页,展示策略、配额、
|
||||
授权/任务原始状态、进度、有效期,并提供仅 Wi-Fi、清晰度、单删和清空;
|
||||
详情页与播放页接入真实授权申请。授权失败直接展示后端 `message`。
|
||||
|
||||
```
|
||||
$ flutter test test/download/download_api_test.dart test/download/download_controller_test.dart test/download/downloads_page_test.dart
|
||||
00:00 +10: All tests passed!
|
||||
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:10 +219 ~1: All tests passed!
|
||||
```
|
||||
|
||||
解读:K2-D 首轮绿。文件分片落盘、断点续传、离线播放、`licenseKey` 本地校验未做,
|
||||
`url` 与 `progress` 只交付 API 方法;这些边界不会写成“已完成”。
|
||||
|
||||
### 动作 13:提交前边界审查(清晰度下拉修复)
|
||||
|
||||
叙述:审查发现当后端策略 `maxQuality` 已是 `720P/1080P` 时,
|
||||
下拉选项会重复同一值并触发 Flutter 断言。先补测试得到红灯,再去重选项;
|
||||
同时处理退出后重新登录页面不刷新、登录闸文案覆盖观看记录/下载的问题。
|
||||
|
||||
```
|
||||
$ flutter test test/download/downloads_page_test.dart
|
||||
Failed assertion: There should be exactly one item with
|
||||
[DropdownButton]'s value: 720P.
|
||||
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
|
||||
00:01 +6 -1: Some tests failed.
|
||||
|
||||
$ flutter test test/download/downloads_page_test.dart
|
||||
00:00 +7: All tests passed!
|
||||
```
|
||||
|
||||
解读:边界缺陷已由测试固定并修复,避免特定后端清晰度值导致下载页崩溃。
|
||||
|
||||
### 动作 14:最终门禁与 Android 构建
|
||||
|
||||
叙述:整理掉格式化造成的无关测试 diff 后,在 develop 合并前后都复跑门禁。
|
||||
首次 Android 构建因本机 Gradle 依赖经代理下载持续约 12 分钟,但最终成功;
|
||||
修复后二次构建命中缓存(2.377 秒)。
|
||||
|
||||
```
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
00:11 +220 ~1: All tests passed!
|
||||
|
||||
$ flutter build apk --debug
|
||||
Running Gradle task 'assembleDebug'... 705.7s
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
|
||||
$ flutter build apk --debug # 最终修复后
|
||||
Running Gradle task 'assembleDebug'... 2,377ms
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
```
|
||||
|
||||
解读:静态检查零告警、220 个测试通过;`~1` 是既有真实后端联调用例因本机未启动
|
||||
8081 服务按设计跳过。Android debug APK 构建通过。
|
||||
|
||||
### 动作 15:Git 提交、合并与推送
|
||||
|
||||
叙述:客户端从干净 develop 建功能分支,功能提交与 `--no-ff` 合并提交均使用
|
||||
项目规定作者。合并前确认 `origin/develop` 无新提交;合并后在 develop 复跑门禁,
|
||||
再 push,最后删除已完成 feature 分支。未改 main。
|
||||
|
||||
```
|
||||
commit=cc4d7c5a9dc6a4f6f0565a32503cce5973af703c
|
||||
author=Zhou Weidong <502333560@qq.com>
|
||||
subject=feat(flutter): 接入分类记录收藏与下载授权
|
||||
|
||||
merge=45f5f25640a274edb51bd90b3b5e52c43ba9d9cd
|
||||
author=Zhou Weidong <502333560@qq.com>
|
||||
subject=merge: 合并 K2 客户端分类记录收藏与下载授权
|
||||
|
||||
$ git push origin develop
|
||||
To http://git.reelshort2.com/jianghaifeng/ReelShort.git
|
||||
00280ca..45f5f25 develop -> develop
|
||||
|
||||
$ git branch -d feature/flutter-k2-library
|
||||
Deleted branch feature/flutter-k2-library (was cc4d7c5).
|
||||
```
|
||||
|
||||
解读:客户端 develop 已与 origin/develop 对齐,工作区干净。
|
||||
|
||||
### 动作 16:真实数据与未验证边界
|
||||
|
||||
叙述:生产环境注入的发现分类、观看记录、收藏、下载授权均调用契约 HTTP 接口,
|
||||
不再用本地演示目录补齐。由于设备侧验证归用户执行,本轮没有把任何开发机请求
|
||||
当作“设备可达”证据。
|
||||
|
||||
```
|
||||
已接生产 HTTP 链路:
|
||||
- GET /app/categories + GET /app/discover/feed
|
||||
- GET /app/watch/recent + DELETE /app/watch/progress/{dramaId} + DELETE /app/watch/progress
|
||||
- GET /app/favorites + DELETE /app/favorites/{dramaId} + POST /app/favorites/toggle
|
||||
- §1.6 全部 8 个 /app/downloads* 契约接口
|
||||
|
||||
仍未做:
|
||||
- 搜索(契约无接口)
|
||||
- 下载文件分片落盘 / 断点续传 / 离线播放 / licenseKey 本地校验
|
||||
- 真机 / 模拟器侧验证(未验证,由用户执行)
|
||||
```
|
||||
|
||||
解读:功能代码和构建门禁已完成;设备网络、实际授权状态与 UI 交互需用户按回执步骤验证。
|
||||
|
||||
### 动作 17:用户真机验收清单
|
||||
|
||||
叙述:本动作只补齐验收证据与回执缺项,不新做功能、不改 ReelShort 产品代码。
|
||||
本轮重新核对 ReelShort `develop` 为 `45f5f25`,FruitServer 文档开工前为 `698bb78`;
|
||||
并复跑 `dart analyze lib test`、`flutter test`、`flutter build apk --debug`。
|
||||
设备侧取证由用户执行,Codex 未执行。下面所有“预期”都是用户在手机上应看到的业务结果;
|
||||
开发机 `curl` 只用于检查后端是否启动,不作为手机可达或设备侧验收证据。
|
||||
|
||||
#### 17.1 本轮门禁复跑原始输出
|
||||
|
||||
叙述:先确认 8081 监听状态,再按 ReelShort `develop` 顺序复跑三个客户端门禁。
|
||||
Flutter 首次在受限沙箱内运行时被拒绝写 SDK 缓存
|
||||
(`engine.stamp.tmp` / `engine.realm: Operation not permitted`),
|
||||
随后在受控环境用同一组命令复跑;下面贴的是复跑成功的原始输出。
|
||||
|
||||
```
|
||||
$ lsof -nP -iTCP:8081 -sTCP:LISTEN
|
||||
(无输出,说明本轮复跑时 8081 未监听)
|
||||
|
||||
$ flutter devices
|
||||
Found 3 connected devices:
|
||||
iPhone (mobile) • 00008101-000875813E42001E • ios • iOS 26.6.1 23G83
|
||||
macOS (desktop) • macos • darwin-arm64 • macOS 15.5 24F74 darwin-arm64
|
||||
Chrome (web) • chrome • web-javascript • Google Chrome 155.0.8059.80
|
||||
|
||||
Checking for wireless devices...
|
||||
|
||||
No wireless devices were found.
|
||||
|
||||
Run "flutter emulators" to list and start any available device emulators.
|
||||
```
|
||||
|
||||
```
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test
|
||||
test/integration/live_watch_flow_test.dart: should_complete_the_watch_flow_when_backend_is_running
|
||||
客户端接口服务未启动,已跳过联调测试
|
||||
test/integration/live_auth_test.dart: should_surface_business_error_when_sign_in_uses_unknown_email
|
||||
客户端接口服务未启动,已跳过联调测试
|
||||
test/integration/live_catalog_test.dart: should_parse_banner_and_sections_when_home_feed_is_fetched
|
||||
客户端接口服务未启动,已跳过联调测试
|
||||
test/integration/live_catalog_test.dart: should_order_episodes_by_number_and_attach_url_when_detail_is_fetched
|
||||
客户端接口服务未启动,已跳过联调测试
|
||||
test/integration/live_catalog_test.dart: should_return_play_url_when_episode_is_playable
|
||||
客户端接口服务未启动,已跳过联调测试
|
||||
00:10 +216 ~5: All tests passed!
|
||||
|
||||
$ flutter build apk --debug
|
||||
Running Gradle task 'assembleDebug'... 2,672ms
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
```
|
||||
|
||||
解读:静态检查零告警;8081 未启动时测试结果是 `+216 ~5`,5 条跳过均来自 live 联调用例的
|
||||
`markTestSkipped`,不是失败;Android debug APK 构建成功(缓存命中,`2,672ms`)。
|
||||
本轮没有设备侧业务证据,也没有用开发机请求替代手机取证。
|
||||
|
||||
#### 17.2 从零前置条件
|
||||
|
||||
以下命令都需要在 Mac 终端执行。第 1 步只在首次搭建环境时执行;已有数据库可跳过。
|
||||
|
||||
1. 启动 PostgreSQL;首次环境再建角色、数据库并初始化脚本。
|
||||
|
||||
```
|
||||
$ brew services start postgresql@18
|
||||
$ export PATH="/opt/homebrew/opt/postgresql@18/bin:$PATH"
|
||||
$ psql -d postgres -c "CREATE ROLE fruitshorts LOGIN PASSWORD 'fruitshorts';"
|
||||
$ psql -d postgres -c "CREATE DATABASE fruitshorts OWNER fruitshorts ENCODING 'UTF8';"
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
|
||||
$ psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/schema.sql
|
||||
$ psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/data.sql
|
||||
$ psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/schema-phase1.sql
|
||||
$ psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/partition-maintenance.sql
|
||||
$ psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/data-phase1.sql
|
||||
```
|
||||
|
||||
预期:五个 SQL 脚本按顺序执行完成,没有主键冲突或表不存在错误。
|
||||
重复执行是幂等的;不要颠倒 `partition-maintenance.sql` 与 `data-phase1.sql` 的顺序。
|
||||
|
||||
2. 构建并启动客户端接口服务 8081。
|
||||
|
||||
```
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
|
||||
$ mvn -pl fruitshorts-app-api -am package -DskipTests
|
||||
$ java -jar fruitshorts-app-api/target/fruitshorts-app-api-1.0.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
预期:启动日志出现 `Started AppApiApplication`;另开终端检查:
|
||||
|
||||
```
|
||||
$ lsof -nP -iTCP:8081 -sTCP:LISTEN
|
||||
java ... TCP *:8081 (LISTEN)
|
||||
$ curl -fsS http://127.0.0.1:8081/healthz
|
||||
{"code":0,...,"data":{"status":"UP","app":"fruitshorts-app-api",...}}
|
||||
```
|
||||
|
||||
`curl` 只证明开发机上的 8081 已启动,不能证明手机可达。
|
||||
|
||||
3. 准备手机与局域网。
|
||||
|
||||
- 手机和 Mac 连接同一个 Wi-Fi;关闭会隔离客户端的访客网络/VPN 代理。
|
||||
- iPhone 首次启动 App 时,在系统弹窗中允许「本地网络」权限。
|
||||
- Mac 防火墙若拦截 Java,到「系统设置 → 网络 → 防火墙 → 选项」允许 `java` 传入连接。
|
||||
- 同网段地址用 `IP=$(ipconfig getifaddr en0)` 获取。若为空,先确认当前 Wi-Fi 网卡名,
|
||||
再改用对应接口(如 `en1`)。
|
||||
|
||||
预期:终端打印形如 `192.168.x.x` 的局域网地址;手机浏览器能访问
|
||||
`http://<该地址>:8081/healthz`,页面显示 `"status":"UP"`。
|
||||
|
||||
4. 获取 Flutter 运行设备号。
|
||||
|
||||
```
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
|
||||
$ flutter devices
|
||||
```
|
||||
|
||||
预期:真机以 `iPhone (mobile) • <UDID> • ios` 形式出现。本轮机器识别到的设备号是
|
||||
`00008101-000875813E42001E`;换手机或重装后必须重新执行 `flutter devices`,
|
||||
不能继续照抄旧 UDID。也可以执行 `xcrun xctrace list devices` 查 iPhone UDID。
|
||||
|
||||
这里的“运行设备号”用于 `flutter run -d`。下载绑定使用的设备号是另一个概念:
|
||||
客户端首次运行会在本地生成并持久化,随后由 `ApiClient` 作为 `X-Device-Id` 发送,
|
||||
用户不需要也不能在界面上手填。测试文件里的 `dio-check-device`、`k1-live-device`
|
||||
只是测试固定值,不是生产 App 的设备号。
|
||||
|
||||
5. 用当前 Mac 的局域网 IP 注入接口地址并运行 App。
|
||||
|
||||
```
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort
|
||||
$ git checkout develop
|
||||
$ git pull origin develop
|
||||
$ git rev-parse HEAD
|
||||
45f5f25640a274edb51bd90b3b5e52c43ba9d9cd
|
||||
|
||||
$ cd ReelShort
|
||||
$ IP=$(ipconfig getifaddr en0)
|
||||
$ echo "API_BASE_URL=http://$IP:8081"
|
||||
$ flutter run -d 00008101-000875813E42001E \
|
||||
--dart-define=API_BASE_URL=http://$IP:8081
|
||||
```
|
||||
|
||||
`API_BASE_URL` 是 Dart 编译期 `--dart-define`,不是 `export API_BASE_URL=...` 环境变量;
|
||||
改了地址必须停止 App 后用新参数重新 `flutter run`,热重载不会替换它。
|
||||
真机不能用 `127.0.0.1` 访问 Mac,Android 模拟器才用 `10.0.2.2:8081`。
|
||||
|
||||
预期:App 启动后首页和发现页能加载封面、标题与分区;手机没有出现网络异常空屏。
|
||||
如果页面一直报网络错误,先在手机浏览器验证第 3 步的 `/healthz`,再检查 Mac 防火墙和
|
||||
本地网络权限;不要用开发机 `curl` 冒充设备通过。
|
||||
|
||||
#### 17.3 K2-A 分类验收
|
||||
|
||||
1. 打开「发现」页。
|
||||
|
||||
预期:先出现分类/分区加载态,随后分类条显示保留的 `For You`、`Hot`、`New` 入口,
|
||||
再显示后端 `/app/categories` 返回的分类名称。默认种子数据下包括「都市情感」「重生逆袭」
|
||||
「甜宠爱情」「悬疑复仇」;数据库改过名称时以实际后端返回名称为准。
|
||||
|
||||
2. 依次点「都市情感」「甜宠爱情」。
|
||||
|
||||
预期:顶部对应分类 chip 高亮;下方只展示 `/app/discover/feed` 中
|
||||
`code == "category-{id}"` 的分区卡片。切换分类时内容跟着切换,不会保留上一个分类的卡片。
|
||||
|
||||
3. 点默认种子中没有已发布内容的「重生逆袭」或「悬疑复仇」。
|
||||
|
||||
预期:分区区域显示「这个分类暂时没有剧集。」。这是无分区空态,不应出现自行拼装的推荐列表、
|
||||
旧分类残留卡片或网络失败文案。
|
||||
|
||||
4. 切回有内容的分类。
|
||||
|
||||
预期:卡片恢复显示;封面和标题与后端发现 feed 一致;点卡片可进入对应详情。
|
||||
|
||||
5. 失败与重试:停止 8081,冷启动 App 后打开「发现」页。
|
||||
|
||||
预期:分类区域显示「分类加载失败。」和「重试」按钮,不白屏。
|
||||
重新启动 8081 后点「重试」,预期分类和分区恢复显示;必要时下拉刷新。
|
||||
|
||||
设备侧只能直接证明服务端分类名称与分类切换结果;`category-{id}` 的代码映射、
|
||||
全量去重和并发/重试细节由 `test/category/category_controller_test.dart` 自动化覆盖。
|
||||
|
||||
解读:K2-A 的用户验收点是“分类条来自服务端、内容只取对应 `category-{id}` 分区、
|
||||
缺失分区显示空态、失败可重试”;真机没有执行这些步骤,当前结论仍为未验证。
|
||||
|
||||
#### 17.4 K2-B 观看记录验收
|
||||
|
||||
1. 登录后播放任意一部剧的免费集约 10 秒,返回并进入「我的 → 观看历史」。
|
||||
|
||||
预期:页面先显示加载态,随后出现该剧卡片;卡片可见封面、剧名、
|
||||
「第 N 集」、进度条、`续看至 mm:ss / mm:ss` 和「更新于 ...」。
|
||||
即使该剧不在首页/发现已加载目录中,也应正常显示,不能因为目录里匹配不到而消失。
|
||||
|
||||
2. N+1 合并卡片:对同一部剧先后播放两个不同剧集,再播放另一部剧的一集,重新打开观看历史。
|
||||
|
||||
预期:同一部剧只出现一张卡片,不因看过多个剧集而重复;卡片展示该剧最新进度和对应集号。
|
||||
不同剧各自一张卡片。详情是按 `dramaId` 去重后补取的,单条卡片加载中不应拖住其他卡片。
|
||||
|
||||
3. 单条删除:点某张卡片右侧的关闭图标。
|
||||
|
||||
预期:卡片立即从页面消失;返回再进入观看历史后仍然不存在。
|
||||
|
||||
4. 单删失败回滚:保持页面已加载,停止 8081,再点另一张卡片的关闭图标。
|
||||
|
||||
预期:卡片先乐观消失,随后回到原位置,并出现「删除记录失败,请重试。」。
|
||||
重新启动 8081 后,删除动作可再次执行。
|
||||
|
||||
5. 清空:点页面右上方「清空」。
|
||||
|
||||
预期:先弹出「清空观看记录?」及「这会从当前账号移除全部观看记录。」;
|
||||
点「暂不」列表保持不变;点「清空」后列表变空并显示「还没有观看记录。」。
|
||||
|
||||
6. 清空失败回滚:有记录且页面已加载时停止 8081,点「清空」并在二次确认中点「清空」。
|
||||
|
||||
预期:列表先清空,随后恢复原记录,并出现「清空观看记录失败,请重试。」。
|
||||
|
||||
7. 三态和失败重试:后端正常时进入页面观察加载态;清空后观察空态;停止 8081 后冷启动 App
|
||||
再进入观看历史,观察「续看进度加载失败。」和「重试」。重启 8081 后点「重试」,
|
||||
预期记录重新加载。
|
||||
|
||||
若单条详情请求失败,预期该卡片显示「剧集信息加载失败」与「重试」,其他卡片仍正常显示;
|
||||
该分支和 4 并发上限由 `test/watch/watch_records_controller_test.dart`、
|
||||
`test/watch/watch_records_page_test.dart` 自动化覆盖。真机验收重点看合并卡片和整页不被单条失败拖垮。
|
||||
|
||||
解读:K2-B 的用户验收点是三态、同一剧 N+1 详情合并为一张卡片、单删与清空的乐观回滚;
|
||||
自动化测试已覆盖控制器行为,手机上的真实网络与回滚表现仍待用户取证。
|
||||
|
||||
#### 17.5 K2-C 收藏验收
|
||||
|
||||
1. 登录后在详情页或播放页点书签图标收藏一部剧。
|
||||
|
||||
预期:图标立即切换为已收藏状态;失败时图标回到原状态并显示「收藏操作失败,请重试。」。
|
||||
|
||||
2. 再收藏 1 至 2 部不同的剧,进入「我的 → 我的收藏」。
|
||||
|
||||
预期:页面展示服务端 `GET /app/favorites` 返回的全部收藏卡片,封面、标题与首页卡片一致;
|
||||
列表不显示分页控件,也不发送自造的 `page/size`。点击卡片可进入详情。
|
||||
|
||||
3. 取消收藏:点某张收藏卡片右侧的粉色收藏图标。
|
||||
|
||||
预期:卡片立即消失;返回再进入我的收藏后仍然不存在。
|
||||
|
||||
4. 取消收藏失败回滚:保持列表已加载,停止 8081,再点另一张卡片的粉色图标。
|
||||
|
||||
预期:卡片先乐观消失,随后回到原位置,并出现「收藏操作失败,请重试。」。
|
||||
重新启动 8081 后可再次取消。
|
||||
|
||||
5. 三态和失败重试:后端正常时进入页面观察加载态;取消全部收藏后观察「还没有收藏。」;
|
||||
停止 8081 后冷启动 App 再进入我的收藏,观察「收藏列表加载失败。」和「重试」。
|
||||
重启 8081 后点「重试」,预期列表恢复。
|
||||
|
||||
6. 服务端状态校准:在另一台设备或另一次登录中取消同一部收藏,再回到当前设备的我的收藏刷新。
|
||||
|
||||
预期:当前列表以服务端结果为准,不继续显示已被取消的收藏。
|
||||
|
||||
解读:K2-C 的用户验收点是服务端全量收藏列表、取消收藏后的乐观回滚和三态;
|
||||
未登录引导另见 17.7,设备侧列表与回滚表现仍待用户取证。
|
||||
|
||||
#### 17.6 K2-D 下载授权验收
|
||||
|
||||
1. 登录后进入「我的 → 下载」。
|
||||
|
||||
预期:先出现加载态,随后显示策略卡:
|
||||
「剩余 X / Y 个授权」、「授权有效期 N 天」以及后端返回的 `expirePolicyText` 原文;
|
||||
下面显示「仅 Wi-Fi 下载」开关和「最高清晰度」下拉框。
|
||||
|
||||
2. 验证 §1.6 的 8 个接口。真机可观察结果如下:
|
||||
|
||||
| # | 接口 | 真机操作与预期可见结果 |
|
||||
|---|---|---|
|
||||
| 1 | `GET /app/downloads/policy` | 打开下载页后策略卡出现,配额、授权天数、`expirePolicyText` 来自后端 |
|
||||
| 2 | `PUT /app/downloads/settings` | 切换仅 Wi-Fi 或清晰度后,页面立即显示新值;离开再进入仍保持 |
|
||||
| 3 | `POST /app/downloads` | 详情页/播放页点下载图标,出现授权成功或后端拒绝提示 |
|
||||
| 4 | `GET /app/downloads` | 打开下载页后出现当前设备授权列表;无授权时显示空态 |
|
||||
| 5 | `GET /app/downloads/{episodeId}/url` | 本批只提供 API 方法,UI 不调用;真机无可观察入口,见自动化测试 |
|
||||
| 6 | `POST /app/downloads/{episodeId}/progress` | 本批只提供方法,不做文件落盘/进度上报;真机无可观察入口,见自动化测试 |
|
||||
| 7 | `DELETE /app/downloads/{episodeId}` | 点单条右侧关闭图标后该授权消失,重新进入仍不存在 |
|
||||
| 8 | `DELETE /app/downloads` | 点「清空」并二次确认后授权列表变空,重新进入仍为空 |
|
||||
|
||||
3. 申请授权成功:进入任意可下载剧集的详情页或播放页,点下载图标(详情页右上角下载按钮,
|
||||
或播放页右侧下载入口)。
|
||||
|
||||
预期:服务端接受时出现「下载授权已就绪。」;进入我的下载后能看到对应剧名、集号、
|
||||
授权状态、任务状态、进度百分比、清晰度(后端有值时)和到期时间。
|
||||
|
||||
4. 授予失败透传后端 `message`:选择付费/未解锁剧集,或把当前设备配额用满后再次申请。
|
||||
|
||||
预期:SnackBar 原样显示后端返回的 `message`,不能替换成前端自造的“付费/未解锁/配额满”判断。
|
||||
只有后端没有可用 message 时才回退到「申请下载失败。」。验收时记录手机屏幕上看到的完整原文;
|
||||
当前环境没有执行这一步,因此具体拒绝文案仍属未验证。
|
||||
|
||||
5. 仅 Wi-Fi/清晰度成功:切换「仅 Wi-Fi 下载」,再选择 `AUTO`、`720P` 或 `1080P`。
|
||||
|
||||
预期:开关/下拉值立即变化,没有崩溃或重复选项;退出下载页再进入,值从
|
||||
`GET /app/downloads/policy` 重新读取且保持。
|
||||
|
||||
6. 设置失败回滚:保持下载页已加载,停止 8081,再切换仅 Wi-Fi 或清晰度。
|
||||
|
||||
预期:值先乐观变化,随后恢复原值,并出现「下载设置更新失败。」。
|
||||
|
||||
7. 单删和清空。
|
||||
|
||||
- 点单条右侧关闭图标:预期该授权立即消失,重新进入后仍不存在。
|
||||
- 停止 8081 后点另一条关闭图标:预期先消失再恢复,并提示「删除下载授权失败。」。
|
||||
- 点「清空」:预期先弹出「清空下载授权?」及「这会吊销当前设备的全部下载授权。」;
|
||||
点「暂不」不变,点「清空」后列表为空并显示「还没有下载授权。」。
|
||||
- 停止 8081 后清空:预期列表先清空再恢复,并提示「清空下载授权失败。」。
|
||||
|
||||
8. 状态枚举后端驱动:查看授权卡上「授权:...」「任务:...」后的文本。
|
||||
|
||||
预期:显示后端原值,如 `ACTIVE/EXPIRED/REVOKED`、
|
||||
`QUEUED/DOWNLOADING/PAUSED/COMPLETED/FAILED/CANCELED`,前端不做中文映射或本地推算。
|
||||
现有数据只可能展示实际出现的状态;其余枚举由下载 API/控制器测试覆盖,
|
||||
设备侧未逐一构造全部后端状态,回执不能写成“所有状态已真机验证”。
|
||||
|
||||
9. 三态和失败重试:后端正常时进入观察加载态;清空授权后观察空态;
|
||||
停止 8081 后冷启动 App 再进入下载页,观察「下载设置加载失败。」和「重试」。
|
||||
重启 8081 后点「重试」,预期策略卡和授权列表恢复。
|
||||
|
||||
本批不落盘、不断点续传、不提供离线播放;即使授权状态为 `ACTIVE`,也不能据此验收文件已下载。
|
||||
|
||||
解读:K2-D 的用户验收点是策略卡、8 个接口的可见结果、后端 `message` 透传、
|
||||
仅 Wi-Fi/清晰度、单删/清空以及状态枚举原样展示。URL/进度两个接口没有本批 UI 入口,
|
||||
只能由自动化测试证明方法契约;真机拒绝文案和全部状态仍未验证。
|
||||
|
||||
#### 17.7 未登录 AuthGate 与失败重试
|
||||
|
||||
1. 在「我的」页退出登录。
|
||||
|
||||
预期:账号回到未登录状态。
|
||||
|
||||
2. 依次进入「我的 → 观看历史」「我的 → 我的收藏」「我的 → 下载」。
|
||||
|
||||
预期:三个页面都显示登录引导:
|
||||
「登录后可继续」、「观看记录、收藏、评论和下载需要登录。」和「去登录」按钮;
|
||||
不显示网络错误、白屏或空列表冒充无数据。
|
||||
|
||||
3. 在未登录的详情页点收藏或在详情页/播放页点下载。
|
||||
|
||||
预期:出现底部登录提示;点「去登录」进入登录页,点「暂不」关闭提示且不发业务请求。
|
||||
|
||||
4. 完成登录后返回观看历史、我的收藏或下载页。
|
||||
|
||||
预期:页面自动从登录引导切换到加载态,再展示对应数据;不需要杀 App 才能刷新。
|
||||
|
||||
5. 失败重试统一检查:先让页面加载成功,再停止 8081 并冷启动 App 进入页面。
|
||||
|
||||
预期:记录页显示「续看进度加载失败。」、收藏页显示「收藏列表加载失败。」、
|
||||
下载页显示「下载设置加载失败。」,均有「重试」按钮;重启 8081 后点「重试」能恢复,
|
||||
不出现白屏或无提示的假成功。
|
||||
|
||||
解读:未登录时三个独立页面都应由页面级 AuthGate 接住;登录或后端恢复后应重新加载。
|
||||
这些手机交互本轮未执行,不能用开发机请求代替。
|
||||
|
||||
#### 17.8 测试跳过口径差异与稳定复现命令
|
||||
|
||||
PM 复核事实与本轮复跑结果一致:
|
||||
|
||||
- 8081 未启动:`flutter test` 呈 `+216 ~5`,5 个 live 用例全部由
|
||||
`markTestSkipped('客户端接口服务未启动,已跳过联调测试')` 跳过。
|
||||
- 8081 已启动:PM 复核呈 `+220 ~1`,4 个 live 用例通过;
|
||||
当前演示数据下 `live_watch_flow_test.dart` 仍由播放断言前的
|
||||
`markTestSkipped('这部演示剧没有可播剧集,已跳过播放相关断言')` 保留 1 个跳过。
|
||||
如果换了一套有可播剧集的种子数据,这个用例可能通过并呈 `+221 ~0`;以实际输出为准。
|
||||
|
||||
5 个 live 用例及跳过机制:
|
||||
|
||||
| 文件 | 用例 | 跳过机制 |
|
||||
|---|---|---|
|
||||
| `test/integration/live_auth_test.dart` | `should_surface_business_error_when_sign_in_uses_unknown_email` | 捕获连接失败后 `markTestSkipped('客户端接口服务未启动,已跳过联调测试')` |
|
||||
| `test/integration/live_catalog_test.dart` | `should_parse_banner_and_sections_when_home_feed_is_fetched` | `serverUp()` 失败后 `markTestSkipped('客户端接口服务未启动,已跳过联调测试')` |
|
||||
| `test/integration/live_catalog_test.dart` | `should_order_episodes_by_number_and_attach_url_when_detail_is_fetched` | 同上 |
|
||||
| `test/integration/live_catalog_test.dart` | `should_return_play_url_when_episode_is_playable` | 同上 |
|
||||
| `test/integration/live_watch_flow_test.dart` | `should_complete_the_watch_flow_when_backend_is_running` | 注册失败后跳过“服务未启动”;注册成功但无可播剧集时跳过“没有可播剧集” |
|
||||
|
||||
稳定复现 `+216 ~5`:
|
||||
|
||||
```
|
||||
$ lsof -nP -iTCP:8081 -sTCP:LISTEN
|
||||
(确认无输出)
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
|
||||
$ flutter test
|
||||
00:10 +216 ~5: All tests passed!
|
||||
```
|
||||
|
||||
稳定复现 `+220 ~1`:
|
||||
|
||||
```
|
||||
# 终端 A:启动 8081 后保持运行
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
|
||||
$ mvn -pl fruitshorts-app-api -am package -DskipTests
|
||||
$ java -jar fruitshorts-app-api/target/fruitshorts-app-api-1.0.0-SNAPSHOT.jar
|
||||
|
||||
# 终端 B:确认 8081 已监听后跑测试
|
||||
$ curl -fsS http://127.0.0.1:8081/healthz
|
||||
$ cd /Users/jianghaifeng/Desktop/ReelShort/ReelShort
|
||||
$ flutter test
|
||||
00:xx +220 ~1: All tests passed!
|
||||
```
|
||||
|
||||
这 3 个 live 测试文件把地址硬编码为 `http://127.0.0.1:8081`,不读取
|
||||
`--dart-define=API_BASE_URL`。因此复现测试口径必须在同一台 Mac 上启动 8081,
|
||||
手机同一局域网只用于真机 App 验收,不能替代这里的环回联调。
|
||||
本轮没有改这 3 个测试文件或产品代码。
|
||||
|
||||
解读:`~5` 与 `~1` 的差异来自 8081 是否启动,不是测试代码漂移;
|
||||
`~1` 还是当前演示数据下播放断言前的跳过,不能把 `~5` 误报成 5 个失败。
|
||||
|
||||
#### 17.9 提交作者口径差异登记
|
||||
|
||||
叙述:已发生的历史提交不改写,不回滚,不 force push。下面用原始 Git 输出登记事实。
|
||||
|
||||
```
|
||||
$ git show -s --format='%H%n author: %an <%ae>%n committer: %cn <%ce>%n subject: %s' cc4d7c5
|
||||
cc4d7c5a9dc6a4f6f0565a32503cce5973af703c
|
||||
author: Zhou Weidong <502333560@qq.com>
|
||||
committer: Zhou Weidong <502333560@qq.com>
|
||||
subject: feat(flutter): 接入分类记录收藏与下载授权
|
||||
|
||||
$ git show -s --format='%H%n author: %an <%ae>%n committer: %cn <%ce>%n subject: %s' 45f5f25
|
||||
45f5f25640a274edb51bd90b3b5e52c43ba9d9cd
|
||||
author: Zhou Weidong <502333560@qq.com>
|
||||
committer: Zhou Weidong <502333560@qq.com>
|
||||
subject: merge: 合并 K2 客户端分类记录收藏与下载授权
|
||||
```
|
||||
|
||||
差异口径与最终裁定:
|
||||
|
||||
- specs 规范要求提交作者统一为 `Zhou Weidong <502333560@qq.com>`,上述两个历史提交按此口径产生。
|
||||
- 用户曾口头要求使用 `jianghaifeng <haifeng_hello@163.com>`;该口径已作废,不再作为后续提交依据。
|
||||
- 两个历史提交均已 push 到 `develop`;保留原始历史,不通过回滚或 force push 改写,不动 `main`。
|
||||
- 已裁定:后续提交统一使用 `Zhou Weidong <502333560@qq.com>`,author 与 committer 均按此口径。
|
||||
- 本回执提交 `cfce77a` 曾误用 `jianghaifeng <haifeng_hello@163.com>` / `trae-solo-agent <solo-agent@trae.ai>`,现已通过 amend 修正为 `Zhou Weidong <502333560@qq.com>`;amend 后最终提交号以本轮 `git log -2` 原始输出为准。
|
||||
|
||||
解读:提交作者差异是已发生事实,已推送的历史提交不通过回滚或 force push 改写;
|
||||
jianghaifeng 口径作废,后续统一使用 Zhou Weidong,本回执提交已通过 amend 校正。
|
||||
|
||||
#### 17.10 仍未验证的边界
|
||||
|
||||
- 设备侧业务取证由用户执行,Codex 未执行;本轮没有手机屏幕截图、设备日志或手机到 8081 的连接证据。
|
||||
- 具体后端拒绝 `message` 原文尚未在真机触发并记录。
|
||||
- 8 个下载接口中,`GET /app/downloads/{episodeId}/url` 与
|
||||
`POST /app/downloads/{episodeId}/progress` 本批没有 UI 调用入口,只有自动化 API 契约测试。
|
||||
- `ACTIVE/EXPIRED/REVOKED` 与 `QUEUED/DOWNLOADING/PAUSED/COMPLETED/FAILED/CANCELED`
|
||||
只验证“前端展示后端原值”的机制;没有逐一构造全部状态做真机验收。
|
||||
- 文件分片落盘、断点续传、离线播放、`licenseKey` 本地校验不在 K2 范围,仍未实现,也不能验收。
|
||||
- 收藏接口没有分页参数,本批按全量列表验收,不验收服务端分页。
|
||||
- 搜索契约没有接口,本批不验收搜索。
|
||||
Reference in new issue
Block a user