package xianyu import ( "encoding/json" "errors" "fmt" "planA/planB/initialization/golabl" "planA/planB/modules/logs" "planA/planB/service" "planA/planB/tool" planBType "planA/planB/type" planBTypeXianyu "planA/planB/type/xianyu" planAType "planA/type" "strconv" "strings" "time" ) type XianYu struct { } // NewXianYu 创建闲鱼平台 func NewXianYu() *XianYu { return &XianYu{} } // AddGoodsTask 添加商品 // @param taskMsg 任务内容 // @return string body 信息 // @return error 错误 func (xianYu *XianYu) AddGoodsTask(taskMsg planAType.TaskBody) (string, error) { //生成唯一请求标识(用于出错精准查询日志) logUuid, generateUUIDErr := tool.GenerateUUID() if generateUUIDErr != nil { return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr) } taskMsg, publishGoodsErr := publishGoods(logUuid, taskMsg) if publishGoodsErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, publishGoodsErr) } taskMsg.Detail.Error = "发布成功!" return tool.ReturnSuccess(taskMsg) } func (xianYu *XianYu) GetGoodsTask() (string, error) { // 生成唯一请求标识(用于出错精准查询日志) logUuid, generateUUIDErr := tool.GenerateUUID() if generateUUIDErr != nil { return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr) } const pageSize = 100 const maxPage = 100 const maxRecordsPerRange = 10000 // 每个时间范围最多获取10000条 var lastUpdateTime int64 = 0 // 统计变量 totalFetched := 0 // 总共获取到的商品数(包括重复) duplicateCount := 0 // 重复商品数量 uniqueCount := 0 // 不重复商品数量 // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, fmt.Errorf("解析应用id与应用秘钥失败: %v", unmarshalErr)) } // 第一阶段:只拉取任务数据,更新总数,不写入wait firstTimeGoodsErr := xianYu.phaseOneGoodsOnlyCount(token, pageSize, maxPage) if firstTimeGoodsErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, firstTimeGoodsErr) } // 查询body_wait是否存在,确定第二阶段的开始时间 exist, isTaskBodyWaitExistErr := service.IsTaskBodyWaitExist() if isTaskBodyWaitExistErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, isTaskBodyWaitExistErr) } if exist { // 获取最后一条数据的更新时间作为开始时间 lastBodyWaitDataJson, getLastGoodsUpdateTimeErr := service.GetTaskBodyWaitLast() if getLastGoodsUpdateTimeErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, getLastGoodsUpdateTimeErr) } // 解析 lastBodyWaitData 到结构体 var lastBodyWaitData planAType.TaskBody unmarshalErr := json.Unmarshal([]byte(lastBodyWaitDataJson), &lastBodyWaitData) if unmarshalErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, unmarshalErr) } lastUpdateTime = lastBodyWaitData.BookInfo.Price - (86400 * 30) //wait中最后一条数据的更新时间-30天作为下次的开始时间 // 将数据的更新时间给到 lastUpdateTime fmt.Println("使用wait中最后一条数据的时间作为开始时间: ", lastUpdateTime) } else { // 如果没有wait数据,使用当前时间180天前的时间戳作为开始时间 lastUpdateTime = time.Now().Unix() - 180*24*60*60 fmt.Println("没有wait数据,使用180天前的时间作为开始时间: ", lastUpdateTime, time.Unix(lastUpdateTime, 0).Format("2006-01-02 15:04:05")) } // 第二阶段:获取商品(写入wait) phaseTwoGoodsErr := xianYu.phaseTwoGoods(token, pageSize, &totalFetched, &lastUpdateTime, maxRecordsPerRange) if phaseTwoGoodsErr != nil { fmt.Println(phaseTwoGoodsErr.Error()) return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, phaseTwoGoodsErr) } // 更新状态为推送中 updateTaskStatusErr := service.UpdateTaskStatus(planAType.TaskStatusPushTaskStatus) if updateTaskStatusErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, updateTaskStatusErr) } // 重新设置任务进度 if updateTaskHeaderErr := service.SetTaskCount(strconv.FormatInt(golabl.Task.Footer.TaskCountTrue, 10)); updateTaskHeaderErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, updateTaskHeaderErr) } // 去重复与保存 deduplicateToBodyOverErr := xianYu.deduplicateToBodyOver(&duplicateCount, &uniqueCount) if deduplicateToBodyOverErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, deduplicateToBodyOverErr) } // 输出统计信息 statsLogMsg := fmt.Sprintf(` ════════════════════════════════════════════════════════════════ 【闲鱼店铺拉取】 请求ID:%s 时间: %s 店铺ID:%v 店铺名称:%v 总共获取商品数(含重复): %d 不重复商品数: %d 重复商品数: %d 重复率: %.2f%% ════════════════════════════════════════════════════════════════`, logUuid, time.Now().Format("2006-01-02 15:04:05.000"), golabl.Task.TaskId, golabl.Task.Header.ShopName, totalFetched, uniqueCount, duplicateCount, float64(duplicateCount)/float64(totalFetched)*100) fmt.Println(statsLogMsg) tool.LoggingMiddleware(logs.LOG_LEVEL_INFO, statsLogMsg) return tool.ReturnSuccess(planAType.TaskBody{}) } // OperationGoodsTask 操作商品 // @param taskMsg 任务内容 // @return string body 信息 // @return string error 错误 func (xianYu *XianYu) OperationGoodsTask(taskMsg planAType.TaskBody) (string, error) { //生成唯一请求标识(用于出错精准查询日志) logUuid, generateUUIDErr := tool.GenerateUUID() if generateUUIDErr != nil { return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr) } //暂停 2 秒 time.Sleep(2 * time.Second) fmt.Println(taskMsg.Detail.Status) switch taskMsg.Detail.Status { case 1: return executeGoodsLaunch(logUuid, taskMsg) //上架 {"book_info":{"isbn":"9787115600387"},"detail":{"goods_id":1562238986012229,"status":1}} case 2: return executeGoodsDownShelf(logUuid, taskMsg) // 下架 {"book_info":{"isbn":"9787115600387"},"detail":{"goods_id":1562238986012229,"status":2}} case 4: return executeGoodsUpdateStock(logUuid, taskMsg) //修改商品库存 {"book_info":{"isbn":"9787115600387"},"detail":{"goods_id":1562238986012229,"status":4,"stock":2}} case 5: return executeGoodsUpdatePrice(logUuid, taskMsg) //修改商品价格 {"book_info":{"isbn":"9787115600387"},"detail":{"goods_id":1562238986012229,"status":5,"price":5000}} case 6: //发布商品 taskMsg, publishGoodsErr := publishGoods(logUuid, taskMsg) if publishGoodsErr != nil { return "", publishGoodsErr } return tool.ReturnSuccess(taskMsg) case 7: //下架 _, setSaleStatusGoodsTaskErr := executeGoodsDownShelf(logUuid, taskMsg) if setSaleStatusGoodsTaskErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, setSaleStatusGoodsTaskErr) } //发布商品 taskMsg, publishGoodsErr := publishGoods(logUuid, taskMsg) if publishGoodsErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, publishGoodsErr) } return tool.ReturnSuccess(taskMsg) default: return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("未知操作类型")) } } // IncStockTask 增量库存b // @param taskMsg 任务内容 // @return string body 信息 // @return string error 错误 func (xianYu *XianYu) IncStockTask(taskMsg planAType.TaskBody) (string, error) { //生成唯一请求标识(用于出错精准查询日志) logUuid, generateUUIDErr := tool.GenerateUUID() if generateUUIDErr != nil { return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr) } // 获取商品id getGoodsByShopIdAndIsbn, GetGoodsByShopIdAndIsbnErr := tool.GetGoodsByShopIdAndIsbn(golabl.Task.Header.ShopId, taskMsg.BookInfo.Isbn) if GetGoodsByShopIdAndIsbnErr != nil { return "", GetGoodsByShopIdAndIsbnErr } if getGoodsByShopIdAndIsbn.Code != "200" { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("请求ERP获取商品编码与skuid失败: %v", getGoodsByShopIdAndIsbn)) } if len(getGoodsByShopIdAndIsbn.Data) == 0 { //新发布 task, addGoodsTaskErr := xianYu.AddGoodsTask(taskMsg) if addGoodsTaskErr != nil { return "", addGoodsTaskErr } return task, nil } else { // 当前任务的价格 taskPrice := taskMsg.Detail.Price // 单位:分 // 价格 + 运费(如果 PriceType != "0") if golabl.Task.Header.PriceType != "0" { taskPrice = taskPrice + taskMsg.Detail.ShippingCost } // 价格模板计算 taskPrice = tool.BuildPrice(golabl.Task.Header.PriceMod, taskPrice) if taskPrice == 0 { taskMsg.Detail.Error = "任务价格不在价格模板区间内!" return tool.ReturnSuccess(taskMsg) } // 1元 = 100分,价格相差1元以上即 >= 100分 const priceDiffThreshold = 100 // 1元 // 收集所有匹配条件的商品(价格差<1元) var matchedItems []struct { TrilateralId string SkuId string Stock int64 Price int64 PriceDiff int64 // 价格差绝对值 } // 记录不匹配原因 var firstMismatchReason string for _, item := range getGoodsByShopIdAndIsbn.Data { // 解析价格(单位:分) itemPrice, _ := strconv.ParseInt(item.TotalPrice, 10, 64) // 解析库存 itemStock, _ := strconv.ParseInt(item.Stock, 10, 64) // 计算价格差(绝对值) priceDiff := abs(itemPrice - taskPrice) // 价格相差1元以上 if priceDiff >= priceDiffThreshold { if firstMismatchReason == "" { firstMismatchReason = fmt.Sprintf("商品[%s]价格相差超过1元: 任务价格=%d分, 商品价格=%d分, 差价=%d分", item.TrilateralId, taskPrice, itemPrice, priceDiff) } continue } // 价格相差小于1元 → 加入候选列表 matchedItems = append(matchedItems, struct { TrilateralId string SkuId string Stock int64 Price int64 PriceDiff int64 }{ TrilateralId: item.TrilateralId, SkuId: item.SkuId, Stock: itemStock, Price: itemPrice, PriceDiff: priceDiff, }) } // 逻辑: // 1. 所有价格相差1元以上 → 重新发布 // 2. 否则 → 找到价格相差最小的增加库存,如果多个最小差价一样则对第一条增加库存 if len(matchedItems) == 0 { // 所有商品价格相差≥1元 → 重新发布 fmt.Printf("[重新发布] %s\n", firstMismatchReason) task, addGoodsTaskErr := xianYu.AddGoodsTask(taskMsg) if addGoodsTaskErr != nil { return "", addGoodsTaskErr } return task, nil } // 找到价格相差最小的商品,如果多个最小差价一样则对第一条增加库存 minDiff := int64(999999999) var targetItem struct { TrilateralId string SkuId string Stock int64 } for _, item := range matchedItems { // 找到更小的差价,或者差价相等但为第一条 if item.PriceDiff < minDiff || (item.PriceDiff == minDiff && targetItem.TrilateralId == "") { minDiff = item.PriceDiff targetItem.TrilateralId = item.TrilateralId targetItem.SkuId = item.SkuId targetItem.Stock = item.Stock } } // 将 targetItem.TrilateralId 转为 int64 trilateralId, trilateralIdParseIntErr := strconv.ParseInt(targetItem.TrilateralId, 10, 64) if trilateralIdParseIntErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, trilateralIdParseIntErr) } // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, fmt.Errorf("解析应用id与应用秘钥失败: %v", unmarshalErr)) } // 获取商品详情 getGoodsDetailReq := planBTypeXianyu.GoodsDetailReq{ AppId: token.AppId, AppSecret: token.AppSecret, ProductId: trilateralId, } // 发送请求 goodsDetailResp, goodsDetailRespErr := xianYu.getGoodsDetail(getGoodsDetailReq) if goodsDetailRespErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("获取商品详情失败: %v", goodsDetailRespErr)) } var goodDetailRet planBTypeXianyu.GoodDetailRet unmarshalErr = json.Unmarshal([]byte(goodsDetailResp), &goodDetailRet) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("解析商品详情失败: %v", unmarshalErr)) } //检验商品数量 if goodDetailRet.Code == 200 { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("在闲鱼中未查询到该商品id %v %v", trilateralId, goodDetailRet.Msg)) } //增量修改库存 taskMsg.Detail.GoodsId = trilateralId taskMsg.Detail.Stock = taskMsg.Detail.Stock + goodDetailRet.Data.Stock quantity, updateGoodsQuantityErr := executeGoodsUpdateStock(logUuid, taskMsg) if updateGoodsQuantityErr != nil { return "", updateGoodsQuantityErr } //暂停 5秒 time.Sleep(5 * time.Second) return quantity, nil } } // abs 返回绝对值 func abs(x int64) int64 { if x < 0 { return -x } return x } func (xianYu *XianYu) SetGoodsTask() string { return "闲鱼商品修改任务" } // *******************************私有方法************************************ // // 获取省市区 信息 func getProvinceCityDistrict(types int64, id int) (int, int, int, error) { if types == 0 { // 直接指定区域的省市区 //根据区id 获取省、市、区code provinceCode, cityCode, districtCode, getRegionIdErr := service.GetRegionId(strconv.Itoa(id)) if getRegionIdErr != nil { return 0, 0, 0, getRegionIdErr } return provinceCode, cityCode, districtCode, nil } else if types == 1 { // 返回指定省下的随机区 region, getRandomDistrictInProvinceErr := service.GetRandomDistrictInProvince(id) if getRandomDistrictInProvinceErr != nil { return 0, 0, 0, getRandomDistrictInProvinceErr } //根据区id 获取省、市、区code provinceCode, cityCode, districtCode, getRegionIdErr := service.GetRegionId(region["id"]) if getRegionIdErr != nil { return 0, 0, 0, getRegionIdErr } return provinceCode, cityCode, districtCode, nil } else if types == 2 { //在全国返回随机省下的随机区 region, getRandomDistrictErr := service.GetRandomDistrict() if getRandomDistrictErr != nil { return 0, 0, 0, getRandomDistrictErr } //根据区id 获取省、市、区code provinceCode, cityCode, districtCode, getRegionIdErr := service.GetRegionId(region["id"]) if getRegionIdErr != nil { return 0, 0, 0, getRegionIdErr } return provinceCode, cityCode, districtCode, nil } return 0, 0, 0, fmt.Errorf("参数错误") } // 商品新增 // @param token 授权令牌 // @param logUuid 日志ID // @param goodsInfo 添加商品信息 // @return XianYuAddGoodsResponse 商品新增结果 // @return string 添加商品结果json // @return error 错误信息 func addGoods(logUuid string, goodsInfo planBTypeXianyu.GoodsAdd) (planBTypeXianyu.XianYuAddGoodsResponse, string, error) { var goodsAdd planBTypeXianyu.XianYuAddGoodsResponse goodsInfoStr, marshalErr := json.Marshal(goodsInfo) if marshalErr != nil { return goodsAdd, "", marshalErr } goodsAddStr, xianYuGoodsAddErr := golabl.XianYuDll.XianYuGoodsAddNew(string(goodsInfoStr), golabl.Config.FileUrl.XianYuDll) if xianYuGoodsAddErr != nil { return goodsAdd, "", xianYuGoodsAddErr } unmarshalErr := json.Unmarshal([]byte(goodsAddStr), &goodsAdd) if unmarshalErr != nil { return goodsAdd, "", unmarshalErr } if goodsAdd.Code != 0 || goodsAdd.Msg != "OK" { //记录请求日志 addGoodsReqMsg := fmt.Sprintf(` ════════════════════════════════════════════════════════════════ 【闲鱼商品添加请求】 请求ID: %s 时间: %s 参数: %s ════════════════════════════════════════════════════════════════`, logUuid, time.Now().Format("2006-01-02 15:04:05.000"), string(goodsInfoStr)) tool.LoggingMiddleware(logs.LOG_LEVEL_INFO, addGoodsReqMsg) return goodsAdd, goodsAddStr, errors.New("闲鱼 XianYuGoodsAdd 错误:" + goodsAddStr) } return goodsAdd, goodsAddStr, nil } // 商品上架 func launchGoods(logUuid string, launchGoodsInfo planBTypeXianyu.Product) (planBTypeXianyu.XianYuAddGoodsResponse, string, error) { var launchGoods planBTypeXianyu.XianYuAddGoodsResponse launchGoodsInfoStr, marshalErr := json.Marshal(launchGoodsInfo) if marshalErr != nil { return launchGoods, "", marshalErr } launchGoodsStr, xianYuLaunchGoodsAddErr := golabl.XianYuDll.XianYuLaunchGoods(string(launchGoodsInfoStr), golabl.Config.FileUrl.XianYuDll) if xianYuLaunchGoodsAddErr != nil { return launchGoods, "", xianYuLaunchGoodsAddErr } unmarshalErr := json.Unmarshal([]byte(launchGoodsStr), &launchGoods) if unmarshalErr != nil { return launchGoods, "", unmarshalErr } if launchGoods.Code != 0 { //记录请求日志 addGoodsReqMsg := fmt.Sprintf(` ════════════════════════════════════════════════════════════════ 【闲鱼上架商品请求】 请求ID: %s 时间: %s 参数: %s ════════════════════════════════════════════════════════════════`, logUuid, time.Now().Format("2006-01-02 15:04:05.000"), string(launchGoodsInfoStr)) tool.LoggingMiddleware(logs.LOG_LEVEL_INFO, addGoodsReqMsg) return launchGoods, launchGoodsStr, errors.New("闲鱼 XianYuLaunchGoods 错误:" + launchGoodsStr) } return launchGoods, launchGoodsStr, nil } // phaseOneGoodsOnlyCount 第一阶段只获取商品总数,不写入wait队列 func (xianYu *XianYu) phaseOneGoodsOnlyCount(token planBTypeXianyu.Token, pageSize int, maxPage int) error { for page := 1; page <= maxPage; page++ { xianYuListReq := planBTypeXianyu.GoodsListReq{ AppId: token.AppId, AppSecret: token.AppSecret, UpdateTime: nil, // 不传入时间,获取所有商品 ProductStatus: 22, PageNo: page, PageSize: pageSize, } listJson, err := xianYu.sendGoodsListRequest(xianYuListReq) if err != nil { return fmt.Errorf("获取商品列表失败,页码: %d, 错误: %v", page, err) } var list planBTypeXianyu.GoodsListRet err = json.Unmarshal([]byte(listJson), &list) if err != nil { return fmt.Errorf("解析响应失败,页码: %d, 错误: %v", page, err) } if list.Code != 0 { return fmt.Errorf("获取商品列表失败 code=%d, msg=%s", list.Code, list.Msg) } // 更新header进度总数(第一页) if page == 1 { fmt.Println("总数: ", strconv.Itoa(list.Data.Count)) if updateTaskHeaderErr := service.SetTaskCount(strconv.Itoa(list.Data.Count)); updateTaskHeaderErr != nil { return updateTaskHeaderErr } // 获取到总数后即可退出,不需要继续拉取 fmt.Println("第一阶段完成,已获取商品总数,不写入wait队列") break } } return nil } // phaseTwoGoods 第二阶段拉取商品信息(按时间范围分批) // 修改:结束时间固定为当前时间,开始时间为当前时间往前推30天 func (xianYu *XianYu) phaseTwoGoods(token planBTypeXianyu.Token, pageSize int, totalFetched *int, lastUpdateTime *int64, maxRecordsPerRange int) error { // 第二阶段:以当前时间为结束时间,开始时间为当前时间往前推30天 // 注意:lastUpdateTime 参数在此版本中不再使用,改为固定从"当前时间-30天"开始 now := time.Now().Unix() endTime := now // 结束时间固定为当前时间 currentUpdateTimeFrom := now - 30*24*60*60 // 开始时间 = 当前时间 - 30天 fmt.Printf("第二阶段开始,开始时间: %d (%s), 结束时间: %d (%s) [当前时间]", currentUpdateTimeFrom, time.Unix(currentUpdateTimeFrom, 0).Format("2006-01-02 15:04:05"), endTime, time.Unix(endTime, 0).Format("2006-01-02 15:04:05")) if currentUpdateTimeFrom > 0 { //currentUpdateTimeFrom := *lastUpdateTime maxLoopCount := 100 // 最大循环次数保护 loopCount := 0 var currentUpdateTimeEnd int64 // 声明在循环外部,避免每次迭代重置 for loopCount < maxLoopCount { loopCount++ // 检查开始时间是否已超过当前时间 if currentUpdateTimeFrom > time.Now().Unix() { fmt.Printf("开始时间 %d 已超过当前时间,停止获取\n", currentUpdateTimeFrom) break } // 设置结束时间:首次循环用当前时间,后续循环由pageExceededThreshold或>=10000条件块设置 if loopCount == 1 { currentUpdateTimeEnd = endTime // 首次循环赋值 } // 检查结束时间是否已超过半年(180天),超过则停止获取 halfYearAgo := time.Now().Unix() - 180*24*60*60 fmt.Printf("[调试] loopCount=%d, currentUpdateTimeEnd=%d (%s), halfYearAgo=%d (%s), = maxLoopCount { fmt.Printf("达到最大循环次数 %d,强制退出\n", maxLoopCount) break } fmt.Printf("开始获取时间范围: %d (%s) 到 %d (%s)\n", currentUpdateTimeFrom, time.Unix(currentUpdateTimeFrom, 0).Format("2006-01-02 15:04:05"), currentUpdateTimeEnd, time.Unix(currentUpdateTimeEnd, 0).Format("2006-01-02 15:04:05")) currentPage := 1 batchGoodsCount := 0 lastItemUpdateTime := int64(0) pageExceededThreshold := false // 标记是否页码超过阈值 // 在当前时间范围内分页获取数据 for { // 检查当前页码是否超过100 if currentPage > 100 { fmt.Printf("警告:当前页码 %d 已超过100,将使用上一次的结束时间作为新的开始时间\n", currentPage) pageExceededThreshold = true break } xianYuListReq := planBTypeXianyu.GoodsListReq{ AppId: token.AppId, AppSecret: token.AppSecret, UpdateTime: []int64{currentUpdateTimeFrom, currentUpdateTimeEnd}, ProductStatus: 22, PageNo: currentPage, PageSize: pageSize, } listJson, err := xianYu.sendGoodsListRequest(xianYuListReq) if err != nil { return fmt.Errorf("获取商品列表失败(时间范围),页码: %d, 错误: %v", currentPage, err) } var list planBTypeXianyu.GoodsListRet err = json.Unmarshal([]byte(listJson), &list) if err != nil { return fmt.Errorf("解析响应失败(时间范围),页码: %d, 错误: %v", currentPage, err) } if list.Code == 100001 { fmt.Println("大于半年了,结束查询111") fmt.Println("开始时间", time.Unix(currentUpdateTimeFrom, 0).Format("2006-01-02 15:04:05")) fmt.Println("结束时间", time.Unix(currentUpdateTimeEnd, 0).Format("2006-01-02 15:04:05")) break } if list.Code != 0 { return fmt.Errorf("获取商品列表失败 code=%d, msg=%s", list.Code, list.Msg) } // 如果当前页没有数据 if len(list.Data.List) == 0 { // 如果当前页是第一页且没有数据,说明整个时间范围都没有数据 if currentPage == 1 { fmt.Printf("时间范围 %d - %d 内无数据\n", currentUpdateTimeFrom, currentUpdateTimeEnd) break } // 当前页没有数据,但前面有数据,说明当前时间范围的数据已取完 fmt.Printf("当前时间范围数据已取完,共获取 %d 条数据\n", batchGoodsCount) break } // 有数据 // 收集商品数据并统计 for _, goods := range list.Data.List { *totalFetched++ // 获取商品详情并写入数据库 err = xianYu.processGoodsDetail(goods, token) if err != nil { fmt.Printf("处理商品 %s 失败: %v\n", goods.ProductID, err) continue } //拉取后暂停0.01秒 time.Sleep(10 * time.Millisecond) } batchGoodsCount += len(list.Data.List) // 记录最后一条商品的更新时间 lastItem := list.Data.List[len(list.Data.List)-1] lastItemUpdateTime = lastItem.UpdateTime fmt.Printf("第二阶段 - 当前时间范围已获取: %d 条,累计总数: %d,当前页码: %d,最后商品时间: %d (%s)\n", batchGoodsCount, *totalFetched, currentPage, lastItemUpdateTime, time.Unix(lastItemUpdateTime, 0).Format("2006-01-02 15:04:05")) // 更新进度 if getTaskFooterErr := service.GetTaskFooter(); getTaskFooterErr != nil { return getTaskFooterErr } processed := int64(len(list.Data.List)) if updateTaskProgressErr := tool.UpdateTaskProgress(processed); updateTaskProgressErr != nil { return updateTaskProgressErr } // 判断是否继续下一页 // 如果返回的数据少于 pageSize,说明没有下一页了 if len(list.Data.List) < pageSize { fmt.Printf("当前页数据不足 %d 条,当前时间范围数据已取完\n", pageSize) break } currentPage++ } // 不足10000条时,以上一轮的开始时间作为本次的结束时间 // 本次结束时间 = 上一轮开始时间,本次开始时间 = 本次结束时间 - 30天 if pageExceededThreshold { if lastItemUpdateTime > 0 { currentUpdateTimeEnd = lastItemUpdateTime currentUpdateTimeFrom = currentUpdateTimeEnd - 30*24*60*60 fmt.Printf("页码超过100,使用最后一页最后一条时间 %d (%s) 作为新的结束时间,开始时间: %d (%s)\n", currentUpdateTimeEnd, time.Unix(currentUpdateTimeEnd, 0).Format("2006-01-02 15:04:05"), currentUpdateTimeFrom, time.Unix(currentUpdateTimeFrom, 0).Format("2006-01-02 15:04:05")) } else { currentUpdateTimeEnd = currentUpdateTimeFrom currentUpdateTimeFrom = currentUpdateTimeFrom - 30*24*60*60 fmt.Printf("页码超过100但无数据,回退30天\n") } continue } if batchGoodsCount == 0 { currentUpdateTimeEnd = currentUpdateTimeFrom currentUpdateTimeFrom = currentUpdateTimeFrom - 30*24*60*60 fmt.Printf("未获取到数据,时间窗口往前移动30天\n") } else { currentUpdateTimeEnd = currentUpdateTimeFrom currentUpdateTimeFrom = currentUpdateTimeEnd - 30*24*60*60 fmt.Printf("获取 %d 条(<10000),以上一轮开始时间作为结束时间,开始时间: %d (%s)\n", batchGoodsCount, currentUpdateTimeFrom, time.Unix(currentUpdateTimeFrom, 0).Format("2006-01-02 15:04:05")) } // 检查新的开始时间是否已超过当前时间 if currentUpdateTimeFrom > time.Now().Unix() { fmt.Printf("开始时间 %d (%s) 已超过当前时间 %d (%s),停止获取\n", currentUpdateTimeFrom, time.Unix(currentUpdateTimeFrom, 0).Format("2006-01-02 15:04:05"), time.Now().Unix(), time.Now().Format("2006-01-02 15:04:05")) break } } if loopCount >= maxLoopCount { fmt.Printf("警告:已达到最大循环次数 %d,强制退出\n", maxLoopCount) } } return nil } // deduplicateToBodyOver 拉取任务读取body_wait去重复后写入到body_over中 func (xianYu *XianYu) deduplicateToBodyOver(duplicateCount *int, uniqueCount *int) error { page := 1 pageSize := 100 // 按店铺存储去重后的商品数据 shopGoodsMap := make(map[string][]planBTypeXianyu.GoodsDetailRet) // 修改:使用复合key(店铺名+商品ID)进行去重,避免同一店铺重复相同商品 processedKeys := make(map[string]bool) // 在循环前删除 body_over与body_backup,避免重复写入 deleteTaskBodyOverErr := service.DeleteTaskBodyOver() if deleteTaskBodyOverErr != nil { return deleteTaskBodyOverErr } deleteTaskBodyBackupErr := service.DeleteTaskBodyBackup() if deleteTaskBodyBackupErr != nil { return deleteTaskBodyBackupErr } num := 0 // 获取body_wait总数量 bodyWaitCount, getTaskBodyWaitCountErr := service.GetTaskBodyWaitCount() if getTaskBodyWaitCountErr != nil { return getTaskBodyWaitCountErr } pageTotal := (bodyWaitCount + int64(pageSize) - 1) / int64(pageSize) // 调试计数器 debugCount := 0 for { list, getTaskBodyOverListErr := service.GetTaskBodyWaitList(page, pageSize) if getTaskBodyOverListErr != nil { return getTaskBodyOverListErr } if len(list) <= 0 { // 没有数据,结束循环 break } for _, v := range list { // 解析v到结构体 goods := planAType.TaskBody{} jsonUnmarshalErr := json.Unmarshal([]byte(v), &goods) if jsonUnmarshalErr != nil { return fmt.Errorf("将json转为结构体失败: %v\n", jsonUnmarshalErr) } // 解析商品详情获取真实的商品ID var goodsItem planBTypeXianyu.GoodsDetailRet jsonUnmarshalErr = json.Unmarshal([]byte(goods.Detail.Error), &goodsItem) if jsonUnmarshalErr != nil { return fmt.Errorf("将json转为结构体失败: %v\n", jsonUnmarshalErr) } //提取 Isbn if goodsItem.BookData.ISBN == "" { goodsItem.BookData.ISBN = tool.ExtractISBN978(goodsItem.Title) } //Isbn 为空则跳过 if goodsItem.BookData.ISBN == "" { fmt.Println("####################商品无法获取 Isbn,跳过:", goodsItem.Title) continue } // 使用 ProductID 作为唯一标识(int64类型) goodsId := goodsItem.ProductID // 获取闲鱼会员名 username := "" if len(goodsItem.PublishShop) > 0 { username = goodsItem.PublishShop[0].UserName } // 修改:使用店铺名+商品ID作为复合key进行去重 uniqueKey := fmt.Sprintf("%s_%d", username, goodsId) // 调试:打印前10条数据的ID if debugCount < 10 { fmt.Printf("[去重调试] 第%d条 - 商品ID: %d, 店铺: %s, 复合Key: %s, Title: %s\n", debugCount+1, goodsId, username, uniqueKey, goodsItem.Title) debugCount++ } if !processedKeys[uniqueKey] { // 标记为已处理 processedKeys[uniqueKey] = true *uniqueCount++ // 按店铺暂存数据 shopGoodsMap[username] = append(shopGoodsMap[username], goodsItem) // 写入到body_over goods.Detail.Status = 1 addTaskToBodyOverErr := service.AddTaskToBodyOver(goods, []string{"body_over", "body_backup"}) if addTaskToBodyOverErr != nil { return addTaskToBodyOverErr } // 检查每个店铺的商品数量,达到batchSize则推送 if len(shopGoodsMap[username]) >= pageSize { fmt.Println("推送 username ", username, " 长度 ", len(shopGoodsMap[username])) _, err := pushShopGoodsData(username, shopGoodsMap[username], int64(page), pageTotal, &num) if err != nil { return err } // 清空该店铺的数据 shopGoodsMap[username] = []planBTypeXianyu.GoodsDetailRet{} } } else { // 重复数据 计次 *duplicateCount++ // 调试:打印前10条重复数据 if *duplicateCount <= 10 { fmt.Printf("[去重调试] 发现重复商品: 店铺=%s, 商品ID=%d, 复合Key=%s\n", username, goodsId, uniqueKey) } } } page++ // 更新进度 if getTaskFooterErr := service.GetTaskFooter(); getTaskFooterErr != nil { return getTaskFooterErr } con := int64(len(list)) if con >= golabl.Task.Footer.TaskCountTrue { con = golabl.Task.Footer.TaskCountTrue - con } if updateTaskProgressErr := tool.UpdateTaskProgress(con); updateTaskProgressErr != nil { return updateTaskProgressErr } // 暂停1秒 time.Sleep(1 * time.Second) } // 循环结束后,推送所有店铺剩余的数据(不足batchSize的部分) for shopId, goodsList := range shopGoodsMap { if len(goodsList) > 0 { _, err := pushShopGoodsData(shopId, goodsList, pageTotal, pageTotal, &num) if err != nil { return err } //打印每个店铺的商品数量 fmt.Println("推送 username ", shopId, " 长度 ", len(goodsList)) } } // 删除body_wait deleteTaskBodyWaitErr := service.DeleteTaskBodyWait() if deleteTaskBodyWaitErr != nil { return deleteTaskBodyWaitErr } fmt.Printf("[去重完成] 总处理: %d, 唯一: %d, 重复: %d\n", *uniqueCount+*duplicateCount, *uniqueCount, *duplicateCount) return nil } // pushShopGoodsData 推送单个店铺的商品数据到接口 func pushShopGoodsData(username string, goodsList []planBTypeXianyu.GoodsDetailRet, currentPage int64, totalPage int64, totalCount *int) (string, error) { if len(goodsList) == 0 { return "", nil } // 将获取的数据推送写入店铺商品数据接口 ret, retStr, writeXianyuGoodsDataErr := writeXianyuGoodsData(goodsList, username, int(currentPage), totalPage) if writeXianyuGoodsDataErr != nil { return "", writeXianyuGoodsDataErr } if ret.Code != "200" { return retStr, fmt.Errorf("添加商品失败 %v", retStr) } *totalCount = *totalCount + len(goodsList) return retStr, nil } // processGoodsDetail 处理单个商品的详情并写入数据库(修改版) func (xianYu *XianYu) processGoodsDetail(goodsItem planBTypeXianyu.GoodsListRetProduct, token planBTypeXianyu.Token) error { // 获取商品详情 xianYuDetailReq := planBTypeXianyu.GoodsDetailReq{ AppId: token.AppId, AppSecret: token.AppSecret, ProductId: goodsItem.ProductID, } detailJson, err := xianYu.getGoodsDetail(xianYuDetailReq) if err != nil { return err } var goodDetailRet planBTypeXianyu.GoodDetailRet err = json.Unmarshal([]byte(detailJson), &goodDetailRet) if err != nil { return err } // 检查返回码 if goodDetailRet.Code != 0 { return fmt.Errorf("获取商品详情失败 code=%d, msg=%s", goodDetailRet.Code, goodDetailRet.Msg) } // 将内容转为 json detailDataJson, marshalErr := json.Marshal(goodDetailRet.Data) if marshalErr != nil { return marshalErr } // 调试:打印商品ID信息 //fmt.Printf("[商品处理] ProductID: %v, 详情ProductID: %d, Title: %s\n", // goodsItem.ProductID, goodDetailRet.Data.ProductID, goodDetailRet.Data.Title) // 构建任务数据 bodyWait := planAType.TaskBody{ BookInfo: planAType.BookInfo{ Isbn: goodDetailRet.Data.BookData.ISBN, BookName: goodDetailRet.Data.Title, Author: goodDetailRet.Data.BookData.Author, Publishing: goodDetailRet.Data.BookData.Publisher, PublicationDate: "", Binding: "", PagesCount: 0, WordsCount: 0, Format: 0, Price: goodsItem.UpdateTime, // 使用UpdateTime作为时间戳 }, Detail: planAType.TaskDetail{ Error: string(detailDataJson), GoodsId: goodDetailRet.Data.ProductID, // 使用详情中的ProductID Stock: goodDetailRet.Data.Stock, }, } // 验证商品 ID不为空 if bodyWait.Detail.GoodsId == 0 { fmt.Printf("[警告] 商品 %s 的GoodsId为0\n", goodsItem.ProductID) } // 写入数据库 bodyWaitJson, err := json.Marshal(bodyWait) if err != nil { return fmt.Errorf("将bodyWait转为json失败: %v", err) } return service.AddTaskToBodyWait(string(bodyWaitJson)) } // 发送商品列表请求 func (xianYu *XianYu) sendGoodsListRequest(req planBTypeXianyu.GoodsListReq) (string, error) { reqJson, marshalErr := json.Marshal(req) if marshalErr != nil { return "", marshalErr } listJson, err := golabl.XianYuDll.XianYuGetGoodsList(string(reqJson), golabl.Config.FileUrl.XianYuDll) if err != nil { return "", err } return listJson, nil } // 获取商品详情 func (xianYu *XianYu) getGoodsDetail(req planBTypeXianyu.GoodsDetailReq) (string, error) { reqJson, marshalErr := json.Marshal(req) if marshalErr != nil { return "", marshalErr } detailJson, err := golabl.XianYuDll.XianYuGetGoodsDetail(string(reqJson), golabl.Config.FileUrl.XianYuDll) if err != nil { return "", err } return detailJson, nil } // writeXianyuGoodsData 写入商品数据 // @param goodsListStr 商品列表 // @param username 闲鱼会员名 // @param page 当前页 // @param pageTotal 总页数 // @return error 错误信息 func writeXianyuGoodsData(goodsListStr []planBTypeXianyu.GoodsDetailRet, username string, page int, pageTotal int64) (planBType.AsyncTaskResponse, string, error) { var ret planBType.AsyncTaskResponse marshal, marshalErr := json.Marshal(goodsListStr) if marshalErr != nil { return ret, "", marshalErr } params := map[string]string{ "taskId": golabl.Task.TaskId, "shopId": username, "goodsListStr": string(marshal), "allNum": strconv.FormatInt(pageTotal, 10), "num": strconv.Itoa(page), } // 将 params 转为 json paramsJson, marshalErr := json.Marshal(params) if marshalErr != nil { return ret, "", marshalErr } fmt.Println(string(paramsJson)) retStr, submitFormDataErr := tool.SubmitFormData(golabl.Config.FileUrl.XianYuAddGoodsUrl, params) if submitFormDataErr != nil { return ret, retStr, submitFormDataErr } unmarshalErr := json.Unmarshal([]byte(retStr), &ret) if unmarshalErr != nil { return ret, retStr, unmarshalErr } return ret, retStr, nil } // executeGoodsLaunch 上架商品 // @param logUuid 日志ID // @param taskMsg 任务内容 // @return error 错误信息 func executeGoodsLaunch(logUuid string, taskMsg planAType.TaskBody) (string, error) { // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("解析应用id与应用秘钥 taskHeader.ShopMsg.Token = %v %w", golabl.Task.Header.ShopMsg.Token, unmarshalErr)) } // 上架商品 launchGoodsInfo := planBTypeXianyu.Product{ AppId: token.AppId, AppSecret: token.AppSecret, ProductID: taskMsg.Detail.GoodsId, SpecifyPublishTime: "", UserName: []string{token.Username}, } //转为json jsonData, marshalErr := json.Marshal(launchGoodsInfo) if marshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr) } var launchGoods planBTypeXianyu.XianYuAddGoodsResponse launchGoodsStr, xianYuLaunchGoodsAddErr := golabl.XianYuDll.XianYuLaunchGoods(string(jsonData), golabl.Config.FileUrl.XianYuDll) if xianYuLaunchGoodsAddErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, xianYuLaunchGoodsAddErr) } unmarshalErr = json.Unmarshal([]byte(launchGoodsStr), &launchGoods) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr) } if launchGoods.Code != 0 { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("上架商品失败 %s", launchGoods.Msg)) } return tool.ReturnSuccess(taskMsg) } // executeGoodsDownShelf 下架商品 // @param logUuid 日志ID // @param taskMsg 任务内容 // @return error 错误信息 func executeGoodsDownShelf(logUuid string, taskMsg planAType.TaskBody) (string, error) { var downShelf planBTypeXianyu.DownShelf // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("解析应用id与应用秘钥 taskHeader.ShopMsg.Token = %v %w", golabl.Task.Header.ShopMsg.Token, unmarshalErr)) } downShelf.AppId = token.AppId downShelf.AppSecret = token.AppSecret downShelf.ProductID = taskMsg.Detail.GoodsId //转为json jsonData, marshalErr := json.Marshal(downShelf) if marshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr) } shelf, xianYuExecuteGoodsDownShelfErr := golabl.XianYuDll.XianYuExecuteGoodsDownShelf(string(jsonData), golabl.Config.FileUrl.XianYuDll) if xianYuExecuteGoodsDownShelfErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, xianYuExecuteGoodsDownShelfErr) } var downShelfRes planBTypeXianyu.XianYuAddGoodsResponse unmarshalErr = json.Unmarshal([]byte(shelf), &downShelfRes) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr) } if downShelfRes.Code != 0 { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("下架商品失败 %s", downShelfRes.Msg)) } return tool.ReturnSuccess(taskMsg) } // executeGoodsUpdateStock 修改库存 func executeGoodsUpdateStock(logUuid string, taskMsg planAType.TaskBody) (string, error) { var updateStock planBTypeXianyu.UpdateStock // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("解析应用id与应用秘钥 taskHeader.ShopMsg.Token = %v %w", golabl.Task.Header.ShopMsg.Token, unmarshalErr)) } updateStock.AppId = token.AppId updateStock.AppSecret = token.AppSecret updateStock.ProductID = taskMsg.Detail.GoodsId updateStock.Stock = taskMsg.Detail.Stock //转为json jsonData, marshalErr := json.Marshal(updateStock) if marshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr) } updateStockStr, xianYuExecuteGoodsUpdateStockErr := golabl.XianYuDll.XianYuExecuteGoodsUpdateStock(string(jsonData), golabl.Config.FileUrl.XianYuDll) if xianYuExecuteGoodsUpdateStockErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, xianYuExecuteGoodsUpdateStockErr) } var updateStockRes planBTypeXianyu.XianYuAddGoodsResponse unmarshalErr = json.Unmarshal([]byte(updateStockStr), &updateStockRes) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr) } if updateStockRes.Code != 0 { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("修改商库存品失败 %s", updateStockRes.Msg)) } taskMsg.Detail.Error = "增加库存成功!" return tool.ReturnSuccess(taskMsg) } // 修改价格 func executeGoodsUpdatePrice(logUuid string, taskMsg planAType.TaskBody) (string, error) { var updatePrice planBTypeXianyu.UpdatePrice // 价格0 不能发布 if taskMsg.Detail.Price == 0 { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 价格不能为0")) } // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("解析应用id与应用秘钥 taskHeader.ShopMsg.Token = %v %w", golabl.Task.Header.ShopMsg.Token, unmarshalErr)) } updatePrice.AppId = token.AppId updatePrice.AppSecret = token.AppSecret updatePrice.ProductID = taskMsg.Detail.GoodsId updatePrice.Price = taskMsg.Detail.Price updatePrice.OriginalPrice = tool.BuildGoodsPrice(taskMsg.Detail.Price) //转为json jsonData, marshalErr := json.Marshal(updatePrice) if marshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr) } updatePriceStr, xianYuExecuteGoodsUpdatePrice := golabl.XianYuDll.XianYuExecuteGoodsUpdatePrice(string(jsonData), golabl.Config.FileUrl.XianYuDll) if xianYuExecuteGoodsUpdatePrice != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, xianYuExecuteGoodsUpdatePrice) } var updatePriceRes planBTypeXianyu.XianYuAddGoodsResponse unmarshalErr = json.Unmarshal([]byte(updatePriceStr), &updatePriceRes) if unmarshalErr != nil { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr) } if updatePriceRes.Code != 0 { return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("修改商品价格失败 %s", updatePriceRes.Msg)) } return tool.ReturnSuccess(taskMsg) } // 闲鱼发布 func publishGoods(logUuid string, taskMsg planAType.TaskBody) (planAType.TaskBody, error) { // 价格不能小于0 if taskMsg.Detail.Price <= 0 { return taskMsg, fmt.Errorf("价格不能小于等于0") } //获取出版社信息并解析 if getPublishingErr := service.GetPublishingVid(&taskMsg); getPublishingErr != nil { return taskMsg, fmt.Errorf("获取出版社信息失败-原因来自:%v", getPublishingErr) } //违规词处理 if golabl.Config.Server.Filter == 1 { //开启违规词处理 if taskMsgErr := tool.FilterWord(&taskMsg); taskMsgErr != nil { return taskMsg, taskMsgErr } } // 构建参数 var goodsAdd planBTypeXianyu.GoodsAdd // 解析应用 id与应用秘钥 var token planBTypeXianyu.Token unmarshalErr := json.Unmarshal([]byte(golabl.Task.Header.ShopMsg.Token), &token) if unmarshalErr != nil { return taskMsg, fmt.Errorf("解析应用id与应用秘钥 taskHeader.ShopMsg.Token = %v %w", golabl.Task.Header.ShopMsg.Token, unmarshalErr) } // 应用 ID goodsAdd.AppId = token.AppId // 应用密钥 goodsAdd.AppSecret = token.AppSecret // token goodsAdd.Token = "" // API 使用的店铺ID goodsAdd.ApiShopId = 0 // 平台类型 goodsAdd.TypePlatform = 4 // 店铺 ID goodsAdd.ShopId = 0 // 店铺 Token goodsAdd.ShopToken = "" // 店铺名称 goodsAdd.ShopName = "" // 发货省,格式为省级行政区划代码(如210000代表辽宁省) provinceCode, cityCode, districtCode, getProvinceCityDistrictErr := getProvinceCityDistrict(0, 20) if getProvinceCityDistrictErr != nil { return taskMsg, fmt.Errorf("获取省、市、区信息失败: %v", getProvinceCityDistrictErr) } goodsAdd.Province = provinceCode // 发货市,格式为市级行政区划代码(如210100代表沈阳市) goodsAdd.City = cityCode // 发货区,格式为区级行政区划代码(如210101代表和平区) goodsAdd.District = districtCode // 商品类型 goodsAdd.TypeGoods = "" // 分类类型 goodsAdd.TypeClass = "" // 类目 ID spBizType := int32(24) //默认 图书类目 goodsAdd.CatIds = "c3c6e8d1d63c0618b108d382c4e6ea42" //默认类目ID(文学,小说) if golabl.Task.Header.ShopMsg.PublishType == "1" { spBizType = 99 //其他 goodsAdd.CatIds = golabl.Task.Header.ShopMsg.CategoryId //根据用户选择 } isbn := taskMsg.BookInfo.Isbn // 如果isbn是678开头的 if strings.HasPrefix(taskMsg.BookInfo.Isbn, "678") { //如果类目ID为空,则使用默认类目ID(其他闲置) // goodsAdd.CatIds = "86cddebb2de0815c267e0a01017d9f44" //资料册 // goodsAdd.CatIds = "2dfa3034d88aedcc1921b9e373cead75" //期刊/杂志 goodsAdd.CatIds = "8bd8d9724880b84d28d88a08a19453dc" //学习笔记(无ISBN可以发布成功)OK spBizType = 99 //(其他) // isbn = "" } goodsAdd.SpBizType = spBizType if len(taskMsg.BookInfo.ImageObject.CarouselUrlArray) == 0 { // 无图片信息 isbn计次 setNoImgCountErr := service.SetNoImgCount(taskMsg.BookInfo.Isbn) if setNoImgCountErr != nil { return taskMsg, fmt.Errorf("无图片信息isbn计次错误 isbn %v %v", taskMsg.BookInfo.Isbn, setNoImgCountErr.Error()) } return taskMsg, fmt.Errorf("缺少轮播图") } // 构建详情图 contentImgs := tool.BuildDetailGallery(golabl.Task.Header.ShopMsg.GoodsDetailFirstImgUrlArray, golabl.Task.Header.ShopMsg.GoodsDetailLastImgUrlArray, taskMsg.BookInfo.ImageObject.DetailUrlObject, taskMsg.BookInfo.ImageObject.CarouselUrlArray[0]) oldCarouselUrlArray := append([]string{}, taskMsg.BookInfo.ImageObject.CarouselUrlArray...) //原始轮播图,用于后续处理,不会被打上水印 //存在水印图片,则打水印 if golabl.Task.Header.ShopMsg.WatermarkImgUrl != "" { //获取水印图片 watermarkImgUrl, watermarkImgErr := tool.GetWatermarkImg() if watermarkImgErr != nil { return taskMsg, fmt.Errorf("1图片打水印失败 %v", fmt.Errorf("获取水印图片失败 %v", watermarkImgErr)) } //打水印 watermarkFromURLExsBase64Arr, watermarkFromURLExsErr := tool.AddWatermarkFromURLExs(taskMsg.BookInfo.ImageObject.CarouselUrlArray, watermarkImgUrl, golabl.Task.Header.ShopMsg.WatermarkPosition) if watermarkFromURLExsErr != nil { return taskMsg, fmt.Errorf("2图片打水印失败 %v", watermarkFromURLExsErr) } //图片上传到图片空间 toMinIo, uploadToMinIoErr := tool.UploadToMinIo(watermarkFromURLExsBase64Arr) if uploadToMinIoErr != nil { return taskMsg, fmt.Errorf("图片上传到图片空间失败 %v", uploadToMinIoErr) } //将上传的图片替换到商品轮播图中 for i := 0; i < len(toMinIo); i++ { taskMsg.BookInfo.ImageObject.CarouselUrlArray[i] = toMinIo[i] } } // 构建主图(轮播图) //refactorCarouselGallery := tool.BuildCarouselGalleryOld(golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray, taskMsg.BookInfo.ImageObject.CarouselUrlArray) refactorCarouselGallery := tool.BuildCarouselGallery(golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray, oldCarouselUrlArray, taskMsg.BookInfo.ImageObject.CarouselUrlArray, golabl.Task.Header.ShopMsg.WatermarkPosition) // 如果轮播图没有图片,并且是优先官图,则使用默认图片 if len(refactorCarouselGallery) == 0 && golabl.Task.Header.ImgType == 3 && taskMsg.BookInfo.ImageObject.DefaultImageUrl != "" { refactorCarouselGallery = append(refactorCarouselGallery, taskMsg.BookInfo.ImageObject.DefaultImageUrl) } if len(taskMsg.BookInfo.ImageObject.DetailUrlObject.LiveShootingUrl) == 0 && len(refactorCarouselGallery) > 0 { taskMsg.BookInfo.ImageObject.DetailUrlObject.LiveShootingUrl = []string{refactorCarouselGallery[0]} } if len(refactorCarouselGallery) == 0 { return taskMsg, fmt.Errorf("缺少构造轮播图图片-未提交 isbn %v", taskMsg.BookInfo.Isbn) } //构建商品名称 title := tool.BuildGoodsName( golabl.Task.Header.ShopMsg.GoodsNamePrefix, // 商品名称前缀 golabl.Task.Header.ShopMsg.GoodsNameSuffix, // 商品名称后缀 golabl.Task.Header.ShopMsg.TitleConsistOf, // 标题组成 golabl.Task.Header.ShopMsg.SpaceCharacter, // 间隔符 taskMsg.BookInfo) // 图书信息 taskMsg.Detail.GoodsName = title // 构建商品信息 content := taskMsg.BookInfo.BookName + " " + taskMsg.BookInfo.Isbn + " " + taskMsg.BookInfo.Author + " " + taskMsg.BookInfo.Publishing content = content + "\n" + golabl.Task.Header.ShopMsg.ShopContext // 店铺信息 goodsAdd.Shop = []planBTypeXianyu.ShopInfo{ { UserName: token.Username, Province: provinceCode, City: cityCode, District: districtCode, Title: title, Content: content, MainImgs: refactorCarouselGallery, ContentImgs: contentImgs, }, } // 成色 goodsAdd.StuffStatus = taskMsg.Detail.Condition if goodsAdd.StuffStatus == 0 { goodsAdd.StuffStatus = 90 } //库存 if taskMsg.Detail.Stock == 0 && (golabl.Task.Header.TaskType == 1 || golabl.Task.Header.TaskType == 2 || golabl.Task.Header.TaskType == 6) { //如果库存为0 则给默认库存 taskMsg.Detail.Stock = golabl.Task.Header.ShopMsg.DefStock } else { if taskMsg.Detail.Stock == 0 && golabl.Task.Header.TaskType == 8 { return taskMsg, fmt.Errorf("库存不能为0") } } url := "http://127.0.0.1:8095" tool.HttpGetRequest(url) //价格 + 运费 if golabl.Task.Header.PriceType != "0" { taskMsg.Detail.Price = taskMsg.Detail.Price + taskMsg.Detail.ShippingCost } //构建销售价格 price := tool.BuildPrice(golabl.Task.Header.PriceMod, taskMsg.Detail.Price) if price == 0 { return taskMsg, fmt.Errorf("不在价格区间内 isbn:%v", taskMsg.BookInfo.Isbn) } taskMsg.Detail.Price = price //构建定价 taskMsgBookInfoPrice := tool.BuildGoodsPrice(price) goodsAdd.ItemBizType = 2 goodsAdd.SpBizType = spBizType goodsAdd.Price = taskMsg.Detail.Price goodsAdd.Stock = taskMsg.Detail.Stock goodsAdd.OriginalPrice = taskMsgBookInfoPrice // 图书类商品信息 if strings.HasPrefix(taskMsg.BookInfo.Isbn, "678") { } else { goodsAdd.BookData = []planBTypeXianyu.BookInfo{ { ISBN: isbn, Title: title, Author: taskMsg.BookInfo.Author, Publisher: taskMsg.Publishing.Value, }, } } // 构建商品编码 outGoodsId := "" if taskMsg.Detail.OutGoodsId != "" { outGoodsId = taskMsg.Detail.OutGoodsId } else { outGoodsId = taskMsg.BookInfo.Isbn } //货号 skuCode := "" if taskMsg.Detail.SkuCode != "" { skuCode = taskMsg.Detail.SkuCode } else { skuCode = outGoodsId } goodsAdd.OuterId = skuCode goodsAdd.SkuItems = []planBTypeXianyu.SkuItems{ { OuterID: outGoodsId, Price: taskMsg.Detail.Price, SkuText: taskMsg.BookInfo.BookName, Stock: taskMsg.Detail.Stock, }, } // 闲鱼批次商品 KEY goodsAdd.ItemKey = strconv.FormatInt(time.Now().Unix(), 10) // 新增商品 goodsAddRet, goodsAddStr, err := addGoods(logUuid, goodsAdd) if err != nil { return taskMsg, fmt.Errorf("商品提交 %v", err) } if len(goodsAddRet.Data.Success) <= 0 { return taskMsg, fmt.Errorf("新增商品失败 %v 闲鱼返回信息 %v", err, goodsAddStr) } // 上架商品 launchGoodsInfo := planBTypeXianyu.Product{ AppId: token.AppId, AppSecret: token.AppSecret, Token: "", NotifyURL: "", ProductID: goodsAddRet.Data.Success[0].ProductID, SpecifyPublishTime: "", UserName: []string{token.Username}, } //延迟1分钟 time.Sleep(time.Minute) //商品上架 if taskMsg.Detail.IsOnsale == 0 { _, _, err = launchGoods(logUuid, launchGoodsInfo) if err != nil { return taskMsg, fmt.Errorf("商品提交 %v", err) } } taskMsg.Detail.GoodsId = goodsAddRet.Data.Success[0].ProductID taskMsg.Detail.OutGoodsId = outGoodsId taskMsg.Detail.Img = refactorCarouselGallery[0] return taskMsg, nil }