fix(street): 晋升时重建副图父引用(草稿行 id 到 正式行 id)

This commit is contained in:
toom1996
2026-09-21 22:21:44 +08:00
parent 16e024561d
commit 9fe51036e6
3 changed files with 119 additions and 15 deletions

View File

@ -449,22 +449,74 @@ func (r *reviewRepository) SaveStreetSnapFromDraft(ctx context.Context, draftID
return dErr
}
if len(imgs) > 0 {
rows := make([]model.StreetSnapImage, 0, len(imgs))
for i, im := range imgs {
rows = append(rows, model.StreetSnapImage{
SnapID: snapID,
Image: im.Image,
Name: im.Name,
SortOrder: uint32(i + 1),
Phash: im.Phash,
IsDuplicate: im.IsDuplicate,
DupOf: im.DupOf,
CreatedAt: now,
UpdatedAt: now,
})
// 第一遍:插入主图(is_detail=0),并记下「草稿行 id → 新正式行 id」的映射。
//
// 必须两遍插入:副图的 parent_image_id 存的是**草稿表**的行 id,
// 而这里插入的正式行拿到的是全新 id。若先插副图,父引用就会指向草稿表的旧 id,
// 详情页折叠结构随之错乱(见计划「任务 2」标题下的说明)。
newIDByDraftID := make(map[uint32]uint32, len(imgs))
mains := make([]model.StreetSnapImage, 0, len(imgs))
for _, im := range imgs {
if im.IsDetail == 1 {
continue
}
mains = append(mains, model.StreetSnapImage{
SnapID: snapID,
Image: im.Image,
Name: im.Name,
SortOrder: uint32(len(mains) + 1),
IsDetail: 0,
Phash: im.Phash,
IsDuplicate: im.IsDuplicate,
DupOf: im.DupOf,
CreatedAt: now,
UpdatedAt: now,
})
}
if cErr := tx.Create(&rows).Error; cErr != nil {
return cErr
for i := range mains {
if cErr := tx.Create(&mains[i]).Error; cErr != nil {
return cErr
}
}
mainIdx := 0
for _, im := range imgs {
if im.IsDetail == 1 {
continue
}
newIDByDraftID[im.ID] = mains[mainIdx].ID
mainIdx++
}
// 第二遍:插入副图,把父引用改写成新主图行 id。
details := make([]model.StreetSnapImage, 0, len(imgs)-len(mains))
for _, im := range imgs {
if im.IsDetail != 1 {
continue
}
newParent, ok := newIDByDraftID[im.ParentImageID]
row := model.StreetSnapImage{
SnapID: snapID,
Image: im.Image,
Name: im.Name,
SortOrder: uint32(len(mains) + len(details) + 1),
IsDetail: 1,
Phash: im.Phash,
IsDuplicate: im.IsDuplicate,
DupOf: im.DupOf,
CreatedAt: now,
UpdatedAt: now,
}
if ok {
row.ParentImageID = newParent
} else {
row.IsDetail = 0 // 父行缺失:按主图渲染,与读侧容错口径一致
}
details = append(details, row)
}
if len(details) > 0 {
if cErr := tx.Create(&details).Error; cErr != nil {
return cErr
}
}
}
return nil

View File

@ -159,3 +159,50 @@ func TestStreetMainDetailReparentGrandchildren(t *testing.T) {
}
}
}
// TestStreetPromoteRebuildsParentRef 晋升时必须把副图的父引用从「草稿行 id」改写为「新正式行 id」。
func TestStreetPromoteRebuildsParentRef(t *testing.T) {
repo := NewReviewRepository(testDB(t))
ctx := context.Background()
draftID := seedStreetDraft(t, repo)
m1 := seedStreetDraftImage(t, repo, draftID, "main", 1)
d2 := seedStreetDraftImage(t, repo, draftID, "detail-a", 2)
d3 := seedStreetDraftImage(t, repo, draftID, "detail-b", 3)
if err := repo.AttachStreetDraftImages(ctx, draftID, m1, []uint32{d2, d3}); err != nil {
t.Fatalf("预置主副图失败: %v", err)
}
snapID, err := repo.SaveStreetSnapFromDraft(ctx, draftID)
if err != nil {
t.Fatalf("晋升失败: %v", err)
}
t.Cleanup(func() {
db := repo.(*reviewRepository).db
db.Exec("DELETE FROM street_snap_images WHERE snap_id = ?", snapID)
db.Exec("DELETE FROM street_snaps WHERE id = ?", snapID)
})
db := repo.(*reviewRepository).db
var rows []model.StreetSnapImage
if err := db.WithContext(ctx).Where("snap_id = ? AND is_deleted = 0", snapID).
Order("sort_order ASC, id ASC").Find(&rows).Error; err != nil {
t.Fatalf("读正式图失败: %v", err)
}
if len(rows) != 3 {
t.Fatalf("期望 3 行正式图,实际 %d", len(rows))
}
mainRow := rows[0]
if mainRow.IsDetail != 0 || mainRow.ParentImageID != 0 {
t.Fatalf("首行应是主图,实际 is_detail=%d parent=%d", mainRow.IsDetail, mainRow.ParentImageID)
}
for _, row := range rows[1:] {
if row.IsDetail != 1 {
t.Fatalf("第 %d 行应是副图", row.ID)
}
if row.ParentImageID != mainRow.ID {
t.Fatalf("副图 parent 应指向新主图行 id %d,实际 %d", mainRow.ID, row.ParentImageID)
}
}
}