R5:视频上传回货憨憨并关联商品(首版单商品) #17

Open
opened 2026-09-03 14:42:55 +08:00 by ila · 3 comments
Owner

基本信息

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

依赖与并行

  • 前置工单:#13(R4a)、#14(R4b)已完成;#15(R4c)待验收
  • 是否允许与前置工单并行:否
  • 原因:上传的输入是取视频链路产出的本地文件与 videos 表记录。#15 修改了 videos 与 products 的状态写入时机,必须在其之上做。

子项目影响

  • 仅影响的子项目 / 交付单元:cmsp 桌面端
  • 是否跨子项目:否
  • 是否修改共享接口或契约:否(调用货憨憨既有接口,不改变其契约)
  • 各子项目需要执行的验证:见「验证方式」

原始需求

  • 来源:用户对话
  • 提出时间:2026-09-01 起多次,2026-09-03 确认接口
  • 关键原话:
    • 「不行,上传后要保存。只上传拿到了 id,要保存,这个 id 才会和指定商品关联。」
    • 「huohanhan_batch_save_product_video.har 是批量上传视频的途径,分析是否符合要求」
    • 「现在可以上传了吗」

要解决什么

现状

app.go 的 UploadVideos 是占位实现,点「上传数据」直接报错,返回「「上传数据」还没实现,见工单 R5」。

本地已有 4 个下载完成的视频,upload_status 全为 pending,文件均存在,products 表已具备上传所需的 id(货憨憨内部记录 ID)与 platform_shop_id。

目标

把本地视频上传回货憨憨并关联到对应商品,使 Shopee 商品页刷新后能看到视频,从而打通 Epic 的最后一环。

接口契约(2026-09-03 由 HAR 确认,Q1 / Q2 关闭)

证据:payloads/huohanhan_batch_save_product_video.har(9-03 抓取,共 11 个请求)。

第一步:上传素材

POST /api/product/material/uploadFiles      multipart/form-data
  files       = <二进制>  filename="40483130206_1.mp4"  Content-Type: video/mp4
  isLocalFile = true
  fileType    = 1
→ {"type":"SUCCESS","message":"success","code":"200",
   "bean":["https://hhh-prod-1307856765.cos.ap-guangzhou.myqcloud.com/video/1126865595209777153.mp4"]}

bean 是数组,元素就是最终可用的视频 URL。

上传接口不需要 productId 或 platformShopId —— 素材与商品是解耦的。只上传不关联,结果只是一个孤儿素材。

第二步:关联到商品

POST /api/product/batchEdit/batchUpdateShopProductVideo   application/json
[{"id":"1114185406108811264","platformShopId":"1044466812","videoUrl":"<第一步的 URL>"}]
→ {"type":"SUCCESS","message":"success","code":"200","extension":{}}
  • 请求体是裸数组,不是 {type, bean} 包装
  • 每个元素只有三个字段
  • 天然支持批量:一次调用可放多个商品
  • videoUrl 传空字符串是删除动作(HAR 第 1 个请求就是删除),即该接口是覆盖语义,一个商品只能挂一个视频

第三步(可选):回读校验

POST /api/product/batchEdit/getShopItemInfoPage      form 编码
size=1&current=1&descs=&ascs=&ids=<id>&fields=video,videoUploadIdStr,videoFailReason,tempVideoUrl

返回 records[0].video 为空数组表示无视频,有元素表示已挂上。

明确否决的另一条路

payloads/huohanhan_save_product_info.har(单商品编辑表单)走的是 common-product/uploadVideo + common-product/updateProductItem。本工单不采用,原因:

  • updateProductItem 需要回传整份商品:56 个字段、26589 字节、20 个 SKU
  • getDetail 返回的 bean.itemInfo 无法直接回传:13 个字段缺失、9 个结构不同
  • 其中 model[].priceInfo 的值就不一致 —— 那是价格数据,照抄会把真实店铺的价格写坏,且不可逆

batchUpdateShopProductVideo 只改 videoUrl 一个字段,完全不碰价格、库存、SKU。

做什么 / 不做什么

  • 做:
    • 单商品上传全链路:读本地文件 → uploadFiles → batchUpdateShopProductVideo → 回读校验
    • 上传前检查货憨憨图片空间容量
    • 写回 videos.remote_url / videos.status 与 products.upload_status
    • 第一版只支持一次处理一个商品,且必须由使用者显式点击
  • 不做:
    • 不做批量队列(放到下一个工单,先确认线上没被写坏)
    • 不采用 updateProductItem 路线
    • 不实现视频删除功能
    • 不改取视频、图搜、下载逻辑
    • 不把上传作为任何查询流程的副作用触发

已确认方案

安全边界(AGENTS.md 红线,最高优先级)

这是对真实店铺的写操作,不可逆。

  1. 必须由使用者显式点击「上传数据」触发,不得作为下载数据、取视频或任何查询流程的副作用发生。
  2. 点击后必须弹出二次确认,明确列出:将上传几个商品、商品的蝦皮 ID 与名称、每个商品会挂哪个本地视频文件。
  3. 第一版只允许勾选一个商品,勾选多个时提示「首版一次只能上传一个商品,确认线上结果正常后再开放批量」。
  4. 任何一步返回非 SUCCESS 立即停止,不自动重试。
  5. 全过程通过 logx 输出,但不得记录完整 token、Cookie、Authorization 和视频文件的完整 Base64。

