feat: 支持蝦皮批量创建 PDD 采集任务 (#185)
This commit is contained in:
+12
-4
@@ -543,9 +543,11 @@ func DeletePddProducts(db *sql.DB, goodsIDs []string) (int64, error) {
|
||||
type CollectTaskResult struct {
|
||||
Created int
|
||||
|
||||
SkippedCollecting int // 正在采集且**没超时**,得等客户端来领/提交
|
||||
SkippedCollected int // 已经采集完成,本来就不需要重新采集
|
||||
SkippedDeleted int // 商品不存在或已被软删除
|
||||
SkippedShopeeMissing int // 浏览器提交的蝦皮商品已不存在
|
||||
SkippedUnlinked int // 蝦皮商品尚未关联 PDD
|
||||
SkippedCollecting int // 正在采集且**没超时**,得等客户端来领/提交
|
||||
SkippedCollected int // 已经采集完成,本来就不需要重新采集
|
||||
SkippedDeleted int // 商品不存在或已被软删除
|
||||
|
||||
// RetryWaitText 是 SkippedCollecting 里最快能重试的还需等待时长,
|
||||
// 形如 "12 分钟";SkippedCollecting 为 0 时是空串。
|
||||
@@ -555,7 +557,7 @@ type CollectTaskResult struct {
|
||||
// Skipped 是跳过总数,供测试和粗粒度统计用;
|
||||
// 界面提示要按原因分开说,见 FormatCollectTaskMessage,不要直接显示这个数。
|
||||
func (r CollectTaskResult) Skipped() int {
|
||||
return r.SkippedCollecting + r.SkippedCollected + r.SkippedDeleted
|
||||
return r.SkippedShopeeMissing + r.SkippedUnlinked + r.SkippedCollecting + r.SkippedCollected + r.SkippedDeleted
|
||||
}
|
||||
|
||||
// CreatePddCollectTasks 为勾选的 PDD 商品创建不指定客户端的采集任务。
|
||||
@@ -784,6 +786,12 @@ func formatRetryWait(remain time.Duration) string {
|
||||
// (比如还要等多久),见 #24。
|
||||
func FormatCollectTaskMessage(r CollectTaskResult) string {
|
||||
var reasons []string
|
||||
if r.SkippedShopeeMissing > 0 {
|
||||
reasons = append(reasons, fmt.Sprintf("%d 个蝦皮商品已不存在", r.SkippedShopeeMissing))
|
||||
}
|
||||
if r.SkippedUnlinked > 0 {
|
||||
reasons = append(reasons, fmt.Sprintf("%d 个未关联 PDD", r.SkippedUnlinked))
|
||||
}
|
||||
if r.SkippedCollecting > 0 {
|
||||
reason := fmt.Sprintf("%d 个正在采集中", r.SkippedCollecting)
|
||||
if r.Created > 0 {
|
||||
|
||||
@@ -112,6 +112,43 @@ func CreateShopeePddCollectTaskForUser(db *sql.DB, actor *model.User, shopeeGood
|
||||
return CreatePddCollectTasksForUser(db, actor, []string{product.PddGoodsID}, "")
|
||||
}
|
||||
|
||||
// CreateShopeePddCollectTasksForUser 把勾选的蝦皮商品转换成当前 PDD 商品后批量建采集任务。
|
||||
// 蝦皮与 PDD 可以多对一,因此交给统一 PDD 服务前先按 PDD goods_id 去重;
|
||||
// 商品不存在和未关联按原因跳过,其余状态、防重和客户端权限仍由统一服务处理。
|
||||
func CreateShopeePddCollectTasksForUser(db *sql.DB, actor *model.User, shopeeGoodsIDs []string, clientID string) (CollectTaskResult, error) {
|
||||
shopeeGoodsIDs = dedupe(shopeeGoodsIDs)
|
||||
if len(shopeeGoodsIDs) == 0 {
|
||||
return CollectTaskResult{}, invalidInput("没有选择蝦皮商品")
|
||||
}
|
||||
links, err := repository.ListShopeePddLinksByGoodsIDs(db, shopeeGoodsIDs)
|
||||
if err != nil {
|
||||
return CollectTaskResult{}, err
|
||||
}
|
||||
|
||||
var skipped CollectTaskResult
|
||||
pddGoodsIDs := make([]string, 0, len(shopeeGoodsIDs))
|
||||
for _, shopeeGoodsID := range shopeeGoodsIDs {
|
||||
pddGoodsID, exists := links[shopeeGoodsID]
|
||||
if !exists {
|
||||
skipped.SkippedShopeeMissing++
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(pddGoodsID) == "" {
|
||||
skipped.SkippedUnlinked++
|
||||
continue
|
||||
}
|
||||
pddGoodsIDs = append(pddGoodsIDs, pddGoodsID)
|
||||
}
|
||||
|
||||
result, err := CreatePddCollectTasksForUser(db, actor, dedupe(pddGoodsIDs), clientID)
|
||||
if err != nil {
|
||||
return CollectTaskResult{}, err
|
||||
}
|
||||
result.SkippedShopeeMissing = skipped.SkippedShopeeMissing
|
||||
result.SkippedUnlinked = skipped.SkippedUnlinked
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AssociateSybPdd 从顺运宝明细解析出可信的蝦皮商品,再复用统一关联逻辑。
|
||||
func AssociateSybPdd(db *sql.DB, sybID, rawURL string, confirmReplace bool) (string, error) {
|
||||
context, err := repository.GetSybOrderContext(db, strings.TrimSpace(sybID))
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -120,6 +121,92 @@ func TestCreateShopeePddCollectTaskForUser_记录当前用户(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateShopeePddCollectTasksForUser_按Pdd去重并分类跳过(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, _, _ := insertTaskUsers(t, db)
|
||||
for _, goodsID := range []string{"S-A", "S-B", "S-UNLINKED", "S-DELETED"} {
|
||||
seedShopeeProduct(t, db, goodsID, goodsID)
|
||||
}
|
||||
for _, goodsID := range []string{"S-A", "S-B"} {
|
||||
if _, err := AssociateShopeePdd(db, goodsID, pddURLA, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := AssociateShopeePdd(db, "S-DELETED", pddURLB, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repository.SoftDeletePddProduct(db, "937122477375"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result, err := CreateShopeePddCollectTasksForUser(db, admin,
|
||||
[]string{"S-A", "S-B", "S-A", "S-UNLINKED", "S-DELETED", "S-MISSING"}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Created != 1 || result.SkippedUnlinked != 1 || result.SkippedShopeeMissing != 1 || result.SkippedDeleted != 1 {
|
||||
t.Fatalf("批量分类或 PDD 去重错误: %+v", result)
|
||||
}
|
||||
var taskCount int
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount); err != nil || taskCount != 1 {
|
||||
t.Fatalf("相同 PDD 商品只应创建一条任务,count=%d err=%v", taskCount, err)
|
||||
}
|
||||
message := FormatCollectTaskMessage(result)
|
||||
for _, want := range []string{"已创建 1 个", "1 个蝦皮商品已不存在", "1 个未关联 PDD", "1 个已删除的"} {
|
||||
if !strings.Contains(message, want) {
|
||||
t.Errorf("批量结果提示 %q 缺少 %q", message, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateShopeePddCollectTasksForUser_已采集和正常采集中分别跳过(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, _, _ := insertTaskUsers(t, db)
|
||||
seedShopeeProduct(t, db, "S-COLLECTED", "已采集")
|
||||
seedShopeeProduct(t, db, "S-COLLECTING", "采集中")
|
||||
AssociateShopeePdd(db, "S-COLLECTED", pddURLA, false)
|
||||
AssociateShopeePdd(db, "S-COLLECTING", pddURLB, false)
|
||||
if err := repository.SetCollectResult(db, "737116531267", "商品", "店铺", collectedThreeDimensions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result, err := CreatePddCollectTasksForUser(db, admin, []string{"937122477375"}, ""); err != nil || result.Created != 1 {
|
||||
t.Fatalf("准备采集中任务失败: result=%+v err=%v", result, err)
|
||||
}
|
||||
|
||||
result, err := CreateShopeePddCollectTasksForUser(db, admin,
|
||||
[]string{"S-COLLECTED", "S-COLLECTING"}, "")
|
||||
if err != nil || result.Created != 0 || result.SkippedCollected != 1 || result.SkippedCollecting != 1 {
|
||||
t.Fatalf("状态分类错误: result=%+v err=%v", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateShopeePddCollectTasksForUser_拒绝不可见客户端且整批不创建(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, buyerA, buyerB := prepareClientAssignmentUsers(t, db)
|
||||
if err := RegisterClient(db, model.Client{ClientID: "client-batch-b", Name: "B 的客户端"}, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := AssignClient(db, admin, "client-batch-b", buyerB.UserID, time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
seedShopeeProduct(t, db, "S-PERM", "权限商品")
|
||||
AssociateShopeePdd(db, "S-PERM", pddURLA, false)
|
||||
|
||||
_, err := CreateShopeePddCollectTasksForUser(db, buyerA, []string{"S-PERM"}, "client-batch-b")
|
||||
if !IsValidationError(err) {
|
||||
t.Fatalf("伪造他人客户端应返回校验错误,实际 %v", err)
|
||||
}
|
||||
var taskCount int
|
||||
db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount)
|
||||
if taskCount != 0 {
|
||||
t.Fatalf("权限失败后不应创建任务,实际 %d", taskCount)
|
||||
}
|
||||
product, _ := repository.GetPddProductByGoodsID(db, "737116531267")
|
||||
if product.CollectStatus != model.CollectPending {
|
||||
t.Fatalf("权限失败后商品状态应保持 pending,实际 %s", product.CollectStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSybPddCollectTasksForUser_同一PDD商品去重(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, _, _ := insertTaskUsers(t, db)
|
||||
|
||||
Reference in New Issue
Block a user