diff --git a/server/app/goauto/product/service.go b/server/app/goauto/product/service.go
index a5e056f..db0f51d 100644
--- a/server/app/goauto/product/service.go
+++ b/server/app/goauto/product/service.go
@@ -100,6 +100,10 @@ type SaveResponse struct {
Replayed bool `json:"replayed,omitempty"`
}
+type DetailResponse struct {
+ Product ListProductView `json:"product"`
+}
+
type Service struct{ DB *gorm.DB }
func NewService(db *gorm.DB) *Service { return &Service{DB: db} }
@@ -197,19 +201,27 @@ func (service *Service) Update(ctx context.Context, productID uint64, request Up
if result.RowsAffected == 0 {
return SaveResponse{}, &ServiceError{Code: CodeProductNotFound, Message: "PDD 商品不存在"}
}
- return service.Detail(ctx, productID)
+ var updated models.PDDProduct
+ if err := db.First(&updated, productID).Error; err != nil {
+ return SaveResponse{}, internalError(err)
+ }
+ view, err := makeView(updated)
+ return SaveResponse{Product: view}, err
}
-func (service *Service) Detail(ctx context.Context, productID uint64) (SaveResponse, error) {
+func (service *Service) Detail(ctx context.Context, productID uint64) (DetailResponse, error) {
var record models.PDDProduct
if err := service.DB.WithContext(ctx).First(&record, productID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
- return SaveResponse{}, &ServiceError{Code: CodeProductNotFound, Message: "PDD 商品不存在"}
+ return DetailResponse{}, &ServiceError{Code: CodeProductNotFound, Message: "PDD 商品不存在"}
}
- return SaveResponse{}, internalError(err)
+ return DetailResponse{}, internalError(err)
}
- view, err := makeView(record)
- return SaveResponse{Product: view}, err
+ items, err := service.makeCollectionViews(ctx, []models.PDDProduct{record})
+ if err != nil {
+ return DetailResponse{}, err
+ }
+ return DetailResponse{Product: items[0]}, nil
}
func (service *Service) List(ctx context.Context, request ListRequest) (ListResponse, error) {
@@ -244,6 +256,18 @@ func (service *Service) List(ctx context.Context, request ListRequest) (ListResp
if err := query.Order("updated_at DESC, id DESC").Offset((request.Page - 1) * request.PageSize).Limit(request.PageSize).Find(&records).Error; err != nil {
return ListResponse{}, internalError(err)
}
+ items, err := service.makeCollectionViews(ctx, records)
+ if err != nil {
+ return ListResponse{}, err
+ }
+ return ListResponse{Items: items, Total: total, Page: request.Page, PageSize: request.PageSize}, nil
+}
+
+func (service *Service) makeCollectionViews(ctx context.Context, records []models.PDDProduct) ([]ListProductView, error) {
+ items := make([]ListProductView, 0, len(records))
+ if len(records) == 0 {
+ return items, nil
+ }
productIDs := make([]uint64, 0, len(records))
for _, record := range records {
productIDs = append(productIDs, record.ID)
@@ -253,27 +277,24 @@ func (service *Service) List(ctx context.Context, request ListRequest) (ListResp
TaskID uint64
}
activeTasks := map[uint64]uint64{}
- if len(productIDs) > 0 {
- var rows []activeTaskRow
- if err := service.DB.WithContext(ctx).Model(&models.CollectionTask{}).
- Select("pdd_product_id, MIN(id) AS task_id").
- Where("pdd_product_id IN ? AND status IN ?", productIDs, []string{models.TaskStatusPending, models.TaskStatusRunning}).
- Group("pdd_product_id").Scan(&rows).Error; err != nil {
- return ListResponse{}, internalError(err)
- }
- for _, row := range rows {
- activeTasks[row.PDDProductID] = row.TaskID
- }
+ var rows []activeTaskRow
+ if err := service.DB.WithContext(ctx).Model(&models.CollectionTask{}).
+ Select("pdd_product_id, MIN(id) AS task_id").
+ Where("pdd_product_id IN ? AND status IN ?", productIDs, []string{models.TaskStatusPending, models.TaskStatusRunning}).
+ Group("pdd_product_id").Scan(&rows).Error; err != nil {
+ return nil, internalError(err)
+ }
+ for _, row := range rows {
+ activeTasks[row.PDDProductID] = row.TaskID
}
var ruleCount int64
if err := service.DB.WithContext(ctx).Model(&models.CollectionRule{}).Count(&ruleCount).Error; err != nil {
- return ListResponse{}, internalError(err)
+ return nil, internalError(err)
}
- items := make([]ListProductView, 0, len(records))
for _, record := range records {
view, err := makeView(record)
if err != nil {
- return ListResponse{}, err
+ return nil, err
}
item := ListProductView{ProductView: view, CollectionSelectable: true}
if record.Status == "disabled" {
@@ -289,7 +310,7 @@ func (service *Service) List(ctx context.Context, request ListRequest) (ListResp
}
items = append(items, item)
}
- return ListResponse{Items: items, Total: total, Page: request.Page, PageSize: request.PageSize}, nil
+ return items, nil
}
func validateCreateRequest(request SaveRequest) error {
diff --git a/server/app/goauto/product/service_test.go b/server/app/goauto/product/service_test.go
index cc998a4..6ad3aa4 100644
--- a/server/app/goauto/product/service_test.go
+++ b/server/app/goauto/product/service_test.go
@@ -190,3 +190,49 @@ func TestListDisablesCollectionWhenNoRuleExists(t *testing.T) {
t.Fatalf("missing-rule product remained selectable: %+v", listed.Items[0])
}
}
+
+func TestDetailUsesSameCollectionEligibilityAsList(t *testing.T) {
+ db := openProductDatabase(t)
+ service := NewService(db)
+ rule := models.CollectionRule{Name: "rule", ContentJSON: `{}`}
+ if err := db.Create(&rule).Error; err != nil {
+ t.Fatal(err)
+ }
+ products := []models.PDDProduct{
+ {GoodsID: "555555", URL: "https://mobile.yangkeduo.com/goods.html?goods_id=555555", Status: "active"},
+ {GoodsID: "666666", URL: "https://mobile.yangkeduo.com/goods.html?goods_id=666666", Status: "disabled"},
+ {GoodsID: "777777", URL: "https://mobile.yangkeduo.com/goods.html?goods_id=777777", Status: "active"},
+ }
+ if err := db.Create(&products).Error; err != nil {
+ t.Fatal(err)
+ }
+ active := models.CollectionTask{
+ PDDProductID: products[2].ID, RuleID: rule.ID, Status: models.TaskStatusPending,
+ URLSnapshot: products[2].URL, GoodsIDSnapshot: products[2].GoodsID, RuleSnapshot: rule.ContentJSON,
+ }
+ if err := db.Create(&active).Error; err != nil {
+ t.Fatal(err)
+ }
+ listed, err := service.List(context.Background(), ListRequest{Page: 1, PageSize: 20})
+ if err != nil {
+ t.Fatal(err)
+ }
+ byID := map[uint64]ListProductView{}
+ for _, item := range listed.Items {
+ byID[item.ID] = item
+ }
+ for _, product := range products {
+ detail, err := service.Detail(context.Background(), product.ID)
+ if err != nil {
+ t.Fatalf("detail %d: %v", product.ID, err)
+ }
+ want := byID[product.ID]
+ got := detail.Product
+ if got.CollectionSelectable != want.CollectionSelectable || got.CollectionDisabledReason != want.CollectionDisabledReason {
+ t.Fatalf("detail/list eligibility mismatch for %d: detail=%+v list=%+v", product.ID, got, want)
+ }
+ if (got.ActiveCollectionTaskID == nil) != (want.ActiveCollectionTaskID == nil) {
+ t.Fatalf("detail/list active task mismatch for %d: detail=%+v list=%+v", product.ID, got, want)
+ }
+ }
+}
diff --git a/web/src/views/goauto/pdd-products/index.vue b/web/src/views/goauto/pdd-products/index.vue
index c5b478c..c3d4a61 100644
--- a/web/src/views/goauto/pdd-products/index.vue
+++ b/web/src/views/goauto/pdd-products/index.vue
@@ -12,10 +12,10 @@
已选择 {{ selectedProducts.length }} 条;仅选择当前页,每个商品创建一个独立采集任务,不合并、不拆单。