图片空间容量门禁

Epic #2 的 Q8 记录:货憨憨图片空间已用约 98.7%,剩余约 26 GB。视频体积远大于图片,批量上传很容易撞上限。

  • 上传前调用容量查询接口(设置页占位处写的是 product/material/getMaterialSize,该接口尚无抓包证据,需先确认真实路径与返回结构)
  • 剩余容量不足以放下本次文件时直接停止并提示,不发起上传
  • 上传接口返回「容量不足」类错误时立即停止,不得反复重试
  • 若容量接口最终无法确认,则在工单记录并降级为:上传前在确认框中显示「无法查询剩余容量,请自行确认」,不得跳过这一提示

一个商品只挂一个视频

batchUpdateShopProductVideo 的 videoUrl 是单个字符串,覆盖语义。而 max_videos_per_product 当前为 2。

处理方式(负责人未另行指定,按 2026-09-03 推荐方案采用):上传时取该商品 videos 表中第一个 status 为 downloaded 且文件存在的记录,max_videos_per_product 保持 2 不变 —— 多下载的那个作为备选保留,将来第一个视频有问题时可以换。不修改配置默认值。

状态写回

  • 上传成功:videos.remote_url 记返回的 COS 地址,videos.status 置 uploaded,products.upload_status 置 done
  • 上传失败:products.upload_status 置 failed,last_error 记中文原因
  • 上传中:products.upload_status 置 running
  • SQLite 是唯一事实来源,不引入任何 JSON 或内存中的第二份状态

回读校验

第二步返回 SUCCESS 后,调用 getShopItemInfoPage 回读该商品,确认 records[0].video 非空再写 upload_status 为 done。回读失败或仍为空时记为失败并提示,不假定成功。

实现位置

  • internal/huohanhan/upload.go、upload_test.go(新建):两个接口的封装与响应解析。复用现有 Client.Request(它已支持自定义 contentType 与 401 重试),不新建 HTTP 客户端
  • multipart 请求体用标准库 mime/multipart 构造。视频 1—5 MB,一次性读入内存可接受,不做流式
  • internal/store/video.go:新增按商品取可用视频、写回 remote_url 与状态的方法
  • app.go:UploadVideos 落地
  • frontend/src/views/ProductListView.vue:二次确认框与单选限制

预计修改文件:

  • internal/huohanhan/upload.go、upload_test.go(新建)
  • internal/store/video.go、video_test.go
  • internal/store/product.go(若需新增查询)
  • app.go
  • frontend/src/views/ProductListView.vue

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

需求变化记录

日期 变化内容 原因 用户确认
2026-09-03 Q1 / Q2 关闭,接口契约由 HAR 确认 取得 huohanhan_batch_save_product_video.har 是
2026-09-03 否决 updateProductItem 路线 需回传 56 字段整份商品,priceInfo 值不一致会写坏价格 是
2026-09-03 首版限制为单商品、显式触发、二次确认 对真实店铺的不可逆写操作 是
2026-09-03 一商品一视频,取第一个可用的,不改 max_videos_per_product 接口为覆盖语义;保留备选视频 未另行指定,按推荐方案

设计与原型门禁

  • 修改类型:非 UI 为主 + 小范围 UI(一个二次确认框)
  • 所需设计证据:架构与 API 设计(已在「接口契约」一节完成,依据为真实 HAR,非推测)
  • 无需 UI 原型的原因:仅新增一个确认对话框,复用 Naive UI 既有组件与「参数设置」页已确认的交互模式,无新页面、无导航变化
  • 状态:已确认(接口部分)

文档影响

  • 更新业务规则与术语:上传链路的接口契约、「一个商品只能挂一个视频」的覆盖语义、写操作必须显式确认的边界
  • 更新架构与代码地图:新增 internal/huohanhan/upload.go
  • 更新需求总览:关闭 Q1 / Q2,更新 Q8 的处理方式

交付文档影响

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

任务记录与可选快照

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

验收标准

  • 「上传数据」必须由使用者显式点击才触发,不作为任何流程的副作用
  • 弹出二次确认,列出商品蝦皮 ID、名称和将上传的本地文件名
  • 勾选多个商品时提示首版只能上传一个,不发起任何请求
  • uploadFiles 请求为 multipart,字段名与 HAR 一致(files / isLocalFile 为 true / fileType 为 1),文件名取本地文件名
  • 正确解析 bean 数组取出 COS 地址
  • batchUpdateShopProductVideo 请求体为裸数组,元素含且仅含 id / platformShopId / videoUrl
  • 上传前完成容量检查;容量不足直接停止不发起上传
  • 上传成功后回读 getShopItemInfoPage,确认 video 非空才写 done
  • 任一步返回非 SUCCESS 立即停止,不自动重试,错误信息为可读中文
  • 状态正确写回 videos.remote_url / videos.status / products.upload_status
  • 日志不含完整 token、Cookie、Authorization 与视频 Base64
  • internal/ 下不得 import wails;不新增第三方依赖
  • 前端不得出现绕过 Wails 生成绑定的写法

