R5b:批量上传视频,以磁盘为准并按货憨憨规则本地预检 #19

Open
opened 2026-09-03 16:48:34 +08:00 by ila · 1 comment
Owner

基本信息

  • 类型:需求
  • 所属 Epic:#2
  • 所属 MVP / 版本:#6
  • 阶段:待实施

依赖与并行

  • 前置工单:#17(R5 首版单商品)、#18(R4e)
  • 是否允许与前置工单并行:是
  • 原因:真实依赖是「前置工单的代码已合入 main」,不是「已通过人工验收」。
    #17 的 d1149b6、0fe24f7 与 #18 的 16e4f0f 均已推送到 origin/main,
    本工单在其之上修改。#17 的真机验收关注上传链路本身,与本工单新增的
    批量调度、磁盘扫描和本地预检不重叠。

子项目影响

  • 仅影响的子项目 / 交付单元:cmsp 桌面端
  • 是否跨子项目:否
  • 是否修改共享接口或契约:否(复用 #17 已实现的三个货憨憨接口封装)
  • 各子项目需要执行的验证:见「验证方式」

原始需求

  • 来源:用户对话
  • 提出时间:2026-09-03
  • 关键原话:
    • 「批量勾选后,点击上传,先去对应子目录检测视频文件在不在,不在就跳过,
      上传状态改为缺少视频,有视频就上传,然后继续。」
    • 「不要去管视频来源,有些视频可能是其他地方来的,你只要在上传时检测视频
      子目录里的视频文件是否存在,不存在,改状态,存在就上传。」
    • 货憨憨对视频文件的要求:「1、视频大小:最大 30MB;2、视频时长:10s~60s;
      3、视频格式:mp4;4、视频像素要求:像素宽高不超过 1280px * 1280px」
    • 「容量无门禁。」

要解决什么

现状

#17 实现的上传只支持一次一个商品,且强制 len(productIDs) == 1。
选定文件的方式是「取该商品 videos 表中第一个 status='downloaded'
且文件真实存在的记录」——依赖 videos 表。

这带来两个问题:

  1. 手工放进目录的视频传不了。 负责人明确说明「有些视频可能是其他地方来的」,
    这类文件在 videos 表里没有记录,当前实现看不见它们。
  2. 库与磁盘漂移会挡住上传。 #18 修掉了一个漂移来源,但漂移无法根绝
    (手动挪文件、换 video_dir、外部程序写入)。

目标

批量勾选商品后一次点击完成上传。以磁盘为事实来源,不查 videos 表、
不关心视频来源。上传前用 ffprobe 按货憨憨的四条规则本地预检,
不合规的跳过而不是白传。

做什么 / 不做什么

  • 做:
    • 批量上传,逐个串行处理,单个失败不影响后续
    • 以磁盘子目录为准查找视频文件,不查 videos 表
    • 上传前本地 ffprobe 预检(大小、时长、格式、像素)
    • 三个新的 upload_status 取值及对应的界面显示与筛选
    • 上传成功后按 local_path 补写 videos 记录
  • 不做:
    • 不做容量门禁(负责人 2026-09-03 明确决定),保留确认框那行静态提示
    • 不改取视频、图搜、下载逻辑
    • 不改三个货憨憨接口的封装(#17 已完成且已真机验证过上传链路)
    • 不引入按 upload_status 跳过的断点逻辑(原因见下)
    • 不实现视频删除
    • 不做「取视频时按四条规则筛同款」——那是 R4 范围,另开工单

已确认方案

处理流程

「上传数据」对每个勾选的商品串行执行,任一分支结束后继续下一个商品:

1. 读远端状态:CheckShopProductVideo(product.ID)     ← 只读
     已有视频(Confirmed() 为真)
        └─ 勾选数 == 1 → 确认框红字警告,使用者确认后覆盖上传
        └─ 勾选数 >  1 → upload_status = existing,跳过
2. 扫描磁盘:<video_dir>/<SafeDirName(itemId)>/*.mp4,按文件名升序
     没有文件 → upload_status = missing,跳过
3. 本地预检(ffprobe,见下表)
     不合规 → upload_status = invalid,last_error 写明违反哪一条,跳过
4. 上传:uploadFiles → batchUpdateShopProductVideo → 回读校验
     成功 → upload_status = done,补写 videos 记录
     失败 → upload_status = failed,last_error 记中文原因

逐个串行,不要用接口的数组批量能力。 接口支持一次传多个商品,
但那样某个商品失败时无法定位是哪个,错误也没法精确写回。
素材上传本来也是一个文件一次。

单商品失败不是全局停止门:网络错误、货憨憨返回失败、回读不通过,
都只影响当前商品,记录后继续下一个。这一点与淘宝登录失效的全局停止门不同。

覆盖策略(负责人 2026-09-03 确认)

batchUpdateShopProductVideo 是覆盖语义,一个商品只挂一个视频,
传新地址即替换旧的,且不可恢复。

  • 批量(勾选 > 1)永不覆盖:远端已有视频一律跳过
  • 单个(勾选 == 1)可以有意识地覆盖:保留 #17 的红字警告与二次确认

判断依据用实时读取的远端状态,不用本地 video_diagnosis——
后者只在「下载数据」时刷新,可能是几天前的。多一次只读请求,
批量二三十个的代价可以忽略。

本地预检规则

项 判定 数据来源
大小 ≤ 30 MB(按 30 * 1024 * 1024 字节) os.Stat 或 ffprobe
时长 10.0 <= duration <= 60.0,不取整、不四舍五入 ffprobe
格式 容器为 mp4 ffprobe format_name 含 mp4
像素 宽 ≤ 1280 且 高 ≤ 1280 ffprobe 视频流 width / height

时长必须严格判定:本地已有的 49365131605_1.mp4 是 9.985011 秒,
差 0.015 秒卡在门槛下,任何取整都会把它误判为合规。

internal/downloader 现有的 ProbeResult 只有 Duration / Size /
FormatName,需要扩展出 Width / Height。ffprobe 调用命令相应调整,
保持现有的「ffprobe 找不到就报明确中文错误,不静默跳过」行为。

预检失败的 last_error 必须写明违反了哪一条,例如
「视频时长 9.99 秒,不满足 10—60 秒要求」,不能只写「视频不合规」。

新增的 upload_status 取值

Go 侧常量沿用现有英文风格(pending / running / done / failed):

常量 值 界面显示 含义
UploadSkippedExisting existing 已有视频 货憨憨那边已经有视频,批量时跳过
UploadMissingVideo missing 缺少视频 子目录里没有 mp4 文件
UploadInvalidVideo invalid 视频不合规 有文件但违反四条规则之一

前端「上传状态」列与筛选下拉框补上这三项。
前端按状态值分支,不得按中文文本分支(AGENTS.md)。

★ 这三个状态不得成为断点 ★

video_status = 'none' 曾经因为被断点逻辑永久跳过而造成过问题(见 #15)。
本工单不得重蹈覆辙:

  • 批量上传只处理使用者当次勾选的商品,不得因为 upload_status 是
    missing / invalid / existing / failed 就把商品排除在处理之外
  • 这三个状态只是上一次尝试的结果记录,每次点上传都会重新扫盘、重新预检、
    重新读远端,状态随之刷新
  • 因此不需要额外的复位机制:换了视频文件、补下载了视频、远端视频被删,
    下次点上传自然就会得到新结果

界面上也不得因为这些状态禁用勾选框。

确认框

批量时不能逐个弹窗,改为一次汇总。内容至少包含:

将处理 17 个商品
  可上传    12 个,共 38.4 MB
  缺少视频   3 个
  视频不合规 2 个(时长或像素不符合货憨憨要求)
  已有视频   0 个,将跳过
无法查询货憨憨剩余容量(接口未确认)。图片空间已用约 98.7%,
剩余约 26 GB,请自行确认后再继续。

前三类的统计由扫盘和本地预检得出,不需要联网;
「已有视频」需要读远端,在确认框阶段不做(否则弹框前要等 N 次请求),
放到实际执行时逐个读。确认框里对这一项写「执行时逐个检查,已有视频的会跳过」。

容量(负责人 2026-09-03 明确决定:不做门禁)

不实现、不猜测、不调用任何容量查询接口。
确认框保留上面那行静态提示,除此之外不做任何容量相关的判断或分支。
这是负责人的明确决定,不是遗漏。

上传成功后补写 videos 记录

手工放进目录的视频在 videos 表里没有记录。上传成功后按 local_path
写入或更新一条:

  • product_id、local_path、file_size、remote_url、status = uploaded
  • source_item 留空——磁盘上还原不出视频来自哪个淘宝同款
  • 已存在同 local_path 的记录则更新,不重复插入
  • 不得使用 ReplaceVideos:那会清掉该商品的其它视频记录

进度反馈

批量可能耗时较久,需要进度反馈。复用现有的 logx 运行日志即可,
每个商品处理完输出一行结果。不要新建一套任务队列或进度事件——
internal/task 的 Runner 是为取视频设计的(串行 CDP + 并行下载),
上传是纯串行 HTTP,套上去反而复杂。上传期间禁用「下载数据」「下载视频」按钮。

预计修改文件:

  • internal/downloader/downloader.go、downloader_test.go(ProbeResult 增加宽高)
  • internal/store/product.go、product_test.go(三个新状态常量)
  • internal/store/video.go、video_test.go(按 local_path 写入或更新)
  • app.go(批量调度、扫盘、预检)
  • frontend/src/views/ProductListView.vue(汇总确认框、状态显示与筛选)

不新增第三方依赖。internal/ 下不得 import wails。不新增 migration。

需求变化记录

日期 变化内容 原因 用户确认
2026-09-03 解除 #17 的单商品限制,改为批量 首版单商品已完成一次真实上传 是
2026-09-03 选定视频改为扫描磁盘子目录,不查 videos 表 「不要去管视频来源,有些视频可能是其他地方来的」 是
2026-09-03 新增本地 ffprobe 预检四项规则 负责人提供了货憨憨的视频要求 是
2026-09-03 批量永不覆盖,单个可覆盖;判断用实时远端状态 覆盖不可恢复,本地 video_diagnosis 可能过期 是
2026-09-03 不做容量门禁 负责人明确决定 是

设计与原型门禁

  • 修改类型:非 UI 为主 + 小范围 UI(汇总确认框、状态列与筛选项)
  • 所需设计证据:无需原型
  • 无需 UI 原型的原因:复用现有工具栏按钮、Naive UI 确认框与状态标签,
    无新页面、无导航变化;核心是后端调度与校验逻辑。
  • 状态:无

文档影响

  • 更新业务规则与术语:货憨憨的四条视频要求、三个新 upload_status 取值、
    「批量永不覆盖、单个可覆盖」的边界、以及这三个状态不作为断点的约定
  • 更新架构与代码地图:批量上传的调度位置与磁盘扫描规则

交付文档影响

  • 无交付文档影响,原因:内部工具,无外部使用者文档。

任务记录与可选快照

  • 单次任务事实来源:当前 Gitea 工单正文与评论
  • 默认不创建任务快照

验收标准

  • 可勾选多个商品一次上传,逐个串行处理
  • 单个商品失败(网络、货憨憨报错、回读不通过)后继续处理后续商品
  • 视频文件来自扫描 <video_dir>/<SafeDirName(itemId)>/*.mp4,不查 videos 表
  • 同目录多个 mp4 时按文件名升序取第一个
  • 子目录不存在或没有 mp4 → upload_status = missing,跳过
  • 预检不合规 → upload_status = invalid,last_error 写明违反哪一条,跳过
  • 时长 9.985 秒判定为不合规(不取整)
  • 大小 > 30 MB、宽或高 > 1280、非 mp4 容器,各自判定为不合规
  • 勾选 > 1 时,远端已有视频的商品 → upload_status = existing,跳过,不覆盖
  • 勾选 == 1 时,远端已有视频仍弹红字警告,确认后覆盖(保持 #17 行为)
  • 确认框显示可上传数、总大小、缺少视频数、不合规数
  • 确认框保留那行容量静态提示
  • 上传成功后按 local_path 写入或更新 videos 记录,source_item 留空,
    不得使用 ReplaceVideos,不清掉该商品的其它视频记录
  • 前端「上传状态」列与筛选支持三个新取值,按状态值分支不按中文文本
  • 不存在按 upload_status 跳过商品的断点逻辑;这些状态不禁用勾选
  • 未实现任何容量查询或容量判断逻辑
  • 未新增 migration、未新增第三方依赖、internal/ 未 import wails
  • 前端无绕过 Wails 生成绑定的写法

验证方式

go vet ./...
go build ./...
go test ./internal/... -v
go test ./...
cd frontend && npx vite build

全部用 httptest 假服务器,绝对不许连真实货憨憨。

单元测试需覆盖:

  • 扫盘:目录不存在 / 目录为空 / 多个 mp4 取升序第一个 / 目录里有非 mp4 文件时忽略
  • 预检边界:9.985 秒不合规、10.0 秒合规、60.0 秒合规、60.01 秒不合规
  • 预检边界:30*1024*1024 字节合规、多 1 字节不合规
  • 预检边界:1280×1280 合规、1281×1280 与 1280×1281 均不合规
  • 预检:非 mp4 容器不合规
  • 预检失败的 last_error 包含具体数值和规则
  • 批量:三个商品其中第二个上传失败,断言第三个仍被处理,状态各自正确
  • 批量:勾选 > 1 且远端已有视频 → 跳过且没有发出任何写请求
  • 上传成功后 videos 记录被写入/更新,该商品的其它 videos 记录未被删除
  • ffprobe 不存在时报明确中文错误,不静默跳过

真机验证(负责人执行):

  1. 先勾选 3—5 个试水,不要一上来就整批
  2. 确认框的统计数字与实际相符
  3. 观察运行日志逐个商品的处理结果
  4. 到货憨憨确认:成功的商品视频已挂上,被跳过的商品原有视频没有被动过
  5. 确认商品的价格、库存、SKU 均无变化

风险和回退

  • 风险 1(最高):批量往真实店铺写入,不可逆。缓解:批量永不覆盖远端已有视频;
    逐个串行便于定位;确认框汇总;建议先试 3—5 个。
  • 风险 2:无容量门禁,空间已用 98.7%。这是负责人的明确决定。
    实际影响是批量上传可能中途因容量不足而连续失败——但失败是单商品失败,
    会记录并继续,不会写坏数据。
  • 风险 3:55066525387 上传成功后视频在货憨憨消失,原因至今未查明
    (该视频完全符合四条规则,不是格式问题)。若批量后大面积复现,
    需要暂停并单独排查。本工单不解决这个问题。
  • 回退:改动集中在上述五个文件,git revert 即可。
    已写入货憨憨的视频不会因回退而撤销,需要时在货憨憨界面把 videoUrl 清空。
## 基本信息 - 类型:需求 - 所属 Epic:#2 - 所属 MVP / 版本:#6 - 阶段:待实施 ## 依赖与并行 - 前置工单:#17(R5 首版单商品)、#18(R4e) - 是否允许与前置工单并行:**是** - 原因:真实依赖是「前置工单的代码已合入 main」,不是「已通过人工验收」。 #17 的 `d1149b6`、`0fe24f7` 与 #18 的 `16e4f0f` 均已推送到 `origin/main`, 本工单在其之上修改。#17 的真机验收关注上传链路本身,与本工单新增的 批量调度、磁盘扫描和本地预检不重叠。 ## 子项目影响 - 仅影响的子项目 / 交付单元:cmsp 桌面端 - 是否跨子项目:否 - 是否修改共享接口或契约:否(复用 #17 已实现的三个货憨憨接口封装) - 各子项目需要执行的验证:见「验证方式」 ## 原始需求 - 来源:用户对话 - 提出时间:2026-09-03 - 关键原话: - 「批量勾选后,点击上传,先去对应子目录检测视频文件在不在,不在就跳过, 上传状态改为缺少视频,有视频就上传,然后继续。」 - 「不要去管视频来源,有些视频可能是其他地方来的,你只要在上传时检测视频 子目录里的视频文件是否存在,不存在,改状态,存在就上传。」 - 货憨憨对视频文件的要求:「1、视频大小:最大 30MB;2、视频时长:10s~60s; 3、视频格式:mp4;4、视频像素要求:像素宽高不超过 1280px * 1280px」 - 「容量无门禁。」 ## 要解决什么 ### 现状 #17 实现的上传只支持一次一个商品,且强制 `len(productIDs) == 1`。 选定文件的方式是「取该商品 `videos` 表中第一个 `status='downloaded'` 且文件真实存在的记录」——**依赖 `videos` 表**。 这带来两个问题: 1. **手工放进目录的视频传不了。** 负责人明确说明「有些视频可能是其他地方来的」, 这类文件在 `videos` 表里没有记录,当前实现看不见它们。 2. **库与磁盘漂移会挡住上传。** #18 修掉了一个漂移来源,但漂移无法根绝 (手动挪文件、换 `video_dir`、外部程序写入)。 ### 目标 批量勾选商品后一次点击完成上传。**以磁盘为事实来源**,不查 `videos` 表、 不关心视频来源。上传前用 ffprobe 按货憨憨的四条规则本地预检, 不合规的跳过而不是白传。 ## 做什么 / 不做什么 - 做: - 批量上传,逐个串行处理,单个失败不影响后续 - 以磁盘子目录为准查找视频文件,不查 `videos` 表 - 上传前本地 ffprobe 预检(大小、时长、格式、像素) - 三个新的 `upload_status` 取值及对应的界面显示与筛选 - 上传成功后按 `local_path` 补写 `videos` 记录 - 不做: - **不做容量门禁**(负责人 2026-09-03 明确决定),保留确认框那行静态提示 - 不改取视频、图搜、下载逻辑 - 不改三个货憨憨接口的封装(#17 已完成且已真机验证过上传链路) - **不引入按 `upload_status` 跳过的断点逻辑**(原因见下) - 不实现视频删除 - 不做「取视频时按四条规则筛同款」——那是 R4 范围,另开工单 ## 已确认方案 ### 处理流程 「上传数据」对**每个勾选的商品**串行执行,任一分支结束后继续下一个商品: ``` 1. 读远端状态:CheckShopProductVideo(product.ID) ← 只读 已有视频(Confirmed() 为真) └─ 勾选数 == 1 → 确认框红字警告,使用者确认后覆盖上传 └─ 勾选数 > 1 → upload_status = existing,跳过 2. 扫描磁盘:<video_dir>/<SafeDirName(itemId)>/*.mp4,按文件名升序 没有文件 → upload_status = missing,跳过 3. 本地预检(ffprobe,见下表) 不合规 → upload_status = invalid,last_error 写明违反哪一条,跳过 4. 上传:uploadFiles → batchUpdateShopProductVideo → 回读校验 成功 → upload_status = done,补写 videos 记录 失败 → upload_status = failed,last_error 记中文原因 ``` **逐个串行,不要用接口的数组批量能力。** 接口支持一次传多个商品, 但那样某个商品失败时无法定位是哪个,错误也没法精确写回。 素材上传本来也是一个文件一次。 **单商品失败不是全局停止门**:网络错误、货憨憨返回失败、回读不通过, 都只影响当前商品,记录后继续下一个。这一点与淘宝登录失效的全局停止门不同。 ### 覆盖策略(负责人 2026-09-03 确认) `batchUpdateShopProductVideo` 是覆盖语义,一个商品只挂一个视频, 传新地址即替换旧的,且不可恢复。 - **批量(勾选 > 1)永不覆盖**:远端已有视频一律跳过 - **单个(勾选 == 1)可以有意识地覆盖**:保留 #17 的红字警告与二次确认 判断依据用**实时读取的远端状态**,不用本地 `video_diagnosis`—— 后者只在「下载数据」时刷新,可能是几天前的。多一次只读请求, 批量二三十个的代价可以忽略。 ### 本地预检规则 | 项 | 判定 | 数据来源 | |---|---|---| | 大小 | ≤ 30 MB(按 `30 * 1024 * 1024` 字节) | `os.Stat` 或 ffprobe | | 时长 | `10.0 <= duration <= 60.0`,**不取整、不四舍五入** | ffprobe | | 格式 | 容器为 mp4 | ffprobe `format_name` 含 `mp4` | | 像素 | 宽 ≤ 1280 **且** 高 ≤ 1280 | ffprobe 视频流 `width` / `height` | 时长必须严格判定:本地已有的 `49365131605_1.mp4` 是 **9.985011 秒**, 差 0.015 秒卡在门槛下,任何取整都会把它误判为合规。 `internal/downloader` 现有的 `ProbeResult` 只有 `Duration` / `Size` / `FormatName`,**需要扩展出 `Width` / `Height`**。ffprobe 调用命令相应调整, 保持现有的「ffprobe 找不到就报明确中文错误,不静默跳过」行为。 预检失败的 `last_error` 必须写明**违反了哪一条**,例如 「视频时长 9.99 秒,不满足 10—60 秒要求」,不能只写「视频不合规」。 ### 新增的 upload_status 取值 Go 侧常量沿用现有英文风格(`pending` / `running` / `done` / `failed`): | 常量 | 值 | 界面显示 | 含义 | |---|---|---|---| | `UploadSkippedExisting` | `existing` | 已有视频 | 货憨憨那边已经有视频,批量时跳过 | | `UploadMissingVideo` | `missing` | 缺少视频 | 子目录里没有 mp4 文件 | | `UploadInvalidVideo` | `invalid` | 视频不合规 | 有文件但违反四条规则之一 | 前端「上传状态」列与筛选下拉框补上这三项。 **前端按状态值分支,不得按中文文本分支**(AGENTS.md)。 ### ★ 这三个状态不得成为断点 ★ `video_status = 'none'` 曾经因为被断点逻辑永久跳过而造成过问题(见 #15)。 本工单**不得重蹈覆辙**: - 批量上传**只处理使用者当次勾选的商品**,不得因为 `upload_status` 是 `missing` / `invalid` / `existing` / `failed` 就把商品排除在处理之外 - 这三个状态只是**上一次尝试的结果记录**,每次点上传都会重新扫盘、重新预检、 重新读远端,状态随之刷新 - 因此不需要额外的复位机制:换了视频文件、补下载了视频、远端视频被删, 下次点上传自然就会得到新结果 界面上也不得因为这些状态禁用勾选框。 ### 确认框 批量时不能逐个弹窗,改为一次汇总。内容至少包含: ``` 将处理 17 个商品 可上传 12 个,共 38.4 MB 缺少视频 3 个 视频不合规 2 个(时长或像素不符合货憨憨要求) 已有视频 0 个,将跳过 无法查询货憨憨剩余容量(接口未确认)。图片空间已用约 98.7%, 剩余约 26 GB,请自行确认后再继续。 ``` 前三类的统计由**扫盘和本地预检**得出,不需要联网; 「已有视频」需要读远端,**在确认框阶段不做**(否则弹框前要等 N 次请求), 放到实际执行时逐个读。确认框里对这一项写「执行时逐个检查,已有视频的会跳过」。 ### 容量(负责人 2026-09-03 明确决定:不做门禁) 不实现、不猜测、不调用任何容量查询接口。 确认框保留上面那行静态提示,除此之外不做任何容量相关的判断或分支。 这是负责人的明确决定,不是遗漏。 ### 上传成功后补写 videos 记录 手工放进目录的视频在 `videos` 表里没有记录。上传成功后按 `local_path` 写入或更新一条: - `product_id`、`local_path`、`file_size`、`remote_url`、`status = uploaded` - **`source_item` 留空**——磁盘上还原不出视频来自哪个淘宝同款 - 已存在同 `local_path` 的记录则更新,不重复插入 - **不得使用 `ReplaceVideos`**:那会清掉该商品的其它视频记录 ### 进度反馈 批量可能耗时较久,需要进度反馈。复用现有的 logx 运行日志即可, 每个商品处理完输出一行结果。**不要新建一套任务队列或进度事件**—— `internal/task` 的 Runner 是为取视频设计的(串行 CDP + 并行下载), 上传是纯串行 HTTP,套上去反而复杂。上传期间禁用「下载数据」「下载视频」按钮。 预计修改文件: - `internal/downloader/downloader.go`、`downloader_test.go`(ProbeResult 增加宽高) - `internal/store/product.go`、`product_test.go`(三个新状态常量) - `internal/store/video.go`、`video_test.go`(按 local_path 写入或更新) - `app.go`(批量调度、扫盘、预检) - `frontend/src/views/ProductListView.vue`(汇总确认框、状态显示与筛选) **不新增第三方依赖。`internal/` 下不得 import wails。不新增 migration。** ## 需求变化记录 | 日期 | 变化内容 | 原因 | 用户确认 | |---|---|---|---| | 2026-09-03 | 解除 #17 的单商品限制,改为批量 | 首版单商品已完成一次真实上传 | 是 | | 2026-09-03 | 选定视频改为扫描磁盘子目录,不查 `videos` 表 | 「不要去管视频来源,有些视频可能是其他地方来的」 | 是 | | 2026-09-03 | 新增本地 ffprobe 预检四项规则 | 负责人提供了货憨憨的视频要求 | 是 | | 2026-09-03 | 批量永不覆盖,单个可覆盖;判断用实时远端状态 | 覆盖不可恢复,本地 `video_diagnosis` 可能过期 | 是 | | 2026-09-03 | 不做容量门禁 | 负责人明确决定 | 是 | ## 设计与原型门禁 - 修改类型:非 UI 为主 + 小范围 UI(汇总确认框、状态列与筛选项) - 所需设计证据:无需原型 - 无需 UI 原型的原因:复用现有工具栏按钮、Naive UI 确认框与状态标签, 无新页面、无导航变化;核心是后端调度与校验逻辑。 - 状态:无 ## 文档影响 - [x] 更新业务规则与术语:货憨憨的四条视频要求、三个新 `upload_status` 取值、 「批量永不覆盖、单个可覆盖」的边界、以及这三个状态不作为断点的约定 - [x] 更新架构与代码地图:批量上传的调度位置与磁盘扫描规则 ## 交付文档影响 - [x] 无交付文档影响,原因:内部工具,无外部使用者文档。 ## 任务记录与可选快照 - 单次任务事实来源:当前 Gitea 工单正文与评论 - [x] 默认不创建任务快照 ## 验收标准 - [ ] 可勾选多个商品一次上传,逐个串行处理 - [ ] 单个商品失败(网络、货憨憨报错、回读不通过)后继续处理后续商品 - [ ] 视频文件来自扫描 `<video_dir>/<SafeDirName(itemId)>/*.mp4`,**不查 `videos` 表** - [ ] 同目录多个 mp4 时按文件名升序取第一个 - [ ] 子目录不存在或没有 mp4 → `upload_status = missing`,跳过 - [ ] 预检不合规 → `upload_status = invalid`,`last_error` 写明违反哪一条,跳过 - [ ] **时长 9.985 秒判定为不合规**(不取整) - [ ] 大小 > 30 MB、宽或高 > 1280、非 mp4 容器,各自判定为不合规 - [ ] 勾选 > 1 时,远端已有视频的商品 → `upload_status = existing`,跳过,**不覆盖** - [ ] 勾选 == 1 时,远端已有视频仍弹红字警告,确认后覆盖(保持 #17 行为) - [ ] 确认框显示可上传数、总大小、缺少视频数、不合规数 - [ ] 确认框保留那行容量静态提示 - [ ] 上传成功后按 `local_path` 写入或更新 `videos` 记录,`source_item` 留空, **不得使用 `ReplaceVideos`**,不清掉该商品的其它视频记录 - [ ] 前端「上传状态」列与筛选支持三个新取值,**按状态值分支不按中文文本** - [ ] **不存在按 `upload_status` 跳过商品的断点逻辑**;这些状态不禁用勾选 - [ ] 未实现任何容量查询或容量判断逻辑 - [ ] 未新增 migration、未新增第三方依赖、`internal/` 未 import wails - [ ] 前端无绕过 Wails 生成绑定的写法 ## 验证方式 ``` go vet ./... go build ./... go test ./internal/... -v go test ./... cd frontend && npx vite build ``` **全部用 `httptest` 假服务器,绝对不许连真实货憨憨。** 单元测试需覆盖: - 扫盘:目录不存在 / 目录为空 / 多个 mp4 取升序第一个 / 目录里有非 mp4 文件时忽略 - 预检边界:**9.985 秒不合规**、10.0 秒合规、60.0 秒合规、60.01 秒不合规 - 预检边界:`30*1024*1024` 字节合规、多 1 字节不合规 - 预检边界:1280×1280 合规、1281×1280 与 1280×1281 均不合规 - 预检:非 mp4 容器不合规 - 预检失败的 `last_error` 包含具体数值和规则 - 批量:三个商品其中第二个上传失败,断言第三个仍被处理,状态各自正确 - 批量:勾选 > 1 且远端已有视频 → 跳过且**没有发出任何写请求** - 上传成功后 `videos` 记录被写入/更新,该商品的其它 `videos` 记录**未被删除** - ffprobe 不存在时报明确中文错误,不静默跳过 真机验证(负责人执行): 1. 先勾选 **3—5 个**试水,不要一上来就整批 2. 确认框的统计数字与实际相符 3. 观察运行日志逐个商品的处理结果 4. 到货憨憨确认:成功的商品视频已挂上,被跳过的商品**原有视频没有被动过** 5. 确认商品的价格、库存、SKU 均无变化 ## 风险和回退 - **风险 1(最高)**:批量往真实店铺写入,不可逆。缓解:批量永不覆盖远端已有视频; 逐个串行便于定位;确认框汇总;建议先试 3—5 个。 - **风险 2**:无容量门禁,空间已用 98.7%。这是负责人的明确决定。 实际影响是批量上传可能中途因容量不足而连续失败——但失败是单商品失败, 会记录并继续,不会写坏数据。 - **风险 3**:`55066525387` 上传成功后视频在货憨憨消失,原因至今未查明 (该视频完全符合四条规则,不是格式问题)。若批量后大面积复现, 需要暂停并单独排查。**本工单不解决这个问题。** - **回退**:改动集中在上述五个文件,`git revert` 即可。 已写入货憨憨的视频不会因回退而撤销,需要时在货憨憨界面把 `videoUrl` 清空。
Author
Owner

最终实施证据

提交:c6bafd9 feat: 批量上传视频,以磁盘为准并按货憨憨规则本地预检 (#19)
已推送到 origin/main。

改动文件(8 个)

文件 内容
internal/downloader/downloader.go / _test.go ProbeResult 增加 Width / Height;FindFirstMP4 扫盘;UploadValidationError 四条规则预检
internal/store/product.go / _test.go 三个新状态常量 existing / missing / invalid,上传状态筛选
internal/store/video.go / _test.go UpsertUploadedVideo(按 product_id + local_path 写入或更新)
app.go 批量串行调度、findUploadVideo(只扫盘)、validateUploadVideo、四条分支
frontend/src/views/ProductListView.vue 汇总确认框、三个新状态的显示与筛选

Wails 绑定变更:GetUploadPreview(productIDs []string) (UploadPreview, error)
返回值由单商品详情改为批量汇总。UploadVideos 签名未变。
下次运行前需重启 run_dev.bat 重新生成绑定。

验证结果

go vet ./...        通过
go build ./...      通过
go test ./...       全部通过
npx vite build      通过(仅原有 chunk 体积警告)

11 项硬性验收逐条复核,全部通过:实施全程未向 www.huohanhan.com 发出请求;
选文件只依据磁盘扫描(findUploadVideo 不查 videos 表);
时长判定为 Duration < 10.0 || > 60.0 直接比较,math.Round 仅用于错误文案;
existing 分支在任何写请求之前 return;三个新状态全项目只写不读、
不存在按 upload_status 排除商品的逻辑;补写记录用 UpsertUploadedVideo
而非 ReplaceVideos;Go 侧无任何容量逻辑;前端无绕过绑定的写法且按状态值分支;
未新增 migration、依赖、未 import wails;未修改 internal/huohanhan/、
internal/task/、internal/store/store.go;未改动 cmsp.db、payloads/、
config.yaml。

预检边界测试(Test上传视频本地预检边界)

9.985 秒不合规 ✓   10 秒合规 ✓   60 秒合规 ✓   60.01 秒不合规 ✓
30MB 合规 ✓   多 1 字节不合规 ✓
1281×1280 不合规 ✓   1280×1281 不合规 ✓   matroska 容器不合规 ✓

对当前库内 4 个商品的预期行为

蝦皮 ID 本地视频 预期结果
40483130206 12.01s / 1.6MB / 720×960 上传
49365131605 9.99s invalid,跳过
55066525387 27.17s,合规 远端已有视频 → existing,批量时跳过
47117207389 文件在 视频/ 根目录,不在 47117207389/ 子目录 missing,跳过

最后一个需要注意:该视频是改子目录规则之前下载的,文件名为
淘宝-1126520796908732416-916174578078-1.mp4,位于视频根目录。
新逻辑只扫子目录,因此会判为「缺少视频」。
需要上传的话得把文件移入 <video_dir>/47117207389/ 并改名。

未验证内容

  • 全部接口调用由 httptest 假服务器覆盖,从未与真实货憨憨交互。
  • 批量行为、确认框统计、跳过逻辑在真实环境下均未验证。

待负责人真机验证

  1. 先重启 run_dev.bat(绑定签名变了)
  2. 先勾选 3—5 个试水,不要整批
  3. 确认框统计数字与实际相符
  4. 观察运行日志逐个商品的结果
  5. 到货憨憨确认:成功的商品视频已挂上;被跳过的商品原有视频没有被动过
  6. 确认价格、库存、SKU 均无变化

特别提醒:55066525387 上传成功后视频在货憨憨消失,原因至今未查明
(该视频完全符合四条规则)。本次批量验证时请重点观察
40483130206 上传后过一段时间视频是否仍在。若再次消失,
应暂停批量并单独排查该问题,本工单不解决它。

文档影响:声明需更新业务规则与术语、架构与代码地图两处 Wiki。
尚未执行,待验收通过后与 #15 / #17 / #18 一并处理。

工单保持「待验收」。

### 最终实施证据 **提交**:`c6bafd9` feat: 批量上传视频,以磁盘为准并按货憨憨规则本地预检 (#19) 已推送到 `origin/main`。 **改动文件**(8 个) | 文件 | 内容 | |---|---| | `internal/downloader/downloader.go` / `_test.go` | `ProbeResult` 增加 `Width` / `Height`;`FindFirstMP4` 扫盘;`UploadValidationError` 四条规则预检 | | `internal/store/product.go` / `_test.go` | 三个新状态常量 `existing` / `missing` / `invalid`,上传状态筛选 | | `internal/store/video.go` / `_test.go` | `UpsertUploadedVideo`(按 `product_id` + `local_path` 写入或更新) | | `app.go` | 批量串行调度、`findUploadVideo`(只扫盘)、`validateUploadVideo`、四条分支 | | `frontend/src/views/ProductListView.vue` | 汇总确认框、三个新状态的显示与筛选 | **Wails 绑定变更**:`GetUploadPreview(productIDs []string) (UploadPreview, error)` 返回值由单商品详情改为批量汇总。`UploadVideos` 签名未变。 **下次运行前需重启 `run_dev.bat` 重新生成绑定。** **验证结果** ``` go vet ./... 通过 go build ./... 通过 go test ./... 全部通过 npx vite build 通过(仅原有 chunk 体积警告) ``` 11 项硬性验收逐条复核,全部通过:实施全程未向 www.huohanhan.com 发出请求; 选文件只依据磁盘扫描(`findUploadVideo` 不查 `videos` 表); 时长判定为 `Duration < 10.0 || > 60.0` 直接比较,`math.Round` 仅用于错误文案; `existing` 分支在任何写请求之前 return;三个新状态全项目只写不读、 不存在按 `upload_status` 排除商品的逻辑;补写记录用 `UpsertUploadedVideo` 而非 `ReplaceVideos`;Go 侧无任何容量逻辑;前端无绕过绑定的写法且按状态值分支; 未新增 migration、依赖、未 import wails;未修改 `internal/huohanhan/`、 `internal/task/`、`internal/store/store.go`;未改动 `cmsp.db`、`payloads/`、 `config.yaml`。 **预检边界测试**(`Test上传视频本地预检边界`) ``` 9.985 秒不合规 ✓ 10 秒合规 ✓ 60 秒合规 ✓ 60.01 秒不合规 ✓ 30MB 合规 ✓ 多 1 字节不合规 ✓ 1281×1280 不合规 ✓ 1280×1281 不合规 ✓ matroska 容器不合规 ✓ ``` **对当前库内 4 个商品的预期行为** | 蝦皮 ID | 本地视频 | 预期结果 | |---|---|---| | `40483130206` | 12.01s / 1.6MB / 720×960 | **上传** | | `49365131605` | 9.99s | `invalid`,跳过 | | `55066525387` | 27.17s,合规 | 远端已有视频 → `existing`,批量时跳过 | | `47117207389` | 文件在 `视频/` 根目录,**不在 `47117207389/` 子目录** | `missing`,跳过 | 最后一个需要注意:该视频是改子目录规则之前下载的,文件名为 `淘宝-1126520796908732416-916174578078-1.mp4`,位于视频根目录。 新逻辑只扫子目录,因此会判为「缺少视频」。 需要上传的话得把文件移入 `<video_dir>/47117207389/` 并改名。 **未验证内容** - 全部接口调用由 `httptest` 假服务器覆盖,**从未与真实货憨憨交互**。 - 批量行为、确认框统计、跳过逻辑在真实环境下均未验证。 **待负责人真机验证** 1. **先重启 `run_dev.bat`**(绑定签名变了) 2. 先勾选 **3—5 个**试水,不要整批 3. 确认框统计数字与实际相符 4. 观察运行日志逐个商品的结果 5. 到货憨憨确认:成功的商品视频已挂上;**被跳过的商品原有视频没有被动过** 6. 确认价格、库存、SKU 均无变化 **特别提醒**:`55066525387` 上传成功后视频在货憨憨消失,原因至今未查明 (该视频完全符合四条规则)。本次批量验证时请重点观察 `40483130206` 上传后过一段时间视频是否仍在。若再次消失, 应暂停批量并单独排查该问题,本工单不解决它。 **文档影响**:声明需更新业务规则与术语、架构与代码地图两处 Wiki。 **尚未执行**,待验收通过后与 #15 / #17 / #18 一并处理。 工单保持「待验收」。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: chengma/cmsp#19