fix: validate cover image lists before replacement
This commit is contained in:
@@ -185,6 +185,9 @@ func (e ShopeeProduct) updateCover(c *gin.Context, shopeeID, coverURL, filename,
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("商品编辑信息准备失败:%w", err)
|
||||
}
|
||||
if strategy == "replace_first" && !info.HasCompleteImageList() {
|
||||
return nil, fmt.Errorf("商品编辑信息缺少完整图片列表,已拒绝更新主图")
|
||||
}
|
||||
sourceURL := coverURL
|
||||
if len(content) > 0 {
|
||||
coverURL, err = client.UploadImage(ctx, filename, content, contentType)
|
||||
|
||||
@@ -15,17 +15,62 @@ import (
|
||||
)
|
||||
|
||||
type ProductEditInfo struct {
|
||||
ID string `json:"id"`
|
||||
ItemID string `json:"itemId"`
|
||||
ItemName string `json:"itemName"`
|
||||
MainImage string `json:"mainImage"`
|
||||
PlatformShopID string `json:"platformShopId"`
|
||||
Platform string `json:"platform"`
|
||||
CategoryID string `json:"categoryId"`
|
||||
Image struct {
|
||||
ImageIDList []string `json:"imageIdList"`
|
||||
ImageURLList []string `json:"imageUrlList"`
|
||||
} `json:"image"`
|
||||
ID string `json:"id"`
|
||||
ItemID string `json:"itemId"`
|
||||
ItemName string `json:"itemName"`
|
||||
MainImage string `json:"mainImage"`
|
||||
PlatformShopID string `json:"platformShopId"`
|
||||
Platform string `json:"platform"`
|
||||
CategoryID string `json:"categoryId"`
|
||||
Image ProductEditImage `json:"image"`
|
||||
imagePresent bool
|
||||
}
|
||||
|
||||
type ProductEditImage struct {
|
||||
ImageIDList []string `json:"imageIdList"`
|
||||
ImageURLList []string `json:"imageUrlList"`
|
||||
imageIDPresent bool
|
||||
imageURLPresent bool
|
||||
}
|
||||
|
||||
// UnmarshalJSON tracks whether Huohanhan explicitly supplied each list. A
|
||||
// missing/null field must never be treated as a valid empty image list.
|
||||
func (p *ProductEditInfo) UnmarshalJSON(data []byte) error {
|
||||
type plain ProductEditInfo
|
||||
var decoded plain
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
*p = ProductEditInfo(decoded)
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return err
|
||||
}
|
||||
rawImage, ok := fields["image"]
|
||||
if !ok || bytes.Equal(bytes.TrimSpace(rawImage), []byte("null")) {
|
||||
return nil
|
||||
}
|
||||
var imageFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(rawImage, &imageFields); err != nil || imageFields == nil {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("image 不是对象")
|
||||
}
|
||||
return err
|
||||
}
|
||||
p.imagePresent = true
|
||||
if raw, ok := imageFields["imageUrlList"]; ok && !bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
p.Image.imageURLPresent = true
|
||||
}
|
||||
if raw, ok := imageFields["imageIdList"]; ok && !bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
p.Image.imageIDPresent = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasCompleteImageList accepts a confirmed empty list but rejects missing,
|
||||
// null, malformed, or mismatched lists before a cover update is submitted.
|
||||
func (p ProductEditInfo) HasCompleteImageList() bool {
|
||||
return p.imagePresent && p.Image.imageURLPresent && p.Image.imageIDPresent && len(p.Image.ImageURLList) == len(p.Image.ImageIDList)
|
||||
}
|
||||
|
||||
type ProductEditTaskStatus struct {
|
||||
@@ -179,7 +224,10 @@ func CoverImageListWithStrategy(info ProductEditInfo, coverURL, strategy string)
|
||||
if strategy == "replace_first" {
|
||||
var urls, ids []string
|
||||
for i, u := range info.Image.ImageURLList {
|
||||
if strings.TrimSpace(u) == "" || u == coverURL {
|
||||
// Index zero is the image being replaced. Remove duplicate copies of
|
||||
// the new image only from the remaining references; doing that before
|
||||
// dropping index zero would incorrectly drop ref1 for [new, ref1].
|
||||
if i == 0 || strings.TrimSpace(u) == "" || u == coverURL {
|
||||
continue
|
||||
}
|
||||
urls = append(urls, u)
|
||||
@@ -189,9 +237,6 @@ func CoverImageListWithStrategy(info ProductEditInfo, coverURL, strategy string)
|
||||
ids = append(ids, "")
|
||||
}
|
||||
}
|
||||
if len(urls) > 0 {
|
||||
urls, ids = urls[1:], ids[1:]
|
||||
}
|
||||
urls, ids = append([]string{coverURL}, urls...), append([]string{""}, ids...)
|
||||
return urls, ids
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ func Test统一替换第一张策略(t *testing.T) {
|
||||
{"一张旧图", []string{"a"}, []string{"new"}},
|
||||
{"八张旧图", []string{"a", "b", "c", "d", "e", "f", "g", "h"}, []string{"new", "b", "c", "d", "e", "f", "g", "h"}},
|
||||
{"九张旧图", []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"}, []string{"new", "b", "c", "d", "e", "f", "g", "h", "i"}},
|
||||
{"新图已在首位", []string{"new", "ref1", "ref2"}, []string{"new", "ref1", "ref2"}},
|
||||
{"新图原在后面", []string{"a", "new", "b"}, []string{"new", "b"}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@@ -147,6 +148,29 @@ func Test统一替换第一张策略(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test编辑信息必须明确返回完整图片列表(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, raw string
|
||||
ok bool
|
||||
}{
|
||||
{"完整空列表", `{"image":{"imageUrlList":[],"imageIdList":[]}}`, true},
|
||||
{"缺少 image", `{}`, false},
|
||||
{"缺少 URL", `{"image":{"imageIdList":[]}}`, false},
|
||||
{"URL 为 null", `{"image":{"imageUrlList":null,"imageIdList":[]}}`, false},
|
||||
{"ID 为 null", `{"image":{"imageUrlList":[],"imageIdList":null}}`, false},
|
||||
{"长度不等", `{"image":{"imageUrlList":["a"],"imageIdList":[]}}`, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
var info ProductEditInfo
|
||||
if err := json.Unmarshal([]byte(tc.raw), &info); err != nil {
|
||||
t.Fatalf("%s: %v", tc.name, err)
|
||||
}
|
||||
if got := info.HasCompleteImageList(); got != tc.ok {
|
||||
t.Fatalf("%s: got %v", tc.name, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test按URL转存主图(t *testing.T) {
|
||||
src := "http://122.228.200.167:8020/uploads/images/24720421377.jpg?v=1789627038"
|
||||
bean := `["https://hhh-prod-1307856765.cos.ap-guangzhou.myqcloud.com/video/1131990643264757761.jpg"]`
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# LOCAL-022:主图替换列表安全修复(R1、R4)
|
||||
|
||||
## 目标
|
||||
|
||||
修复 `coverStrategy=replace_first` 的参考图误删问题,并在 Huohanhan 编辑信息未返回完整图片列表时拒绝更新,防止把异常响应误当成空图片列表。
|
||||
|
||||
## 验收
|
||||
|
||||
- `[new, ref1, ref2]` 更新为 `new` 后保留 `ref1, ref2`。
|
||||
- `[old, new, ref1]` 更新后只保留一份 `new`,且保留 `ref1`。
|
||||
- 0/1/8/9 张旧图均保持正确数量和 URL/ID 下标对应。
|
||||
- 缺少 `image`、缺少/为 null 的 `imageUrlList` 或 `imageIdList`、两表长度不等时,不提交 Huohanhan 主图更新。
|
||||
- 明确返回合法双空数组时允许新图成为唯一图片。
|
||||
|
||||
## 状态
|
||||
|
||||
已完成。
|
||||
Reference in New Issue
Block a user