feat: add stale-aware shopee sync endpoint

This commit is contained in:
QiuSW
2026-09-14 12:00:22 +08:00
parent 122982568e
commit 247099f05f
4 changed files with 55 additions and 2 deletions
+31
View File
@@ -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)
+6 -2
View File
@@ -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
}
+1
View File
@@ -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) {
+17
View File
@@ -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` 通过。