daShangDao_planA/planB/dispatcher/kongfuzi/kongfizi.go
97694732@qq.com 422ddfbeba 各种修改
2026-06-29 08:58:33 +08:00

1635 lines
58 KiB
Go
Raw 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 kongfuzi
import (
"encoding/json"
"errors"
"fmt"
"planA/planB/initialization/golabl"
"planA/planB/logic"
"planA/planB/modules/logs"
"planA/planB/service"
"planA/planB/tool"
planBTypeKfz "planA/planB/type/kfz"
planAType "planA/type"
"strconv"
"strings"
"time"
)
type KongFuZi struct {
}
// NewKongFuZi 创建孔夫子平台
func NewKongFuZi() *KongFuZi {
return &KongFuZi{}
}
// AddGoodsTask 添加商品
// @param taskMsg 任务内容
// @return string body 信息
// @return error 错误
func (kongFuZi *KongFuZi) 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)
}
// GetGoodsTask 获取商品
// @return string body 信息
// @return error 错误
func (kongFuZi *KongFuZi) GetGoodsTask() (string, error) {
// 生成唯一请求标识(用于出错精准查询日志)
logUuid, generateUUIDErr := tool.GenerateUUID()
if generateUUIDErr != nil {
return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr)
}
const pageSize = 100
const maxRecordsPerRange = 10000 // 每个时间范围最多获取10000条
const timeRangeDays = 30 // 时间范围天数
var lastAddTime 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, getLastGoodsAddTimeErr := service.GetTaskBodyWaitLast()
if getLastGoodsAddTimeErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, getLastGoodsAddTimeErr)
}
// 解析 lastBodyWaitData 到结构体
var lastBodyWaitData planAType.TaskBody
unmarshalErr := json.Unmarshal([]byte(lastBodyWaitDataJson), &lastBodyWaitData)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, unmarshalErr)
}
// 将数据的添加时间给到 lastAddTime
lastAddTime = lastBodyWaitData.BookInfo.Price
// 第二阶段使用时间范围分批拉取goroutine + 120秒超时防止 DLL 永久阻塞)
phaseTwoResultCh := make(chan error, 1)
go func() {
fmt.Println("[DEBUG] PhaseTwoGoods goroutine started (exist=true branch)")
phaseTwoResultCh <- PhaseTwoGoods(pageSize, &totalFetched, &lastAddTime, maxRecordsPerRange, timeRangeDays)
}()
// 等待 PhaseTwoGoods 完成或超时
select {
case phaseTwoErr := <-phaseTwoResultCh:
fmt.Printf("[DEBUG] PhaseTwoGoods completed (exist=true): %v\n", phaseTwoErr)
if phaseTwoErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, phaseTwoErr)
}
case <-time.After(120 * time.Second):
fmt.Println("[ERROR] PhaseTwoGoods 超时120秒可能是 DLL 函数 KongfzShopItemList 不可用")
return "", fmt.Errorf("PhaseTwoGoods 超时120秒可能是 DLL 函数 KongfzShopItemList 不可用")
}
} else {
// 第一阶段:获取第一页商品数量作为总数
firstTimeGoodsErr := phaseOneGoods(pageSize, &totalFetched)
if firstTimeGoodsErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, firstTimeGoodsErr)
}
// 计算起始时间当前时间往前180天
lastAddTime = time.Now().Unix() - 180*24*60*60
// 第二阶段使用时间范围分批拉取goroutine + 120秒超时防止 DLL 永久阻塞)
phaseTwoResultCh := make(chan error, 1)
go func() {
fmt.Println("[DEBUG] PhaseTwoGoods goroutine started")
phaseTwoResultCh <- PhaseTwoGoods(pageSize, &totalFetched, &lastAddTime, maxRecordsPerRange, timeRangeDays)
}()
// 等待 PhaseTwoGoods 完成或超时
select {
case phaseTwoErr := <-phaseTwoResultCh:
fmt.Printf("[DEBUG] PhaseTwoGoods completed: %v\n", phaseTwoErr)
if phaseTwoErr != nil {
return tool.ReturnErr(logUuid, planAType.TaskBody{}, golabl.TaskType, phaseTwoErr)
}
case <-time.After(120 * time.Second):
fmt.Println("[ERROR] PhaseTwoGoods 超时120秒可能是 DLL 函数 KongfzShopItemList 不可用")
return "", fmt.Errorf("PhaseTwoGoods 超时120秒可能是 DLL 函数 KongfzShopItemList 不可用")
}
}
//更新状态为推送中
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)
tool.LoggingMiddleware(logs.LOG_LEVEL_INFO, statsLogMsg)
return tool.ReturnSuccess(planAType.TaskBody{})
}
// OperationGoodsTask 操作商品
// @param taskMsg 任务内容
// @return string body 信息
// @return string error 错误
func (kongFuZi *KongFuZi) OperationGoodsTask(taskMsg planAType.TaskBody) (string, error) {
//生成唯一请求标识(用于出错精准查询日志)
logUuid, generateUUIDErr := tool.GenerateUUID()
if generateUUIDErr != nil {
return "", fmt.Errorf("生成唯一请求标识失败: %v", generateUUIDErr)
}
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) //{"book_info":{"isbn":"9787800822780","detail":{"price":5000,"shipping_cost":5000,"status":6,"stock":1 }}
if publishGoodsErr != nil {
return "", publishGoodsErr
}
return tool.ReturnSuccess(taskMsg)
case 7:
//删除商品
logic.DelTask(taskMsg)
//延迟 10 秒
time.Sleep(10 * time.Second)
//发布商品
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 增量库存
// @param taskMsg 任务内容
// @return string body 信息
// @return error 错误
func (kongFuZi *KongFuZi) 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 := kongFuZi.AddGoodsTask(taskMsg)
if addGoodsTaskErr != nil {
return "", addGoodsTaskErr
}
return task, nil
} else {
// 当前任务的品相和价格
taskCondition := taskMsg.Detail.Condition
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 // 价格差绝对值
}
// 记录不匹配原因
hasConditionMismatch := false
hasPriceMismatch := false
var firstMismatchReason string
for _, item := range getGoodsByShopIdAndIsbn.Data {
// 解析品相
itemQuality, _ := strconv.ParseInt(item.Quality, 10, 64)
// 解析价格(单位:分)
itemPrice, _ := strconv.ParseInt(item.TotalPrice, 10, 64)
// 解析库存
itemStock, _ := strconv.ParseInt(item.Stock, 10, 64)
// 计算价格差(绝对值)
priceDiff := abs(itemPrice - taskPrice)
// 品相不相等
if itemQuality != taskCondition {
hasConditionMismatch = true
if firstMismatchReason == "" {
firstMismatchReason = fmt.Sprintf("商品[%s]品相不匹配: 任务品相=%d, 商品品相=%d", item.TrilateralId, taskCondition, itemQuality)
}
continue
}
// 价格相差1元以上
if priceDiff >= priceDiffThreshold {
hasPriceMismatch = true
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. 所有品相不相等 → 重新发布matchedItems为空因为没有品相相等的
// 2. 所有价格相差1元以上 → 重新发布matchedItems为空因为没有价格差<1元的
// 3. 否则 → 找到价格相差最小的增加库存,如果多个最小差价一样则对第一条增加库存
if len(matchedItems) == 0 {
// 所有商品都不满足条件(品相不相等 或 价格相差≥1元→ 重新发布
if hasConditionMismatch && hasPriceMismatch {
fmt.Printf("[重新发布] %s\n", firstMismatchReason)
} else if hasConditionMismatch {
fmt.Printf("[重新发布] %s\n", firstMismatchReason)
} else {
fmt.Printf("[重新发布] %s\n", firstMismatchReason)
}
task, addGoodsTaskErr := kongFuZi.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)
}
// 获取商品详情
getGoodsListReq := planBTypeKfz.GetGoodsListReq{
ItemId: targetItem.TrilateralId,
}
getGoodsListReqJson, marshalErr := json.Marshal(getGoodsListReq)
if marshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr)
}
// 发送请求
goodsListResp, goodsListRespErr := sendGoodsListRequest(string(getGoodsListReqJson))
if goodsListRespErr != nil {
return "", fmt.Errorf("获取商品列表失败 %v", goodsListRespErr)
}
//检验商品数量
if len(goodsListResp.SuccessResponse.List) == 0 {
//在孔夫子中未查询到该商品id 重新新发布
task, addGoodsTaskErr := kongFuZi.AddGoodsTask(taskMsg)
if addGoodsTaskErr != nil {
return "", addGoodsTaskErr
}
// 记录特殊错误
//将task 转为 结构体
taskBody := planAType.TaskBody{}
unmarshalErr := json.Unmarshal([]byte(task), &taskBody)
if unmarshalErr != nil {
return "", fmt.Errorf("转换taskBody失败: %v", unmarshalErr)
}
taskBody.Detail.Error = fmt.Sprintf("未查询到商品id: %v", trilateralId) + " " + taskBody.Detail.Error
//将taskBody 转为json
taskByte, marshalErrs := json.Marshal(taskBody)
if marshalErrs != nil {
return "", fmt.Errorf("转换taskBody失败: %v", marshalErrs)
}
task = string(taskByte)
return task, nil
}
//增量修改库存
taskMsg.Detail.GoodsId = trilateralId
taskMsg.Detail.Stock = taskMsg.Detail.Stock + int32(goodsListResp.SuccessResponse.List[0].Number)
quantity, updateGoodsQuantityErr := executeGoodsUpdateStock(logUuid, taskMsg)
if updateGoodsQuantityErr != nil {
return "", updateGoodsQuantityErr
}
return quantity, nil
}
}
// abs 返回绝对值
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
func (kongFuZi *KongFuZi) SetGoodsTask() string {
//TODO implement me
return ""
}
// *******************************私有方法************************************ //
// UploadImageToKfz 将图片上传到孔夫子图片空间
// @param imgUrl 图片地址
// @return string 图片地址
// @return error 错误
func UploadImageToKfz(imgUrl string) (string, error) {
//将图片保存到本地
imgTempUrl, saveBase64ImageByDateErr := tool.SaveBase64ImageByDate(imgUrl, golabl.Config.FileUrl.KfzImgTempUrl)
if saveBase64ImageByDateErr != nil {
return "", fmt.Errorf("保存图片失败 %v", saveBase64ImageByDateErr)
}
//将图片上传到孔夫子
upload, kfzGoodsImageUploadErr := golabl.KfzDll.KfzGoodsImageUpload(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token, imgTempUrl)
if kfzGoodsImageUploadErr != nil {
return "", fmt.Errorf("上传图片失败1 %v", kfzGoodsImageUploadErr)
}
// 解析数据
var uploadData planBTypeKfz.UploadImgRet
unmarshalErr := json.Unmarshal([]byte(upload), &uploadData)
if unmarshalErr != nil {
return "", fmt.Errorf("解析上传图片数据失败 %v", unmarshalErr)
}
// 修复:先判断 ErrorResponse 是否为 nil
if uploadData.ErrorResponse != nil && uploadData.ErrorResponse.Code != 0 {
fmt.Println("错误码:", uploadData.ErrorResponse.Code)
return "", fmt.Errorf("上传图片失败2 错误码 %v 错误描述 %v", uploadData.ErrorResponse.Code, uploadData.ErrorResponse.SubMsg)
}
// 修复:判断 SuccessResponse 是否为 nil
if uploadData.SuccessResponse == nil {
return "", fmt.Errorf("上传图片成功但返回数据为空")
}
return uploadData.SuccessResponse.Image.Url, nil
}
// 商品新增
// @param logUuid 日志ID
// @param goodsInfo 商品信息
// @return GoodsAddResponseWrapper 商品新增结果
// @return string 商品新增结果json
// @return error 错误信息
func addGoods(logUuid string, goodsInfoStr string) (planBTypeKfz.AddGoodsRet, string, error) {
var goodsAdd planBTypeKfz.AddGoodsRet
//发送请求
goodsAddStr, publishGoodsErr := golabl.KfzDll.PublishGoods(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, 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("孔夫子 GoodsAdd 错误:" + goodsAddStr)
}
if publishGoodsErr != nil {
return goodsAdd, "", publishGoodsErr
}
jsonUnmarshal := json.Unmarshal([]byte(goodsAddStr), &goodsAdd)
if jsonUnmarshal != nil {
return goodsAdd, "", fmt.Errorf("解析孔夫子 GoodsAdd 接口返回json失败: %v", jsonUnmarshal)
}
return goodsAdd, goodsAddStr, nil
}
// phaseOneGoods 第一阶段拉取商品信息(获取总数)
// @param pageSize 每页数量
// @param totalFetched 获取到的商品总数
// @return error 错误信息
func phaseOneGoods(pageSize int, totalFetched *int) error {
// 第一阶段:获取第一页商品,获取总数
getGoodsListReq := planBTypeKfz.GetGoodsListReq{
Type: "sale", // 第一阶段不传时间范围,获取所有商品的第一页
PageNum: 1,
PageSize: pageSize,
SortOrder: "addTime",
SortType: "ASC",
}
getGoodsListReqJson, marshalErr := json.Marshal(getGoodsListReq)
if marshalErr != nil {
return fmt.Errorf("参数转换错误 %v", marshalErr)
}
// 发送请求
goodsListResp, goodsListRespErr := sendGoodsListRequest(string(getGoodsListReqJson))
if goodsListRespErr != nil {
return fmt.Errorf("获取商品列表失败: %v", goodsListRespErr)
}
if goodsListResp.SuccessResponse == nil {
return fmt.Errorf("孔夫子商品列表返回数据为空")
}
// 更新进度总数
totalCount := goodsListResp.SuccessResponse.Total
fmt.Println("总数 ", totalCount)
if updateTaskHeaderErr := service.SetTaskCount(strconv.Itoa(totalCount)); updateTaskHeaderErr != nil {
return updateTaskHeaderErr
}
// 收集商品数据并写入 body_wait
for _, goods := range goodsListResp.SuccessResponse.List {
*totalFetched++
// 将商品转为 JSON 存入 Detail.Error
jsonData, jsonMarshalErr := json.Marshal(goods)
if jsonMarshalErr != nil {
return fmt.Errorf("将商品转为json失败: %v\n", jsonMarshalErr)
}
// 解析添加时间转为时间戳(秒)
addTimeUnix := parseAddTimeToUnix(goods.AddTime)
// 构建 bodyWait
bodyWait := planAType.TaskBody{
BookInfo: planAType.BookInfo{
Isbn: goods.Isbn,
BookName: goods.ItemName,
Author: goods.Author,
Publishing: goods.Press,
PublicationDate: goods.PubDate,
Binding: goods.Binding,
PagesCount: int64(goods.PageNum),
Format: 0,
WordsCount: 0,
Price: addTimeUnix, // 使用添加时间作为 Price 字段存储
},
Detail: planAType.TaskDetail{
Error: string(jsonData),
GoodsId: goods.ItemId,
Stock: int32(goods.Number),
},
}
// 将 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
}
}
fmt.Printf("第一阶段 - 总数:%v 当前已取出:%v \n", totalCount, *totalFetched)
return nil
}
// PhaseTwoGoods 第二阶段拉取商品信息(按时间范围分批拉取)
// @param pageSize 每页数量
// @param totalFetched 获取到的商品总数
// @param lastAddTime 最后一条数据的添加时间(秒级时间戳)
// @param maxRecordsPerRange 每次请求的时间范围最多获取的记录数
// @param timeRangeDays 时间范围天数
// @return error 错误信息
func PhaseTwoGoods(pageSize int, totalFetched *int, lastAddTime *int64, maxRecordsPerRange int, timeRangeDays int) error {
fmt.Printf("[DEBUG] PhaseTwoGoods called, lastAddTime=%d, currentTime=%d\n", *lastAddTime, time.Now().Unix())
if *lastAddTime > 0 {
currentAddTimeFrom := *lastAddTime
maxLoopCount := 100 // 最大循环次数保护
loopCount := 0
var lastPageGoodsList []planBTypeKfz.KfzGoodsItem // 记录上一页的商品列表
// 去重:加载 body_wait 中已有的 GoodsId避免 phaseOneGoods 和 PhaseTwoGoods 重复写入同一商品
bodyWaitCount, _ := service.GetTaskBodyWaitCount()
existingGoodsIds := make(map[int64]bool)
if bodyWaitCount > 0 {
existingPage := 1
existingPageSize := 1000
for {
existingList, _ := service.GetTaskBodyWaitList(existingPage, existingPageSize)
if len(existingList) == 0 {
break
}
for _, v := range existingList {
var taskBody planAType.TaskBody
if err := json.Unmarshal([]byte(v), &taskBody); err == nil {
existingGoodsIds[taskBody.Detail.GoodsId] = true
}
}
existingPage++
}
fmt.Printf("[dedup] PhaseTwoGoods: 加载 body_wait 中已有 %d 个商品ID用于去重\n", len(existingGoodsIds))
}
for loopCount < maxLoopCount {
loopCount++
// 检查开始时间是否已超过当前时间
if currentAddTimeFrom > time.Now().Unix() {
fmt.Printf("开始时间 %d 已超过当前时间,停止获取\n", currentAddTimeFrom)
break
}
// 每次循环都重新设置结束时间为开始时间+timeRangeDays天
currentAddTimeEnd := currentAddTimeFrom + int64(timeRangeDays)*24*60*60
fmt.Printf("开始获取时间范围: %d (%s) 到 %d (%s)\n",
currentAddTimeFrom, time.Unix(currentAddTimeFrom, 0).Format("2006-01-02 15:04:05"),
currentAddTimeEnd, time.Unix(currentAddTimeEnd, 0).Format("2006-01-02 15:04:05"))
currentPage := 1
batchGoodsCount := 0
lastItemAddTime := int64(0)
hasDataInRange := false
lastPageGoodsList = nil // 重置上一页商品列表
// 在当前时间范围内分页获取数据
for {
getGoodsListReq := planBTypeKfz.GetGoodsListReq{
Type: "sale",
PageNum: currentPage,
PageSize: pageSize,
AddTimeBegin: formatUnixTime(currentAddTimeFrom),
AddTimeEnd: formatUnixTime(currentAddTimeEnd),
SortOrder: "addTime",
SortType: "ASC",
}
getGoodsListReqJson, marshalErr := json.Marshal(getGoodsListReq)
if marshalErr != nil {
return fmt.Errorf("参数转换错误 %v", marshalErr)
}
goodsListResp, goodsListRespErr := sendGoodsListRequest(string(getGoodsListReqJson))
if goodsListRespErr != nil {
// DLL 不可用时直接退出第二阶段,跳到去重写入步骤
fmt.Printf("获取商品列表失败,退出第二阶段: %v\n", goodsListRespErr)
return nil
}
if goodsListResp.SuccessResponse == nil || len(goodsListResp.SuccessResponse.List) == 0 {
// 如果当前页是第一页且没有数据
if currentPage == 1 {
// 整个时间范围都没有数据,直接推进到结束时间
currentAddTimeFrom = currentAddTimeEnd
fmt.Printf("时间范围 %d - %d 内无数据,推进开始时间到: %d\n", currentAddTimeFrom-int64(timeRangeDays)*24*60*60, currentAddTimeEnd, currentAddTimeFrom)
break
}
// 当前页没有数据,但上一页有数据
if len(lastPageGoodsList) > 0 {
lastItemOfLastPage := lastPageGoodsList[len(lastPageGoodsList)-1]
newStartTime := parseAddTimeToUnix(lastItemOfLastPage.AddTime)
if newStartTime > currentAddTimeFrom {
currentAddTimeFrom = newStartTime
fmt.Printf("当前页无数据,使用上一页最后一条商品时间作为新开始时间: %d\n", currentAddTimeFrom)
} else {
currentAddTimeFrom = newStartTime + 1
fmt.Printf("当前页无数据时间相同将时间加1秒推进: %d\n", currentAddTimeFrom)
}
} else {
currentAddTimeFrom = currentAddTimeEnd
fmt.Printf("当前页无数据且无上一页数据,将开始时间推进到结束时间: %d\n", currentAddTimeFrom)
}
hasDataInRange = false
break
}
// 有数据,记录上一页的商品列表
lastPageGoodsList = goodsListResp.SuccessResponse.List
hasDataInRange = true
// 收集商品数据并统计
for _, goods := range goodsListResp.SuccessResponse.List {
*totalFetched++
// 将商品转为 JSON 存入 Detail.Error
jsonData, jsonMarshalErr := json.Marshal(goods)
if jsonMarshalErr != nil {
return fmt.Errorf("将商品转为json失败: %v\n", jsonMarshalErr)
}
// 解析添加时间转为时间戳(秒)
addTimeUnix := parseAddTimeToUnix(goods.AddTime)
// 构建 bodyWait
bodyWait := planAType.TaskBody{
BookInfo: planAType.BookInfo{
Isbn: goods.Isbn,
BookName: goods.ItemName,
Author: goods.Author,
Publishing: goods.Press,
PublicationDate: goods.PubDate,
Binding: goods.Binding,
PagesCount: int64(goods.PageNum),
Format: 0,
WordsCount: 0,
Price: addTimeUnix,
},
Detail: planAType.TaskDetail{
Error: string(jsonData),
GoodsId: goods.ItemId,
Stock: int32(goods.Number),
},
}
bodyWaitJson, jsonMarshalErr := json.Marshal(bodyWait)
if jsonMarshalErr != nil {
return fmt.Errorf("将bodyWait转为json失败: %v\n", jsonMarshalErr)
}
// 去重:检查 GoodsId 是否已存在于 body_waitphaseOneGoods 可能已写过)
if existingGoodsIds[goods.ItemId] {
fmt.Printf("[dedup] 跳过重复商品 GoodsId=%d (%s)\n", goods.ItemId, goods.ItemName)
continue
}
existingGoodsIds[goods.ItemId] = true
addTaskToBodyWaitErr := service.AddTaskToBodyWait(string(bodyWaitJson))
if addTaskToBodyWaitErr != nil {
return addTaskToBodyWaitErr
}
}
batchGoodsCount += len(goodsListResp.SuccessResponse.List)
// 记录最后一条商品的添加时间
lastItem := goodsListResp.SuccessResponse.List[len(goodsListResp.SuccessResponse.List)-1]
lastItemAddTime = parseAddTimeToUnix(lastItem.AddTime)
fmt.Printf("第二阶段 - 当前时间范围已获取: %d 条,累计总数: %d当前页码: %d最后商品时间: %d\n",
batchGoodsCount, *totalFetched, currentPage, lastItemAddTime)
// 判断是否需要结束当前时间范围
if batchGoodsCount >= maxRecordsPerRange || len(goodsListResp.SuccessResponse.List) < pageSize {
if lastItemAddTime == currentAddTimeFrom {
currentAddTimeFrom = lastItemAddTime + 1
fmt.Printf("最后商品时间与开始时间相同推进1秒: %d -> %d\n", lastItemAddTime, currentAddTimeFrom)
} else {
currentAddTimeFrom = lastItemAddTime
}
fmt.Printf("当前时间范围已获取 %d 条数据,准备进入下一时间范围 更新开始时间为: %d \n", batchGoodsCount, currentAddTimeFrom)
break
}
currentPage++
// 获取 footer 信息
if getTaskFooterErr := service.GetTaskFooter(); getTaskFooterErr != nil {
return getTaskFooterErr
}
con := int64(len(goodsListResp.SuccessResponse.List))
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)
}
// 判断是否需要继续循环
if !hasDataInRange {
if currentAddTimeFrom > time.Now().Unix() {
fmt.Printf("开始时间 %d 已超过当前时间,停止获取\n", currentAddTimeFrom)
break
}
fmt.Printf("继续下一轮查询,新起始时间: %d\n", currentAddTimeFrom)
continue
}
if batchGoodsCount < maxRecordsPerRange && currentAddTimeFrom > 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
}
// deduplicateToBodyOver 读取body_wait去重复后写入到body_over中
// @param duplicateCount 重复商品数量
// @param uniqueCount 不重复商品数量
// @return error 错误信息
func deduplicateToBodyOver(duplicateCount *int, uniqueCount *int) error {
page := 1
pageSize := 1000
var dataList []planBTypeKfz.KfzGoodsItem
// 在循环外维护一个已处理的商品 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 {
var taskBody planAType.TaskBody
jsonUnmarshalErr := json.Unmarshal([]byte(v), &taskBody)
if jsonUnmarshalErr != nil {
return fmt.Errorf("将json转为结构体失败: %v\n", jsonUnmarshalErr)
}
if !processedGoodsIds[taskBody.Detail.GoodsId] {
processedGoodsIds[taskBody.Detail.GoodsId] = true
*uniqueCount++
var kfzGoodsItem planBTypeKfz.KfzGoodsItem
jsonUnmarshalErr = json.Unmarshal([]byte(taskBody.Detail.Error), &kfzGoodsItem)
if jsonUnmarshalErr != nil {
return fmt.Errorf("将json转为结构体失败: %v\n", jsonUnmarshalErr)
}
dataList = append(dataList, kfzGoodsItem)
// 写入到body_over
taskBody.Detail.Status = 1
addTaskToBodyOverErr := service.AddTaskToBodyOver(taskBody, []string{"body_over", "body_backup"})
if addTaskToBodyOverErr != nil {
return addTaskToBodyOverErr
}
} else {
*duplicateCount++
}
}
// 将获取的数据推送写入店铺商品数据接口(待实现)
if len(dataList) > 0 {
pageFlag := 0
//如果是最后一批数据则将 pageFlag 设置为 1
if page == int(pageTotal) {
pageFlag = 1
}
pushShopGoodsDataRet, pushShopGoodsDataErr := pushShopGoodsData(dataList, pageFlag)
if pushShopGoodsDataErr != nil {
return pushShopGoodsDataErr
}
var pushShopGoodsDatas planBTypeKfz.AddGoodsToErpRet
jsonUnmarshalErr := json.Unmarshal([]byte(pushShopGoodsDataRet), &pushShopGoodsDatas)
if jsonUnmarshalErr != nil {
return fmt.Errorf("将json转为结构体失败: %v\n", jsonUnmarshalErr)
}
if pushShopGoodsDatas.Code != 200 {
return fmt.Errorf("推送商品数据失败: %v\n", pushShopGoodsDatas.Msg)
}
fmt.Printf("当前页 %v 总页数 %v 当前数据量 %v 总数据量 %v \n", page, pageTotal, len(dataList), num)
}
num = num + len(dataList)
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
}
// 清空 dataList
dataList = []planBTypeKfz.KfzGoodsItem{}
// 暂停1秒
time.Sleep(1 * time.Second)
}
// 删除body_wait
deleteTaskBodyWaitErr := service.DeleteTaskBodyWait()
if deleteTaskBodyWaitErr != nil {
return deleteTaskBodyWaitErr
}
return nil
}
// sendGoodsListRequest 发送商品列表请求
// @param reqJson 请求JSON
// @return GetGoodsListResp 响应结构体
// @return error 错误
func sendGoodsListRequest(reqJson string) (*planBTypeKfz.GetGoodsListResp, error) {
// 使用超时包装,防止 DLL 调用永久阻塞
// 使用容量为1的缓冲通道避免 goroutine 泄露
resultCh := make(chan string, 1)
errCh := make(chan error, 1)
go func() {
fmt.Println("[DEBUG] goroutine started, about to call GetGoodsList...")
goodsListStr, getGoodsListErr := golabl.KfzDll.GetGoodsList(
golabl.Config.KfzConfig.AppId,
golabl.Config.KfzConfig.AppSecret,
golabl.Task.Header.ShopMsg.Token,
reqJson)
fmt.Println("[DEBUG] GetGoodsList returned, err:", getGoodsListErr)
if getGoodsListErr != nil {
errCh <- getGoodsListErr
return
}
// 使用 select+default 确保超时后 goroutine 能安全退出
select {
case resultCh <- goodsListStr:
default:
// channel 已满(超时已触发),直接退出
fmt.Println("[DEBUG] resultCh full, goroutine exiting")
}
}()
// 等待结果或超时30秒
select {
case goodsListStr := <-resultCh:
// 正常返回
var goodsListResp planBTypeKfz.GetGoodsListResp
fmt.Println(goodsListStr)
unmarshalErr := json.Unmarshal([]byte(goodsListStr), &goodsListResp)
if unmarshalErr != nil {
fmt.Printf("解析孔夫子商品列表返回json失败: %v [孔夫子数据:%v]", unmarshalErr, goodsListStr)
return nil, fmt.Errorf("解析孔夫子商品列表返回json失败: %v [孔夫子数据:%v]", unmarshalErr, goodsListStr)
}
return &goodsListResp, nil
case err := <-errCh:
return nil, err
case <-time.After(30 * time.Second):
return nil, fmt.Errorf("DLL 调用 GetGoodsList 超时30秒可能是 DLL 函数不可用或网络问题")
}
}
// parseAddTimeToUnix 获取商品的添加时间Unix时间戳
// @param addTime 添加时间Unix时间戳秒级
// @return int64 Unix时间戳
func parseAddTimeToUnix(addTime int64) int64 {
return addTime
}
// formatUnixTime 将Unix时间戳转为孔夫子时间格式字符串
// @param unixTime Unix时间戳
// @return string 时间字符串格式yyyy-MM-dd HH:mm:ss
func formatUnixTime(unixTime int64) string {
if unixTime <= 0 {
return ""
}
return time.Unix(unixTime, 0).Format("2006-01-02 15:04:05")
}
// 上架
func executeGoodsLaunch(logUuid string, taskMsg planAType.TaskBody) (string, error) {
// 上架商品
launchGoodsInfo := planBTypeKfz.Product{ItemId: strconv.FormatInt(taskMsg.Detail.GoodsId, 10)}
//转为json
jsonData, marshalErr := json.Marshal(launchGoodsInfo)
if marshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr)
}
var launchGoods planBTypeKfz.ProductRet
launchGoodsStr, kfzPutOnSaleErr := golabl.KfzDll.PutOnSale(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token, string(jsonData))
if kfzPutOnSaleErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, kfzPutOnSaleErr)
}
unmarshalErr := json.Unmarshal([]byte(launchGoodsStr), &launchGoods)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr)
}
if launchGoods.ErrorResponse != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("上架商品失败 %s", launchGoods.ErrorResponse))
}
return tool.ReturnSuccess(taskMsg)
}
// 下架
func executeGoodsDownShelf(logUuid string, taskMsg planAType.TaskBody) (string, error) {
// 下架商品
launchGoodsInfo := planBTypeKfz.Product{ItemId: strconv.FormatInt(taskMsg.Detail.GoodsId, 10)}
//转为json
jsonData, marshalErr := json.Marshal(launchGoodsInfo)
if marshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr)
}
var launchGoods planBTypeKfz.ProductRet
launchGoodsStr, kfzPutOffSaleErr := golabl.KfzDll.PutOffSale(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token, string(jsonData))
if kfzPutOffSaleErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, kfzPutOffSaleErr)
}
unmarshalErr := json.Unmarshal([]byte(launchGoodsStr), &launchGoods)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr)
}
if launchGoods.ErrorResponse != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("下架商品失败 %s", launchGoods.ErrorResponse))
}
return tool.ReturnSuccess(taskMsg)
}
// 改价格
func executeGoodsUpdatePrice(logUuid string, taskMsg planAType.TaskBody) (string, error) {
// 价格0 不能发布
if taskMsg.Detail.Price == 0 {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("拼多多商品 价格不能为0"))
}
//将价格由分转为元
price := tool.FenToYuanFloat64(taskMsg.Detail.Price)
// 改价格
launchGoodsInfo := planBTypeKfz.UpdatePriceReq{ItemId: strconv.FormatInt(taskMsg.Detail.GoodsId, 10), Price: price}
//转为json
jsonData, marshalErr := json.Marshal(launchGoodsInfo)
if marshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr)
}
var launchGoods planBTypeKfz.UpdatePriceRet
launchGoodsStr, kfzUpdateGoodsPriceErr := golabl.KfzDll.UpdateGoodsPrice(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token, string(jsonData))
if kfzUpdateGoodsPriceErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, kfzUpdateGoodsPriceErr)
}
unmarshalErr := json.Unmarshal([]byte(launchGoodsStr), &launchGoods)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr)
}
if launchGoods.ErrorResponse != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("改价格商品失败 %s", launchGoods.ErrorResponse))
}
return tool.ReturnSuccess(taskMsg)
}
// 改库存
func executeGoodsUpdateStock(logUuid string, taskMsg planAType.TaskBody) (string, error) {
// 改库存
launchGoodsInfo := planBTypeKfz.UpdateStockReq{ItemId: strconv.FormatInt(taskMsg.Detail.GoodsId, 10), Number: int64(taskMsg.Detail.Stock)}
//转为json
jsonData, marshalErr := json.Marshal(launchGoodsInfo)
if marshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, marshalErr)
}
var launchGoods planBTypeKfz.ProductRet
launchGoodsStr, kfzUpdateGoodsStockErr := golabl.KfzDll.UpdateGoodsStock(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token, string(jsonData))
if kfzUpdateGoodsStockErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, kfzUpdateGoodsStockErr)
}
unmarshalErr := json.Unmarshal([]byte(launchGoodsStr), &launchGoods)
if unmarshalErr != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, unmarshalErr)
}
if launchGoods.ErrorResponse != nil {
return tool.ReturnErr(logUuid, taskMsg, golabl.TaskType, fmt.Errorf("改库存商品失败 %s", launchGoods.ErrorResponse))
}
taskMsg.Detail.Error = "增加库存成功!"
return tool.ReturnSuccess(taskMsg)
}
// 添加商品到ERP接口
func pushShopGoodsData(shopGoodsList []planBTypeKfz.KfzGoodsItem, pageFlag int) (string, error) {
// 拼接商品列表
var goodsList []map[string]string
for _, shopGoods := range shopGoodsList {
goods := map[string]string{
"isbn": shopGoods.Isbn,
"itemName": shopGoods.ItemName,
"price": fmt.Sprintf("%.2f", shopGoods.Price),
"quality": strconv.Itoa(int(shopGoods.Quality)),
"author": shopGoods.Author,
"press": shopGoods.Press,
"pubDate": shopGoods.PubDate,
"itemId": strconv.FormatInt(shopGoods.ItemId, 10),
"addTime": strconv.FormatInt(shopGoods.AddTime, 10),
"beginSaleTime": strconv.FormatInt(int64(shopGoods.BeginSaleTime), 10),
"isDraft": strconv.Itoa(shopGoods.IsDraft),
"discount": strconv.Itoa(shopGoods.Discount),
"stock": strconv.Itoa(shopGoods.Number),
"myCatId": strconv.Itoa(shopGoods.MyCatId),
"bearShipping": shopGoods.BearShipping,
"weight": fmt.Sprintf("%.2f", shopGoods.Weight),
"catId": strconv.FormatInt(int64(shopGoods.CatId), 10),
"isNewBook": strconv.Itoa(shopGoods.IsNewBook),
"bizType": strconv.Itoa(shopGoods.BizType),
"certifyStatus": shopGoods.CertifyStatus,
"weightPiece": fmt.Sprintf("%.2f", shopGoods.WeightPiece),
"mouldId": strconv.Itoa(shopGoods.MouldId),
"booklibId": strconv.Itoa(int(shopGoods.BooklibId)),
"isOnSale": strconv.Itoa(shopGoods.IsOnSale),
"isDelete": strconv.Itoa(shopGoods.IsDelete),
"updateTime": shopGoods.UpdateTime,
"endSaleTime": strconv.FormatInt(int64(shopGoods.EndSaleTime), 10),
"userId": strconv.Itoa(int(shopGoods.UserId)),
"imgUrl": golabl.Config.FileUrl.KfzImgHttpUrl + shopGoods.ImgUrl,
"oriPrice": fmt.Sprintf("%.2f", shopGoods.OriPrice),
"itemSn": shopGoods.ItemSn,
}
goodsList = append(goodsList, goods)
}
shopGoodslistData := map[string]interface{}{
"total": strconv.Itoa(len(goodsList)),
"list": goodsList,
}
data := map[string]interface{}{
"shopId": golabl.Task.Header.ShopId,
"shopType": golabl.Task.Header.ShopType,
"token": golabl.Task.Header.ShopMsg.Token,
"sycFlag": 1,
"taskId": golabl.Task.TaskId,
"shopGoodsResponse": shopGoodslistData, // 关键修改
"pageFlag": pageFlag,
}
//转为json
jsonData, marshalErr := json.Marshal(data)
if marshalErr != nil {
return "", marshalErr
}
response, err := tool.PostJSON(golabl.Config.FileUrl.KfzAddGoodsUrl, string(jsonData))
if err != nil {
return "", err
}
return response, 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
url := "http://127.0.0.1:8095"
tool.HttpGetRequest(url)
var jsonData []byte
var marshalErr error
var img string
var skuCode string
fmt.Println(taskMsg.BookInfo.Isbn)
if strings.HasPrefix(taskMsg.BookInfo.Isbn, "678") {
//模板13 无isbn
goodsAdd13, err := template13(taskMsg)
if err != nil {
return planAType.TaskBody{}, err
}
//将 goodsAdd 转为json字符串
jsonData, marshalErr = json.Marshal(goodsAdd13)
if marshalErr != nil {
return taskMsg, fmt.Errorf("构建商品参数失败 %v", marshalErr)
}
img = goodsAdd13.ImgUrl
skuCode = goodsAdd13.ItemSn
} else {
//模板17 普通图书
goodsAdd17, err := template17(taskMsg)
if err != nil {
return planAType.TaskBody{}, err
}
//将 goodsAdd 转为json字符串
jsonData, marshalErr = json.Marshal(goodsAdd17)
if marshalErr != nil {
return taskMsg, fmt.Errorf("构建商品参数失败 %v", marshalErr)
}
img = goodsAdd17.ImgUrl
skuCode = goodsAdd17.ItemSn
}
// 构建商品编码
outGoodsId := ""
if taskMsg.Detail.OutGoodsId != "" {
outGoodsId = taskMsg.Detail.OutGoodsId
} else {
outGoodsId = taskMsg.BookInfo.Isbn
}
// 新增商品
goods, _, addGoodsErr := addGoods(logUuid, string(jsonData))
if addGoodsErr != nil {
// 如果错误信息包含 "该图书必须使用图书条目库上传",则修改错误信息
if strings.Contains(addGoodsErr.Error(), "该图书必须使用图书条目库上传") {
addGoodsErr = fmt.Errorf("该图书可能涉及政治请手动上传")
}
return taskMsg, fmt.Errorf("新增商品错误 %v", addGoodsErr)
}
//判断错误
if goods.ErrorResponse != nil {
return taskMsg, fmt.Errorf("新增商品失败 %v", goods.ErrorResponse.SubMsg)
}
taskMsg.Detail.GoodsId = goods.SuccessResponse.Item.ItemId
taskMsg.Detail.OutGoodsId = outGoodsId
taskMsg.Detail.Img = img
taskMsg.Detail.SkuCode = skuCode
return taskMsg, nil
}
// 模板17
func template17(taskMsg planAType.TaskBody) (planBTypeKfz.GoodsAdd17, error) {
var goodsAdd planBTypeKfz.GoodsAdd17
// *********************构建参数 开始******************************** //
//模板编号 默认 17
goodsAdd.Tpl = "17"
//分类编号
if value, exists := golabl.KfzGetCommonCategory[string(taskMsg.BookInfo.CatIdObject.KongFuZiCatId)]; exists {
goodsAdd.CatId = value
} else {
goodsAdd.CatId = "43000000000000000"
}
//构建商品名称
goodsAdd.ItemName = 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.ItemName
//售价
goodsAdd.Price = tool.FenToYuan(taskMsg.Detail.Price)
//库存
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 goodsAdd, fmt.Errorf("库存不能为0")
}
}
goodsAdd.Number = strconv.FormatInt(int64(taskMsg.Detail.Stock), 10)
//品相
goodsAdd.Quality = strconv.FormatInt(taskMsg.Detail.Condition, 10)
if goodsAdd.Quality == "" || goodsAdd.Quality == "0" {
goodsAdd.Quality = strconv.FormatInt(golabl.Task.Header.ShopMsg.ConditionDef, 10)
}
//货号
goodsAdd.ItemSn = taskMsg.Detail.SkuCode
if len(taskMsg.BookInfo.ImageObject.CarouselUrlArray) == 0 {
// 无图片信息 isbn计次
setNoImgCountErr := service.SetNoImgCount(taskMsg.BookInfo.Isbn)
if setNoImgCountErr != nil {
return goodsAdd, fmt.Errorf("无图片信息isbn计次错误 isbn %v %v", taskMsg.BookInfo.Isbn, setNoImgCountErr.Error())
}
return goodsAdd, fmt.Errorf("缺少轮播图")
}
// 将 轮播图不是 孔夫子图片空间的图片替换成孔夫子图片空间图片
for k, v := range taskMsg.BookInfo.ImageObject.CarouselUrlArray {
if !strings.Contains(v, "kfzimg.com") {
kfz, uploadImageToKfzErr := UploadImageToKfz(v)
if uploadImageToKfzErr != nil {
return goodsAdd, uploadImageToKfzErr
}
// 添加到轮播图
taskMsg.BookInfo.ImageObject.CarouselUrlArray[k] = kfz
}
}
//将 最后的图片不是 孔夫子图片空间的图片替换成孔夫子图片空间图片
for k, v := range golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray {
if !strings.Contains(v, "kfzimg.com") {
kfz, uploadImageToKfzErr := UploadImageToKfz(v)
if uploadImageToKfzErr != nil {
return goodsAdd, uploadImageToKfzErr
}
// 添加到轮播图
golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray[k] = kfz
}
}
//处理图片
oldCarouselUrlArray := append([]string{}, taskMsg.BookInfo.ImageObject.CarouselUrlArray...) //原始轮播图,用于后续处理,不会被打上水印
// 存在水印图片,则打水印
if golabl.Task.Header.ShopMsg.WatermarkImgUrl != "" {
//获取水印图片
watermarkImgUrl, watermarkImgErr := tool.GetWatermarkImg()
if watermarkImgErr != nil {
return goodsAdd, fmt.Errorf("获取水印图片失败 %v", watermarkImgErr)
}
//打水印
watermarkFromURLExsBase64Arr, watermarkFromURLExsErr := tool.AddWatermarkFromURLExs(taskMsg.BookInfo.ImageObject.CarouselUrlArray, watermarkImgUrl, golabl.Task.Header.ShopMsg.WatermarkPosition)
if watermarkFromURLExsErr != nil {
return goodsAdd, fmt.Errorf("图片打水印失败 %v", watermarkFromURLExsErr)
}
//图片上传到孔夫子
toPdd, uploadImageToPddErr := tool.UploadImageToKfz(watermarkFromURLExsBase64Arr)
if uploadImageToPddErr != nil {
return goodsAdd, fmt.Errorf("图片上传到拼多多失败 %v", uploadImageToPddErr)
}
//将上传的图片替换到商品轮播图中
for i := 0; i < len(toPdd); i++ {
taskMsg.BookInfo.ImageObject.CarouselUrlArray[i] = toPdd[i]
}
}
imgUrlArr := tool.BuildCarouselGallery(golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray, oldCarouselUrlArray, taskMsg.BookInfo.ImageObject.CarouselUrlArray, golabl.Task.Header.ShopMsg.WatermarkPosition)
//商品主图
goodsAdd.ImgUrl = imgUrlArr[0]
//多个商品图片
goodsAdd.Images = strings.Join(imgUrlArr, ";")
//运费设置
bearShipping := "seller"
if golabl.Task.Header.ShopMsg.IsParcel == "0" {
bearShipping = "buyer"
// 设置运费模板编号
goodsAdd.MouldId = "1"
}
goodsAdd.BearShipping = bearShipping
//运费模板编号
//将 golabl.Task.Header.ShopMsg.CostTemplateId 转为int
costTemplateId, atoiErr := strconv.Atoi(golabl.Task.Header.ShopMsg.CostTemplateId)
if atoiErr != nil {
return goodsAdd, fmt.Errorf("运费模板编号转换错误 %v", atoiErr)
}
goodsAdd.MouldId = strconv.Itoa(costTemplateId)
//重量
goodsAdd.Weight = fmt.Sprintf("%v", float64(golabl.Task.Header.ShopMsg.BookWeight/100))
//商品标准本数
goodsAdd.WeightPiece = fmt.Sprintf("%v", float64(golabl.Task.Header.ShopMsg.StandardNumber/100))
// *********************构建模板参数 开始******************************** //
// ISBN
goodsAdd.Isbn = taskMsg.BookInfo.Isbn
// 作者
if taskMsg.BookInfo.Author == "" {
taskMsg.BookInfo.Author = "佚名"
}
goodsAdd.Author = taskMsg.BookInfo.Author
// 出版社
if taskMsg.Publishing.Value == "" {
taskMsg.Publishing.Value = "2006-01"
}
goodsAdd.Press = taskMsg.Publishing.Value
// 出版社日期
goodsAdd.PubDate = taskMsg.BookInfo.PublicationDate
// 装帧
if taskMsg.BookInfo.Binding == "" {
taskMsg.BookInfo.Binding = "平装"
}
goodsAdd.Binding = taskMsg.BookInfo.Binding
// 开本
goodsAdd.PageSize = strconv.FormatInt(taskMsg.BookInfo.Format, 10)
// 页数
goodsAdd.PageNum = strconv.FormatInt(taskMsg.BookInfo.PagesCount, 10)
// 字数
goodsAdd.WordNum = fmt.Sprintf("%v", float64(taskMsg.BookInfo.WordsCount/1000))
// 图书定价
goodsAdd.OriPrice = fmt.Sprintf("%v", float64(tool.BuildGoodsPrice(taskMsg.Detail.Price)/100))
return goodsAdd, nil
}
// 模板 13
func template13(taskMsg planAType.TaskBody) (planBTypeKfz.GoodsAdd13, error) {
var goodsAdd planBTypeKfz.GoodsAdd13
// *********************构建参数 开始******************************** //
//模板编号 默认 13
goodsAdd.Tpl = "13"
//分类编号
if value, exists := golabl.KfzGetCommonCategory[string(taskMsg.BookInfo.CatIdObject.KongFuZiCatId)]; exists {
goodsAdd.CatId = value
} else {
goodsAdd.CatId = "43000000000000000"
}
//构建商品名称
goodsAdd.ItemName = 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.ItemName
//售价
goodsAdd.Price = tool.FenToYuan(taskMsg.Detail.Price)
//库存
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 goodsAdd, fmt.Errorf("库存不能为0")
}
}
goodsAdd.Number = strconv.FormatInt(int64(taskMsg.Detail.Stock), 10)
//品相
goodsAdd.Quality = strconv.FormatInt(taskMsg.Detail.Condition, 10)
if goodsAdd.Quality == "" || goodsAdd.Quality == "0" {
goodsAdd.Quality = strconv.FormatInt(golabl.Task.Header.ShopMsg.ConditionDef, 10)
}
//货号
goodsAdd.ItemSn = taskMsg.Detail.SkuCode
if len(taskMsg.BookInfo.ImageObject.CarouselUrlArray) == 0 {
// 无图片信息 isbn计次
setNoImgCountErr := service.SetNoImgCount(taskMsg.BookInfo.Isbn)
if setNoImgCountErr != nil {
return goodsAdd, fmt.Errorf("无图片信息isbn计次错误 isbn %v %v", taskMsg.BookInfo.Isbn, setNoImgCountErr.Error())
}
return goodsAdd, fmt.Errorf("缺少轮播图")
}
// 将 轮播图不是 孔夫子图片空间的图片替换成孔夫子图片空间图片
for k, v := range taskMsg.BookInfo.ImageObject.CarouselUrlArray {
if !strings.Contains(v, "kfzimg.com") {
kfz, uploadImageToKfzErr := UploadImageToKfz(v)
if uploadImageToKfzErr != nil {
return goodsAdd, uploadImageToKfzErr
}
// 添加到轮播图
taskMsg.BookInfo.ImageObject.CarouselUrlArray[k] = kfz
}
}
//将 最后的图片不是 孔夫子图片空间的图片替换成孔夫子图片空间图片
for k, v := range golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray {
if !strings.Contains(v, "kfzimg.com") {
kfz, uploadImageToKfzErr := UploadImageToKfz(v)
if uploadImageToKfzErr != nil {
return goodsAdd, uploadImageToKfzErr
}
// 添加到轮播图
golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray[k] = kfz
}
}
//处理图片
oldCarouselUrlArray := append([]string{}, taskMsg.BookInfo.ImageObject.CarouselUrlArray...) //原始轮播图,用于后续处理,不会被打上水印
// 存在水印图片,则打水印
if golabl.Task.Header.ShopMsg.WatermarkImgUrl != "" {
//获取水印图片
watermarkImgUrl, watermarkImgErr := tool.GetWatermarkImg()
if watermarkImgErr != nil {
return goodsAdd, fmt.Errorf("获取水印图片失败 %v", watermarkImgErr)
}
//打水印
watermarkFromURLExsBase64Arr, watermarkFromURLExsErr := tool.AddWatermarkFromURLExs(taskMsg.BookInfo.ImageObject.CarouselUrlArray, watermarkImgUrl, golabl.Task.Header.ShopMsg.WatermarkPosition)
if watermarkFromURLExsErr != nil {
return goodsAdd, fmt.Errorf("图片打水印失败 %v", watermarkFromURLExsErr)
}
//图片上传到孔夫子
toPdd, uploadImageToPddErr := tool.UploadImageToKfz(watermarkFromURLExsBase64Arr)
if uploadImageToPddErr != nil {
return goodsAdd, fmt.Errorf("图片上传到拼多多失败 %v", uploadImageToPddErr)
}
//将上传的图片替换到商品轮播图中
for i := 0; i < len(toPdd); i++ {
taskMsg.BookInfo.ImageObject.CarouselUrlArray[i] = toPdd[i]
}
}
imgUrlArr := tool.BuildCarouselGallery(golabl.Task.Header.ShopMsg.CarouseLastImgUrlArray, oldCarouselUrlArray, taskMsg.BookInfo.ImageObject.CarouselUrlArray, golabl.Task.Header.ShopMsg.WatermarkPosition)
//商品主图
goodsAdd.ImgUrl = imgUrlArr[0]
//多个商品图片
goodsAdd.Images = strings.Join(imgUrlArr, ";")
//运费设置
bearShipping := "seller"
if golabl.Task.Header.ShopMsg.IsParcel == "0" {
bearShipping = "buyer"
// 设置运费模板编号
goodsAdd.MouldId = "1"
}
goodsAdd.BearShipping = bearShipping
//运费模板编号
//将 golabl.Task.Header.ShopMsg.CostTemplateId 转为int
costTemplateId, atoiErr := strconv.Atoi(golabl.Task.Header.ShopMsg.CostTemplateId)
if atoiErr != nil {
return goodsAdd, fmt.Errorf("运费模板编号转换错误 %v", atoiErr)
}
goodsAdd.MouldId = strconv.Itoa(costTemplateId)
//重量
goodsAdd.Weight = fmt.Sprintf("%v", float64(golabl.Task.Header.ShopMsg.BookWeight/100))
//商品标准本数
goodsAdd.WeightPiece = fmt.Sprintf("%v", float64(golabl.Task.Header.ShopMsg.StandardNumber/100))
// *********************构建模板参数 开始******************************** //
// 作者
if taskMsg.BookInfo.Author == "" {
taskMsg.BookInfo.Author = "佚名"
}
goodsAdd.Author = taskMsg.BookInfo.Author
// 出版社
if taskMsg.Publishing.Value == "" {
taskMsg.Publishing.Value = "2006-01"
}
goodsAdd.Press = taskMsg.Publishing.Value
// 出版社日期
goodsAdd.PubDate = taskMsg.BookInfo.PublicationDate
// 装帧
if taskMsg.BookInfo.Binding == "" {
taskMsg.BookInfo.Binding = "平装"
}
goodsAdd.Binding = taskMsg.BookInfo.Binding
// 开本
goodsAdd.PageSize = strconv.FormatInt(taskMsg.BookInfo.Format, 10)
// 页数
goodsAdd.PageNum = strconv.FormatInt(taskMsg.BookInfo.PagesCount, 10)
// 字数
goodsAdd.WordNum = fmt.Sprintf("%v", float64(taskMsg.BookInfo.WordsCount/1000))
// 图书定价
goodsAdd.OriPrice = fmt.Sprintf("%v", float64(tool.BuildGoodsPrice(taskMsg.Detail.Price)/100))
return goodsAdd, nil
}