验证方式

可离线执行:

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

单元测试用 httptest 起假货憨憨服务器,不得连真实货憨憨,需覆盖:

  • multipart 请求体的字段名、文件名与 Content-Type 正确
  • bean 数组解析;bean 为空数组时报错而不是静默成功
  • 关联请求体序列化后与 HAR 中的形状逐字段一致
  • 非 SUCCESS 响应转为可读中文错误且不重试
  • 回读返回空 video 数组时判为失败
  • 容量不足时不发起上传
  • 本地文件缺失时报错,不上传空内容

真机验证(必须由负责人执行,且限定范围):

  1. 只对一个指定的测试商品执行,不要对整批操作
  2. 建议使用 40483130206(id 为 1114185406108811264,platformShopId 为 1044466812,本地视频已存在),该商品在 9-03 的 HAR 中已被人工走过同一链路,风险可控
  3. 上传后在货憨憨界面确认视频已挂上,且价格、库存、SKU 均无变化
  4. 到 Shopee 商品页确认视频可见
  5. 确认无误后再考虑开放批量(下一个工单)

风险和回退

  • 风险 1(最高):对真实店铺的不可逆写入。缓解:首版单商品、显式触发、二次确认、指定测试商品、不自动重试。
  • 风险 2:图片空间仅剩约 26 GB 且已用 98.7%,视频体积大,容易撞上限甚至影响货憨憨其它业务。缓解:上传前容量门禁;返回容量不足立即停止。
  • 风险 3:容量查询接口 product/material/getMaterialSize 尚无抓包证据,路径与返回结构未经验证。缓解:实施时先确认;无法确认则降级为确认框提示,并在工单记录。
  • 风险 4:batchUpdateShopProductVideo 是覆盖语义,对已有视频的商品执行会替换掉原视频。缓解:只对 video_diagnosis 为 missing 的商品开放上传,确认框中显示当前视频状态。
  • 回退:代码可 git revert。但已经写入货憨憨的数据不会因此回滚 —— 需要在货憨憨界面手动把 videoUrl 清空(HAR 证实传空字符串即删除)。这正是首版限定单商品的原因。
