Files

5.3 KiB
Raw Permalink Blame History

id, title, phase, deps, status, created, context_ref, work_branch, write_paths
id title phase deps status created context_ref work_branch write_paths
T-244 兼容 ERP 图片响应缺少媒体类型 2
T-241
DONE 2026-07-29 24eef29 null
docs/tasks/T-244.md
docs/current-state.md
backend-api/internal/platform/shunyunbao/image.go
backend-api/internal/platform/shunyunbao/image_test.go

Bug / 背景

完整单号同步成功后,商品图片可能显示“图片获取失败”。只读数据审计确认图片引用为规范正 整数,图片记录为 FAILED / ERP_IMAGE_INVALID,不是货运导入失败。

真实 /api/p/file?id=<numeric-id> 响应为 HTTP 200,正文是有效且很小的 JPEG,但 ERP 未返回 Content-Type、Content-Length,只通过 Content-Disposition 提供 .jpg 文件名。当前 Go 客户端无条件执行 mime.ParseMediaType(Content-Type),空响应头在正文 进入本地图片存储前就被归类为无效图片。

图片缓存是货运同步后的 best-effort 阶段,所以该错误不会回滚已成功保存的货运头和商品; 页面只显示统一失败标签,实际稳定错误码保存在图片状态记录中。

修订契约

  • HTTP 2xx 且 Content-Type 为 image/jpeg、image/png、image/webp 时沿用现有路径。
  • Content-Type 缺失或规范化后为 application/octet-stream 时,只读取正文前最多 512 字节;JPEG/PNG 使用 Go 标准库 http.DetectContentType,WebP 使用严格 RIFF....WEBP 文件签名判定。
  • 嗅探只接受 JPEG、PNG、WebP;空正文、JSON、HTML、SVG、未知二进制继续返回 ErrFreightImageInvalid。
  • 已读取的前缀必须无损拼回返回流,后续本地存储继续执行完整解码、声明类型一致性、 尺寸/像素、20 MB、归一化 JPEG 和 SHA-256 校验。
  • ERP 明确声明其他媒体类型时 fail-closed,不用文件扩展名覆盖响应头。
  • 不信任或解析 Content-Disposition 文件名,不扩大 URL、重定向或 ERP 会话边界。

方案

  1. 在顺运宝图片适配器中增加小型媒体类型解析函数:先规范化响应头,支持类型直接返回; 仅对缺失/通用二进制响应调用内容嗅探。
  2. 使用固定 512 字节 bufio.Reader 执行 Peek;短图片允许 io.EOF,空正文拒绝。成功 时返回组合了 buffered reader 和原响应 body closer 的流,避免丢失或复制完整图片。
  3. 继续在读取正文前拒绝已知超出 20 MB 的 Content-Length;未知长度由现有 asset store 的 LimitReader(MaxInputBytes+1) 强制限制。
  4. 测试覆盖缺失媒体类型的 JPEG、通用二进制 PNG/WebP、短正文、明确 JSON、嗅探非图片、 声明图片类型、超大已知长度和前缀不丢失。
  5. 修复部署后重新以相同完整订单号同步即可重试 status != READY 的图片;本任务不增加 独立重试按钮或后台定时重试。

验收要点

  • 缺少 Content-Type 的有效 JPEG 保存为本系统归一化 JPEG,图片状态为 READY。
  • application/octet-stream 的有效 PNG/WebP 可以进入既有解码与存储路径。
  • 嗅探不会丢失正文前缀,完整内容只读取一次并最终关闭 ERP 响应 body。
  • JSON、HTML、SVG、空正文、未知二进制和明确不支持的媒体类型仍被拒绝。
  • 已知或流式超限图片仍受 20 MB 限制,不能绕过 asset store。
  • 401/403、404、非 2xx、会话清理、固定 URL 和数字 ID 校验不回归。
  • 货运同步仍为图片 best-effort,不因单张图片失败回滚业务数据。
  • 标准 Go 测试、race、vet 和三个入口构建通过。

边界

  • 不修改顺运宝 ERP,不上传或回写 ERP 图片。
  • 不直接在 Admin 页面引用 ERP URL;图片仍保存到 CMROUBAO_ASSET_DIR 并从鉴权路由读取。
  • 不接受 SVG、GIF、BMP、ICO、PDF 或任意未知格式。
  • 不以扩展名、文件名或 Content-Disposition 替代正文验证。
  • 不记录图片正文、Cookie、Token、真实订单、商品、店铺或图片 ID。

执行记录

  • 2026-07-29:真实只读审计确认货运同步成功,单张图片失败码为 ERP_IMAGE_INVALID;受控响应探测确认 HTTP 200 正文为有效 JPEG,但 Content-Type 和 Content-Length 均缺失。根因位于 source adapter 的响应头强校验, 不是 ERP 鉴权、数字图片 ID、图片不存在、本地目录或图片体积。
  • 2026-07-29:适配器仅对缺失媒体类型或 application/octet-stream 响应使用固定 512 字节 buffered peek;支持头直接沿用,明确 JSON/HTML/SVG 等类型保持 fail-closed。 成功流组合 buffered reader 与原 body closer,既不丢前缀,也不复制完整图片。
  • 2026-07-29:测试确认 Go 1.23 的 http.DetectContentType 不识别 WebP,因此增加 严格 RIFF....WEBP 前缀检测;asset store 随后仍执行完整 WebP 解码、媒体类型一致性、 尺寸/像素与 20 MB 校验。端到端测试已将缺失响应头的有效 JPEG 归一化保存并重新解码。
  • 2026-07-29:go test ./...、go test -race ./...、go vet ./... 和 API、 authctl、migrate 三个入口构建通过。测试覆盖缺失/通用媒体类型、JPEG/PNG/WebP、 前缀完整性、body 关闭语义、明确非图片、嗅探非图片、已知超限、401/403、404 与非 2xx; 自动化验证未访问真实 ERP。