docs(plans): K100 剧集批量上传计划与执行留痕
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
97c2153c2e
commit
3a581163aa
3 files changed
+205
-76
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# K100 后台剧集多选上传(分片 + 断点续传 + 批量建集)
|
||||
|
||||
> 日期:2026-10-21
|
||||
> 范围:仅 `ReelShortWeb`;不改 FruitServer 后端、不改 ReelShort 客户端、不改接口契约。
|
||||
|
||||
## 目标
|
||||
|
||||
剧集管理页「批量上传」:多选视频 → 队列分片上传(文件并发 2 / 分片并发 3)→ 单项与字节加权总进度 → 失败隔离/可重试 → localStorage 断点续传 → 上传完成后批量创建分集。
|
||||
|
||||
## 步骤
|
||||
|
||||
1. 从 `develop` 拉 `feature/episode-batch-upload`。
|
||||
2. 新建 `src/views/contentManagement/upload/uploadSessions.ts`:`reel_upload_sessions` 读写/过滤/清理。
|
||||
3. 新建 `useUploadQueue.ts`:状态机、文件并发泵、进度加权、pause/retry/remove、complete 写回 assetId/url、挂接 session。
|
||||
4. 新建 `UploadTaskRow.vue` / `BatchUploadDialog.vue` / `CreateEpisodesDialog.vue`。
|
||||
5. `EpisodeManagementView.vue` 仅加「批量上传」入口,保留原单文件上传。
|
||||
6. `zh-CN.ts` / `en.ts` 补齐文案;白名单 mp4/mov/mkv/webm;超限标红不入队;`beforeunload`。
|
||||
7. 门禁:`pnpm lint:eslint` + `pnpm build:dev`。
|
||||
8. 提交 `feat(admin): 剧集多选分片上传与断点续传` → `--no-ff` 合回 `develop` 并 push。
|
||||
|
||||
## 风险 / 待确认
|
||||
|
||||
- 续传需用户重新选择本地文件才能匹配;换机器/改名无法自动续传(产品已接受)。
|
||||
- 暂停不 abort 进行中分片,已在传任务会跑完再停派发。
|
||||
- `EpisodeManagementView.vue` 存量已超 500 行,本轮只加入口,不整体拆旧编辑器。
|
||||
- 手工联调(多选/暂停/刷新续传/建集)由 PM 在 8080+3005 复核,本轮不操作真机、不 kill 已运行端口。
|
||||
+74
-76
@@ -1,97 +1,95 @@
|
||||
# INBOX
|
||||
|
||||
## K85 收藏数 / 评论数实时更新
|
||||
> 本轮任务:K100 后台剧集多选上传(分片 + 断点续传 + 进度展示)。**[已处理]**
|
||||
> 开工前置:先读 `AGENTS.md`、`CODING_STANDARDS.md`、`GIT_WORKFLOW.md` 本文件,再读
|
||||
> `ReelShortWeb/src/utils/uploader.ts`、`src/api/admin/media.ts`、`src/views/contentManagement/EpisodeManagementView.vue`。
|
||||
|
||||
### 现象
|
||||
### Codex 回执(K100)
|
||||
|
||||
首页 feed 右侧互动栏已经能显示数字,但**收藏数、评论数点完不变**:
|
||||
- ReelShortWeb 分支:`feature/episode-batch-upload` → `--no-ff` 合入 `develop` 并 push
|
||||
- 提交:`feat(admin): 剧集多选分片上传与断点续传`
|
||||
- 门禁:`pnpm lint:eslint` 零错误;`pnpm build:dev` 成功
|
||||
- 未验证:PM 手工多选/暂停/刷新续传/批量建集(8080+3005)
|
||||
- 风险:续传需重选相同本地文件;暂停不 abort 进行中分片
|
||||
|
||||
- 收藏(点星标)后数字不动,杀掉 App 重进又回到旧值
|
||||
- 发表评论后评论数不动
|
||||
## 现状(已勘察,不要重复排查)
|
||||
|
||||
### 已定位的根因(勘察结论,不要再重复排查)
|
||||
- 后端分片上传已具备且不改:`POST /admin/media/upload/init` → `POST /admin/media/upload/chunk` →
|
||||
`POST /admin/media/upload/complete`;断点查询 `GET /admin/media/upload/{uploadId}`
|
||||
返回 `receivedChunks/totalChunks/chunkSize/percent/status/assetId/url`。
|
||||
- 前端已有 `src/utils/uploader.ts`:`uploadFile()` 分片并发 3、`resumeUpload()` 按服务端已收片数续传、
|
||||
`probeVideoDuration()` 探测时长。**这两个函数已可用,本次在其上做批量队列 UI**。
|
||||
- 当前剧集管理 `EpisodeManagementView.vue` 只有一个 `el-upload` 单文件入口(`http-request="uploadVideo"`),
|
||||
**不支持多选、没有进度条、没有断点续传入口**,`onSession` 也没有落盘。
|
||||
|
||||
**后端(FruitServer)**
|
||||
## 目标
|
||||
|
||||
1. `drama.favorite_count` 从建剧时置 0 之后**没有任何地方自增/自减**:
|
||||
- `FavoriteService.toggle()` 只写/删 `user_favorite`,注释写着「计数走异步聚合」,
|
||||
但仓库里**没有这个聚合任务**,所以收藏数永远是 0
|
||||
- `remove()` / `removeBatch()` 也不维护计数
|
||||
- `LikeService` 是同步维护 `drama.like_count` 的(`DramaService.adjustLikeCount`),收藏应对齐这个做法
|
||||
2. 数据库现状(本机 PG `fruitshorts`):`drama.favorite_count` 全为 0,
|
||||
而 `user_favorite` 实际有记录(剧 1 有 4 条、剧 2 有 2 条、剧 5 有 5 条……)→ 需要回填
|
||||
3. 评论数没有单个剧集的计数接口:`GET /app/comments` 是游标翻页,不返回总数
|
||||
在剧集管理页提供「批量上传」:一次选多个视频 → 队列分片上传 → 每个文件独立进度 → 失败可重试 →
|
||||
刷新/重进页面能续传 → 上传完成后批量创建分集。
|
||||
|
||||
**客户端(ReelShort)**
|
||||
## A. 上传队列(新建 `src/views/contentManagement/upload/useUploadQueue.ts`)
|
||||
|
||||
4. 首页 feed 的收藏回调(`player_view.dart` 约 468 行)是内联实现,**没有调用已有的
|
||||
`_applyFavoriteDelta`**;只有全屏播放页的 `_toggleSave`(`player_view_actions.dart`)调了
|
||||
→ 首页收藏后数字不变
|
||||
5. 两处 rail 的 `commentCount: drama.commentCount` 是静态值,评论面板关闭后不刷新
|
||||
6. 详情页 `DramaStatsCard` 用 `drama.favoriteCount`,`drama_detail_logic.toggleFavorite()`
|
||||
切换后不更新显示
|
||||
1. 输入:`File[]` + `dramaId` + 可选 `bizType`
|
||||
2. 每项状态机:`waiting → uploading → merging → done / failed / paused`;对外暴露 `items`、`overallPercent`、
|
||||
`start()`、`pause()`、`retry(id)`、`retryAll()`、`remove(id)`、`clear()`
|
||||
3. 队列并发:同时最多 2 个文件上传(文件内分片并发沿用 `uploadFile` 的 3)
|
||||
4. 进度:文件内 `onProgress` 回调更新单项百分比;总进度 = 各文件字节加权,不是简单平均
|
||||
5. 失败隔离:单文件失败不影响其它文件,错误原因记在该项 `error`
|
||||
6. 暂停语义(本轮简化,不做 abort):暂停后不再派发新任务,已在传的分片让其完成
|
||||
7. 上传完成后 `completeUploadApi` 拿到 `assetId/url/durationSec` 写回该项
|
||||
|
||||
### 任务
|
||||
## B. 断点续传(关键)
|
||||
|
||||
#### A. FruitServer(后端)
|
||||
1. 上传开始(拿到 `uploadId`)即写入 `localStorage`:键 `reel_upload_sessions`,
|
||||
值数组项含 `{ uploadId, fileName, fileSize, lastModified, dramaId, bizType, createdAt }`
|
||||
2. 页面进入时读取该键,过滤 `dramaId` 匹配且 24 小时内的会话,弹出提示「发现 N 个未完成上传,是否续传」
|
||||
3. 续传前用 `file.name + size + lastModified` 匹配本地重新选择的文件;
|
||||
**文件匹配不上(换机器/改名)则不自动续传,提示后由用户手动重选**
|
||||
4. 续传走现有 `resumeUpload(file, uploadId)`;`GET /admin/media/upload/{uploadId}` 返回 404 或已 completed 的会话要从 localStorage 清理
|
||||
5. 上传成功、用户移除、用户放弃续传三种情况都要清理对应记录
|
||||
|
||||
1. `fruitshorts-module-content/.../service/DramaService.java`
|
||||
- 新增 `adjustFavoriteCount(Long dramaId, int delta)`:SQL 自增,
|
||||
参考现有 `adjustLikeCount`,减号用 `GREATEST(favorite_count - 1, 0)`
|
||||
2. `fruitshorts-module-playback/.../service/FavoriteService.java`
|
||||
- 类注释里「计数走异步聚合」改成与 `LikeService` 一致的同步维护说明
|
||||
- `toggle()`:插入关系后 `adjustFavoriteCount(+1)`;`physicalDelete` 返回行数 > 0 时 `-1`
|
||||
- `remove()`:删除成功(返回行数 > 0)时 `-1`
|
||||
- `removeBatch()`:按 dramaId 逐个 `physicalDelete` 并按其返回值 `-1`
|
||||
(保持与前端「取消收藏」一致的物理删除语义,避免逻辑删除挡住再次收藏)
|
||||
3. `fruitshorts-app-api/.../controller/AppCommentController.java`
|
||||
- 新增 `GET /app/comments/count?dramaId={id}` → `Result<Long>`,
|
||||
内部 `commentService.countByDrama(dramaId)`(匿名可访问,与列表一致)
|
||||
4. `sql/` 下加一次性回填脚本 `sql/backfill_favorite_count.sql`
|
||||
- `UPDATE drama d SET favorite_count = (SELECT count(*) FROM user_favorite f WHERE f.drama_id = d.id AND f.deleted = 0);`
|
||||
- 只放脚本,不要在应用启动时自动执行
|
||||
5. 测试
|
||||
- `FavoriteService` 单测:收藏 +1、取消 -1、不会减到负数、removeBatch 多剧集各自扣减
|
||||
- `AppCommentController` 契约测试:`/app/comments/count` 返回数字
|
||||
## C. 批量创建分集
|
||||
|
||||
#### B. ReelShort(客户端)
|
||||
1. 上传完成列表提供「批量创建分集」按钮
|
||||
2. 默认值:按文件名自然排序(去扩展名后再比较,`第2集` 排在 `第10集` 之前);
|
||||
集号 = 该剧当前最大集号 + 1 起顺延;标题取文件名去扩展名;是否免费沿用现有新建默认值
|
||||
3. 弹窗内每行可改:集号、标题、是否免费;冲突集号在提交前本地校验并标红
|
||||
4. 提交:逐条调用现有 `createEpisodeApi`(`src/api/admin/episode.ts`),
|
||||
逐条显示成功/失败,失败的保留在弹窗内可重试;全部结束后刷新剧集列表
|
||||
5. **不新增后端接口**,契约不动
|
||||
|
||||
1. `lib/core/data/remote/comment_api.dart`
|
||||
- 新增 `Future<int> count(String dramaId)`,解析 `data`(Long 可能是字符串,用现有 `jsonToInt`)
|
||||
2. `lib/core/data/repositories/comment_repository.dart`
|
||||
- 接口与 `HttpCommentRepository` 同步新增 `count`
|
||||
3. `lib/pages/player/player_view.dart`
|
||||
- 首页 feed 的 `onSave` 改为复用 `_toggleSave(context, drama)`(内部已有 `_applyFavoriteDelta`)
|
||||
- 新增 `Map<String, int> _commentCounts`
|
||||
- 新增 `_openComments(context, drama)`:`await openCommentsWithAuth(...)`,
|
||||
结束后 `await commentRepository.count(drama.id)` 并 `setState` 写入 `_commentCounts`;
|
||||
失败静默(保留旧值,不弹错)
|
||||
- 首页 rail 与播放页 rail 的 `commentCount` 都改成
|
||||
`_commentCounts[drama.id] ?? drama.commentCount`;`onComments` 都走 `_openComments`
|
||||
4. `lib/pages/drama_detail/drama_detail_logic.dart`(收藏数同步)
|
||||
- 记录本地收藏增量,把调整后的收藏数提供给 `DramaStatsCard`
|
||||
(不要改 `Drama` 模型;用 getter 或在 view 层叠加 delta)
|
||||
- 只在切换成功且状态确实变化时 ±1,失败回滚
|
||||
5. 测试
|
||||
- `CommentApi.count` 的解析测试(数字 / 字符串两种返回)
|
||||
- 收藏增量逻辑(可抽纯函数或独立小对象)单测:+1、-1、失败不变
|
||||
## D. 组件拆分(红线:单文件 ≤ 500 行)
|
||||
|
||||
### 验收
|
||||
- `src/views/contentManagement/upload/BatchUploadDialog.vue`:多选、队列列表、总进度、暂停/重试/移除
|
||||
- `src/views/contentManagement/upload/UploadTaskRow.vue`:单行(文件名、大小、进度条、状态、操作)
|
||||
- `src/views/contentManagement/upload/CreateEpisodesDialog.vue`:批量建分集表单
|
||||
- `src/views/contentManagement/upload/useUploadQueue.ts`:队列逻辑(纯逻辑单元,不写 UI)
|
||||
- `EpisodeManagementView.vue` 只加一个「批量上传」入口按钮,并保持原单文件上传入口可用
|
||||
|
||||
- 后端:`mvn test` 全过;`mvn -pl fruitshorts-app-api -am package -DskipTests` 出包;
|
||||
本地执行回填脚本后 `GET /app/home/feed` 的 `favoriteCount` 与 `user_favorite` 实际条数一致
|
||||
- 客户端:`dart analyze lib test` 零告警、`flutter test` 全过
|
||||
- 功能:收藏后 rail 数字立即 ±1;重进 App 数字不回退;发表评论后关闭面板,评论数 +1(重新拉取)
|
||||
## E. 细节要求
|
||||
|
||||
### Git
|
||||
- 文件类型白名单:`mp4/mov/mkv/webm`(`accept="video/*"` + `before-upload` 二次校验)
|
||||
- 单文件大小上限从现有常量取值,超限直接标红不入队
|
||||
- 所有文案走 `src/locales/zh-CN.ts` / `en.ts`,禁止硬编码中文
|
||||
- 进度与错误展示用项目现有样式变量,不新引入 UI 依赖
|
||||
- 上传期间:关闭页面前用 `beforeunload` 提示存在未完成上传
|
||||
|
||||
- FruitServer:从 `develop` 拉 `fix/favorite-comment-count`,
|
||||
提交 `fix(interaction): 收藏计数同步维护并新增评论数接口`,`--no-ff` 合回 `develop` 并 push
|
||||
- ReelShort:从 `develop` 拉 `fix/interaction-count-refresh`,
|
||||
提交 `fix(player): 收藏与评论数实时刷新`,`--no-ff` 合回 `develop` 并 push
|
||||
- 作者统一 `Zhou Weidong <502333560@qq.com>`;各自仓库 `docs/plans/PROGRESS.md` 追加 K85 留痕
|
||||
## F. 门禁
|
||||
|
||||
### 约束
|
||||
- `pnpm lint:eslint` 零错误
|
||||
- `pnpm build:dev` 构建成功
|
||||
- 手工验证(PM 会复核):本地起 8080 + 3005,多选 3 个视频 → 三条并行进度 → 中途暂停 → 刷新页面 → 出现续传提示 → 续传完成 → 批量建分集成功
|
||||
|
||||
- 全程中文;禁止 `git add -A`;不改 specs 仓库;不动 `pubspec.lock` 以外的无关文件
|
||||
- 完成后停住,等待验收
|
||||
## Git
|
||||
|
||||
- ReelShortWeb:从 `develop` 拉 `feature/episode-batch-upload` → 提交
|
||||
`feat(admin): 剧集多选分片上传与断点续传` → `--no-ff` 合回 `develop` 并 push
|
||||
- 作者 `Zhou Weidong <502333560@qq.com>`;禁止 `git add -A`;源码首行 `// Author: fruit.com.cn`
|
||||
|
||||
## 约束
|
||||
|
||||
- 先写计划 `docs/plans/2026-10-21-K100-episode-batch-upload.md`,再改代码
|
||||
- 每跑一个命令,把「命令 + 原始输出 + 解读」追加到 `docs/plans/PROGRESS.md`
|
||||
- 不改 FruitServer、不改 ReelShort 客户端、不改接口契约
|
||||
- 禁止 `git add -A`;不要操作真机
|
||||
- 完成后停住等 PM 验收,回执需贴门禁原始输出
|
||||
@@ -24360,3 +24360,108 @@ Running Gradle task 'assembleDebug'... 26.2s
|
||||
|
||||
解读:静态分析零告警,559 项全量测试通过,debug APK 构建成功;
|
||||
`pubspec.lock` 已还原,无依赖变更,未安装或操作真机。
|
||||
|
||||
## 2026-10-21 K100 后台剧集多选上传
|
||||
|
||||
### 动作 0:开工前置 — 已读以上文档
|
||||
|
||||
已逐份 Read:
|
||||
- `AGENTS.md`
|
||||
- `CODING_STANDARDS.md`
|
||||
- `GIT_WORKFLOW.md`
|
||||
- `FruitServer/docs/plans/INBOX.md`
|
||||
- `ReelShortWeb/src/utils/uploader.ts`
|
||||
- `ReelShortWeb/src/api/admin/media.ts`
|
||||
- `ReelShortWeb/src/api/admin/episode.ts`
|
||||
- `ReelShortWeb/src/views/contentManagement/EpisodeManagementView.vue`
|
||||
|
||||
解读:后端分片接口与 `uploadFile`/`resumeUpload` 可复用;剧集页仅有单文件 `el-upload`;本轮只改 ReelShortWeb,严格按 INBOX A~F 落地队列/续传/批量建集/组件拆分/i18n/门禁。
|
||||
|
||||
|
||||
### 动作 1:写计划 + 拉 feature 分支
|
||||
|
||||
```
|
||||
$ git checkout develop && git pull --ff-only origin develop && git checkout -b feature/episode-batch-upload
|
||||
Already on 'develop'
|
||||
Your branch is up to date with 'origin/develop'.
|
||||
Already up to date.
|
||||
Switched to a new branch 'feature/episode-batch-upload'
|
||||
## feature/episode-batch-upload
|
||||
8e1a02a chore(web): 忽略本机启动脚本与运行日志
|
||||
```
|
||||
|
||||
解读:已写计划 `2026-10-21-K100-episode-batch-upload.md`;分支自最新 develop 拉出,工作区干净,可开始编码。
|
||||
|
||||
|
||||
### 动作 2:实现 A~E 源码
|
||||
|
||||
已新增/修改:
|
||||
- `upload/uploadSessions.ts`、`useUploadQueue.ts`
|
||||
- `UploadTaskRow.vue`、`BatchUploadDialog.vue`、`CreateEpisodesDialog.vue`
|
||||
- `EpisodeManagementView.vue`(批量上传入口 + 进页续传打开弹窗)
|
||||
- `locales/zh-CN.ts`、`en.ts`(`episodeBatch` 文案)
|
||||
|
||||
解读:队列文件并发 2、分片并发 3、字节加权进度、localStorage 续传、批量建集弹窗与 i18n 已落地;未改后端与契约。
|
||||
|
||||
|
||||
### 动作 3:门禁 pnpm lint:eslint
|
||||
|
||||
```
|
||||
$ pnpm lint:eslint
|
||||
|
||||
> fruitshorts-admin@1.0.0 lint:eslint /Users/jianghaifeng/Desktop/ReelShort/ReelShortWeb
|
||||
> eslint --fix --ext .js,.ts,.vue ./src
|
||||
|
||||
```
|
||||
|
||||
解读:eslint 零错误(--fix 后无残留报错输出)。
|
||||
|
||||
|
||||
### 动作 4:门禁 pnpm build:dev
|
||||
|
||||
```
|
||||
$ pnpm build:dev
|
||||
|
||||
> fruitshorts-admin@1.0.0 build:dev /Users/jianghaifeng/Desktop/ReelShort/ReelShortWeb
|
||||
> VITE_CJS_IGNORE_WARNING=1 vite build --mode dev
|
||||
|
||||
[warn] Circular chunk: element-plus -> vue-chunks -> element-plus. Please adjust the manual chunk logic for these chunks.
|
||||
|
||||
Build successful. Please see dist-dev directory
|
||||
```
|
||||
|
||||
解读:构建成功;element-plus 循环 chunk 为既有告警,非本轮引入。
|
||||
|
||||
|
||||
### 动作 5:复跑门禁(修 duplicate import / CreateEpisodes 微调后)
|
||||
|
||||
```
|
||||
$ pnpm lint:eslint
|
||||
> eslint --fix --ext .js,.ts,.vue ./src
|
||||
(无错误输出)
|
||||
|
||||
$ pnpm build:dev
|
||||
Build successful. Please see dist-dev directory
|
||||
```
|
||||
|
||||
解读:二次门禁仍全绿,可提交。
|
||||
|
||||
|
||||
### 动作 6:ReelShortWeb 提交并合入 develop
|
||||
|
||||
```
|
||||
$ git commit # feature/episode-batch-upload
|
||||
[feature/episode-batch-upload 6b3620c] feat(admin): 剧集多选分片上传与断点续传
|
||||
9 files changed, 1210 insertions(+), 8 deletions(-)
|
||||
|
||||
$ git merge --no-ff feature/episode-batch-upload
|
||||
(commit-msg 拒收 merge: 前缀,改用 chore(admin): …)
|
||||
[develop 7d39e3c] chore(admin): 合并剧集多选分片上传与断点续传
|
||||
|
||||
$ git push origin develop && git push origin feature/episode-batch-upload
|
||||
8e1a02a..7d39e3c develop -> develop
|
||||
* [new branch] feature/episode-batch-upload -> feature/episode-batch-upload
|
||||
```
|
||||
|
||||
解读:功能提交作者 Zhou Weidong;已 --no-ff 合入 develop 并 push;停住等 PM 验收。
|
||||
|
||||
Reference in new issue
Block a user