## 基本信息 - 类型:需求 - 所属 Epic:#2 - 所属 MVP / 版本:#6 - 阶段:待确认 ## 依赖与并行 - 前置工单:#13(R4a)、#14(R4b)已完成;#15(R4c)待验收 - 是否允许与前置工单并行:否 - 原因:上传的输入是取视频链路产出的本地文件与 `videos` 表记录。#15 修改了 `videos` 与 `products` 的状态写入时机,必须在其之上做。 ## 子项目影响 - 仅影响的子项目 / 交付单元:cmsp 桌面端 - 是否跨子项目:否 - 是否修改共享接口或契约:否(调用货憨憨既有接口,不改变其契约) - 各子项目需要执行的验证:见「验证方式」 ## 原始需求 - 来源:用户对话 - 提出时间:2026-09-01 起多次,2026-09-03 确认接口 - 关键原话: - 「不行,上传后要保存。只上传拿到了 id,要保存,这个 id 才会和指定商品关联。」 - 「huohanhan_batch_save_product_video.har 是批量上传视频的途径,分析是否符合要求」 - 「现在可以上传了吗」 ## 要解决什么 ### 现状 `app.go` 的 `UploadVideos` 是占位实现,点「上传数据」直接报错,返回「「上传数据」还没实现,见工单 R5」。 本地已有 4 个下载完成的视频,`upload_status` 全为 `pending`,文件均存在,`products` 表已具备上传所需的 `id`(货憨憨内部记录 ID)与 `platform_shop_id`。 ### 目标 把本地视频上传回货憨憨并关联到对应商品,使 Shopee 商品页刷新后能看到视频,从而打通 Epic 的最后一环。 ## 接口契约(2026-09-03 由 HAR 确认,Q1 / Q2 关闭) 证据:`payloads/huohanhan_batch_save_product_video.har`(9-03 抓取,共 11 个请求)。 ### 第一步:上传素材 ``` POST /api/product/material/uploadFiles multipart/form-data files = <二进制> filename="40483130206_1.mp4" Content-Type: video/mp4 isLocalFile = true fileType = 1 → {"type":"SUCCESS","message":"success","code":"200", "bean":["https://hhh-prod-1307856765.cos.ap-guangzhou.myqcloud.com/video/1126865595209777153.mp4"]} ``` `bean` 是**数组**,元素就是最终可用的视频 URL。 上传接口**不需要** `productId` 或 `platformShopId` —— 素材与商品是解耦的。只上传不关联,结果只是一个孤儿素材。 ### 第二步:关联到商品 ``` POST /api/product/batchEdit/batchUpdateShopProductVideo application/json [{"id":"1114185406108811264","platformShopId":"1044466812","videoUrl":"<第一步的 URL>"}] → {"type":"SUCCESS","message":"success","code":"200","extension":{}} ``` - 请求体是**裸数组**,不是 `{type, bean}` 包装 - 每个元素只有三个字段 - 天然支持批量:一次调用可放多个商品 - `videoUrl` 传空字符串是**删除**动作(HAR 第 1 个请求就是删除),即该接口是**覆盖语义,一个商品只能挂一个视频** ### 第三步(可选):回读校验 ``` POST /api/product/batchEdit/getShopItemInfoPage form 编码 size=1&current=1&descs=&ascs=&ids=<id>&fields=video,videoUploadIdStr,videoFailReason,tempVideoUrl ``` 返回 `records[0].video` 为空数组表示无视频,有元素表示已挂上。 ### 明确否决的另一条路 `payloads/huohanhan_save_product_info.har`(单商品编辑表单)走的是 `common-product/uploadVideo` + `common-product/updateProductItem`。**本工单不采用**,原因: - `updateProductItem` 需要回传**整份商品**:56 个字段、26589 字节、20 个 SKU - `getDetail` 返回的 `bean.itemInfo` 无法直接回传:13 个字段缺失、9 个结构不同 - 其中 `model[].priceInfo` 的**值**就不一致 —— 那是价格数据,照抄会把真实店铺的价格写坏,且不可逆 `batchUpdateShopProductVideo` 只改 `videoUrl` 一个字段,完全不碰价格、库存、SKU。 ## 做什么 / 不做什么 - 做: - 单商品上传全链路:读本地文件 → uploadFiles → batchUpdateShopProductVideo → 回读校验 - 上传前检查货憨憨图片空间容量 - 写回 `videos.remote_url` / `videos.status` 与 `products.upload_status` - **第一版只支持一次处理一个商品,且必须由使用者显式点击** - 不做: - **不做批量队列**(放到下一个工单,先确认线上没被写坏) - 不采用 `updateProductItem` 路线 - 不实现视频删除功能 - 不改取视频、图搜、下载逻辑 - 不把上传作为任何查询流程的副作用触发 ## 已确认方案 ### 安全边界(AGENTS.md 红线,最高优先级) 这是**对真实店铺的写操作,不可逆**。 1. **必须由使用者显式点击「上传数据」触发**,不得作为下载数据、取视频或任何查询流程的副作用发生。 2. 点击后**必须弹出二次确认**,明确列出:将上传几个商品、商品的蝦皮 ID 与名称、每个商品会挂哪个本地视频文件。 3. **第一版只允许勾选一个商品**,勾选多个时提示「首版一次只能上传一个商品,确认线上结果正常后再开放批量」。 4. 任何一步返回非 SUCCESS 立即停止,**不自动重试**。 5. 全过程通过 logx 输出,但**不得记录完整 token、Cookie、Authorization 和视频文件的完整 Base64**。 ### 图片空间容量门禁 Epic #2 的 Q8 记录:货憨憨图片空间已用约 98.7%,剩余约 26 GB。视频体积远大于图片,批量上传很容易撞上限。 - 上传前调用容量查询接口(设置页占位处写的是 `product/material/getMaterialSize`,**该接口尚无抓包证据,需先确认真实路径与返回结构**) - 剩余容量不足以放下本次文件时**直接停止并提示**,不发起上传 - 上传接口返回「容量不足」类错误时**立即停止,不得反复重试** - 若容量接口最终无法确认,则在工单记录并降级为:上传前在确认框中显示「无法查询剩余容量,请自行确认」,**不得跳过这一提示** ### 一个商品只挂一个视频 `batchUpdateShopProductVideo` 的 `videoUrl` 是单个字符串,覆盖语义。而 `max_videos_per_product` 当前为 2。 **处理方式(负责人未另行指定,按 2026-09-03 推荐方案采用)**:上传时取该商品 `videos` 表中**第一个 status 为 downloaded 且文件存在**的记录,`max_videos_per_product` 保持 2 不变 —— 多下载的那个作为备选保留,将来第一个视频有问题时可以换。**不修改配置默认值。** ### 状态写回 - 上传成功:`videos.remote_url` 记返回的 COS 地址,`videos.status` 置 `uploaded`,`products.upload_status` 置 `done` - 上传失败:`products.upload_status` 置 `failed`,`last_error` 记中文原因 - 上传中:`products.upload_status` 置 `running` - **SQLite 是唯一事实来源**,不引入任何 JSON 或内存中的第二份状态 ### 回读校验 第二步返回 SUCCESS 后,调用 `getShopItemInfoPage` 回读该商品,确认 `records[0].video` 非空再写 `upload_status` 为 `done`。回读失败或仍为空时记为失败并提示,**不假定成功**。 ### 实现位置 - `internal/huohanhan/upload.go`、`upload_test.go`(新建):两个接口的封装与响应解析。复用现有 `Client.Request`(它已支持自定义 contentType 与 401 重试),**不新建 HTTP 客户端** - multipart 请求体用标准库 `mime/multipart` 构造。视频 1—5 MB,一次性读入内存可接受,不做流式 - `internal/store/video.go`:新增按商品取可用视频、写回 `remote_url` 与状态的方法 - `app.go`:`UploadVideos` 落地 - `frontend/src/views/ProductListView.vue`:二次确认框与单选限制 预计修改文件: - `internal/huohanhan/upload.go`、`upload_test.go`(新建) - `internal/store/video.go`、`video_test.go` - `internal/store/product.go`(若需新增查询) - `app.go` - `frontend/src/views/ProductListView.vue` **不新增第三方依赖。`internal/` 下不得 import wails。** ## 需求变化记录 | 日期 | 变化内容 | 原因 | 用户确认 | |---|---|---|---| | 2026-09-03 | Q1 / Q2 关闭,接口契约由 HAR 确认 | 取得 `huohanhan_batch_save_product_video.har` | 是 | | 2026-09-03 | 否决 `updateProductItem` 路线 | 需回传 56 字段整份商品,`priceInfo` 值不一致会写坏价格 | 是 | | 2026-09-03 | 首版限制为单商品、显式触发、二次确认 | 对真实店铺的不可逆写操作 | 是 | | 2026-09-03 | 一商品一视频,取第一个可用的,不改 `max_videos_per_product` | 接口为覆盖语义;保留备选视频 | 未另行指定,按推荐方案 | ## 设计与原型门禁 - 修改类型:非 UI 为主 + 小范围 UI(一个二次确认框) - 所需设计证据:架构与 API 设计(已在「接口契约」一节完成,依据为真实 HAR,非推测) - 无需 UI 原型的原因:仅新增一个确认对话框,复用 Naive UI 既有组件与「参数设置」页已确认的交互模式,无新页面、无导航变化 - 状态:已确认(接口部分) ## 文档影响 - [x] 更新业务规则与术语:上传链路的接口契约、「一个商品只能挂一个视频」的覆盖语义、写操作必须显式确认的边界 - [x] 更新架构与代码地图:新增 `internal/huohanhan/upload.go` - [x] 更新需求总览:关闭 Q1 / Q2,更新 Q8 的处理方式 ## 交付文档影响 - [x] 无交付文档影响,原因:内部工具,无外部使用者文档。 ## 任务记录与可选快照 - 单次任务事实来源:当前 Gitea 工单正文与评论 - [x] 默认不创建任务快照 ## 验收标准 - [ ] 「上传数据」必须由使用者显式点击才触发,不作为任何流程的副作用 - [ ] 弹出二次确认,列出商品蝦皮 ID、名称和将上传的本地文件名 - [ ] 勾选多个商品时提示首版只能上传一个,不发起任何请求 - [ ] `uploadFiles` 请求为 multipart,字段名与 HAR 一致(`files` / `isLocalFile` 为 true / `fileType` 为 1),文件名取本地文件名 - [ ] 正确解析 `bean` 数组取出 COS 地址 - [ ] `batchUpdateShopProductVideo` 请求体为裸数组,元素含且仅含 `id` / `platformShopId` / `videoUrl` - [ ] 上传前完成容量检查;容量不足直接停止不发起上传 - [ ] 上传成功后回读 `getShopItemInfoPage`,确认 `video` 非空才写 `done` - [ ] 任一步返回非 SUCCESS 立即停止,不自动重试,错误信息为可读中文 - [ ] 状态正确写回 `videos.remote_url` / `videos.status` / `products.upload_status` - [ ] 日志不含完整 token、Cookie、Authorization 与视频 Base64 - [ ] `internal/` 下不得 import wails;不新增第三方依赖 - [ ] 前端不得出现绕过 Wails 生成绑定的写法 ## 验证方式 可离线执行: ``` go vet ./... go build ./... go test ./internal/... -v go test ./... cd frontend && npx vite build ``` 单元测试用 `httptest` 起假货憨憨服务器,**不得连真实货憨憨**,需覆盖: - multipart 请求体的字段名、文件名与 Content-Type 正确 - `bean` 数组解析;`bean` 为空数组时报错而不是静默成功 - 关联请求体序列化后与 HAR 中的形状逐字段一致 - 非 SUCCESS 响应转为可读中文错误且不重试 - 回读返回空 video 数组时判为失败 - 容量不足时不发起上传 - 本地文件缺失时报错,不上传空内容 **真机验证(必须由负责人执行,且限定范围)**: 1. **只对一个指定的测试商品执行**,不要对整批操作 2. 建议使用 `40483130206`(`id` 为 `1114185406108811264`,`platformShopId` 为 `1044466812`,本地视频已存在),该商品在 9-03 的 HAR 中已被人工走过同一链路,风险可控 3. 上传后在货憨憨界面确认视频已挂上,且**价格、库存、SKU 均无变化** 4. 到 Shopee 商品页确认视频可见 5. 确认无误后再考虑开放批量(下一个工单) ## 风险和回退 - **风险 1(最高)**:对真实店铺的不可逆写入。缓解:首版单商品、显式触发、二次确认、指定测试商品、不自动重试。 - **风险 2**:图片空间仅剩约 26 GB 且已用 98.7%,视频体积大,容易撞上限甚至影响货憨憨其它业务。缓解:上传前容量门禁;返回容量不足立即停止。 - **风险 3**:容量查询接口 `product/material/getMaterialSize` **尚无抓包证据**,路径与返回结构未经验证。缓解:实施时先确认;无法确认则降级为确认框提示,并在工单记录。 - **风险 4**:`batchUpdateShopProductVideo` 是覆盖语义,对已有视频的商品执行会**替换掉原视频**。缓解:只对 `video_diagnosis` 为 `missing` 的商品开放上传,确认框中显示当前视频状态。 - **回退**:代码可 `git revert`。**但已经写入货憨憨的数据不会因此回滚** —— 需要在货憨憨界面手动把 `videoUrl` 清空(HAR 证实传空字符串即删除)。这正是首版限定单商品的原因。
Author
Owner

