daShangDao_planA/planB/dispatcher/pinduoduo/pinduoduo.go

1716 lines
64 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pinduoduo
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"planA/planB/initialization/golabl"
"planA/planB/logic"
"planA/planB/modules/logs"
"planA/planB/service"
"planA/planB/tool"
planBTypeModules "planA/planB/type/modules"
planBTypePinduoduo "planA/planB/type/pinduoduo"
planAType "planA/type"
"strconv"
"strings"
"time"
)
type PinDuoDuo struct {
}
// NewPinDuoDuo 创建拼多多平台
// @return *PinDuoDuo
func NewPinDuoDuo() *PinDuoDuo {
return &PinDuoDuo{}
}
// AddGoodsTask 添加商品
// @param taskMsg 任务内容
// @return string body 信息
// @return error 错误
func (pinDuoDuo *PinDuoDuo) 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 "", publishGoodsErr
}
return tool.ReturnSuccess(taskMsg)
}
// GetGoodsTask 获取商品
// @return string body 信息
// @return error 错误
func (pinDuoDuo *PinDuoDuo) 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 lastCreatedAt int64 = 0
// 统计变量
totalFetched := 0 // 总共获取到的商品数(包括重复)
duplicateCount := 0 // 重复商品数量
uniqueCount := 0 // 不重复商品数量
//查询body_wait是否存在如果存在则证明不是第一次执行要获取body_wait中最后一条数据的创建时间作为查询的开始时间
exist, isTaskBodyWaitExistErr := service.IsTaskBodyWaitExist()
if isTaskBodyWaitExistErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, isTaskBodyWaitExistErr)
}
if exist {
// 获取最后一条数据的创建时间
lastBodyWaitDataJson, getLastGoodsCreateTimeErr := service.GetTaskBodyWaitLast()
if getLastGoodsCreateTimeErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, getLastGoodsCreateTimeErr)
}
// 解析 lastBodyWaitData 到结构体
var lastBodyWaitData planAType.TaskBody
unmarshalErr := json.Unmarshal([]byte(lastBodyWaitDataJson), &lastBodyWaitData)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, unmarshalErr)
}
//将数据的创建时间给到 lastCreatedAt
lastCreatedAt = lastBodyWaitData.BookInfo.Price
//第二阶段 获取商品
phaseTwoGoodsErr := PhaseTwoGoods(pageSize, &totalFetched, &lastCreatedAt, maxRecordsPerRange)
if phaseTwoGoodsErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, phaseTwoGoodsErr)
}
} else {
//第一阶段 拉取任务数据
firstTimeGoodsErr := phaseOneGoods(pageSize, maxPage, &totalFetched, &lastCreatedAt)
if firstTimeGoodsErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, firstTimeGoodsErr)
}
//第二阶段 获取商品
phaseTwoGoodsErr := PhaseTwoGoods(pageSize, &totalFetched, &lastCreatedAt, maxRecordsPerRange)
if phaseTwoGoodsErr != nil {
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 := 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 信息
func (pinDuoDuo *PinDuoDuo) OperationGoodsTask(taskMsg planAType.TaskBody) (string, error) {
//生成唯一请求标识(用于出错精准查询日志)
logUuid, generateUUIDErr := tool.GenerateUUID()
if generateUUIDErr != nil {
return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr)
}
switch taskMsg.Detail.Status {
case 1, 2:
return setSaleStatusGoodsTask(logUuid, taskMsg) //设置商品上下架状态 status=1 上架 status=2 下架 {"book_info":{"isbn":"9787543982888"},"detail":{"goods_id":936170582125,"status":2}}
case 4:
return updateGoodsQuantity(logUuid, taskMsg, 1, 0) //修改商品库存 {"book_info":{"isbn":"9787532080786"},"detail":{"goods_id":935177284615,"status":4,"stock":2,"sku_id":1882660479308}}
case 5:
return updateSkuPrice(logUuid, taskMsg) //修改商品价格 {"book_info":{"isbn":"9787543982888"},"detail":{"goods_id":939229985495,"status":5,"price":5000,"sku_id":1886207421871}}
case 6:
//发布商品
taskMsg, publishGoodsErr := publishGoods(logUuid, taskMsg)
if publishGoodsErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, publishGoodsErr)
}
return tool.ReturnSuccess(taskMsg)
case 7:
//下架
taskMsg.Detail.Status = 2
_, setSaleStatusGoodsTaskErr := setSaleStatusGoodsTask(logUuid, taskMsg)
if setSaleStatusGoodsTaskErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, setSaleStatusGoodsTaskErr)
}
//删除商品
logic.DelTask(taskMsg)
//发布商品
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("未知操作类型 %v", taskMsg.Detail.Status))
}
}
// IncStockTask 增量库存
// @param taskMsg 任务内容
// @return string body 信息
// @return error 错误
func (pinDuoDuo *PinDuoDuo) 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 tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, GetGoodsByShopIdAndIsbnErr)
}
if getGoodsByShopIdAndIsbn.Code != "200" {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("ERP未找到商品"))
}
if len(getGoodsByShopIdAndIsbn.Data) == 0 {
//新发布
task, addGoodsTaskErr := publishGoods(logUuid, taskMsg)
if addGoodsTaskErr != nil {
return "", addGoodsTaskErr
}
//根据回调查询商品回到信息直到成功
//found := false
//startTime := time.Now()
//maxDuration := 3 * time.Minute // 最大查询时间 3分钟
//for !found {
// // 检查是否超时
// if time.Since(startTime) > maxDuration {
// fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~超时了~~~~~~~~~~~~~~~~~~~~~~~~~~")
// found = true
// break // 跳出内层循环
// }
list, getPddNoticeMsgErr := service.GetPddNoticeMsg(golabl.Task.TaskId)
if getPddNoticeMsgErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, getPddNoticeMsgErr)
}
for _, v := range list {
var pddNoticeMsg planBTypePinduoduo.PddNoticeMsg
unmarshalErr := json.Unmarshal([]byte(v), &pddNoticeMsg)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr)
}
//必须是发布上架
if pddNoticeMsg.Type == "pdd_goods_GoodsOnShelf" {
// task.Detail.GoodsId 转为字符串
goodsId := strconv.FormatInt(task.Detail.GoodsId, 10)
if pddNoticeMsg.GoodsId == goodsId {
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~找到了~~~~~~~~~~~~~~~~~~~~~~~~~~")
//跳出最外层循环
//found = true
break // 跳出内层循环
}
}
}
//// 避免过于频繁的查询,可以添加短暂延迟
//if !found {
// time.Sleep(1 * time.Second)
//}
//}
task.Detail.Error = "发布成功!"
return tool.ReturnSuccess(task)
} 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 := publishGoods(logUuid, taskMsg)
if addGoodsTaskErr != nil {
return "", addGoodsTaskErr
}
task.Detail.Error = "所有商品价格相差超过1元重新发布成功"
return tool.ReturnSuccess(task)
}
// 找到价格相差最小的商品,如果多个最小差价一样则对第一条增加库存
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.SkuId 转为 int64
skuId, skuIdParseIntErr := strconv.ParseInt(targetItem.SkuId, 10, 64)
if skuIdParseIntErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, skuIdParseIntErr)
}
// 将 targetItem.TrilateralId 转为 int64
trilateralId, trilateralIdParseIntErr := strconv.ParseInt(targetItem.TrilateralId, 10, 64)
if trilateralIdParseIntErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, trilateralIdParseIntErr)
}
//增量修改库存
taskMsg.Detail.GoodsId = trilateralId
taskMsg.Detail.SkuId = skuId
quantity, updateGoodsQuantityErr := updateGoodsQuantity(logUuid, taskMsg, 2, targetItem.Stock)
if updateGoodsQuantityErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, updateGoodsQuantityErr)
}
return quantity, nil
}
}
// abs 返回绝对值
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
func (pinDuoDuo *PinDuoDuo) SetGoodsTask() string {
return ""
}
// *******************************私有方法************************************ //
// 构建商品属性列表
// @param isbn
// @param bookName 书名
// @param pageCount 页数
// @param price 价格
// @param publishingVid 出版社Vid
// @param author 作者
// @param format 开本
// @param binding 装帧
// @param wordsCount 字数
// @param publicationDate 出版时间
// @return []GoodsProperties 商品属性列表
func buildGoodsPropertiesList(isbn, bookName string, pageCount, price int64, publishingVid int64, author string, format int64, binding string, wordsCount int64, publicationDate string) []planBTypePinduoduo.GoodsProperties {
var goodsPropertiesArr []planBTypePinduoduo.GoodsProperties
//isbn
if isbn != "" {
goodsPropertiesIsbn := planBTypePinduoduo.GoodsProperties{
RefPid: 425,
Value: isbn,
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesIsbn)
}
//书名
if bookName != "" {
goodsPropertiesBookName := planBTypePinduoduo.GoodsProperties{
RefPid: 876,
Value: bookName,
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesBookName)
}
//页数
if pageCount == 0 {
pageCount = 200
}
goodsPropertiesPageNum := planBTypePinduoduo.GoodsProperties{
RefPid: 692,
Value: strconv.FormatInt(pageCount, 10),
ValueUnit: "页",
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesPageNum)
//定价
goodsPropertiesPrice := planBTypePinduoduo.GoodsProperties{
RefPid: 879,
Value: strconv.FormatInt(price/100, 10),
ValueUnit: "元",
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesPrice)
//出版社
if publishingVid != 0 {
goodsPropertiesPublishing := planBTypePinduoduo.GoodsProperties{
RefPid: 880,
Vid: publishingVid,
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesPublishing)
}
//作者
if author != "" {
goodsPropertiesAuthor := planBTypePinduoduo.GoodsProperties{
RefPid: 882,
Value: author,
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesAuthor)
}
//开本
if format != 0 {
goodsPropertiesFormat := planBTypePinduoduo.GoodsProperties{
RefPid: 890,
Value: strconv.FormatInt(format, 10),
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesFormat)
}
//装帧类型
if binding != "" {
goodsPropertiesBinding := planBTypePinduoduo.GoodsProperties{
RefPid: 888,
Value: binding,
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPropertiesBinding)
}
//字数
if wordsCount != 0 {
goodsWordsCountBinding := planBTypePinduoduo.GoodsProperties{
RefPid: 887,
Value: strconv.FormatInt(wordsCount, 10),
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsWordsCountBinding)
}
//出版时间部分数据出版时间是1970-01视为没有出版时间
if publicationDate != "" && publicationDate != "1970-01" {
goodsPublicationDateBinding := planBTypePinduoduo.GoodsProperties{
RefPid: 881,
Value: publicationDate,
}
goodsPropertiesArr = append(goodsPropertiesArr, goodsPublicationDateBinding)
}
return goodsPropertiesArr
}
// sku规格生成
// @param price 价格
// @param thumbUrl 缩略图
// @param stock 库存
// @param outSkuSn 商品编码
// @param specName 规格名称
// @param isOnsale 上架状态
// @return Sku sku规格
// @return error 错误信息
func buildSkuList(price int64, thumbUrl string, stock int64, outSkuSn string, specChildName string, isOnsale int64) (planBTypePinduoduo.Sku, error) {
//构建变量
specId := golabl.Task.Header.ShopMsg.SpecId
specName := golabl.Task.Header.ShopMsg.SpecName
// 构建 Spec列表
var sku planBTypePinduoduo.Sku
goodsSpec, buildPddGoodsSpecIdErr := buildPddGoodsSpecId(specId, specChildName)
if buildPddGoodsSpecIdErr != nil {
return sku, buildPddGoodsSpecIdErr
}
// 构建SKU_Properties列表
skuProperty := planBTypePinduoduo.SkuProperty{
Punit: specName, // 属性单位
RefPid: specId, // 属性id
Value: goodsSpec.DllGoodsSpec.SpecName, // 属性值
Vid: goodsSpec.DllGoodsSpec.SpecID, // 属性值id
}
skuProperties := []planBTypePinduoduo.SkuProperty{skuProperty}
specIdList := "[" + strconv.FormatInt(skuProperty.Vid, 10) + "]"
// 构建 SKU列表
var onsale int64
if isOnsale == 0 {
onsale = 1
} else {
onsale = 0
}
sku = planBTypePinduoduo.Sku{
IsOnsale: onsale, //上架状态0-已下架1-上架中
LimitQuantity: 999, //sku购买限制只入参999
MultiPrice: price, //团购价格,单位为分
Price: price + 100, //单买价格,单位为分
SkuProperties: skuProperties, //sku属性列表
ThumbUrl: thumbUrl, //缩略图
SpecIdList: specIdList, //商品规格列表
Quantity: stock, //商品库存初始数量
Weight: 250, //重量单位g
OutSkuSn: outSkuSn, //商品编码
}
return sku, nil
}
// buildPddGoodsSpecId 根据名称获取规格信息
// @param specId 商品规格id
// @param specName 规格名称
// @return DllGoodsSpec 规格信息
// @return error 错误信息
func buildPddGoodsSpecId(id int64, name string) (planAType.DllGoodsSpec, error) {
var spec planAType.DllGoodsSpec
specStr, err := golabl.PddDll.PddGoodsSpecIdGet(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, strconv.FormatInt(id, 10), name)
if err != nil {
return spec, err
}
// 解析JSON字符串
err = json.Unmarshal([]byte(specStr), &spec)
if err != nil {
return spec, fmt.Errorf("解析拼多多 PddGoodsSpecIdGet 接口返回json失败: %v [拼多多数据:%v]", err, specStr)
}
return spec, nil
}
// 商品新增
// @param logUuid 日志ID
// @param goodsInfo 商品信息
// @return GoodsAddResponseWrapper 商品新增结果
// @return string 商品新增结果json
// @return error 错误信息
func addGoods(logUuid string, goodsInfo planBTypePinduoduo.GoodsAdd) (planBTypePinduoduo.GoodsAddResponseWrapper, string, error) {
var goodsAdd planBTypePinduoduo.GoodsAddResponseWrapper
goodsInfoStr, jsonMarshalErr := json.Marshal(goodsInfo)
if jsonMarshalErr != nil {
return goodsAdd, "", jsonMarshalErr
}
//发送请求
goodsAddStr, pddGoodsAddErr := golabl.PddDll.PddGoodsAdd(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, string(goodsInfoStr))
//判断是否成功
if strings.Contains(goodsAddStr, "请求失败") || strings.Contains(goodsAddStr, "错误码") {
//记录请求日志
// 记录请求日志
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("拼多多 PddGoodsAdd 错误:" + goodsAddStr)
}
if pddGoodsAddErr != nil {
return goodsAdd, "", pddGoodsAddErr
}
jsonUnmarshal := json.Unmarshal([]byte(goodsAddStr), &goodsAdd)
if jsonUnmarshal != nil {
return goodsAdd, "", fmt.Errorf("解析拼多多 PddGoodsAdd 接口返回json失败: %v", jsonUnmarshal)
}
return goodsAdd, goodsAddStr, nil
}
// 获取商品提交的商品详情
// @param goodsCommitId 商品提交ID
// @param goodsId 商品ID
// @return GoodsCommitDetailResponse 商品提交详情
// @return error 错误信息
func getGoodsCommitDetail(goodsCommitId int64, goodsId int64) (planBTypePinduoduo.GoodsCommitDetailResponse, string, error) {
var goodsCommitDetail planBTypePinduoduo.GoodsCommitDetailResponse
goodsCommitDetailStr, pddGoodsCommitDetailGetErr := golabl.PddDll.PddGoodsCommitDetailGet(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, strconv.FormatInt(goodsCommitId, 10), strconv.FormatInt(goodsId, 10))
if pddGoodsCommitDetailGetErr != nil {
return goodsCommitDetail, "", pddGoodsCommitDetailGetErr
}
unmarshalErr := json.Unmarshal([]byte(goodsCommitDetailStr), &goodsCommitDetail)
if unmarshalErr != nil {
return goodsCommitDetail, "", fmt.Errorf("解析拼多多 PddGoodsCommitDetailGet 接口返回json失败: %v [拼多多数据:%v]", unmarshalErr, goodsCommitDetailStr)
}
return goodsCommitDetail, goodsCommitDetailStr, nil
}
// 第一阶段拉取商品信息
// @param maxPage 最大页数
// @param pageSize 每页数量
// @param totalFetched 获取到的商品总数
// @param lastCreatedAt 最后一条数据的创建时间
// @return error 错误信息
func phaseOneGoods(maxPage int, pageSize int, totalFetched *int, lastCreatedAt *int64) error {
// 第一阶段获取第1页到第100页不传入时间参数
for page := 1; page <= maxPage; page++ {
// 定义参数
params := map[string]string{
"accessToken": golabl.Task.Header.ShopMsg.Token,
"page": strconv.Itoa(page),
"pageSize": strconv.Itoa(pageSize),
}
goodsList, goodsListStr, getGoodsListErr := tool.GetPddGoodsList(params)
if getGoodsListErr != nil {
return fmt.Errorf("获取商品列表失败,页码: %d, 错误: %v", page, getGoodsListErr)
}
if goodsListStr == "{}" {
//如果读取不到数据,重试一次
fmt.Println("通过容器获取获取商品列表数据失败,重试一次")
goodsList, goodsListStr, getGoodsListErr = tool.GetPddGoodsList(params)
if getGoodsListErr != nil {
return fmt.Errorf("获取商品列表失败,页码: %d, 错误: %v", page, getGoodsListErr)
}
}
if goodsListStr == "{}" {
fmt.Println("---------------------------------------错误goodsListStr----------------------------------")
fmt.Println(goodsListStr)
fmt.Println("---------------------------------------错误goodsListStr----------------------------------")
return fmt.Errorf("容器返回数据为空")
}
//更新 header进度总数
if page == 1 {
// 更新进度
fmt.Println("总数 ", strconv.Itoa(goodsList.TotalCount))
if updateTaskHeaderErr := service.SetTaskCount(strconv.Itoa(goodsList.TotalCount)); updateTaskHeaderErr != nil {
return updateTaskHeaderErr
}
}
//如果是需要拉取详情的商品
if golabl.Task.Header.TaskType == 4 {
// 获取原始商品列表
originalGoodsList := goodsList.GoodsList
totalCount := len(originalGoodsList)
if totalCount == 0 {
return nil // 或继续后续处理
}
// 存储所有获取到的商品详情
allGoodsDetailList := make([]planBTypePinduoduo.GoodsItem, 0, totalCount)
// 每100条调用一次
batchSize := 100
n := 0
for i := 0; i < totalCount; i += batchSize {
// 计算当前批次的起始和结束位置
end := i + batchSize
if end > totalCount {
end = totalCount
}
batch := originalGoodsList[i:end]
n++
// 调用接口获取商品详情
fmt.Printf("第 %v 页 第 %v 次 \n", page, n)
goodsDetailList, goodsDetailListStr, getPddGoodsDetailErr := tool.GetPddGoodsDetail(batch)
if getPddGoodsDetailErr != nil {
fmt.Println("----------------------------错误goodsDetailList-------------------------------")
fmt.Printf("batch start %d end %d, batch size %d, total %d\n", i, end, len(batch), totalCount)
fmt.Println(goodsDetailListStr)
fmt.Println("----------------------------错误goodsDetailList-------------------------------")
return getPddGoodsDetailErr
}
// 将当前批次的结果添加到总结果中
allGoodsDetailList = append(allGoodsDetailList, goodsDetailList...)
}
// 赋值回原变量
goodsList.GoodsList = allGoodsDetailList
}
// 收集商品数据并统计
for _, goods := range goodsList.GoodsList {
*totalFetched++
//写入到数据库中
//将goods转为json
jsonData, jsonMarshalErr := json.Marshal(goods)
if jsonMarshalErr != nil {
return fmt.Errorf("将商品转为json失败: %v\n", jsonMarshalErr)
}
//写入到数据库中
if len(goods.SkuList) <= 0 {
return fmt.Errorf("商品sku列表为空 goodsId %v", goods.GoodsId)
}
bodyWait := planAType.TaskBody{
BookInfo: planAType.BookInfo{
Isbn: goods.SkuList[0].OuterId,
BookName: goods.GoodsName,
Author: "",
Publishing: "",
PublicationDate: "",
Binding: "",
PagesCount: 0,
WordsCount: 0,
Format: 0,
Price: goods.CreatedAt,
},
Detail: planAType.TaskDetail{
Status: 1,
Error: string(jsonData),
GoodsId: goods.GoodsId,
Stock: int32(goods.SkuList[0].ReserveQuantity),
},
}
// 将bodyWait 转为json
bodyWaitJson, jsonMarshalErr := json.Marshal(bodyWait)
if jsonMarshalErr != nil {
return fmt.Errorf("将bodyWait转为json失败: %v\n", jsonMarshalErr)
}
//写入 body_wait
addTaskToBodyWaitErr := service.AddTaskToBodyWait(string(bodyWaitJson))
if addTaskToBodyWaitErr != nil {
return addTaskToBodyWaitErr
}
}
// 记录最后一页的最后一条数据的创建时间
if page == maxPage && len(goodsList.GoodsList) > 0 {
*lastCreatedAt = goodsList.GoodsList[len(goodsList.GoodsList)-1].CreatedAt
fmt.Printf("最后一页,最后一条数据的创建时间: %v", *lastCreatedAt)
}
// 如果没有更多数据,提前退出
if len(goodsList.GoodsList) == 0 {
fmt.Println("没有更多数据,退出循环 ")
fmt.Println(goodsListStr)
break
}
// 获取 footer信息
if getTaskFooterErr := service.GetTaskFooter(); getTaskFooterErr != nil {
return getTaskFooterErr
}
con := int64(len(goodsList.GoodsList))
if con >= golabl.Task.Footer.TaskCountTrue {
con = golabl.Task.Footer.TaskCountTrue - con
}
// 更新 进度
if updateTaskProgressErr := tool.UpdateTaskProgress(con); updateTaskProgressErr != nil {
return updateTaskProgressErr
}
// 可选:添加延迟,避免请求过快
// 暂停200豪秒
time.Sleep(200 * time.Millisecond)
fmt.Printf("第一阶段 - 总数:%v 当前已取出:%v \n", goodsList.TotalCount, *totalFetched)
}
return nil
}
// PhaseTwoGoods 第二阶段拉取商品信息
// @param pageSize 每页数量
// @param totalFetched 获取到的商品总数
// @param lastCreatedAt 最后一条数据的创建时间
// @param maxRecordsPerRange 每次请求的时间范围最多获取的记录数
// @return error 错误信息
func PhaseTwoGoods(pageSize int, totalFetched *int, lastCreatedAt *int64, maxRecordsPerRange int) error {
// 第二阶段使用时间范围分批次获取数据每批最多获取10000条
// 设置结束时间为开始时间+30天
endTime := *lastCreatedAt + 30*24*60*60
fmt.Printf("第二阶段开始,结束时间设置为: %d (%s)\n", endTime, time.Unix(endTime, 0).Format("2006-01-02 15:04:05"))
if *lastCreatedAt > 0 {
currentCreatedAtFrom := *lastCreatedAt
maxLoopCount := 100 // 最大循环次数保护
loopCount := 0
var lastPageGoodsList []planBTypePinduoduo.GoodsItem // 记录上一页的商品列表
for loopCount < maxLoopCount {
loopCount++
// 检查开始时间是否已超过当前时间
if currentCreatedAtFrom > time.Now().Unix() {
fmt.Printf("开始时间 %d 已超过当前时间,停止获取\n", currentCreatedAtFrom)
break
}
// 每次循环都重新设置结束时间为开始时间+30天
currentCreatedAtEnd := currentCreatedAtFrom + 30*24*60*60
fmt.Printf("开始获取时间范围: %d 到 %d\n", currentCreatedAtFrom, currentCreatedAtEnd)
currentPage := 1
batchGoodsCount := 0
lastItemCreatedAt := int64(0)
hasDataInRange := false
lastPageGoodsList = nil // 重置上一页商品列表
// 在当前时间范围内分页获取数据
for {
params := map[string]string{
"accessToken": golabl.Task.Header.ShopMsg.Token,
"page": strconv.Itoa(currentPage),
"pageSize": strconv.Itoa(pageSize),
"createdAtFrom": strconv.FormatInt(currentCreatedAtFrom, 10),
"createdAtEnd": strconv.FormatInt(currentCreatedAtEnd, 10),
}
goodsList, goodsListStr, getGoodsListErr := tool.GetPddGoodsList(params)
if getGoodsListErr != nil {
return fmt.Errorf("获取商品列表失败(时间范围),页码: %d, 错误: %v", currentPage, getGoodsListErr)
}
if goodsListStr == "{}" {
fmt.Println("通过容器获取获取商品列表数据失败,重试一次")
goodsList, goodsListStr, getGoodsListErr = tool.GetPddGoodsList(params)
if getGoodsListErr != nil {
return fmt.Errorf("获取商品列表失败(时间范围),页码: %d, 错误: %v", currentPage, getGoodsListErr)
}
}
if goodsListStr == "{}" {
return fmt.Errorf("容器返回数据为空")
}
//如果是需要拉取详情的商品
if golabl.Task.Header.TaskType == 4 {
// 获取原始商品列表
originalGoodsList := goodsList.GoodsList
totalCount := len(originalGoodsList)
if totalCount == 0 {
return nil // 或继续后续处理
}
// 存储所有获取到的商品详情
allGoodsDetailList := make([]planBTypePinduoduo.GoodsItem, 0, totalCount)
// 每100条调用一次
batchSize := 100
n := 0
for i := 0; i < totalCount; i += batchSize {
// 计算当前批次的起始和结束位置
end := i + batchSize
if end > totalCount {
end = totalCount
}
batch := originalGoodsList[i:end]
n++
// 调用接口获取商品详情
fmt.Printf(" 第 %v 次 \n", n)
goodsDetailList, goodsDetailListStr, getPddGoodsDetailErr := tool.GetPddGoodsDetail(batch)
if getPddGoodsDetailErr != nil {
fmt.Println("----------------------------错误goodsDetailList-------------------------------")
fmt.Printf("batch start %d end %d, batch size %d, total %d\n", i, end, len(batch), totalCount)
fmt.Println(goodsDetailListStr)
fmt.Println("----------------------------错误goodsDetailList-------------------------------")
return getPddGoodsDetailErr
}
// 将当前批次的结果添加到总结果中
allGoodsDetailList = append(allGoodsDetailList, goodsDetailList...)
}
// 赋值回原变量
goodsList.GoodsList = allGoodsDetailList
}
// 如果当前页没有数据
if len(goodsList.GoodsList) == 0 {
// 如果当前页是第一页且没有数据
if currentPage == 1 {
// 整个时间范围都没有数据,直接推进到结束时间
currentCreatedAtFrom = currentCreatedAtEnd
fmt.Printf("时间范围 %d - %d 内无数据,推进开始时间到: %d\n", currentCreatedAtFrom-30*24*60*60, currentCreatedAtEnd, currentCreatedAtFrom)
break
}
// 当前页没有数据,但上一页有数据
// 取上一页最后一条数据的创建时间和GoodsId作为新的开始位置
if len(lastPageGoodsList) > 0 {
lastItemOfLastPage := lastPageGoodsList[len(lastPageGoodsList)-1]
newStartTime := lastItemOfLastPage.CreatedAt
lastGoodsId := lastItemOfLastPage.GoodsId
// 使用基于 GoodsId的定位策略
if newStartTime > currentCreatedAtFrom {
currentCreatedAtFrom = newStartTime
fmt.Printf("当前页无数据,使用上一页最后一条商品时间作为新开始时间: %d, 最后商品ID: %d\n", currentCreatedAtFrom, lastGoodsId)
} else {
// 如果时间相同需要基于GoodsId来推进这里简单地将时间加1秒
currentCreatedAtFrom = newStartTime + 1
fmt.Printf("当前页无数据时间相同将时间加1秒推进: %d\n", currentCreatedAtFrom)
}
} else {
// 理论上不会走到这里,但为了安全,将开始时间推进到结束时间
currentCreatedAtFrom = currentCreatedAtEnd
fmt.Printf("当前页无数据且无上一页数据,将开始时间推进到结束时间: %d\n", currentCreatedAtFrom)
}
hasDataInRange = false
break
}
// 有数据,记录上一页的商品列表
lastPageGoodsList = goodsList.GoodsList
hasDataInRange = true
// 收集商品数据并统计
for _, goods := range goodsList.GoodsList {
*totalFetched++
// 写入到数据库中
// 将goods转为json
jsonData, jsonMarshalErr := json.Marshal(goods)
if jsonMarshalErr != nil {
return fmt.Errorf("将商品转为json失败: %v\n", jsonMarshalErr)
}
// 写入到数据库中
if len(goods.SkuList) <= 0 {
return fmt.Errorf("商品sku列表为空 goodsId %v", goods.GoodsId)
}
bodyWait := planAType.TaskBody{
BookInfo: planAType.BookInfo{
Isbn: goods.SkuList[0].OuterId,
BookName: goods.GoodsName,
Author: "",
Publishing: "",
PublicationDate: "",
Binding: "",
PagesCount: 0,
WordsCount: 0,
Format: 0,
Price: goods.CreatedAt,
},
Detail: planAType.TaskDetail{
Error: string(jsonData),
GoodsId: goods.GoodsId,
Stock: int32(goods.SkuList[0].ReserveQuantity),
},
}
// 将bodyWait 转为json
bodyWaitJson, jsonMarshalErr := json.Marshal(bodyWait)
if jsonMarshalErr != nil {
return fmt.Errorf("将bodyWait转为json失败: %v\n", jsonMarshalErr)
}
//写入 body_wait
addTaskToBodyWaitErr := service.AddTaskToBodyWait(string(bodyWaitJson))
if addTaskToBodyWaitErr != nil {
return addTaskToBodyWaitErr
}
}
batchGoodsCount += len(goodsList.GoodsList)
// 记录最后一条商品的创建时间
lastItem := goodsList.GoodsList[len(goodsList.GoodsList)-1]
lastItemCreatedAt = lastItem.CreatedAt
fmt.Printf("第二阶段 - 当前时间范围已获取: %d 条,累计总数: %d当前页码: %d最后商品时间: %d\n",
batchGoodsCount, *totalFetched, currentPage, lastItemCreatedAt)
// 判断是否需要结束当前时间范围
// 1. 如果当前批次已经达到或超过 maxRecordsPerRange
// 2. 或者返回的数据少于 pageSize说明没有下一页了
if batchGoodsCount >= maxRecordsPerRange || len(goodsList.GoodsList) < pageSize {
// 关键修复:使用最后一条商品的时间作为新的开始时间
// 如果最后一条商品时间等于当前开始时间则加1秒避免死循环
if lastItemCreatedAt == currentCreatedAtFrom {
currentCreatedAtFrom = lastItemCreatedAt + 1
fmt.Printf("最后商品时间与开始时间相同推进1秒: %d -> %d\n", lastItemCreatedAt, currentCreatedAtFrom)
} else {
currentCreatedAtFrom = lastItemCreatedAt
}
fmt.Printf("当前时间范围已获取 %d 条数据,准备进入下一时间范围 更新开始时间为: %d \n", batchGoodsCount, currentCreatedAtFrom)
break
}
currentPage++
// 获取 footer信息
if getTaskFooterErr := service.GetTaskFooter(); getTaskFooterErr != nil {
return getTaskFooterErr
}
con := int64(len(goodsList.GoodsList))
if con >= golabl.Task.Footer.TaskCountTrue {
con = golabl.Task.Footer.TaskCountTrue - con
}
// 更新 进度
if updateTaskProgressErr := tool.UpdateTaskProgress(con); updateTaskProgressErr != nil {
return updateTaskProgressErr
}
// 暂停200豪秒
time.Sleep(200 * time.Millisecond)
}
// 判断是否需要继续循环
// 情况1当前时间范围内没有获取到任何数据
if !hasDataInRange {
// 检查新的开始时间是否已超过当前时间
if currentCreatedAtFrom > time.Now().Unix() {
fmt.Printf("开始时间 %d 已超过当前时间,停止获取\n", currentCreatedAtFrom)
break
}
// 否则继续下一轮循环
fmt.Printf("继续下一轮查询,新起始时间: %d\n", currentCreatedAtFrom)
continue
}
// 情况2当前批次获取的数据少于 maxRecordsPerRange 并且开始时间大于当前时间,说明已经没有更多数据了
if batchGoodsCount < maxRecordsPerRange && currentCreatedAtFrom > time.Now().Unix() {
fmt.Printf("当前批次获取 %d 条数据,少于 %d且开始时间已超过当前时间已完成所有数据获取\n", batchGoodsCount, maxRecordsPerRange)
break
}
// 暂停200豪秒
time.Sleep(200 * time.Millisecond)
}
if loopCount >= maxLoopCount {
fmt.Printf("警告:已达到最大循环次数 %d强制退出\n", maxLoopCount)
}
}
return nil
}
// 拉取任务 读取body_wait去重复后写入到body_over中
// @param duplicateCount int64 重复商品数量
// @param uniqueCount int64 不重复商品数量
// @return error 错误信息
func deduplicateToBodyOver(duplicateCount *int, uniqueCount *int) error {
page := 1
pageSize := 1000
var dataList []planBTypePinduoduo.GoodsItem
// 在循环外维护一个已处理的商品 ID集合
processedGoodsIds := make(map[int64]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)
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)
}
if !processedGoodsIds[goods.Detail.GoodsId] {
//写入到去重复集合
processedGoodsIds[goods.Detail.GoodsId] = true
//不重复数据 计次
*uniqueCount++
// goods.Detail.Error(原始json到结构体)
var GoodsItem planBTypePinduoduo.GoodsItem
jsonUnmarshalErr = json.Unmarshal([]byte(goods.Detail.Error), &GoodsItem)
if jsonUnmarshalErr != nil {
return fmt.Errorf("将json转为结构体失败: %v\n", jsonUnmarshalErr)
}
//记录到data数组中之后推送到写入店铺商品数据接口
dataList = append(dataList, GoodsItem)
//写入到body_over
goods.Detail.Status = 1
addTaskToBodyOverErr := service.AddTaskToBodyOver(goods, []string{"body_over", "body_backup"})
if addTaskToBodyOverErr != nil {
return addTaskToBodyOverErr
}
//将指定店铺信息记录到本地
isFileShopId, isShopIDExistsErr := tool.IsShopIDExists(golabl.Task.Header.ShopId)
if isShopIDExistsErr != nil {
return isShopIDExistsErr
}
if isFileShopId {
text := goods.BookInfo.Isbn + " " + GoodsItem.BigImg
txtUrl := golabl.Config.FileUrl.PddGoodsDetailsUrl
fileName := golabl.Task.Header.TaskId
// 构建完整的文件路径
filePath := filepath.Join(txtUrl, fileName+".txt")
// 写入文件
if err := tool.AppendTextToFile(filePath, text); err != nil {
fmt.Println("保存详情信息到文本 失败", err.Error())
}
}
} else {
//重复数据 计次
*duplicateCount++
}
}
// 将获取的数据推送写入店铺商品数据接口
ret, retStr, writePddGoodsDataErr := tool.WritePddGoodsData(dataList, page, pageTotal)
if writePddGoodsDataErr != nil {
return writePddGoodsDataErr
}
if ret.Code != "200" {
return fmt.Errorf("添加商品失败 %v", retStr)
}
num = num + len(dataList)
fmt.Printf("开始添加商品信息到系统店铺中 当前页 %v 总页数 %v 当前数据量 %v 总数据量 %v \n", page, pageTotal, len(dataList), num)
page++
// 获取 footer信息
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
}
//清空 dataStr
dataList = []planBTypePinduoduo.GoodsItem{}
// 暂停1秒
time.Sleep(1 * time.Second)
}
// 删除body_wait
deleteTaskBodyWaitErr := service.DeleteTaskBodyWait()
if deleteTaskBodyWaitErr != nil {
return deleteTaskBodyWaitErr
}
return nil
}
// setSaleStatusGoodsTask 设置商品上下架状态
// @param taskMsg 任务内容
// @return string body 信息
func setSaleStatusGoodsTask(logUuid string, taskMsg planAType.TaskBody) (string, error) {
// 拼多多商品 Id不能为空
if taskMsg.Detail.GoodsId == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 Id不能为空"))
}
var reqDataInfo planBTypePinduoduo.SetSaleStatusGoodsTaskReq
reqDataInfo.GoodsId = taskMsg.Detail.GoodsId
if taskMsg.Detail.Status == 1 {
reqDataInfo.IsOnsale = 1 //上架
} else if taskMsg.Detail.Status == 2 {
reqDataInfo.IsOnsale = 0 //下架
} else {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("任务类型错误"))
}
setSoleStatusGoodsRet, _, err := setSoleStatusGoods(logUuid, reqDataInfo)
if err != nil {
return "", err
}
if !setSoleStatusGoodsRet.GoodsSaleStatusSetResponse.IsSuccess {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("设置商品上下架状态失败"))
}
return tool.ReturnSuccess(taskMsg)
}
// setSoleStatusGoods 商品上下架
// @param logUuid 日志ID
// @param reqDataInfo 请求信息
// @return SetSaleStatusGoodsTaskResponse 结果
// @return string 结果json
// @return error 错误信息
func setSoleStatusGoods(logUuid string, reqDataInfo planBTypePinduoduo.SetSaleStatusGoodsTaskReq) (planBTypePinduoduo.SetSaleStatusGoodsTaskResponse, string, error) {
var setSoleStatusGoods planBTypePinduoduo.SetSaleStatusGoodsTaskResponse
goodsInfoStr, jsonMarshalErr := json.Marshal(reqDataInfo)
if jsonMarshalErr != nil {
return setSoleStatusGoods, "", jsonMarshalErr
}
//发送请求
goodsSoleStatusStr, pddGoodsSoleStatusErr := golabl.PddDll.PddGoodsSaleStatusSet(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, string(goodsInfoStr))
//判断是否成功
if strings.Contains(goodsSoleStatusStr, "请求失败") || strings.Contains(goodsSoleStatusStr, "错误码") {
//记录请求日志
reqMsg := 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, reqMsg)
return setSoleStatusGoods, goodsSoleStatusStr, errors.New("拼多多 PddGoodsSaleStatusSet 错误:" + goodsSoleStatusStr)
}
if pddGoodsSoleStatusErr != nil {
return setSoleStatusGoods, "", pddGoodsSoleStatusErr
}
jsonUnmarshal := json.Unmarshal([]byte(goodsSoleStatusStr), &setSoleStatusGoods)
if jsonUnmarshal != nil {
return setSoleStatusGoods, "", fmt.Errorf("解析拼多多 PddGoodsAdd 接口返回json失败: %v", jsonUnmarshal)
}
return setSoleStatusGoods, goodsSoleStatusStr, nil
}
// updateGoodsQuantity 修改库存
func updateGoodsQuantity(logUuid string, taskMsg planAType.TaskBody, UpdateType int, stock int64) (string, error) {
// 拼多多商品 Id不能为空
if taskMsg.Detail.GoodsId == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 Id不能为空"))
}
// 拼多多商品 SkuId不能为空
if taskMsg.Detail.SkuId == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 SkuId不能为空"))
}
reqDataInfo := planBTypePinduoduo.UpdateGoodsQuantity{
ForceUpdate: true,
GoodsId: taskMsg.Detail.GoodsId,
SkuId: taskMsg.Detail.SkuId,
Quantity: int64(taskMsg.Detail.Stock),
UpdateType: UpdateType,
}
delGoodsRet, _, err := quantityGoods(logUuid, reqDataInfo)
if err != nil {
return "", err
}
if !delGoodsRet.GoodsQuantityUpdateResponse.IsSuccess {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("修改商品库存失败"))
}
if UpdateType == 2 {
taskMsg.Detail.Stock = taskMsg.Detail.Stock + int32(stock)
}
taskMsg.Detail.Error = "增加库存成功!"
return tool.ReturnSuccess(taskMsg)
}
// quantityGoods 修改库存
// @param logUuid 日志ID
// @param reqDataInfo 请求信息
// @return GoodsAddResponseWrapper 结果
// @return string 结果json
// @return error 错误信息
func quantityGoods(logUuid string, reqDataInfo planBTypePinduoduo.UpdateGoodsQuantity) (planBTypePinduoduo.UpdateGoodsQuantityResponse, string, error) {
var quantityGoods planBTypePinduoduo.UpdateGoodsQuantityResponse
goodsInfoStr, jsonMarshalErr := json.Marshal(reqDataInfo)
if jsonMarshalErr != nil {
return quantityGoods, "", jsonMarshalErr
}
//发送请求
quantityGoodsStr, pddGoodsQuantityErr := golabl.PddDll.PddGoodsQuantityUpdate(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, string(goodsInfoStr))
//判断是否成功
if strings.Contains(quantityGoodsStr, "请求失败") || strings.Contains(quantityGoodsStr, "错误码") {
//记录请求日志
reqMsg := 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, reqMsg)
return quantityGoods, quantityGoodsStr, errors.New("拼多多 quantityGoods 错误:" + quantityGoodsStr)
}
if pddGoodsQuantityErr != nil {
return quantityGoods, "", pddGoodsQuantityErr
}
jsonUnmarshal := json.Unmarshal([]byte(quantityGoodsStr), &quantityGoods)
if jsonUnmarshal != nil {
return quantityGoods, "", fmt.Errorf("解析拼多多 quantityGoods 接口返回json失败: %v", jsonUnmarshal)
}
return quantityGoods, quantityGoodsStr, nil
}
// updateSkuPrice 修改商品价格
func updateSkuPrice(logUuid string, taskMsg planAType.TaskBody) (string, error) {
// 拼多多商品 Id不能为空
if taskMsg.Detail.GoodsId == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 Id不能为空"))
}
// 拼多多商品 SkuId不能为空
if taskMsg.Detail.SkuId == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 SkuId不能为空"))
}
// 价格0 不能发布
if taskMsg.Detail.Price == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 价格不能为0"))
}
fen := tool.BuildGoodsPrice(taskMsg.Detail.Price)
yuan := tool.FenToYuan(fen)
reqDataInfo := planBTypePinduoduo.UpdateSkuPrice{
GoodsId: taskMsg.Detail.GoodsId,
MarketPrice: fen,
MarketPriceInYuan: yuan,
SkuPriceList: []planBTypePinduoduo.SkuPriceItem{
{
GroupPrice: taskMsg.Detail.Price,
SinglePrice: taskMsg.Detail.Price + 100,
SkuId: taskMsg.Detail.SkuId,
},
},
}
delGoodsRet, _, err := skuPrice(logUuid, reqDataInfo)
if err != nil {
return "", err
}
if !delGoodsRet.GoodsUpdateSkuPriceResponse.IsSuccess {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("修改商品库存失败"))
}
return tool.ReturnSuccess(taskMsg)
}
// skuPrice 修改价格
// @param logUuid 日志ID
// @param reqDataInfo 请求信息
// @return GoodsAddResponseWrapper 结果
// @return string 结果json
// @return error 错误信息
func skuPrice(logUuid string, reqDataInfo planBTypePinduoduo.UpdateSkuPrice) (planBTypePinduoduo.UpdateGoodsSkuPriceResponse, string, error) {
var skuPriceGoods planBTypePinduoduo.UpdateGoodsSkuPriceResponse
goodsInfoStr, jsonMarshalErr := json.Marshal(reqDataInfo)
if jsonMarshalErr != nil {
return skuPriceGoods, "", jsonMarshalErr
}
//发送请求
skuPriceGoodsStr, pddGoodsSkuPriceErr := golabl.PddDll.PddGoodsSkuPriceUpdate(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, string(goodsInfoStr))
//判断是否成功
if strings.Contains(skuPriceGoodsStr, "请求失败") || strings.Contains(skuPriceGoodsStr, "错误码") {
//记录请求日志
reqMsg := 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, reqMsg)
return skuPriceGoods, skuPriceGoodsStr, errors.New("拼多多 skuPrice 错误:" + skuPriceGoodsStr)
}
if pddGoodsSkuPriceErr != nil {
return skuPriceGoods, "", pddGoodsSkuPriceErr
}
jsonUnmarshal := json.Unmarshal([]byte(skuPriceGoodsStr), &skuPriceGoods)
if jsonUnmarshal != nil {
return skuPriceGoods, "", fmt.Errorf("解析拼多多 skuPrice 接口返回json失败: %v", jsonUnmarshal)
}
return skuPriceGoods, skuPriceGoodsStr, nil
}
// 拼多多发布
func publishGoods(logUuid string, taskMsg planAType.TaskBody) (planAType.TaskBody, error) {
// 价格不能小于0
if taskMsg.Detail.Price <= 0 {
return taskMsg, fmt.Errorf("价格不能小于等于0")
}
//获取出版社信息并解析1
if getPublishingErr := service.GetPublishingVid(&taskMsg); getPublishingErr != nil {
return taskMsg, fmt.Errorf("获取出版社信息失败-原因来自:%v", getPublishingErr)
}
//违规词处理1
if golabl.Config.Server.Filter == 1 {
//开启违规词处理
if taskMsgErr := tool.FilterWord(&taskMsg); taskMsgErr != nil {
return taskMsg, taskMsgErr
}
}
//价格 + 运费
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 原始价格 %v 当前价格 %v 价格模版 %v", taskMsg.BookInfo.Isbn, taskMsg.Detail.Price, price, golabl.Task.Header.PriceMod)
}
taskMsg.Detail.Price = price
var goodsAdd planBTypePinduoduo.GoodsAdd
// *********************构建参数 开始******************************** //
//构建商品名称
goodsAdd.GoodsName = 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 = goodsAdd.GoodsName
// 构建轮播图
//if taskHeader.ShopMsg.WatermarkImgUrl == "" && len(taskHeader.ShopMsg.CarouseLastImgUrlArray) == 0 && len(taskMsg.BookInfo.ImageObject.CarouselUrlArray) == 0 && taskMsg.BookInfo.ImageObject.DefaultImageUrl == "" {
// return tool.ReturnErr(logUuid, taskMsg, _type.GoodsTypeAdd,fmt.Errorf("缺少构造轮播图图片-未提交 isbn %v", taskMsg.BookInfo.Isbn))
//}
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("缺少轮播图")
}
//判断是否拼多多的图片,如果非拼多多图片则上传到拼多多的图片空间
for k, v := range taskMsg.BookInfo.ImageObject.CarouselUrlArray {
// 判断v 是否包含字符串 img.pddpic.com
if !strings.Contains(v, "img.pddpic.com") {
tempUrl, saveBase64ImageByDateErr := tool.SaveBase64ImageByDate(v, golabl.Config.FileUrl.PddImgTempUrl)
if saveBase64ImageByDateErr != nil {
return taskMsg, saveBase64ImageByDateErr
}
//转为base64
imgBase64, format, processImageErr := tool.ProcessImage(tempUrl)
if processImageErr != nil {
return taskMsg, processImageErr
}
// 将base64字符串包装成 ImageResult 类型
imageResult := []planBTypeModules.ImageResult{
{
Success: true, // 假设图片处理成功
Format: format, // 或者根据实际情况获取图片格式
Data: imgBase64, // base64数据
},
}
//上传到拼多多图片空间
toPdd, uploadImageToPddErr := tool.UploadImageToPdd(imageResult)
if uploadImageToPddErr != nil {
return taskMsg, fmt.Errorf("上传图片到拼多多失败 %v", uploadImageToPddErr)
}
taskMsg.BookInfo.ImageObject.CarouselUrlArray[k] = toPdd[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("获取水印图片失败 %v", watermarkImgErr)
}
//打水印
watermarkFromURLExsBase64Arr, watermarkFromURLExsErr := tool.AddWatermarkFromURLExs(taskMsg.BookInfo.ImageObject.CarouselUrlArray, watermarkImgUrl, golabl.Task.Header.ShopMsg.WatermarkPosition)
if watermarkFromURLExsErr != nil {
return taskMsg, fmt.Errorf("图片打水印失败 %v", watermarkFromURLExsErr)
}
//图片上传到拼多多
toPdd, uploadImageToPddErr := tool.UploadImageToPdd(watermarkFromURLExsBase64Arr)
if uploadImageToPddErr != nil {
return taskMsg, fmt.Errorf("图片上传到拼多多失败 %v", uploadImageToPddErr)
}
//将上传的图片替换到商品轮播图中
for i := 0; i < len(toPdd); i++ {
taskMsg.BookInfo.ImageObject.CarouselUrlArray[i] = toPdd[i]
}
}
goodsAdd.CarouselGallery = tool.BuildCarouselGallery(golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray, oldCarouselUrlArray, taskMsg.BookInfo.ImageObject.CarouselUrlArray, golabl.Task.Header.ShopMsg.WatermarkPosition)
if len(taskMsg.BookInfo.ImageObject.DetailUrlObject.LiveShootingUrl) == 0 && len(oldCarouselUrlArray) > 0 {
taskMsg.BookInfo.ImageObject.DetailUrlObject.LiveShootingUrl = []string{oldCarouselUrlArray[0]}
}
if len(goodsAdd.CarouselGallery) == 0 {
return taskMsg, fmt.Errorf("缺少构造轮播图图片-未提交 isbn %v", taskMsg.BookInfo.Isbn)
}
// 构建详情图
goodsAdd.DetailGallery = tool.BuildDetailGallery(golabl.Task.Header.ShopMsg.GoodsDetailFirstImgUrlArray, golabl.Task.Header.ShopMsg.GoodsDetailLastImgUrlArray, taskMsg.BookInfo.ImageObject.DetailUrlObject, oldCarouselUrlArray[0])
if len(goodsAdd.DetailGallery) == 0 {
return taskMsg, fmt.Errorf("缺少构造详情图-未提交 isbn %v", taskMsg.BookInfo.Isbn)
}
// 构建 catId
var catID int64
if taskMsg.BookInfo.CatIdObject.PinDuoDuoCatId == "" {
// 调用拼多多 SDK 取类目信息
pddCalbackStr, pddGoodsOuterCatMappingGetErr := golabl.PddDll.PddGoodsOuterCatMappingGet(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, "15543", "书籍/杂志/报纸", "书籍 "+taskMsg.BookInfo.BookName)
if pddGoodsOuterCatMappingGetErr != nil {
return taskMsg, fmt.Errorf("调用DLL类目映射失败 %w", pddGoodsOuterCatMappingGetErr)
}
// 解析返回的 JSON 字符串
var response planBTypePinduoduo.PddSuccessResponse
if unmarshalErr := json.Unmarshal([]byte(pddCalbackStr), &response); unmarshalErr != nil {
return taskMsg, fmt.Errorf("json.Unmarshal错误 %w %v", unmarshalErr, pddCalbackStr)
}
// 判断 catID4 是否为0
if response.OuterCatMappingGetResponse.CatID4 != 0 {
catID = response.OuterCatMappingGetResponse.CatID4
} else {
catID = response.OuterCatMappingGetResponse.CatID3
}
} else {
// 数据库原本存储的为字符串 转成int64再使用
retCatID, toInt64Err := taskMsg.BookInfo.CatIdObject.PinDuoDuoCatId.ToInt64()
if toInt64Err != nil {
return taskMsg, fmt.Errorf("转换catId错误 %w", toInt64Err)
}
catID = retCatID
}
// 设置 catId
goodsAdd.CatId = catID
// 构建商品类型
goodsAdd.GoodsType = 1
// 构建参考价格
goodsAdd.MarketPrice = tool.BuildGoodsPrice(price)
// 构建商品编码
if taskMsg.Detail.OutGoodsId != "" {
goodsAdd.OutGoodsId = taskMsg.Detail.OutGoodsId
} else {
goodsAdd.OutGoodsId = taskMsg.BookInfo.Isbn
}
// 是否支持假一赔十
goodsAdd.IsFolt = golabl.Task.Header.ShopMsg.IsFolt
// 是否预售
goodsAdd.IsPreSale = golabl.Task.Header.ShopMsg.IsPreSale
// 是否支持7天无理由退换货
goodsAdd.IsRefundable = golabl.Task.Header.ShopMsg.IsRefundable
// 构建是否是二手商品
goodsAdd.SecondHand = golabl.Task.Header.ShopMsg.IsSecondHand
// 构建物流运费模板 ID
goodsAdd.CostTemplateId = golabl.Task.Header.ShopMsg.CostTemplateId
// 构建承诺发货时间
goodsAdd.ShipmentLimitSecond = 48 * 60 * 60
//满两件折扣
goodsAdd.TwoPiecesDiscount = golabl.Task.Header.ShopMsg.TwoDiscount
//构建
taskMsgBookInfoPrice := taskMsg.BookInfo.Price
if taskMsgBookInfoPrice < 10000 {
taskMsgBookInfoPrice = 10000
}
goodsAdd.GoodsProperties = buildGoodsPropertiesList(
taskMsg.BookInfo.Isbn, // ISBN
goodsAdd.GoodsName, // 商品名称
taskMsg.BookInfo.PagesCount, // 页数
taskMsgBookInfoPrice, // 价格
taskMsg.Publishing.Vid, // 出版社 Vid
taskMsg.BookInfo.Author, // 作者
taskMsg.BookInfo.Format, // 开本
taskMsg.BookInfo.Binding, // 装帧
taskMsg.BookInfo.WordsCount, // 字数
taskMsg.BookInfo.PublicationDate, // 出版时间
)
//库存
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")
}
}
//生成一个2秒的延迟
url := "http://127.0.0.1:8095"
tool.HttpGetRequest(url)
// 规格编号
if taskMsg.Detail.SkuCode == "" {
taskMsg.Detail.SkuCode = goodsAdd.OutGoodsId
}
//构建 sku信息
skuThumbnail := oldCarouselUrlArray[0]
if golabl.Task.Header.ShopMsg.SkuWatermarkImgUrl != "" {
//获取水印图片
skuWatermarkImgUrl, skuWatermarkImgErr := tool.GetSkuWatermarkImg()
if skuWatermarkImgErr != nil {
return taskMsg, fmt.Errorf("获取水印图片失败 %v", skuWatermarkImgErr)
}
//sku 打水印
skuThumbnailArr := []string{skuThumbnail}
skuWatermarkFromURLExsBase64Arr, skuWatermarkFromURLExsErr := tool.AddWatermarkFromURLExs(skuThumbnailArr, skuWatermarkImgUrl, "1")
if skuWatermarkFromURLExsErr != nil {
return taskMsg, fmt.Errorf("sku图片打水印失败 %v", skuWatermarkFromURLExsErr)
}
//sku 图片上传到拼多多
skuToPdd, uploadImageToPddErr := tool.UploadImageToPdd(skuWatermarkFromURLExsBase64Arr)
if uploadImageToPddErr != nil {
return taskMsg, fmt.Errorf("图片上传到拼多多失败 %v", uploadImageToPddErr)
}
//将上传的图片替换到商品轮播图中
skuThumbnail = skuToPdd[0]
}
//构建规格名称
var specChildName string
if golabl.Task.Header.ShopMsg.SpecCompose == "0" {
specChildName = golabl.Task.Header.ShopMsg.SpecChildName
} else if golabl.Task.Header.ShopMsg.SpecCompose == "1" {
// 前缀+isbn+后缀
specChildName = golabl.Task.Header.ShopMsg.SpecPrefix + taskMsg.BookInfo.Isbn + golabl.Task.Header.ShopMsg.SpecSuffix
} else if golabl.Task.Header.ShopMsg.SpecCompose == "2" {
// 前缀+书名+后缀
specChildName = golabl.Task.Header.ShopMsg.SpecPrefix + taskMsg.BookInfo.BookName + golabl.Task.Header.ShopMsg.SpecSuffix
} else if golabl.Task.Header.ShopMsg.SpecCompose == "3" {
// 前缀+货号+后缀
specChildName = golabl.Task.Header.ShopMsg.SpecPrefix + strconv.FormatInt(taskMsg.Detail.SkuId, 10) + golabl.Task.Header.ShopMsg.SpecSuffix
}
// 如果规格名称超过30个字符截取前30个字符
if tool.StringLength(specChildName) > 30 {
specChildName = tool.SubstringByWidth(specChildName, 30)
}
// 根据配置重新构建skuCode
if golabl.Task.Header.ShopMsg.SpecCodeCompose == "1" {
taskMsg.Detail.SkuCode = taskMsg.BookInfo.Isbn
}
//构建 sku信息
sku, err := buildSkuList(price, skuThumbnail, int64(taskMsg.Detail.Stock), taskMsg.Detail.SkuCode, specChildName, taskMsg.Detail.IsOnsale)
if err != nil {
return taskMsg, err
}
goodsAdd.SkuList = []planBTypePinduoduo.Sku{sku}
// *********************构建参数 结束******************************** //
// 发送请求
goodsAddRet, _, err := addGoods(logUuid, goodsAdd)
if err != nil {
return taskMsg, fmt.Errorf("商品提交 %v", err)
}
// 获取商品提交的商品详情
goodsCommitDetail, _, getGoodsCommitDetailErr := getGoodsCommitDetail(goodsAddRet.Response.GoodsCommitID, goodsAddRet.Response.GoodsID)
if getGoodsCommitDetailErr != nil {
return taskMsg, fmt.Errorf("获取商品提交的商品详情失败 %w", getGoodsCommitDetailErr)
}
//拼接接口调用成功的返回数据
if len(goodsCommitDetail.GoodsCommitDetailResponse.SkuList) > 0 {
//taskMsg.Detail.SkuCode = goodsCommitDetail.GoodsCommitDetailResponse.SkuList[0].OutSkuSn
taskMsg.Detail.SkuId = goodsCommitDetail.GoodsCommitDetailResponse.SkuList[0].SkuID
}
taskMsg.Detail.GoodsId = goodsAddRet.Response.GoodsID
taskMsg.Detail.ReturnId = goodsAddRet.Response.GoodsCommitID
taskMsg.Detail.OutGoodsId = goodsAdd.OutGoodsId
taskMsg.Detail.Img = goodsAdd.CarouselGallery[0]
return taskMsg, nil
}