From 7923a99c7723af092694730b0443a15060c46c2f Mon Sep 17 00:00:00 2001 From: xiaodongzhu825 <97694732@qq.com> Date: Tue, 16 Jun 2026 16:30:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E5=9C=A8=E5=90=91?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=B1=A0=E5=8F=91=E9=80=81=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=97=B6=E5=80=99=E6=8F=90=E4=BA=A4=E5=AE=9E=E6=8B=8D=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E7=9A=84=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/product.go | 360 ++++++--------------------------------------- 1 file changed, 48 insertions(+), 312 deletions(-) diff --git a/service/product.go b/service/product.go index 1db3d14..2809bbc 100644 --- a/service/product.go +++ b/service/product.go @@ -383,6 +383,7 @@ func (s *ProductService) SaveProduct(req systemReq.ProductRequest, db ...*gorm.D return s.createProduct(req, now, databaseConn) } +// createProduct 创建商品 func (s *ProductService) createProduct(req systemReq.ProductRequest, now int64, db *gorm.DB) (int64, error) { var liveImage datatypes.JSON @@ -416,6 +417,7 @@ func (s *ProductService) createProduct(req systemReq.ProductRequest, now int64, return product.ID, nil } +// updateProduct 修改商品 func (s *ProductService) updateProduct(req systemReq.ProductRequest, now int64, db *gorm.DB) (int64, error) { var product models.Product if err := db.Where("id = ? AND is_del = 0", req.ID).First(&product).Error; err != nil { @@ -651,123 +653,18 @@ func (s *ProductService) UpdatePrice(req systemReq.UpdatePriceRequest) error { return nil } -//// GetProductInventory 获取商品库存数量 -//func (s *ProductService) GetProductInventory(req systemReq.GetProductInventoryRequest) (*systemRes.ProductInventoryResponse, error) { -// databaseConn, err := database.GetTenantDB(req.UserID) -// if err != nil { -// return nil, fmt.Errorf("获取数据库连接失败: %v", err) -// } -// -// // 验证商品是否存在 -// var product models.Product -// if err := databaseConn.Where("id = ? AND is_del = ?", req.ProductID, 0).First(&product).Error; err != nil { -// return nil, fmt.Errorf("商品不存在") -// } -// -// //// type=1: 按品相+ISBN+仓库聚合 -// //if req.Type == 1 { -// // type warehouseStock struct { -// // WarehouseID int64 `gorm:"column:warehouse_id"` -// // WarehouseName string `gorm:"column:warehouse_name"` -// // WarehouseCode string `gorm:"column:warehouse_code"` -// // ProductName string `gorm:"column:product_name"` -// // ISBN string `gorm:"column:isbn"` -// // Appearance int64 `gorm:"column:appearance"` -// // TotalQuantity int64 `gorm:"column:total_quantity"` -// // } -// // -// // var warehouseList []warehouseStock -// // databaseConn.Table("inventory"). -// // Select(` -// // inventory.warehouse_id, -// // w.name as warehouse_name, -// // w.code as warehouse_code, -// // p.name as product_name, -// // p.barcode as isbn, -// // p.appearance as appearance, -// // COALESCE(SUM(inventory.quantity), 0) as total_quantity -// // `). -// // Joins("LEFT JOIN warehouse w ON inventory.warehouse_id = w.id AND w.is_del = ?", 0). -// // Joins("LEFT JOIN product p ON inventory.product_id = p.id AND p.is_del = ?", 0). -// // Where("inventory.product_id = ? AND inventory.is_del = ?", req.ProductID, 0). -// // Group("inventory.warehouse_id, w.name, w.code, p.name, p.barcode, p.appearance"). -// // Scan(&warehouseList) -// // -// // warehouses := make([]systemRes.ProductInventoryWarehouse, 0, len(warehouseList)) -// // totalQuantity := int64(0) -// // for _, ws := range warehouseList { -// // warehouses = append(warehouses, systemRes.ProductInventoryWarehouse{ -// // WarehouseID: ws.WarehouseID, -// // WarehouseName: ws.WarehouseName, -// // WarehouseCode: ws.WarehouseCode, -// // ProductName: ws.ProductName, -// // ISBN: ws.ISBN, -// // Appearance: ws.Appearance, -// // TotalQuantity: ws.TotalQuantity, -// // }) -// // totalQuantity += ws.TotalQuantity -// // } -// -// return &systemRes.ProductInventoryResponse{ -// Quantity: totalQuantity, -// //Warehouses: warehouses, -// }, nil -// } -// -// // type=0: 返回原始数据(总库存) -// var totalQuantity int64 -// //databaseConn.Table("inventory"). -// // Select("COALESCE(SUM(quantity), 0)"). -// // Where("product_id = ? AND is_del = ?", req.ProductID, 0). -// // Scan(&totalQuantity) -// -// return &systemRes.ProductInventoryResponse{ -// Quantity: totalQuantity, -// }, nil -//} - +// GetProductInventory 获取商品库存数量 func (s *ProductService) GetProductInventory(req systemReq.GetProductInventoryRequest) (*systemRes.ProductInventoryResponse, error) { databaseConn, err := database.GetTenantDB(req.UserID) if err != nil { return nil, fmt.Errorf("获取数据库连接失败: %v", err) } - // 验证商品是否存在,并获取商品信息 - var product models.Product - if err := databaseConn.Where("id = ? AND is_del = ?", req.ProductID, 0).First(&product).Error; err != nil { - return nil, fmt.Errorf("商品不存在") - } - var totalQuantity int64 - - // type=1: 按品相+ISBN+仓库分组后统计总数量 - if req.Type == 1 { - type GroupStock struct { - TotalQuantity int64 `gorm:"column:total_quantity"` - } - - var groupList []GroupStock - // 先根据商品的 ISBN 和品相,查询所有匹配的库存记录,再按仓库分组统计 - databaseConn.Table("inventory"). - Select(` - COALESCE(SUM(inventory.quantity), 0) as total_quantity - `). - Joins("LEFT JOIN product p ON inventory.product_id = p.id AND p.is_del = ?", 0). - Where("p.barcode = ? AND p.appearance = ? AND inventory.warehouse_id IS NOT NULL AND inventory.is_del = ?", - product.Barcode, product.Appearance, 0). - Group("inventory.warehouse_id"). - Scan(&groupList) - - // 累加所有分组的数量 - for _, group := range groupList { - totalQuantity += group.TotalQuantity - } - } else { - databaseConn.Table("inventory"). - Select("COALESCE(SUM(quantity), 0)"). - Where("product_id = ? AND is_del = ?", req.ProductID, 0). - Scan(&totalQuantity) - } + databaseConn.Table("inventory"). + Select("COALESCE(SUM(quantity), 0)"). + Where("product_id = ? AND is_del = ?", req.ProductID, 0). + Scan(&totalQuantity) return &systemRes.ProductInventoryResponse{ Quantity: totalQuantity, @@ -914,8 +811,7 @@ func (s *ProductService) RetryOutTask(req systemReq.RetryOutTaskRequest, db ...* // BatchPushProducts 批量推送商品到多个店铺 func (s *ProductService) BatchPushProducts(req systemReq.BatchPushProductRequest) error { - fmt.Printf("[DEBUG BatchPushProducts] 入口 user_id=%d shop_ids=%v shop_types=%v product_ids=%v\n", - req.UserID, req.ShopIDs, req.ShopTypes, req.ProductIDs) + fmt.Printf("[DEBUG BatchPushProducts] 入口 user_id=%d shop_ids=%v shop_types=%v product_ids=%v\n", req.UserID, req.ShopIDs, req.ShopTypes, req.ProductIDs) utils.InfoLog(constant.LoggerChannelWork, map[string]interface{}{ "source": "批量推送-入口", "user_id": req.UserID, @@ -1548,15 +1444,26 @@ func (s *ProductService) PushProductToShop(req systemReq.PushProductToShopReques }, nil } -// 批量上架 -/*func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTask, product models.Product, stock, salePrice, cost, now, userID int64, warehouseCode string, locationCode string, condition int64) { +// batchPushProductBody 批量上架 +func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTask, product models.Product, stock, salePrice, cost, now, userID int64, warehouseCode string, locationCode string, condition int64) { isbn := product.Barcode var imgList []string if product.LiveImage != nil && len(product.LiveImage) > 0 { - if err := json.Unmarshal(product.LiveImage, &imgList); err != nil { + var rawImgList []string + if err := json.Unmarshal(product.LiveImage, &rawImgList); err != nil { s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("解析图片失败: %v", err), db) return } + + for _, imgStr := range rawImgList { + urls := strings.Split(imgStr, ",") + for _, url := range urls { + url = strings.TrimSpace(url) + if url != "" { + imgList = append(imgList, url) + } + } + } } msgData := map[string]interface{}{ @@ -1633,89 +1540,6 @@ func (s *ProductService) PushProductToShop(req systemReq.PushProductToShopReques }) } } -*/ - -// batchPushProductBody 批量上架商品到外部任务 -func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTask, product models.Product, stock, salePrice, cost, now, userID int64, warehouseCode string, locationCode string, condition int64) { - isbn := product.Barcode - - // 使用 parseImageList 解析图片列表,支持多种格式 - imgList := s.parseImageList(product.LiveImage) - - msgData := map[string]interface{}{ - "product_id": product.ID, - "user_id": strconv.FormatInt(userID, 10), - "is_distribution": "1", - } - msgJSON, _ := json.Marshal(msgData) - - bodyData := map[string]interface{}{ - "book_info": map[string]interface{}{ - "isbn": isbn, - "image_object": map[string]interface{}{ - "carousel_url_array": imgList, - }, - }, - "detail": map[string]interface{}{ - "stock": stock, - "price": salePrice, - "shipping_cost": cost, - "msg": string(msgJSON), - "sku_code": warehouseCode + "##" + locationCode, - "condition": condition, - }, - } - - bodyDataJSON, err := json.Marshal(bodyData) - if err != nil { - s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("序列化请求体失败: %v", err), db) - return - } - - bodyList := []string{string(bodyDataJSON)} - taskID := fmt.Sprintf("%d", outTask.OutTaskID) - allBody := strings.Join(bodyList, "") - - signParams := map[string]string{ - "task_id": taskID, - "body": allBody, - } - - sign := utils.SignParams(signParams) - - url := config.AppConfig.ExternalAPI.SyncTaskBodyURL - resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign) - if err != nil { - s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("请求外部接口失败: %v", err), db) - return - } - - var resData systemRes.ExternalAPIResponse - if err := json.Unmarshal([]byte(resp), &resData); err != nil { - s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("解析响应失败: %v", err), db) - return - } - - if resData.Code != "200" { - s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("外部接口返回错误: code=%s, msg=%s", resData.Code, resData.Msg), db) - return - } - - if updateErr := db.Model(&models.OutTaskLog{}). - Where("shop_id = ? AND product_id = ? AND is_del = ?", outTask.ShopID, product.ID, 0). - Updates(map[string]interface{}{ - "status": 1, - "msg": "成功", - "updated_at": now, - }).Error; updateErr != nil { - utils.ErrorLog(constant.LoggerChannelWork, map[string]interface{}{ - "source": "批量推送-更新历史日志状态失败", - "shop_id": outTask.ShopID, - "product_id": product.ID, - "error": fmt.Sprintf("更新失败: %v", updateErr), - }) - } -} // saveRetryLog 保存重试任务日志 func (s *ProductService) saveRetryLog(shopID, outTaskID, productID int64, product models.Product, waveTaskDetail models.WaveTaskDetail, hasWaveTask bool, msg string, db *gorm.DB) { @@ -1765,9 +1589,20 @@ func (s *ProductService) syncPriceToExternalTaskWithOptionalWave(outTask models. isbn := product.Barcode var imgList []string if product.LiveImage != nil && len(product.LiveImage) > 0 { - if err := json.Unmarshal(product.LiveImage, &imgList); err != nil { + var rawImgList []string + if err := json.Unmarshal(product.LiveImage, &rawImgList); err != nil { return fmt.Errorf("解析json失败: %v", err) } + + for _, imgStr := range rawImgList { + urls := strings.Split(imgStr, ",") + for _, url := range urls { + url = strings.TrimSpace(url) + if url != "" { + imgList = append(imgList, url) + } + } + } } msgData := map[string]interface{}{ @@ -1954,13 +1789,24 @@ func (s *ProductService) syncPriceToExternal(waveTaskDetail models.WaveTaskDetai } // syncPriceToExternalTask 同步售价到单个外部任务 -/*func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product models.Product, waveTaskDetail models.WaveTaskDetail, salePrice, cost, userId int64, db *gorm.DB) error { +func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product models.Product, waveTaskDetail models.WaveTaskDetail, salePrice, cost, userId int64, db *gorm.DB) error { isbn := product.Barcode var imgList []string if product.LiveImage != nil && len(product.LiveImage) > 0 { - if err := json.Unmarshal(product.LiveImage, &imgList); err != nil { + var rawImgList []string + if err := json.Unmarshal(product.LiveImage, &rawImgList); err != nil { return fmt.Errorf("解析json失败: %v", err) } + + for _, imgStr := range rawImgList { + urls := strings.Split(imgStr, ",") + for _, url := range urls { + url = strings.TrimSpace(url) + if url != "" { + imgList = append(imgList, url) + } + } + } } msgData := map[string]interface{}{ @@ -2026,78 +1872,6 @@ func (s *ProductService) syncPriceToExternal(waveTaskDetail models.WaveTaskDetai return nil } -*/ - -// syncPriceToExternalTask 同步售价到单个外部任务 -func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product models.Product, waveTaskDetail models.WaveTaskDetail, salePrice, cost, userId int64, db *gorm.DB) error { - isbn := product.Barcode - - // 使用 parseImageList 解析图片列表,支持多种格式 - imgList := s.parseImageList(product.LiveImage) - - msgData := map[string]interface{}{ - "product_id": product.ID, - "user_id": fmt.Sprintf("%d", userId), - } - msgJSON, _ := json.Marshal(msgData) - - bodyData := map[string]interface{}{ - "book_info": map[string]interface{}{ - "isbn": isbn, - "image_object": map[string]interface{}{ - "carousel_url_array": imgList, - }, - }, - "detail": map[string]interface{}{ - "stock": waveTaskDetail.PlannedQuantity, - "price": salePrice, - "shipping_cost": cost, - "msg": string(msgJSON), - }, - } - - bodyDataJSON, err := json.Marshal(bodyData) - if err != nil { - return fmt.Errorf("序列化请求体失败: %v", err) - } - - bodyList := []string{ - string(bodyDataJSON), - } - - taskID := fmt.Sprintf("%d", outTask.OutTaskID) - - allBody := strings.Join(bodyList, "") // 直接无缝拼接(和服务端一致) - - signParams := map[string]string{ - "task_id": taskID, - "body": allBody, - } - - sign := utils.SignParams(signParams) - - // 发送请求 - url := config.AppConfig.ExternalAPI.SyncTaskBodyURL - resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign) - if err != nil { - s.saveOutTaskLog(outTask, product.ID, isbn, product.LiveImage, waveTaskDetail.PlannedQuantity, salePrice, cost, fmt.Sprintf("请求外部接口失败: %v", err), db) - return fmt.Errorf("请求外部接口失败: %v", err) - } - - var resData systemRes.ExternalAPIResponse - if err := json.Unmarshal([]byte(resp), &resData); err != nil { - return fmt.Errorf("解析响应失败: %v", err) - } - - if resData.Code != "200" { - s.saveOutTaskLog(outTask, product.ID, isbn, product.LiveImage, waveTaskDetail.PlannedQuantity, salePrice, cost, fmt.Sprintf("外部接口返回错误: code=%s, msg=%s", resData.Code, resData.Msg), db) - return fmt.Errorf("外部接口返回错误: code=%s, msg=%s", resData.Code, resData.Msg) - } - - s.saveOutTaskLog(outTask, product.ID, isbn, product.LiveImage, waveTaskDetail.PlannedQuantity, salePrice, cost, "成功", db) - - return nil -} // saveOutTaskLog 保存外部任务日志 func (s *ProductService) saveOutTaskLog(outTask models.OutTask, productID int64, isbn string, liveImage []byte, stock, salePrice, cost int64, msg string, db *gorm.DB) { @@ -2314,6 +2088,7 @@ func (s *ProductService) ExportProducts(req systemReq.ExportProductRequest, db . }, nil } +// GetProductLogList 导出商品 func (s *ProductService) GetProductLogList(req systemReq.GetProductLogListRequest, aboutID int64) (*systemRes.ProductLogListResponse, error) { // TODO //if aboutID != 0 { @@ -2948,45 +2723,6 @@ func (s *ProductService) convertProductLogToItem(log models.ProductLog) systemRe } } -// parseImageList 解析图片列表,支持JSON数组和逗号分隔字符串两种格式 -func (s *ProductService) parseImageList(liveImage datatypes.JSON) []string { - if liveImage == nil || len(liveImage) == 0 { - return []string{} - } - - // 尝试解析为字符串数组 - var imgList []string - if err := json.Unmarshal(liveImage, &imgList); err == nil { - // 成功解析为数组,过滤空字符串后返回 - result := make([]string, 0, len(imgList)) - for _, img := range imgList { - trimmed := strings.TrimSpace(img) - if trimmed != "" { - result = append(result, trimmed) - } - } - return result - } - - // 如果解析失败,尝试解析为字符串,然后按逗号分割 - var imgStr string - if err := json.Unmarshal(liveImage, &imgStr); err == nil { - // 去除空格并按逗号分割 - parts := strings.Split(imgStr, ",") - result := make([]string, 0, len(parts)) - for _, part := range parts { - trimmed := strings.TrimSpace(part) - if trimmed != "" { - result = append(result, trimmed) - } - } - return result - } - - // 都不成功,返回空数组 - return []string{} -} - func (s *ProductService) getShopTypeName(shopType int8) string { switch shopType { case 1: