116 lines
3.4 KiB
Go
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
|
|
}
|
|
}
|