feat: add stale-aware shopee sync endpoint
This commit is contained in:
@@ -137,6 +137,32 @@ func (e ShopeeProduct) SyncWaitByShopeeID(c *gin.Context) {
|
||||
e.getByShopeeID(c, true, true)
|
||||
}
|
||||
|
||||
// SyncIfStaleByShopeeID synchronizes only when the local sync timestamp is
|
||||
// older than staleDays (or no local product has been synchronized yet).
|
||||
func (e ShopeeProduct) SyncIfStaleByShopeeID(c *gin.Context) {
|
||||
e.MakeContext(c)
|
||||
staleDays := 1
|
||||
if raw := strings.TrimSpace(c.Query("staleDays")); raw != "" {
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil || value < 0 || value > 3650 {
|
||||
e.Error(400, fmt.Errorf("staleDays must be between 0 and 3650"), "参数错误")
|
||||
return
|
||||
}
|
||||
staleDays = value
|
||||
}
|
||||
db := primaryDB()
|
||||
if db == nil {
|
||||
e.Error(500, fmt.Errorf("database is not initialized"), "数据库未初始化")
|
||||
return
|
||||
}
|
||||
var row models.ShopeeProduct
|
||||
needsSync := true
|
||||
if err := db.Where("shopee_id = ?", strings.TrimSpace(c.Param("shopeeId"))).First(&row).Error; err == nil && row.LastSyncedAt != nil {
|
||||
needsSync = time.Since(*row.LastSyncedAt) >= time.Duration(staleDays)*24*time.Hour
|
||||
}
|
||||
e.getByShopeeID(c, true, needsSync)
|
||||
}
|
||||
|
||||
// SyncStatus returns the current asynchronous refresh state.
|
||||
func (e ShopeeProduct) SyncStatus(c *gin.Context) {
|
||||
e.MakeContext(c)
|
||||
@@ -185,6 +211,7 @@ func (e ShopeeProduct) getByShopeeID(c *gin.Context, requireAPIKey bool, syncSho
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second)
|
||||
defer cancel()
|
||||
want := strings.TrimSpace(c.Param("shopeeId"))
|
||||
var syncedAt *time.Time
|
||||
if want == "" {
|
||||
e.Error(400, fmt.Errorf("shopeeId is required"), "参数错误")
|
||||
return
|
||||
@@ -249,6 +276,9 @@ func (e ShopeeProduct) getByShopeeID(c *gin.Context, requireAPIKey bool, syncSho
|
||||
e.Error(502, err, "Shopee 商品同步失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
syncedAt = &now
|
||||
db.Model(&models.ShopeeProduct{}).Where("shopee_id = ?", want).Update("last_synced_at", now)
|
||||
// The sync task updates the ERP asynchronously. Locate the record again so
|
||||
// getDetail uses the current Huohanhan internal ID and metadata.
|
||||
page, err = client.GetProductPage(ctx, huohanhan.ProductPageParams{
|
||||
@@ -307,6 +337,7 @@ func (e ShopeeProduct) getByShopeeID(c *gin.Context, requireAPIKey bool, syncSho
|
||||
platformInfo, _ := detail.PlatformInfo()
|
||||
platformInfoObject, _ := detailObject(platformInfo)
|
||||
row := models.ShopeeProduct{ShopeeID: want, Payload: string(detail.RawJSON())}
|
||||
row.LastSyncedAt = syncedAt
|
||||
row.ItemName = firstNonEmpty(detailString(itemInfoObject, "itemName"), record.ItemName)
|
||||
row.ShopName = firstNonEmpty(detailString(platformInfoObject, "shopName"), record.ShopName)
|
||||
row.Status = firstNonEmpty(detailString(itemInfoObject, "itemStatus"), record.ItemStatus)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package models
|
||||
|
||||
import "go-admin/common/models"
|
||||
import (
|
||||
"go-admin/common/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ShopeeProduct struct {
|
||||
models.Model
|
||||
@@ -10,7 +13,8 @@ type ShopeeProduct struct {
|
||||
Status string `json:"status" gorm:"size:64"`
|
||||
// Detail responses include images, variants, logistics and descriptions;
|
||||
// some real responses exceed MySQL TEXT's 65,535-byte limit.
|
||||
Payload string `json:"payload" gorm:"type:longtext"`
|
||||
Payload string `json:"payload" gorm:"type:longtext"`
|
||||
LastSyncedAt *time.Time `json:"lastSyncedAt"`
|
||||
models.ModelTime
|
||||
models.ControlBy
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ func registerShopeeProductRouter(v1 *gin.RouterGroup) {
|
||||
v1.GET("/shopee/products/:shopeeId", a.GetByShopeeID)
|
||||
v1.POST("/shopee/products/:shopeeId/sync", a.SyncByShopeeID)
|
||||
v1.POST("/shopee/products/:shopeeId/sync-wait", a.SyncWaitByShopeeID)
|
||||
v1.POST("/shopee/products/:shopeeId/sync-if-stale", a.SyncIfStaleByShopeeID)
|
||||
v1.GET("/shopee/products/:shopeeId/sync-status", a.SyncStatus)
|
||||
}
|
||||
func registerShopeeProductAdminRouter(v1 *gin.RouterGroup, auth *jwt.GinJWTMiddleware) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# LOCAL-012:Shopee 商品条件同步查询
|
||||
|
||||
- 状态:待验收
|
||||
- 目标:按指定天数判断商品是否需要先同步,再获取最新 detail。
|
||||
|
||||
## 接口
|
||||
|
||||
`POST /api/v1/shopee/products/{shopeeId}/sync-if-stale?staleDays=1`
|
||||
|
||||
支持 API Key 查询参数或请求头。没有本地同步记录,或 `lastSyncedAt` 超过指定天数时同步;否则直接读取 detail。默认 `staleDays=1`,允许 0 至 3650。
|
||||
|
||||
## 实施
|
||||
|
||||
- 新增 `shopee_product.last_synced_at` 字段。
|
||||
- 同步完成后记录同步时间。
|
||||
- 保留 `GET`、`/sync`、`/sync-wait` 和 `/sync-status` 原有语义。
|
||||
- 验证:`go test ./app/admin/apis ./app/admin/router ./internal/huohanhan` 通过。
|
||||
Reference in New Issue
Block a user