package integration import ( "bytes" "database/sql" "encoding/json" "errors" "io" "net/http" "github.com/gin-gonic/gin" "cmautobuy/admin/config" "cmautobuy/admin/repository" "cmautobuy/admin/service" ) const catalogMaxBody = 5 << 20 type Handler struct{ db *sql.DB } // Register 只挂载第三方目录接口,不复用 Client 或网页登录中间件。 func Register(r *gin.Engine, db *sql.DB, cfg config.CatalogIntegrationConfig) { h := &Handler{db: db} g := r.Group("/api/v1/integrations/catalog", RequireCatalogToken(cfg)) g.POST("/batches", h.CreateBatch) g.GET("/batches/:batch_id", h.GetBatch) } func (h *Handler) GetBatch(c *gin.Context) { run, err := repository.GetCatalogImportRun(h.db, integrationSource(c), c.Param("batch_id")) if errors.Is(err, repository.ErrCatalogImportRunNotFound) { integrationError(c, 404, "BATCH_NOT_FOUND", "批次不存在", false, nil) return } if err != nil { integrationError(c, 500, "INTERNAL_ERROR", "查询批次失败", true, nil) return } c.JSON(200, gin.H{"source": run.Source, "batch_id": run.BatchID, "update_policy": run.UpdatePolicy, "status": run.Status, "request_count": run.RequestCount, "conflict_count": run.ConflictCount, "observed_at": run.ObservedAt, "created_at": run.CreatedAt, "finished_at": run.FinishedAt, "counts": gin.H{"shopee_created": run.ShopeeCreated, "shopee_updated": run.ShopeeUpdated, "shopee_fields_filled": run.ShopeeFieldsFilled, "shopee_fields_same_source_updated": run.ShopeeFieldsSameSourceUpdated, "shopee_fields_manual_skipped": run.ShopeeFieldsManualSkipped, "shopee_fields_stale_skipped": run.ShopeeFieldsStaleSkipped, "sku_created": run.SKUCreated, "sku_updated": run.SKUUpdated, "sku_filled": run.SKUFilled, "sku_same_source_updated": run.SKUSameSourceUpdated, "sku_skipped": run.SKUSkipped, "sku_manual_skipped": run.SKUManualSkipped, "sku_stale_skipped": run.SKUStaleSkipped, "pdd_created": run.PddCreated, "pdd_updated": run.PddUpdated, "association_created": run.AssociationCreated, "association_unchanged": run.AssociationUnchanged}, "failure_count": run.FailureCount, "error_summary": run.ErrorSummary}) } func (h *Handler) CreateBatch(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, catalogMaxBody) raw, err := io.ReadAll(c.Request.Body) if err != nil { if _, ok := err.(*http.MaxBytesError); ok { integrationError(c, 413, "PAYLOAD_TOO_LARGE", "请求体不能超过 5 MB", false, nil) return } integrationError(c, 400, "INVALID_BODY", "读取请求失败", false, nil) return } var req service.CatalogBatchRequest dec := json.NewDecoder(bytes.NewReader(raw)) dec.DisallowUnknownFields() if err = dec.Decode(&req); err != nil { integrationError(c, 400, "INVALID_JSON", "请求体不是合法的商品目录 JSON", false, nil) return } if dec.Decode(&struct{}{}) != io.EOF { integrationError(c, 400, "INVALID_JSON", "请求体只能包含一个 JSON 对象", false, nil) return } resp, err := service.ImportCatalogBatch(h.db, integrationSource(c), req, raw) if err != nil { var catalogErr *service.CatalogError if errors.As(err, &catalogErr) { integrationError(c, catalogErr.Status, catalogErr.Code, catalogErr.Message, catalogErr.Retryable, catalogErr.Details) return } integrationError(c, 500, "INTERNAL_ERROR", "商品目录批次处理失败", true, nil) return } c.JSON(http.StatusOK, resp) }