Files
cmsp/internal/erpgo/sync.go
T

116 lines
3.4 KiB
Go

package erpgo
import (
"context"
"errors"
"strings"
"time"
"cmsp/internal/store"
)
type SyncFailure struct {
ShopIndex int `json:"shopIndex"`
PlatformShopID string `json:"platformShopId"`
ShopName string `json:"shopName"`
Error *Error `json:"error"`
}
// SyncResult 的计数来自实际提交,不使用界面当前筛选后的商品数量。
type SyncResult struct {
AllShops bool `json:"allShops"`
TotalShops int `json:"totalShops"`
SucceededShops int `json:"succeededShops"`
FailedShops int `json:"failedShops"`
SkippedShops int `json:"skippedShops"`
ProductCount int `json:"productCount"`
Failures []SyncFailure `json:"failures"`
StopError *Error `json:"stopError,omitempty"`
}
type SyncProgress struct {
ShopIndex, TotalShops, Current, Pages int
}
// SyncProductData 按店铺串行查询、逐店铺原子保存,失败不提交该店铺的部分页。
// 全店模式保留已成功店铺,并返回失败/未处理范围;不自动重试失败请求。
func (c *Client) SyncProductData(ctx context.Context, db *store.Store, shopID string, onProgress func(SyncProgress)) (SyncResult, error) {
shopID = strings.TrimSpace(shopID)
result := SyncResult{AllShops: shopID == "", Failures: make([]SyncFailure, 0)}
shops := []store.Shop{{PlatformShopID: shopID}}
if result.AllShops {
var err error
shops, err = c.ListShops(ctx)
if err != nil {
return result, err
}
if ctx.Err() != nil {
return result, failure("REQUEST_CANCELLED", 0, "")
}
if err := db.ReplaceShops(shops, time.Now().Format("2006-01-02 15:04:05")); err != nil {
return result, BridgeError(err)
}
}
result.TotalShops = len(shops)
committedIDs := make(map[string]bool)
for i, shop := range shops {
if ctx.Err() != nil {
result.StopError = failure("REQUEST_CANCELLED", 0, "")
result.SkippedShops = len(shops) - i
break
}
if onProgress != nil {
onProgress(SyncProgress{ShopIndex: i + 1, TotalShops: len(shops)})
}
products, diagnoses, err := c.DownloadAllProducts(ctx, shop.PlatformShopID, func(current, pages int) {
if onProgress != nil {
onProgress(SyncProgress{ShopIndex: i + 1, TotalShops: len(shops), Current: current, Pages: pages})
}
})
if ctx.Err() != nil {
err = failure("REQUEST_CANCELLED", 0, "")
}
if err == nil {
for _, product := range products {
if committedIDs[product.ID] {
err = failure("INVALID_RESPONSE", 200, "")
break
}
}
}
if err == nil {
err = db.SyncProducts(products, diagnoses, time.Now().Format("2006-01-02 15:04:05"))
}
if err != nil {
var safe *Error
errors.As(BridgeError(err), &safe)
result.FailedShops++
result.Failures = append(result.Failures, SyncFailure{ShopIndex: i + 1, PlatformShopID: shop.PlatformShopID, ShopName: shop.ShopName, Error: safe})
if !result.AllShops {
return result, safe
}
if stopProductSync(safe.Code) {
result.StopError = safe
result.SkippedShops = len(shops) - i - 1
break
}
continue
}
for _, product := range products {
committedIDs[product.ID] = true
}
result.SucceededShops++
result.ProductCount += len(products)
}
return result, nil
}
func stopProductSync(code string) bool {
switch code {
case "API_KEY_INVALID", "HHH_AUTH_FAILED", "RATE_LIMITED", "REQUEST_CANCELLED", "NETWORK_ERROR", "SERVICE_UNAVAILABLE", "INTERNAL_ERROR":
return true
default:
return false
}
}