范围确认:容量判断延后(2026-09-03)

负责人指示:「先实现上传,后面再来增加容量判断。」

本工单不做容量判断的任何逻辑。 依据:payloads/ 下所有 HAR 中
均无 product/material/getMaterialSize 的任何证据,路径、请求格式和返回结构
全部是未经验证的推测,不得凭猜测实现。

本工单只保留一行静态提示放在二次确认框里:

无法查询货憨憨剩余容量(接口未确认)。图片空间已用约 98.7%,
剩余约 26 GB,请自行确认后再继续。

除这行文案外,不做任何容量相关的查询、判断、计算或错误分支。
上传接口返回的错误一律转成可读中文并立即停止、不重试。

「上传前完成容量检查;容量不足直接停止不发起上传」这条验收标准
从本工单移除
,改为:确认框中必须出现上述提示文案。
容量门禁待负责人补抓包后另开工单。

至此本工单无待确认项,状态转为待实施,已派给 Codex CLI 实施。

### 范围确认:容量判断延后(2026-09-03) 负责人指示:「先实现上传,后面再来增加容量判断。」 **本工单不做容量判断的任何逻辑。** 依据:`payloads/` 下所有 HAR 中 均无 `product/material/getMaterialSize` 的任何证据,路径、请求格式和返回结构 全部是未经验证的推测,不得凭猜测实现。 本工单只保留一行**静态提示**放在二次确认框里: > 无法查询货憨憨剩余容量(接口未确认)。图片空间已用约 98.7%, > 剩余约 26 GB,请自行确认后再继续。 除这行文案外,不做任何容量相关的查询、判断、计算或错误分支。 上传接口返回的错误一律转成可读中文并立即停止、不重试。 **「上传前完成容量检查;容量不足直接停止不发起上传」这条验收标准 从本工单移除**,改为:确认框中必须出现上述提示文案。 容量门禁待负责人补抓包后另开工单。 至此本工单无待确认项,状态转为**待实施**,已派给 Codex CLI 实施。
Author
Owner

