From 3a581163aa7bd46ad38ef0abe2e1e9484f03b4d4 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Wed, 21 Oct 2026 21:58:54 +0800 Subject: [PATCH] =?UTF-8?q?docs(plans):=20K100=20=E5=89=A7=E9=9B=86?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E4=B8=8A=E4=BC=A0=E8=AE=A1=E5=88=92=E4=B8=8E?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E7=95=99=E7=97=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../2026-10-21-K100-episode-batch-upload.md | 26 +++ docs/plans/INBOX.md | 150 +++++++++--------- docs/plans/PROGRESS.md | 105 ++++++++++++ 3 files changed, 205 insertions(+), 76 deletions(-) create mode 100644 docs/plans/2026-10-21-K100-episode-batch-upload.md diff --git a/docs/plans/2026-10-21-K100-episode-batch-upload.md b/docs/plans/2026-10-21-K100-episode-batch-upload.md new file mode 100644 index 0000000..5e70c59 --- /dev/null +++ b/docs/plans/2026-10-21-K100-episode-batch-upload.md @@ -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 已运行端口。 diff --git a/docs/plans/INBOX.md b/docs/plans/INBOX.md index c24845a..bbf29ff 100644 --- a/docs/plans/INBOX.md +++ b/docs/plans/INBOX.md @@ -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`, - 内部 `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 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 _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 验收,回执需贴门禁原始输出 diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index 9e6a41e..bf040b1 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -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 验收。 +