feat: 回填店铺关联PDD空规格 (#194)

This commit is contained in:
chengma
2026-08-12 18:31:14 +08:00
parent 13105faf1f
commit 8f7fb098ad
5 changed files with 595 additions and 24 deletions
+63 -24
View File
@@ -27,47 +27,86 @@ type ShopSpecSource struct {
func ListShopSpecSources(q Execer, shopName string) ([]ShopSpecSource, error) {
rows, err := q.Query(`
SELECT sp.goods_id, sp.pdd_goods_id,
pp.goods_id, pp.deleted_at, pp.skus_json,
sk.color, sk.size, sk.parse_ok
pp.goods_id, pp.deleted_at, pp.skus_json
FROM shopee_products sp
LEFT JOIN shopee_skus sk ON sk.goods_id = sp.goods_id
LEFT JOIN pdd_products pp ON pp.goods_id = sp.pdd_goods_id
WHERE sp.shopee_shop_name = ?
ORDER BY sp.goods_id, sk.sku_id`, strings.TrimSpace(shopName))
ORDER BY sp.goods_id`, strings.TrimSpace(shopName))
if err != nil {
return nil, fmt.Errorf("读取店铺规格对比数据失败: %w", err)
}
defer rows.Close()
var list []ShopSpecSource
var current *ShopSpecSource
indexByGoodsID := make(map[string]int)
for rows.Next() {
var shopeeID string
var pddID, existingPDDID, deletedAt, skusJSON sql.NullString
var color, size sql.NullString
var parseOK sql.NullInt64
if err := rows.Scan(&shopeeID, &pddID, &existingPDDID, &deletedAt,
&skusJSON, &color, &size, &parseOK); err != nil {
if err := rows.Scan(&shopeeID, &pddID, &existingPDDID, &deletedAt, &skusJSON); err != nil {
return nil, fmt.Errorf("解析店铺规格对比数据失败: %w", err)
}
if current == nil || current.ShopeeGoodsID != shopeeID {
list = append(list, ShopSpecSource{
ShopeeGoodsID: shopeeID,
PDDGoodsID: pddID.String,
PDDExists: existingPDDID.Valid,
PDDDeleted: strings.TrimSpace(deletedAt.String) != "",
PDDSKUsJSON: skusJSON.String,
})
current = &list[len(list)-1]
}
if parseOK.Valid {
current.ShopeeSpecs = append(current.ShopeeSpecs, ComparableShopeeSpec{
Color: color.String, Size: size.String, ParseOK: parseOK.Int64 == 1,
})
}
list = append(list, ShopSpecSource{
ShopeeGoodsID: shopeeID,
PDDGoodsID: pddID.String,
PDDExists: existingPDDID.Valid,
PDDDeleted: strings.TrimSpace(deletedAt.String) != "",
PDDSKUsJSON: skusJSON.String,
})
indexByGoodsID[shopeeID] = len(list) - 1
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历店铺规格对比数据失败: %w", err)
}
rows.Close()
// PDD JSON 可能很大,不能和 SKU JOIN 后重复传输。商品和规格分两次查询,
// 每份 PDD JSON 只从数据库读取一次。
skuRows, err := q.Query(`
SELECT sk.goods_id, sk.color, sk.size, sk.parse_ok
FROM shopee_skus sk
JOIN shopee_products sp ON sp.goods_id = sk.goods_id
WHERE sp.shopee_shop_name = ?
ORDER BY sk.goods_id, sk.sku_id`, strings.TrimSpace(shopName))
if err != nil {
return nil, fmt.Errorf("读取店铺正式规格失败: %w", err)
}
defer skuRows.Close()
for skuRows.Next() {
var goodsID string
var color, size sql.NullString
var parseOK int64
if err := skuRows.Scan(&goodsID, &color, &size, &parseOK); err != nil {
return nil, fmt.Errorf("解析店铺正式规格失败: %w", err)
}
index, exists := indexByGoodsID[goodsID]
if !exists {
continue
}
list[index].ShopeeSpecs = append(list[index].ShopeeSpecs, ComparableShopeeSpec{
Color: color.String, Size: size.String, ParseOK: parseOK == 1,
})
}
if err := skuRows.Err(); err != nil {
return nil, fmt.Errorf("遍历店铺正式规格失败: %w", err)
}
return list, nil
}
// ReplaceEmptyPDDSpecs 用生成的规格骨架替换仍保持预期原值的 PDD 规格。
// expectedRaw 是乐观锁:读取后若被其他进程改过,更新返回 false,不覆盖新数据。
func ReplaceEmptyPDDSpecs(q Execer, pddGoodsID, expectedRaw, newRaw, updatedAt string) (bool, error) {
result, err := q.Exec(`
UPDATE pdd_products
SET skus_json = ?, updated_at = ?
WHERE goods_id = ? AND deleted_at IS NULL
AND COALESCE(skus_json, '') = ?`,
newRaw, updatedAt, strings.TrimSpace(pddGoodsID), expectedRaw)
if err != nil {
return false, fmt.Errorf("回填 PDD 商品 %s 的规格失败: %w", pddGoodsID, err)
}
rows, err := result.RowsAffected()
if err != nil {
return false, fmt.Errorf("读取 PDD 商品 %s 的回填结果失败: %w", pddGoodsID, err)
}
return rows == 1, nil
}