最终实施证据

提交:d1149b6 feat: 视频上传回货憨憨并关联商品,首版限单商品 (#17)
已推送到 origin/main。

改动文件(6 个)

文件 内容
internal/huohanhan/upload.go(新建) UploadMaterial(multipart)、UpdateShopProductVideo(裸数组三字段)、HasShopProductVideo(回读校验)
internal/huohanhan/upload_test.go(新建) httptest 假服务器覆盖三个接口
internal/store/video.go / _test.go 取首个 downloaded 且文件真实存在的视频;MarkVideoUploaded 写回 remote_url 与状态
app.go UploadVideos 落地、GetUploadPreview、Go 侧单商品拦截、失败状态写回
frontend/src/views/ProductListView.vue 二次确认框、单选限制、上传期间禁用下载操作

复用现有 Client.Request(已支持自定义 contentType 与 401 重试),未新建 HTTP 客户端。

新增 Wails 绑定:GetUploadPreview(productIDs []string) (UploadPreview, error)
(frontend/wailsjs/go/ 为生成产物,已在 .gitignore)。
UploadVideos 为既有绑定,签名未变。

验证结果

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

10 项硬性验收逐条复核,全部通过:实施全程未向 www.huohanhan.com 发出请求
(日志中出现的该域名均为读取 HAR 文件时的 Host 字段);前端无绕过 Wails
生成绑定的写法;internal/ 未 import wails;Go 侧无任何容量相关逻辑;
batchUpdateShopProductVideo 传的是 product.ID 而非 ItemID;
单商品限制拦截在 Go 侧(app.go:970);回读校验未省略,
HasShopProductVideo 确认后才写 done;go.mod / go.sum 未变;
未执行 git 以外的远端操作;payloads/、config.yaml、
config.example.yaml 与参考实现目录均未改动。

审核时清理了 app.go 中一处死代码(_ = preview)。

未验证内容(重要)

  • 全部接口调用由 httptest 假服务器覆盖,从未与真实货憨憨交互。
  • 上传能否真正成功、Shopee 端视频是否可见、是否会影响商品其它字段,
    全部未经验证。
  • 容量相关行为完全未实现,仅有一行静态提示。

待负责人真机验证(务必限定范围)

  1. 只勾选一个商品,不要批量。 建议使用 40483130206
    (id 为 1114185406108811264,platformShopId 为 1044466812,
    本地视频 40483130206_1.mp4 已存在),
    该商品在 9-03 的 HAR 中已被人工走过同一链路,风险最低。
  2. 点「上传数据」,确认弹出二次确认框且信息正确。
  3. 上传后到货憨憨界面确认:视频已挂上,且价格、库存、SKU 均无变化。
  4. 到 Shopee 商品页确认视频可见(可能有延迟)。
  5. 确认无误后再考虑开放批量(另开工单)。

若发现商品数据被写坏,可在货憨憨界面把视频清空回滚
(HAR 证实 videoUrl 传空字符串即删除),并立即反馈。

文档影响:本工单声明需更新业务规则与术语、架构与代码地图、需求总览
(关闭 Q1 / Q2)三处 Wiki。尚未执行,待验收通过后一并处理。

工单保持「待验收」。

### 最终实施证据 **提交**:`d1149b6` feat: 视频上传回货憨憨并关联商品,首版限单商品 (#17) 已推送到 `origin/main`。 **改动文件**(6 个) | 文件 | 内容 | |---|---| | `internal/huohanhan/upload.go`(新建) | `UploadMaterial`(multipart)、`UpdateShopProductVideo`(裸数组三字段)、`HasShopProductVideo`(回读校验) | | `internal/huohanhan/upload_test.go`(新建) | `httptest` 假服务器覆盖三个接口 | | `internal/store/video.go` / `_test.go` | 取首个 `downloaded` 且文件真实存在的视频;`MarkVideoUploaded` 写回 `remote_url` 与状态 | | `app.go` | `UploadVideos` 落地、`GetUploadPreview`、Go 侧单商品拦截、失败状态写回 | | `frontend/src/views/ProductListView.vue` | 二次确认框、单选限制、上传期间禁用下载操作 | 复用现有 `Client.Request`(已支持自定义 contentType 与 401 重试),未新建 HTTP 客户端。 **新增 Wails 绑定**:`GetUploadPreview(productIDs []string) (UploadPreview, error)` (`frontend/wailsjs/go/` 为生成产物,已在 `.gitignore`)。 `UploadVideos` 为既有绑定,签名未变。 **验证结果** ``` go vet ./... 通过 go build ./... 通过 go test ./... 全部通过 npx vite build 通过(仅原有 chunk 体积警告) ``` 10 项硬性验收逐条复核,全部通过:实施全程未向 www.huohanhan.com 发出请求 (日志中出现的该域名均为读取 HAR 文件时的 Host 字段);前端无绕过 Wails 生成绑定的写法;`internal/` 未 import wails;Go 侧无任何容量相关逻辑; `batchUpdateShopProductVideo` 传的是 `product.ID` 而非 `ItemID`; 单商品限制拦截在 Go 侧(`app.go:970`);回读校验未省略, `HasShopProductVideo` 确认后才写 `done`;`go.mod` / `go.sum` 未变; 未执行 git 以外的远端操作;`payloads/`、`config.yaml`、 `config.example.yaml` 与参考实现目录均未改动。 审核时清理了 `app.go` 中一处死代码(`_ = preview`)。 **未验证内容(重要)** - 全部接口调用由 `httptest` 假服务器覆盖,**从未与真实货憨憨交互**。 - 上传能否真正成功、Shopee 端视频是否可见、是否会影响商品其它字段, **全部未经验证**。 - 容量相关行为完全未实现,仅有一行静态提示。 **待负责人真机验证(务必限定范围)** 1. **只勾选一个商品,不要批量。** 建议使用 `40483130206` (`id` 为 `1114185406108811264`,`platformShopId` 为 `1044466812`, 本地视频 `40483130206_1.mp4` 已存在), 该商品在 9-03 的 HAR 中已被人工走过同一链路,风险最低。 2. 点「上传数据」,确认弹出二次确认框且信息正确。 3. 上传后到货憨憨界面确认:视频已挂上,且**价格、库存、SKU 均无变化**。 4. 到 Shopee 商品页确认视频可见(可能有延迟)。 5. 确认无误后再考虑开放批量(另开工单)。 若发现商品数据被写坏,可在货憨憨界面把视频清空回滚 (HAR 证实 `videoUrl` 传空字符串即删除),并立即反馈。 **文档影响**:本工单声明需更新业务规则与术语、架构与代码地图、需求总览 (关闭 Q1 / Q2)三处 Wiki。**尚未执行**,待验收通过后一并处理。 工单保持「待验收」。
Author
Owner

真机验证发现缺陷并修复(2026-09-03)

现象:负责人对 55066525387(id 为 1124684165671051264)执行上传,
报错「回读商品视频失败:货憨憨尚未显示已关联的视频」。
但到货憨憨界面确认,视频其实已经上传成功。

根因:回读判据写错了,不是接口问题。

uploadFiles 与 batchUpdateShopProductVideo 都返回了 SUCCESS,
失败卡在回读这一步。原实现只看 records[0].video 非空。

但 video 字段装的是已经在 Shopee 上生效的视频,不是刚设置的那个。
货憨憨推送到 Shopee 是异步的,保存成功后 video 必然还是空的,
视频这时在 tempVideoUrl 和 videoUploadIdStr 里。

证据在 payloads/huohanhan_save_product_info.har 的第 3 个请求,
那是一次保存成功后立刻发起的 getDetail:

video            = []
videoUploadIdStr = sg-11110106-6vbma-msnl9mrjxxqd2d
tempVideoUrl     = https://hhh-prod-1307856765.cos.ap-guangzhou.myqcloud.com/video/1126859448838946817.mp4

对照批量 HAR 也一致:视频还在时 video 里是 Shopee CDN 地址
(cvf.shopee.tw/...),删除后 video 与 tempVideoUrl 同时清空。

这条证据在建单时就已经在仓库里,定回读判据时没有用上。

修复(提交 0fe24f7,已推送 origin/main):

HasShopProductVideo 改为 CheckShopProductVideo,返回四个字段并按优先级判定:

条件 判定
videoFailReason 非空 失败,原因写入 last_error
video 非空 成功,且已在 Shopee 生效
tempVideoUrl 或 videoUploadIdStr 非空 成功,等待同步
四者皆空 失败

配套变化:

  • upload_status = 'done' 的语义明确为「已提交并回读确认」,
    不是「Shopee 已可见」——后者是异步结果,本地无法立刻断言。
    日志分别提示这两种情况。
  • 确认框的覆盖警告改用 Confirmed():等待同步中的视频同样会被本次上传覆盖,
    原来只看 video 会漏掉这种情况导致警告不显示。这是同一个根因的第二处影响。
  • 新增 5 条回读判定测试,其中
    Test刚保存完video为空但tempVideoUrl有值应判为成功 直接复刻本次故障,
    注释里写明了故障来源,防止再犯。

数据订正:55066525387 被误判写成的 upload_status='failed' 已改回 done,
last_error 清空,videos.status 改为 uploaded。

遗留:该商品的 videos.remote_url 为空——COS 地址由 uploadFiles 返回,
当时因判定失败没有落库,事后无法补回。不影响功能,仅少一条溯源信息。
后续正常上传的商品会正常记录。

验收标准修正:原「上传成功后回读 getShopItemInfoPage,
确认 video 非空才写 done」改为「按上表四字段优先级判定,
video 非空或 tempVideoUrl/videoUploadIdStr 非空均判为成功」。

验证:go vet ./...、go test ./...、npx vite build 全部通过。

仍待负责人确认:55066525387 的价格、库存、SKU 是否未被改动;
以及稍后 Shopee 商品页能否看到视频。

### 真机验证发现缺陷并修复(2026-09-03) **现象**:负责人对 `55066525387`(`id` 为 `1124684165671051264`)执行上传, 报错「回读商品视频失败:货憨憨尚未显示已关联的视频」。 但到货憨憨界面确认,**视频其实已经上传成功**。 **根因**:回读判据写错了,不是接口问题。 `uploadFiles` 与 `batchUpdateShopProductVideo` 都返回了 SUCCESS, 失败卡在回读这一步。原实现只看 `records[0].video` 非空。 但 `video` 字段装的是**已经在 Shopee 上生效**的视频,不是刚设置的那个。 货憨憨推送到 Shopee 是**异步**的,保存成功后 `video` 必然还是空的, 视频这时在 `tempVideoUrl` 和 `videoUploadIdStr` 里。 证据在 `payloads/huohanhan_save_product_info.har` 的第 3 个请求, 那是一次保存成功后**立刻发起**的 `getDetail`: ``` video = [] videoUploadIdStr = sg-11110106-6vbma-msnl9mrjxxqd2d tempVideoUrl = https://hhh-prod-1307856765.cos.ap-guangzhou.myqcloud.com/video/1126859448838946817.mp4 ``` 对照批量 HAR 也一致:视频还在时 `video` 里是 Shopee CDN 地址 (`cvf.shopee.tw/...`),删除后 `video` 与 `tempVideoUrl` 同时清空。 这条证据在建单时就已经在仓库里,定回读判据时没有用上。 **修复**(提交 `0fe24f7`,已推送 `origin/main`): `HasShopProductVideo` 改为 `CheckShopProductVideo`,返回四个字段并按优先级判定: | 条件 | 判定 | |---|---| | `videoFailReason` 非空 | 失败,原因写入 `last_error` | | `video` 非空 | 成功,且已在 Shopee 生效 | | `tempVideoUrl` 或 `videoUploadIdStr` 非空 | 成功,等待同步 | | 四者皆空 | 失败 | 配套变化: - `upload_status = 'done'` 的语义明确为「已提交并回读确认」, **不是**「Shopee 已可见」——后者是异步结果,本地无法立刻断言。 日志分别提示这两种情况。 - 确认框的覆盖警告改用 `Confirmed()`:等待同步中的视频同样会被本次上传覆盖, 原来只看 `video` 会漏掉这种情况导致警告不显示。**这是同一个根因的第二处影响。** - 新增 5 条回读判定测试,其中 `Test刚保存完video为空但tempVideoUrl有值应判为成功` 直接复刻本次故障, 注释里写明了故障来源,防止再犯。 **数据订正**:`55066525387` 被误判写成的 `upload_status='failed'` 已改回 `done`, `last_error` 清空,`videos.status` 改为 `uploaded`。 **遗留**:该商品的 `videos.remote_url` 为空——COS 地址由 `uploadFiles` 返回, 当时因判定失败没有落库,事后无法补回。不影响功能,仅少一条溯源信息。 后续正常上传的商品会正常记录。 **验收标准修正**:原「上传成功后回读 `getShopItemInfoPage`, 确认 `video` 非空才写 `done`」改为「按上表四字段优先级判定, `video` 非空或 `tempVideoUrl`/`videoUploadIdStr` 非空均判为成功」。 验证:`go vet ./...`、`go test ./...`、`npx vite build` 全部通过。 **仍待负责人确认**:`55066525387` 的价格、库存、SKU 是否未被改动; 以及稍后 Shopee 商品页能否看到视频。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: chengma/cmsp#17