init:xianyu_noisbn_publishGoods

This commit is contained in:
Cai1Cai1 2026-06-25 10:15:10 +08:00
commit e3429f9bad
289 changed files with 42656 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/logs/
/planB/logs/
/planA.exe
/taskDb.db
/planB/planB.exe
/.idea
/planB/.idea
/planB/img/

BIN
__debug_bin.exe Normal file

Binary file not shown.

BIN
__debug_bin.exe2740605166 Normal file

Binary file not shown.

BIN
__debug_bin.exe630942224 Normal file

Binary file not shown.

133
config.yaml Normal file
View File

@ -0,0 +1,133 @@
server:
port: "8080" #服务器端口
f_port : "8284" #F程序端口
filter: 1 #是否开启违禁词过滤器 0=关闭 1=开启
replace_mark: "0" #标题违规词是否替换* 0 不替换 1 替换(替换会继续发布,不替换则不发布)
redis_exp: 192 #redis过期时间 192小时8天
read_db: "mysql" #读数据库 mysql sqlite
err_pause_time: 3000 #错误暂停时间(毫秒)
sign_key: "jRQdCh52Z55Kzh1hADaA2ZtdTKetj2PXk60Tz5Yc0iz9aD8Wafbk7CwAZ8cz69A9zb9caZ3k9dnR3Ys06J5nYFPrZ0xE9p6TY8DCD538ryiRjW81YTPmk41tCEnXizPh" #签名密钥
data_day: 2 #数据保存时间(天)
is_c: true #是否启动 C程序
speed: #限速器
pdd_speed: 18 #拼多多 每秒多少个任务
xianyu_speed: 5 #闲鱼 每秒多少个任务
watermark: 15 #打水印速率的个数
minio: #minio 图片空间
url: "103.236.68.64:19000" #minio地址
access_key_id: "minio" #minio keyId
secret_access_key: "bhkXyaD2WdAF7C6z" #minio key
bucket_name: "task-xianyu" #存储桶
target_dir: "test" #目标目录
use_ssl: false #是否使用 SSL
alive:
fluent: 50 #存活状态-流畅时间(毫秒)
slow: 200 #存活状态-缓慢时间(毫秒)
pool_config:
size: 500 #协程数量
with_expiry_duration: 10 #过期时间
with_pre_alloc: true #预分配
with_max_blocking_tasks: 2000 #阻塞任务数
with_nonblocking : true #非阻塞
mysql_config:
db_name: "task_user" #数据库名称
user: "root" #数据库用户名
password: "root" #数据库密码
host: "127.0.0.1" #数据库地址
port: 3306 #数据库端口
loglevel: "silent" #数据库日志级别 info=开发环境输出所有日志SQL、执行时间等 warn=预发布:输出警告+错误 error=生产环境:只输出错误日志 silent=生产环境:关闭所有日志
max_retry_times: 3 #数据库重试次数
base_retry_interval: 100 #数据库重试间隔(毫秒)
max_retry_interval: 3 #数据库最大重试间隔(秒)
max_open_conns: 100 #数据库最大打开连接数
max_idle_conns: 20 #数据库最大空闲连接数
conn_max_idle_time: 30 #数据库连接最大空闲时间(分钟)
conn_max_lifetime: 1 #数据库连接最大生命周期(小数)
psi_mysql_config:
db_name: "psi" #数据库名称
user: "psi" #数据库用户名
password: "6d7f5DK2G5PCasBp" #数据库密码
host: "175.27.224.66" #数据库地址
port: 3306 #数据库端口
loglevel: "silent" #数据库日志级别 info=开发环境输出所有日志SQL、执行时间等 warn=预发布:输出警告+错误 error=生产环境:只输出错误日志 silent=生产环境:关闭所有日志
max_retry_times: 3 #数据库重试次数
base_retry_interval: 100 #数据库重试间隔(毫秒)
max_retry_interval: 3 #数据库最大重试间隔(秒)
max_open_conns: 100 #数据库最大打开连接数
max_idle_conns: 20 #数据库最大空闲连接数
conn_max_idle_time: 30 #数据库连接最大空闲时间(分钟)
conn_max_lifetime: 1 #数据库连接最大生命周期(小数)
redis_config:
- db_name: "任务池"
db: 0
addr: "127.0.0.1:6379"
password: "123456"
- db_name: "书品库"
db: 7
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "店铺信息"
db: 8
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "出版社信息列表"
db: 3
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "省市区列表"
db: 4
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "没有图片的 isbn"
db: 5
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "没有书籍的 isbn"
db: 6
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "淘宝店铺"
db: 9
addr: "36.212.12.247:6379"
password: "long6166@@"
pdd_config:
client_id: "203c5a7ba8bd4b8488d5e26f93052642" #拼多多clientId
client_secret: "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" #拼多多clientSecret
kfz_config:
app_id: 576 #孔夫子appid
app_secret: "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8" #孔夫子appsecret
http_url:
task_url: "http://127.0.0.1:8080" #A 程序接口地址
file_url:
xian_yu_dll: "D:\\source\\planA\\planB\\modules\\xianYu" #闲鱼 DLL库路径
pdd_dll: "D:\\source\\planA\\planB\\modules\\pdd" #拼多多 DLL库路径
kfz_dll: "D:\\source\\planA\\planB\\modules\\kfz" #孔夫子 DLL库路径
log_dll: "D:\\source\\planA\\planB\\modules\\logs" #日志 DLL库路径
image_dll: "D:\\source\\planA\\planB\\modules\\image" #水印 DLL库路径
b_file_name: "D:\\source\\planA\\planB\\planB.exe" #B 程序文件路径
c_file_name: "D:\\source\\planA\\planC\\planC.exe" #C 程序文件路径
d_file_name: "D:\\source\\planA\\planD\\planD.exe" #D 程序文件路径
e_file_name: "D:\\source\\planA\\planE\\planE.exe" #E 程序文件路径
f_file_name: "D:\\source\\planA\\planF\\planF.exe" #F 程序文件路径
create_task_url: "https://api.buzhiyushu.cn/zhishu/baseInfo/addNewTask" #新增任务接口
create_task_notice_url: "http://36.212.12.92:8055/task" #核价软件提交数据通知接口
create_operation_task_notice_url: "http://36.212.12.92:8055/taskV2" #操作商品任务核价软件提交数据通知接口
banned_word_substitution_url : "http://36.212.12.247:13001/task/getFilterSetNew" #违禁词替换接口
xy_banned_word_substitution_url : "http://146.56.192.164:19095/health" #闲鱼违禁词替换接口
pdd_token_url: "https://api.buzhiyushu.cn/huidiao/pdd/getToken" #获取系统规定拼多多 token
deduction_url: "https://api.buzhiyushu.cn/zhishu/userRecharge/apiBalancePayment" #扣费接口
pdd_get_goods_url: "http://pdd.buzhiyushu.cn/api/pdd/auth/getShopGoodsList" #查询拼多多商品接口
pdd_get_goods_detail_url: "http://192.168.101.127:8085/api/pdd/auth/newGetShopGoodsDetailList" #查询拼多多商品详情列表接口
pdd_add_goods_url: "http://192.168.101.127:18099/task/putShopGoods" #添加拼多多商品接口
pdd_get_sku_id: "http://192.168.101.127:18099/shopGoods/getShopGoods" #批量获取 skuId接口
xianyu_add_goods_url: "http://119.45.237.193:14008/task/putShopGoods" #添加闲鱼商品接口
kfz_add_goods_url: "http://119.45.237.193:14009/task/kfzverifyPricePublishGoods" #添加孔夫子商品接口
del_task_url: "http://119.45.237.193:14008/shopGoods/delShopGoodsk" #删除任务通知接口
backup_url: "C:\\file\\backup" #备份文件路径
pdd_goods_details_url: "D:\\file\\pdd_goods_details" #保存拼多多详情路径
update_token_url: "http://146.56.227.42:9099/api/updateToken" #更新拼多多 token 到redis
kfz_img_temp_url: "D:\\file\\kfzImg" #孔夫子图片临时路径
kfz_img_http_url: "https://www0.kfzimg.com/" #孔夫子图片 http 路径
get_pdd_goods_shopid_isbn_url : "http://192.168.101.127:18099/shopGoods/selectTrilateralIds" #获取拼多多商品 shopId 和 isbn
get_subscription_expiration_date_url : "http://119.45.237.193:9096/api/user/getKfzUserRecbusiness" #获取订阅到期时间
pdd_img_temp_url: "D:\\file\\kfzImg" #拼多多图片临时路径

36
controlState/lock/lock.go Normal file
View File

@ -0,0 +1,36 @@
package lock
import "sync"
// 用sync.Map替代原生map天然支持并发安全
var lock sync.Map
// GetLock 获取锁返回true表示已上锁false表示未上锁
func GetLock(key string) bool {
v, ok := lock.Load(key)
if !ok {
return false
}
// 断言为bool类型确保存储的是布尔值
locked, ok := v.(bool)
return ok && locked
}
// SetLock 设置锁(原子操作)
func SetLock(key string) {
lock.Store(key, true)
}
// DestroyLock 销毁锁(原子操作)
func DestroyLock(key string) {
lock.Delete(key)
}
// TryLock 尝试加锁(核心:检查+设置原子化)
// 返回true表示加锁成功false表示已被上锁
func TryLock(key string) bool {
// LoadOrStore如果key不存在则存储值返回false如果已存在则返回true
_, loaded := lock.LoadOrStore(key, true)
// loaded为true表示已上锁返回falseloaded为false表示加锁成功返回true
return !loaded
}

View File

@ -0,0 +1,55 @@
package serviceAlive
// ServiceStatus 定义服务状态结构体
type ServiceStatus struct {
Times int // 次数/状态值
Msg string // 消息信息
}
var Service = map[string]ServiceStatus{
"mysql": {Times: 0, Msg: ""},
"redis": {Times: 0, Msg: ""},
"sqlite": {Times: 0, Msg: ""},
"pdd": {Times: 0, Msg: ""},
"通知取出bodyOver接口": {Times: 0, Msg: ""},
"违禁词替换接口": {Times: 0, Msg: ""},
"闲鱼违禁词": {Times: 0, Msg: ""},
}
// SetServiceAlive 设置服务状态
func SetServiceAlive(key string, times int) {
if status, ok := Service[key]; ok {
status.Times = times
Service[key] = status
}
}
// SetServiceAliveWithMsg 设置服务状态(带消息)
func SetServiceAliveWithMsg(key string, times int, msg string) {
Service[key] = ServiceStatus{
Times: times,
Msg: msg,
}
}
// UpdateServiceTimes 只更新服务次数
func UpdateServiceTimes(key string, times int) {
if status, ok := Service[key]; ok {
status.Times = times
Service[key] = status
}
}
// UpdateServiceMsg 只更新服务消息
func UpdateServiceMsg(key string, msg string) {
if status, ok := Service[key]; ok {
status.Msg = msg
Service[key] = status
}
}
// GetServiceStatus 获取服务状态
func GetServiceStatus(key string) (ServiceStatus, bool) {
status, ok := Service[key]
return status, ok
}

33
controller/admin.go Normal file
View File

@ -0,0 +1,33 @@
package controller
import (
"net/http"
"planA/tool"
"github.com/gorilla/mux"
)
// DelRedisTask 删除redis中指定任务
func DelRedisTask(httpMsg http.ResponseWriter, data *http.Request) {
// 从路径参数获取 id
vars := mux.Vars(data)
taskId := vars["id"]
// 验证 taskId
if taskId == "" {
errMsg := "任务 ID不能为空"
tool.Error(httpMsg, errMsg, http.StatusBadRequest)
return
}
// 删除任务
tool.Success(httpMsg, taskId)
}
// DelMysqlTask 删除mysql中指定任务
func DelMysqlTask(httpMsg http.ResponseWriter, data *http.Request) {
tool.Success(httpMsg, "删除mysql中指定任务")
}
// DelSqliteTask 删除sqlite中指定任务
func DelSqliteTask(httpMsg http.ResponseWriter, data *http.Request) {
tool.Success(httpMsg, "删除sqlite中指定任务")
}

37
controller/alive.go Normal file
View File

@ -0,0 +1,37 @@
package controller
import (
"net/http"
"planA/controlState/serviceAlive"
config2 "planA/initialization/config"
"planA/tool"
)
// GetServiceAliveList 获取存活状态列表
func GetServiceAliveList(httpMsg http.ResponseWriter, data *http.Request) {
//获取存活状态列表
aliveConfig, getAliveConfigErr := config2.GetAliveConfig()
if getAliveConfigErr != nil {
tool.Error(httpMsg, getAliveConfigErr.Error(), http.StatusInternalServerError)
return
}
var ret []map[string]interface{}
alive := serviceAlive.Service
for k, v := range alive {
status := 0
// v.Times 是原来的计数值
if v.Times > aliveConfig.Fluent && v.Times < aliveConfig.Slow {
status = 1
} else if v.Times >= aliveConfig.Slow {
status = 2
}
ret = append(ret, map[string]interface{}{
"name": k,
"times": v.Times, // 修改为 v.Times
"msg": v.Msg, // 新增 msg 字段
"status": status,
})
}
tool.Success(httpMsg, ret)
}

77
controller/body.go Normal file
View File

@ -0,0 +1,77 @@
package controller
import (
"encoding/json"
"net/http"
"planA/service"
"planA/tool"
_type "planA/type"
"planA/validator"
)
// GetTbOneBodyWait 查询淘宝bodyWait
func GetTbOneBodyWait(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.GetBodyWaitOneValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 查询bodyWait
bodyWait, getTbBodyWaitErr := service.GetOneBodyFromRight(dataVal.TaskId)
if getTbBodyWaitErr != nil {
tool.Error(httpMsg, getTbBodyWaitErr.Error(), http.StatusInternalServerError)
return
}
// 将 bodyWait 转为结构体
var bodyWaits _type.TaskBody
unmarshalErr := json.Unmarshal([]byte(bodyWait), &bodyWaits)
if unmarshalErr != nil {
tool.Error(httpMsg, unmarshalErr.Error(), http.StatusInternalServerError)
return
}
// 返回结果
tool.Success(httpMsg, bodyWaits)
}
// InsertTbBodyOver 插入 bodyOver
func InsertTbBodyOver(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.InsertTbBodyOver(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
//解析bodyover
var bodyOver _type.TaskBody
unmarshalErr := json.Unmarshal([]byte(dataVal.Data), &bodyOver)
if unmarshalErr != nil {
tool.Error(httpMsg, unmarshalErr.Error(), http.StatusInternalServerError)
return
}
//// 更新任务尾
//updateTaskFootersErr := service.UpdateTaskFooters(dataVal.TaskId, bodyOver.Detail.Status, 1)
//if updateTaskFootersErr != nil {
// tool.Error(httpMsg, updateTaskFootersErr.Error(), http.StatusInternalServerError)
// return
//}
//// 更新任务头
//updateTaskHeadersErr := service.UpdateTaskHeaders(dataVal.TaskId, bodyOver.Detail.Status, 1)
//if updateTaskHeadersErr != nil {
// tool.Error(httpMsg, updateTaskHeadersErr.Error(), http.StatusInternalServerError)
// return
//}
//插入bodyOver
insertErr := service.InsertOneBodyOver(dataVal.TaskId, dataVal.Data)
if insertErr != nil {
tool.Error(httpMsg, insertErr.Error(), http.StatusInternalServerError)
return
}
tool.Success(httpMsg, "")
}

515
controller/delTask.go Normal file
View File

@ -0,0 +1,515 @@
package controller
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
planBType "planA/planB/type"
"planA/service"
serviceMysql "planA/service/mysql"
"planA/tool"
toolPdd "planA/tool/pdd"
_type "planA/type"
mysqlType "planA/type/mysql"
"planA/validator"
"strconv"
"time"
)
// GetDelTaskByPage 分页查询 删除任务
func GetDelTaskByPage(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.GetDelTaskValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
page, size := tool.SetPage(dataVal.Page, dataVal.Size)
delTaskArr, total, getDelTaskByPageErr := serviceMysql.GetDelTaskByPage(page, size, "")
if getDelTaskByPageErr != nil {
return
}
dataRet := map[string]interface{}{
"page": page,
"size": size,
"total": total,
"list": delTaskArr,
}
tool.Success(httpMsg, dataRet)
return
}
// GetDelTaskByPageByUserId 分页查询 删除任务-用户
func GetDelTaskByPageByUserId(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.GetDelTaskByUserIdValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
page, size := tool.SetPage(dataVal.Page, dataVal.Size)
delTaskArr, total, getDelTaskByPageErr := serviceMysql.GetDelTaskByPage(page, size, dataVal.UserId)
if getDelTaskByPageErr != nil {
return
}
dataRet := map[string]interface{}{
"page": page,
"size": size,
"total": total,
"list": delTaskArr,
}
tool.Success(httpMsg, dataRet)
return
}
// GetDelTaskDetail 获取删除任务详情列表
func GetDelTaskDetail(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.GetDelTaskDetailValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
page, size := tool.SetPage(dataVal.Page, dataVal.Size)
delTaskArr, total, getDelTaskByPageErr := serviceMysql.GetDelTaskDetailByPage(page, size, dataVal.TaskId, 3)
if getDelTaskByPageErr != nil {
return
}
dataRet := map[string]interface{}{
"page": page,
"size": size,
"total": total,
"list": delTaskArr,
}
tool.Success(httpMsg, dataRet)
}
// CreateTbDelTask 创建淘宝删除任务
func CreateTbDelTask(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.CreateTbDelTaskValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
delNum := data.FormValue("del_num")
taskCount := 0
if dataVal.TaskType == "1" || dataVal.TaskType == "2" {
if delNum == "" || delNum == "0" {
errMsg := "删除数量不能为空与0"
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 dataVal.TaskCount 转为 int
var err error
taskCount, err = strconv.Atoi(delNum)
if err != nil {
errMsg := "任务数量转换失败: " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
}
delTime := data.FormValue("del_time")
delTimes := time.Time{}
if dataVal.TaskType == "3" {
if delTime == "" || delTime == "0" {
errMsg := "删除时间不能为空"
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 dataVal.DelTime 转为 time.Time
layout := "2006-01-02 15:04:05" // 常见格式
var err error
delTimes, err = time.Parse(layout, delTime)
if err != nil {
errMsg := "时间转换失败: " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
}
// 将 dataVal.TaskType 转为 int
taskType, err := strconv.Atoi(dataVal.TaskType)
if err != nil {
errMsg := "任务类型转换失败: " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 查询店铺数据
shopDataStr, err := service.GetTaskShop(dataVal.ShopID)
if err != nil {
errMsg := "获取店铺数据失败: shopId " + dataVal.ShopID + " " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 解析 json数据
shopData, err := toolPdd.ParseShopData(shopDataStr)
if err != nil {
errMsg := "解析店铺数据失败:" + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
if shopData.Shop == nil {
errMsg := "店铺数据为空"
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
//请求创建任务接口并获取任务 id
taskId, err := CreateTaskRequest(dataVal.ShopID, dataVal.TaskType)
if err != nil {
errMsg := "店铺ID " + dataVal.ShopID + " 淘宝删除任务 请求创建任务接口失败: " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
taskTypes := 0
if taskType == 1 {
taskTypes = 5
} else if taskType == 2 {
taskTypes = 10
} else if taskType == 3 {
taskTypes = 11
}
var priceRange []_type.PriceRange
priceTemplateRangeStr := shopData.PriceTemplate.RangePrice
err = json.Unmarshal([]byte(priceTemplateRangeStr), &priceRange)
if err != nil {
errMsg := "解析价格模板失败:" + err.Error() + " 原始数据:" + shopData.PriceTemplate.RangePrice
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
createAt := time.Now().Unix()
taskData, createTaskDataErr := CreateTaskData(taskId, int64(taskTypes), createAt, shopData.Shop, priceRange, shopData.Spec, shopData.ShopDetail, shopData.ShopContext, strconv.Itoa(taskCount), 1, 1, "1")
if createTaskDataErr != nil {
errMsg := "创建任务数据失败: " + createTaskDataErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 taskData 转为json
taskDataJson, err := json.Marshal(taskData)
if err != nil {
errMsg := "任务数据转换失败: " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
header := string(taskDataJson)
createAts := time.Unix(createAt, 0)
status := 0
taskCountOver := 0
// 创建任务
task := mysqlType.DelTask{
UserID: &shopData.Shop.CreateBy,
ShopID: &dataVal.ShopID,
TaskID: &taskId,
ShopType: &dataVal.TaskType,
TaskType: &taskType,
ShopName: &shopData.Shop.ShopName,
TaskCount: &taskCount,
Header: &header,
CreateAt: &createAts,
StopAt: &delTimes,
Status: &status,
TaskCountOver: &taskCountOver,
}
createDelTaskErr := serviceMysql.CreateDelTask(task)
if createDelTaskErr != nil {
errMsg := "创建任务失败: " + createDelTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 创建详情表
createTableIfNotExistsErr := serviceMysql.CreateTableIfNotExists(taskId)
if createTableIfNotExistsErr != nil {
errMsg := "创建详情表失败: " + createTableIfNotExistsErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, taskId)
}
// CreateTbDelTaskDetails 插入淘宝删除任务数据
func CreateTbDelTaskDetails(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.CreateTbDelTaskDetailsValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
if dataVal.Status == "" {
dataVal.Status = "0"
}
//判断任务是否存在
_, getTaskErr := serviceMysql.GetDelTaskByTaskId(dataVal.TaskID)
if getTaskErr != nil {
if errors.Is(getTaskErr, sql.ErrNoRows) || getTaskErr.Error() == "record not found" {
errMsg := "任务不存在: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
errMsg := "获取任务失败: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 创建详情表
createTableIfNotExistsErr := serviceMysql.CreateTableIfNotExists(dataVal.TaskID)
if createTableIfNotExistsErr != nil {
errMsg := "创建详情表失败: " + createTableIfNotExistsErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 string 类型的 Status 转换为 int64
var statusInt64 int64
if dataVal.Status != "" {
statusInt64, _ = strconv.ParseInt(dataVal.Status, 10, 64)
}
// 转换 GoodsId
var goodsIdInt64 int64
if dataVal.GoodsId != "" {
if val, err := strconv.ParseInt(dataVal.GoodsId, 10, 64); err == nil {
goodsIdInt64 = val
}
}
// 转换 TaskID
var taskIDInt64 int64
if dataVal.TaskID != "" {
if val, err := strconv.ParseInt(dataVal.TaskID, 10, 64); err == nil {
taskIDInt64 = val
}
}
// 获取当前时间
createAtTime := time.Unix(time.Now().Unix(), 0)
createAtStr := time.Now().Format("2006-01-02")
// 插入任务详情
taskDetails := planBType.DelTaskDetail{
TaskID: &dataVal.TaskID,
Isbn: &dataVal.Isbn,
BookName: &dataVal.BookName,
GoodsID: &goodsIdInt64,
Status: &statusInt64,
Err: &dataVal.Err,
DeleteAt: &createAtTime,
DeleteDate: &createAtStr,
CreateAt: &createAtTime,
}
insertTbDelTaskDetailsErr := serviceMysql.InsertDelTaskDetail(taskIDInt64, taskDetails)
if insertTbDelTaskDetailsErr != nil {
errMsg := "插入任务详情失败: " + insertTbDelTaskDetailsErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, "")
}
// UpdateTbDelTaskDetailsStatus 修改指定淘宝删除任务详情状态
func UpdateTbDelTaskDetailsStatus(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.UpdateTbDelTaskDetailsStatusValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 判断任务是否存在
_, getTaskErr := serviceMysql.GetDelTaskByTaskId(dataVal.TaskID)
if getTaskErr != nil {
if errors.Is(getTaskErr, sql.ErrNoRows) || getTaskErr.Error() == "record not found" {
errMsg := "任务不存在: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
errMsg := "获取任务失败: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 dataVal.Status 转为int
statusInt, err := strconv.Atoi(dataVal.Status)
if err != nil {
errMsg := "状态值格式错误"
tool.Error(httpMsg, errMsg, http.StatusBadRequest)
return
}
updateTbDelTaskDetailsStatusErr := serviceMysql.UpdateDelTaskDetailStatus(dataVal.TaskID, dataVal.GoodsId, statusInt, dataVal.Err)
if updateTbDelTaskDetailsStatusErr != nil {
errMsg := "修改任务详情状态失败: " + updateTbDelTaskDetailsStatusErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, "")
}
// UpdateTbDelTaskProgress 修改淘宝任务进度
func UpdateTbDelTaskProgress(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.UpdateTbDelTaskProgressValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 判断任务是否存在
_, getTaskErr := serviceMysql.GetDelTaskByTaskId(dataVal.TaskID)
if getTaskErr != nil {
if errors.Is(getTaskErr, sql.ErrNoRows) || getTaskErr.Error() == "record not found" {
errMsg := "任务不存在: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
errMsg := "获取任务失败: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 dataVal.Num 转为 int
numInt, err := strconv.Atoi(dataVal.Num)
if err != nil {
errMsg := "进度值格式错误"
tool.Error(httpMsg, errMsg, http.StatusBadRequest)
return
}
updateTbDelTaskProgressErr := serviceMysql.UpdateDelTaskProgress(dataVal.TaskID, numInt)
if updateTbDelTaskProgressErr != nil {
errMsg := "修改任务进度失败: " + updateTbDelTaskProgressErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, "")
}
// UpdateTbDelTaskStatus 修改淘宝任务状态
func UpdateTbDelTaskStatus(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.UpdateTbDelTaskStatusValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 判断任务是否存在
_, getTaskErr := serviceMysql.GetDelTaskByTaskId(dataVal.TaskID)
if getTaskErr != nil {
if errors.Is(getTaskErr, sql.ErrNoRows) || getTaskErr.Error() == "record not found" {
errMsg := "任务不存在: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
errMsg := "获取任务失败: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 将 dataVal.Status 转为 int
statusInt, err := strconv.Atoi(dataVal.Status)
if err != nil {
errMsg := "状态值格式错误"
tool.Error(httpMsg, errMsg, http.StatusBadRequest)
return
}
updateTbDelTaskStatusErr := serviceMysql.UpdateDelTaskStatusByTaskId(dataVal.TaskID, statusInt)
if updateTbDelTaskStatusErr != nil {
errMsg := "修改任务状态失败: " + updateTbDelTaskStatusErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, "")
}
// GetTbDelTaskDetailsWait 获取指定任务待执行的任务详情
func GetTbDelTaskDetailsWait(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.GetDelTaskDetailValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
page, size := tool.SetPage(dataVal.Page, dataVal.Size)
delTaskArr, total, getDelTaskByPageErr := serviceMysql.GetDelTaskDetailByPage(page, size, dataVal.TaskId, 0)
if getDelTaskByPageErr != nil {
return
}
dataArr := []map[string]interface{}{}
for _, v := range delTaskArr {
datas := map[string]interface{}{
"task_id": v.TaskID,
"isbn": v.Isbn,
"book_name": v.BookName,
"goods_id": v.GoodsID,
"status": v.Status,
"create_at": v.CreateAt,
}
dataArr = append(dataArr, datas)
}
dataRet := map[string]interface{}{
"page": page,
"size": size,
"total": total,
"list": dataArr,
}
tool.Success(httpMsg, dataRet)
}
// GetTbDelTaskByTaskId 根据任务id 查询任务
func GetTbDelTaskByTaskId(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.TaskIdValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 判断任务是否存在
task, getTaskErr := serviceMysql.GetDelTaskByTaskId(dataVal.TaskID)
if getTaskErr != nil {
if errors.Is(getTaskErr, sql.ErrNoRows) || getTaskErr.Error() == "record not found" {
errMsg := "任务不存在: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
errMsg := "获取任务失败: " + getTaskErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
datas := map[string]interface{}{
"task_id": task.TaskID,
"shop_id": task.ShopID,
"task_type": task.TaskType,
"status": task.Status,
"task_count": task.TaskCount,
"task_count_over": task.TaskCountOver,
"stop_at": task.StopAt,
"create_at": task.CreateAt,
}
tool.Success(httpMsg, datas)
}

510
controller/export.go Normal file
View File

@ -0,0 +1,510 @@
package controller
import (
"database/sql"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"planA/modules/logs"
"planA/rep"
"planA/service"
"planA/tool"
_type "planA/type"
"planA/validator"
"github.com/go-redis/redis/v8"
)
// GetExportTask 导出任务列表
func GetExportTask(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, GetExportValidatorErr := validator.GetExportValidator(data)
if GetExportValidatorErr != nil {
tool.Error(httpMsg, GetExportValidatorErr.Error(), http.StatusInternalServerError)
return
}
page, size := tool.SetPage(dataVal.Page, dataVal.Size)
read := rep.CreateDbFactoryRead()
records, total, getTaskRecordsListErr := read.GetTaskExportList(page, size, "")
if getTaskRecordsListErr != nil {
errMsg := getTaskRecordsListErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
var dataTaskAll []map[string]interface{}
for _, v := range records {
complete, getExportFileProgressErr := service.GetExportFileProgress(v.TaskId)
if getExportFileProgressErr != nil {
errMsg := "获取任务进度失败: " + getExportFileProgressErr.Error()
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
continue
}
taskExportdata := map[string]interface{}{
"task_id": v.TaskId,
"shop_name": v.ShopName,
"status": v.Status,
"total": v.Total,
"file_url": v.FileUrl,
"complete_at": v.CompleteAt.Time,
"create_at": v.CreateAt,
"complete": complete,
}
dataTaskAll = append(dataTaskAll, taskExportdata)
}
dataRet := map[string]interface{}{
"page": page,
"size": size,
"total": total,
"list": dataTaskAll,
}
tool.Success(httpMsg, dataRet)
}
// GetExportTaskByUserId 导出任务列表-用户
func GetExportTaskByUserId(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, GetExportByUserIdValidatorErr := validator.GetExportByUserIdValidator(data)
if GetExportByUserIdValidatorErr != nil {
tool.Error(httpMsg, GetExportByUserIdValidatorErr.Error(), http.StatusInternalServerError)
return
}
page, size := tool.SetPage(dataVal.Page, dataVal.Size)
read := rep.CreateDbFactoryRead()
records, total, getTaskRecordsListErr := read.GetTaskExportList(page, size, dataVal.UserID)
if getTaskRecordsListErr != nil {
errMsg := getTaskRecordsListErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
dataTaskAll := []map[string]interface{}{}
for _, v := range records {
complete, getExportFileProgressErr := service.GetExportFileProgress(v.TaskId)
if errors.Is(getExportFileProgressErr, redis.Nil) {
complete = int(v.Total)
} else if getExportFileProgressErr != nil {
errMsg := "获取任务进度失败: " + getExportFileProgressErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
taskExportdata := map[string]interface{}{
"task_id": v.TaskId,
"shop_name": v.ShopName,
"status": v.Status,
"total": v.Total,
"file_url": v.FileUrl,
"complete_at": v.CompleteAt.Time,
"create_at": v.CreateAt,
"complete": complete,
}
dataTaskAll = append(dataTaskAll, taskExportdata)
}
dataRet := map[string]interface{}{
"page": page,
"size": size,
"total": total,
"list": dataTaskAll,
}
tool.Success(httpMsg, dataRet)
}
// ExportTaskDetail 根据任务 id导出任务详情
func ExportTaskDetail(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, GetExportDetailValidatorErr := validator.GetExportDetailValidator(data)
if GetExportDetailValidatorErr != nil {
tool.Error(httpMsg, GetExportDetailValidatorErr.Error(), http.StatusInternalServerError)
return
}
read := rep.CreateDbFactoryRead()
//查询是任务信息
taskRecord, getTaskRecordsByTaskIDErr := read.GetTaskRecordsByTaskId(dataVal.TaskID)
if getTaskRecordsByTaskIDErr != nil {
errMsg := fmt.Sprintf("获取任务信息失败 %v", getTaskRecordsByTaskIDErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
if taskRecord.IsExport == 1 {
errMsg := "任务已导出过,请在下载中心查看"
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
//获取任务详情总数
total, GetBodyOverCount := service.GetBodyOverCount(dataVal.TaskID)
if GetBodyOverCount != nil {
errMsg := fmt.Sprintf("获取任务详情总数失败 %v", GetBodyOverCount)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
mysqlWrite, sqliteWrite := rep.CreateDbFactoryWrite()
//查询导出任务是存在
taskExport, getTaskExportByTaskIdErr := read.GetTaskExportByTaskId(dataVal.TaskID)
if getTaskExportByTaskIdErr != nil {
errMsg := fmt.Sprintf("获取任务信息失败 %v", getTaskExportByTaskIdErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
if taskExport.Id == 0 {
//创建一条导出任务
var status int64
var fileUrl string
mysqlCreateTaskExportErr := mysqlWrite.CreateTaskExport(_type.TaskExportDTO{
UserId: taskRecord.UserId,
ShopId: taskRecord.ShopId,
TaskId: taskRecord.TaskId,
ShopName: taskRecord.ShopName,
FileUrl: fileUrl,
Status: status,
Total: total,
CompleteAt: sql.NullTime{},
})
if mysqlCreateTaskExportErr != nil {
errMsg := fmt.Sprintf("写入任务信息失败 %v", mysqlCreateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
sqLiteCreateTaskExportErr := sqliteWrite.CreateTaskExport(_type.TaskExportDTO{
UserId: taskRecord.UserId,
ShopId: taskRecord.ShopId,
TaskId: taskRecord.TaskId,
ShopName: taskRecord.ShopName,
FileUrl: fileUrl,
Status: status,
Total: total,
CompleteAt: sql.NullTime{},
})
if sqLiteCreateTaskExportErr != nil {
errMsg := fmt.Sprintf("写入任务信息失败 %v", sqLiteCreateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
} else {
newTotal := taskExport.Total + total
// 如果数据存在 清空完成时间 并且 修改任务总数量
mysqlUpdateTaskExportErr := mysqlWrite.UpdateTaskExport(_type.TaskExportDTO{
Id: taskExport.Id,
UserId: taskExport.UserId,
ShopId: taskExport.ShopId,
TaskId: taskExport.TaskId,
ShopName: taskExport.ShopName,
FileUrl: taskExport.FileUrl,
Status: taskExport.Status,
Total: newTotal,
CompleteAt: sql.NullTime{},
})
if mysqlUpdateTaskExportErr != nil {
errMsg := fmt.Sprintf("修改任务信息失败 %v", mysqlUpdateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
sqLiteUpdateTaskExportErr := sqliteWrite.UpdateTaskExport(_type.TaskExportDTO{
Id: taskExport.Id,
UserId: taskExport.UserId,
ShopId: taskExport.ShopId,
TaskId: taskExport.TaskId,
ShopName: taskExport.ShopName,
FileUrl: taskExport.FileUrl,
Status: taskExport.Status,
Total: newTotal,
CompleteAt: sql.NullTime{},
})
if sqLiteUpdateTaskExportErr != nil {
errMsg := fmt.Sprintf("修改任务信息失败 %v", sqLiteUpdateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
}
//修改任务导出状态
mysqlUpdateTaskRecordsErr := mysqlWrite.UpdateTaskRecords(_type.TaskRecordsDTO{
Id: taskRecord.Id,
UserId: taskRecord.UserId,
ShopId: taskRecord.ShopId,
TaskId: taskRecord.TaskId,
ShopName: taskRecord.ShopName,
IsExport: 1,
TaskType: taskRecord.TaskType,
})
if mysqlUpdateTaskRecordsErr != nil {
errMsg := fmt.Sprintf("修改任务导出状态失败 %v", mysqlUpdateTaskRecordsErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
sqLiteUpdateTaskRecordsErr := sqliteWrite.UpdateTaskRecords(_type.TaskRecordsDTO{
Id: taskRecord.Id,
UserId: taskRecord.UserId,
ShopId: taskRecord.ShopId,
TaskId: taskRecord.TaskId,
ShopName: taskRecord.ShopName,
IsExport: 1,
TaskType: taskRecord.TaskType,
})
if sqLiteUpdateTaskRecordsErr != nil {
errMsg := fmt.Sprintf("修改任务导出状态失败 %v", sqLiteUpdateTaskRecordsErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
go ExportCSV(dataVal.TaskID, total, taskRecord.TaskType)
tool.Success(httpMsg, "")
}
// ExportTaskDetailByUserId 根据任务 id导出任务详情-用户
func ExportTaskDetailByUserId(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, GetExportDetailValidatorErr := validator.GetExportDetailByUserIdValidator(data)
if GetExportDetailValidatorErr != nil {
tool.Error(httpMsg, GetExportDetailValidatorErr.Error(), http.StatusInternalServerError)
return
}
read := rep.CreateDbFactoryRead()
//查询任务信息
task, getTaskRecordsByTaskIdErr := read.GetTaskRecordsByTaskId(dataVal.TaskID)
if getTaskRecordsByTaskIdErr != nil {
errMsg := fmt.Sprintf("获取任务信息失败 %v", getTaskRecordsByTaskIdErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 验证用户
if dataVal.UserID != fmt.Sprintf("%v", task.UserId) {
errMsg := "用户验证失败"
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
if task.IsExport == 1 {
errMsg := "任务已导出过,请在下载中心查看"
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
//获取任务详情总数
total, GetBodyOverCount := service.GetBodyOverCount(dataVal.TaskID)
if GetBodyOverCount != nil {
errMsg := fmt.Sprintf("获取任务详情总数失败 %v", GetBodyOverCount)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
mysqlWrite, sqliteWrite := rep.CreateDbFactoryWrite()
//查询导出任务是存在
taskExport, getTaskExportByTaskIdErr := read.GetTaskExportByTaskId(dataVal.TaskID)
if getTaskExportByTaskIdErr != nil {
errMsg := fmt.Sprintf("获取任务信息失败 %v", getTaskExportByTaskIdErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
if taskExport.Id == 0 {
//向导出任务表写入一条数据
mysqlCreateTaskExportErr := mysqlWrite.CreateTaskExport(_type.TaskExportDTO{
UserId: task.UserId,
ShopId: task.ShopId,
TaskId: dataVal.TaskID,
ShopName: task.ShopName,
FileUrl: "",
Status: 0,
Total: total,
CompleteAt: sql.NullTime{},
})
if mysqlCreateTaskExportErr != nil {
errMsg := fmt.Sprintf("写入任务信息失败 %v", mysqlCreateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
sqLiteCreateTaskExport := sqliteWrite.CreateTaskExport(_type.TaskExportDTO{
UserId: task.UserId,
ShopId: task.ShopId,
TaskId: dataVal.TaskID,
ShopName: task.ShopName,
FileUrl: "",
Status: 0,
Total: total,
CompleteAt: sql.NullTime{},
})
if sqLiteCreateTaskExport != nil {
errMsg := fmt.Sprintf("写入任务信息失败 %v", sqLiteCreateTaskExport)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
} else {
// 如果数据存在 清空完成时间 并且 修改任务总数量
mysqlUpdateTaskExportErr := mysqlWrite.UpdateTaskExport(_type.TaskExportDTO{
Id: taskExport.Id,
UserId: taskExport.UserId,
ShopId: taskExport.ShopId,
TaskId: taskExport.TaskId,
ShopName: taskExport.ShopName,
FileUrl: taskExport.FileUrl,
Status: taskExport.Status,
Total: taskExport.Total + total,
CompleteAt: sql.NullTime{},
})
if mysqlUpdateTaskExportErr != nil {
errMsg := fmt.Sprintf("修改任务信息失败 %v", mysqlUpdateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
sqLiteUpdateTaskExportErr := sqliteWrite.UpdateTaskExport(_type.TaskExportDTO{
Id: taskExport.Id,
UserId: taskExport.UserId,
ShopId: taskExport.ShopId,
TaskId: taskExport.TaskId,
ShopName: taskExport.ShopName,
FileUrl: taskExport.FileUrl,
Status: taskExport.Status,
Total: taskExport.Total + total,
CompleteAt: sql.NullTime{},
})
if sqLiteUpdateTaskExportErr != nil {
errMsg := fmt.Sprintf("修改任务信息失败 %v", sqLiteUpdateTaskExportErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
}
//修改任务导出状态
mysqlUpdateTaskExportStatusErr := mysqlWrite.UpdateTaskExportStatus(dataVal.TaskID, 1, "")
if mysqlUpdateTaskExportStatusErr != nil {
errMsg := fmt.Sprintf("修改任务导出状态失败 %v", mysqlUpdateTaskExportStatusErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
sqLiteUpdateTaskExportStatusErr := sqliteWrite.UpdateTaskExportStatus(dataVal.TaskID, 1, "")
if sqLiteUpdateTaskExportStatusErr != nil {
errMsg := fmt.Sprintf("修改任务导出状态失败 %v", sqLiteUpdateTaskExportStatusErr)
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
go ExportCSV(dataVal.TaskID, total, task.TaskType)
tool.Success(httpMsg, "")
}
// ExportCSV 导出CSV
// taskId 任务id
// total 总数
// taskType 任务类型
// ExportCSV 导出CSV
// taskId 任务id
// total 总数
func ExportCSV(taskId string, total int64, taskType int64) {
// 定义每次获取的数量
batchSize := 1000
csvFileName := fmt.Sprintf("%v.csv", taskId)
// 定义导出目录
exportDir := "file/export"
// 检查并创建目录(如果不存在)
err := os.MkdirAll(exportDir, 0755)
if err != nil {
errMsg := fmt.Sprintf("创建目录失败: %v", err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
// 拼接完整的文件路径
fullPath := filepath.Join(exportDir, csvFileName)
// 检查文件是否已存在
fileExists := false
if _, err := os.Stat(fullPath); err == nil {
fileExists = true
fmt.Printf("文件已存在: %s将在末尾追加数据\n", fullPath)
} else if !os.IsNotExist(err) {
// 其他错误(如权限问题)
errMsg := fmt.Sprintf("检查文件状态失败: %v", err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
// 初始化偏移量
page := 1
// 标记是否是第一次写入用于写入CSV表头
// 如果文件已存在,则不需要写入表头
isFirstWrite := !fileExists
mysqlWrite, sqliteWrite := rep.CreateDbFactoryWrite()
// 更新任务导出状态-导出中
mysqlUpdateTaskExportStatusErr := mysqlWrite.UpdateTaskExportStatus(taskId, 1, "")
if mysqlUpdateTaskExportStatusErr != nil {
errMsg := fmt.Sprintf("更新任务导出状态失败: %v", mysqlUpdateTaskExportStatusErr)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
}
sqLiteUpdateTaskExportStatusErr := sqliteWrite.UpdateTaskExportStatus(taskId, 1, "")
if sqLiteUpdateTaskExportStatusErr != nil {
errMsg := fmt.Sprintf("更新任务导出状态失败: %v", sqLiteUpdateTaskExportStatusErr)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
}
// 循环获取并写入数据
for {
// 每次获取 batchSize条数据
dataBatch, _, err := service.GetBodyOverDataByBatch(taskId, page, batchSize)
if err != nil {
errMsg := fmt.Sprintf("获取任务详情批次数据失败 page:%d, err:%v", page, err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
fmt.Printf("数据长度: %v", len(dataBatch))
// 没有数据了,退出循环
if len(dataBatch) == 0 {
// 导出完成
mysqlUpdateTaskExportStatusErr = mysqlWrite.UpdateTaskExportStatus(taskId, 2, fullPath)
if mysqlUpdateTaskExportStatusErr != nil {
errMsg := fmt.Sprintf("更新任务导出状态失败: %v", mysqlUpdateTaskExportStatusErr)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
}
sqLiteUpdateTaskExportStatusErr = sqliteWrite.UpdateTaskExportStatus(taskId, 2, fullPath)
if sqLiteUpdateTaskExportStatusErr != nil {
errMsg := fmt.Sprintf("更新任务导出状态失败: %v", sqLiteUpdateTaskExportStatusErr)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
}
// 清空body_over
clearBodyOverErr := service.ClearBodyOver(taskId)
if clearBodyOverErr != nil {
errMsg := fmt.Sprintf("清空body_over失败: %v", clearBodyOverErr)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
break
}
// 追加写入CSV文件
// 注意AppendToCSV函数需要修改以支持文件存在时的追加模式
if writeErr := AppendToCSV(fullPath, dataBatch, isFirstWrite, taskId, taskType); writeErr != nil {
errMsg := fmt.Sprintf("写入CSV文件失败 page:%d, err:%v", page, writeErr)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
// 第一次写入后标记为false后续不再写表头
if isFirstWrite {
isFirstWrite = false
}
// 更新偏移量
page++
}
}

36
controller/shop.go Normal file
View File

@ -0,0 +1,36 @@
package controller
import (
"net/http"
"planA/service"
"planA/tool"
toolPdd "planA/tool/pdd"
"planA/validator"
)
// GetShopInfo 查询店铺信息
func GetShopInfo(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.GetShopInfoValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 查询店铺数据
shopDataStr, err := service.GetTaskShop(dataVal.ShopId)
if err != nil {
errMsg := "获取店铺数据失败: shopId " + dataVal.ShopId + " " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 解析 json数据
shopData, err := toolPdd.ParseShopData(shopDataStr)
if err != nil {
errMsg := "解析店铺数据失败:" + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, shopData)
}

1752
controller/task.go Normal file

File diff suppressed because it is too large Load Diff

85
controller/uploadImg.go Normal file
View File

@ -0,0 +1,85 @@
package controller
import (
"encoding/json"
"fmt"
"net/http"
"os/exec"
"planA/initialization/golabl"
planBTypeModules "planA/planB/type/modules"
"planA/service"
"planA/tool"
toolPdd "planA/tool/pdd"
"planA/validator"
"strings"
)
// ImgUploadToPdd 上传图片到拼多多
func ImgUploadToPdd(httpMsg http.ResponseWriter, data *http.Request) {
// 验证表单
dataVal, createTaskValidatorErr := validator.ImgUploadToPddValidator(data)
if createTaskValidatorErr != nil {
tool.Error(httpMsg, createTaskValidatorErr.Error(), http.StatusInternalServerError)
return
}
// 查询店铺数据
shopDataStr, err := service.GetTaskShop(dataVal.ShopId)
if err != nil {
errMsg := "获取店铺数据失败: shopId " + dataVal.ShopId + " " + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
// 解析 json数据
shopData, err := toolPdd.ParseShopData(shopDataStr)
if err != nil {
errMsg := "解析店铺数据失败:" + err.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
token := shopData.Shop.Token
e, calleErr := callExe(dataVal.ImgUrl, token)
if calleErr != nil {
errMsg := "调用E程序失败: " + calleErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
//判断字符串 e 是否包含错误
if strings.Contains(e, "错误") {
errMsg := "调用E程序失败: " + e
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
var pddImg planBTypeModules.GoodsImageUploadResponse
// 解析 JSON字符串
unmarshalErr := json.Unmarshal([]byte(e), &pddImg)
if unmarshalErr != nil {
errMsg := "解析E程序返回数据失败: " + unmarshalErr.Error()
tool.Error(httpMsg, errMsg, http.StatusInternalServerError)
return
}
tool.Success(httpMsg, pddImg.GoodsImageUploadResponse.ImageURL)
}
// 调用E程序
func callExe(imgUrl string, token string) (string, error) {
// 调用exe传入两个参数
cmd := exec.Command(golabl.Config.FileUrl.EFileName, imgUrl, token)
// 执行命令并获取输出
output, err := cmd.Output()
if err != nil {
// 如果命令执行失败,获取错误信息
if exitError, ok := err.(*exec.ExitError); ok {
return "", fmt.Errorf("程序执行失败,退出码: %d, 错误信息: %s",
exitError.ExitCode(), string(exitError.Stderr))
}
return "", fmt.Errorf("执行失败: %v", err)
}
// 返回exe打印的数据去除首尾空白字符
result := strings.TrimSpace(string(output))
return result, nil
}

75
go.mod Normal file
View File

@ -0,0 +1,75 @@
module planA
go 1.25.0
require (
github.com/go-playground/validator/v10 v10.30.1
github.com/go-redis/redis/v8 v8.11.5
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/minio/minio-go/v7 v7.1.0
github.com/panjf2000/ants/v2 v2.11.4
github.com/robfig/cron/v3 v3.0.1
golang.org/x/time v0.14.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.31.1
modernc.org/sqlite v1.46.1
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-gonic/gin v1.12.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

209
go.sum Normal file
View File

@ -0,0 +1,209 @@
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.99 h1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=
github.com/minio/minio-go/v7 v7.0.99/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
github.com/minio/minio-go/v7 v7.1.0 h1:QEt5IStDpxgGjEdtOgpiZ5QhmSl3ax7qy61vi2SwHO8=
github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7Ks6olB3OA=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/panjf2000/ants/v2 v2.11.4 h1:UJQbtN1jIcI5CYNocTj0fuAUYvsLjPoYi0YuhqV/Y48=
github.com/panjf2000/ants/v2 v2.11.4/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

18
initialization/c/c.go Normal file
View File

@ -0,0 +1,18 @@
package c
import (
"fmt"
"planA/initialization/golabl"
"planA/tool/process"
)
// RunC 运行C代码
func RunC() {
if golabl.Config.Server.IsC {
//启动 C程序
if err := process.RunCProgram(); err != nil {
fmt.Println("启动C程序失败:", err)
return
}
}
}

View File

@ -0,0 +1,108 @@
package config
import (
"encoding/json"
"fmt"
"planA/initialization/golabl"
_configDll "planA/modules/config"
"planA/tool"
_type "planA/type"
)
var (
gDir string
)
// Init 初始化
// @param dir string 配置文件目录
// @return _type.Config 配置文件信息
// @return error 错误信息
func Init(dir string) error {
gDir = dir
// 判断 ctx 是否取消
checkContextErr := tool.CheckContext(golabl.Ctx)
// 判断 结果
if checkContextErr != nil {
// 返回 且 返回错误
return checkContextErr
}
//读取配置文件
var config _type.Config
dll, initConfigDLLErr := _configDll.InitConfigDLL()
if initConfigDLLErr != nil {
return initConfigDLLErr
}
configJson, ReadConfigFileErr := dll.ReadConfigFile(dir, "config.yaml")
if ReadConfigFileErr != nil {
return fmt.Errorf("读取配置文件失败:%v", ReadConfigFileErr)
}
jsonUnmarshalErr := json.Unmarshal([]byte(configJson), &config)
if jsonUnmarshalErr != nil {
return fmt.Errorf("解析配置文件失败:%v", jsonUnmarshalErr)
}
golabl.Config = config
return nil
}
// GetPddClient 获取拼多多配置
// @return _type.PddConfig 拼多多配置
// @return error 错误信息
func GetPddClient() (_type.PddConfig, error) {
//读取配置文件
var config _type.Config
dll, initConfigDLLErr := _configDll.InitConfigDLL()
if initConfigDLLErr != nil {
return _type.PddConfig{}, initConfigDLLErr
}
configJson, ReadConfigFileErr := dll.ReadConfigFile(gDir, "config.yaml")
if ReadConfigFileErr != nil {
return _type.PddConfig{}, fmt.Errorf("读取配置文件失败:%v", ReadConfigFileErr)
}
jsonUnmarshalErr := json.Unmarshal([]byte(configJson), &config)
if jsonUnmarshalErr != nil {
return _type.PddConfig{}, fmt.Errorf("解析配置文件失败:%v", jsonUnmarshalErr)
}
return config.PddConfig, nil
}
// GetFileUrlConfig 获取文件路径配置
// @return _type.FileUrl 文件路径配置
// @return error 错误信息
func GetFileUrlConfig() (_type.FileUrl, error) {
//读取配置文件
var config _type.Config
dll, initConfigDLLErr := _configDll.InitConfigDLL()
if initConfigDLLErr != nil {
return _type.FileUrl{}, initConfigDLLErr
}
configJson, ReadConfigFileErr := dll.ReadConfigFile(gDir, "config.yaml")
if ReadConfigFileErr != nil {
return _type.FileUrl{}, fmt.Errorf("读取配置文件失败:%v", ReadConfigFileErr)
}
jsonUnmarshalErr := json.Unmarshal([]byte(configJson), &config)
if jsonUnmarshalErr != nil {
return _type.FileUrl{}, fmt.Errorf("解析配置文件失败:%v", jsonUnmarshalErr)
}
return config.FileUrl, nil
}
// GetAliveConfig 获取存活状态配置
// @return _type.Alive 存活状态配置
// @return error 错误信息
func GetAliveConfig() (_type.Alive, error) {
//读取配置文件
var config _type.Config
dll, initConfigDLLErr := _configDll.InitConfigDLL()
if initConfigDLLErr != nil {
return _type.Alive{}, initConfigDLLErr
}
configJson, ReadConfigFileErr := dll.ReadConfigFile(gDir, "config.yaml")
if ReadConfigFileErr != nil {
return _type.Alive{}, fmt.Errorf("读取配置文件失败:%v", ReadConfigFileErr)
}
jsonUnmarshalErr := json.Unmarshal([]byte(configJson), &config)
if jsonUnmarshalErr != nil {
return _type.Alive{}, fmt.Errorf("解析配置文件失败:%v", jsonUnmarshalErr)
}
return config.Alive, nil
}

124
initialization/cron/cron.go Normal file
View File

@ -0,0 +1,124 @@
package cron
import (
"fmt"
"planA/modules/logs"
"github.com/robfig/cron/v3"
)
// Init 定时器初始化
func Init() {
c := cron.New(cron.WithSeconds()) // 支持秒级别的精度
// 每日执行删除sqlite过期记录
_, delSqlIteErr := c.AddFunc("0 0 0 * * ?", func() {
DeleteOldSkuWatermarkImage() //删除过期的 sku水印图片
DeleteOldWatermarkImage() //删除过期的水印图片
DeleteOldExportFile() //删除过期的导出文件
DeleteOldRedis() //删除 redis中过期数据
DeleteOldRecords() //删除task_record过期记录
DeleteOldExport() //删除task_export过期记录
DeleteZipFile() //删除 zip文件
DeleteDelTaskAndDelTaskDetails() //删除任务
})
if delSqlIteErr != nil {
logs.LoggingMiddleware("error", "定时任务 每日执行删除sqlite过期记录 失败")
return
}
//心跳检测 10秒
_, heartbeatErr := c.AddFunc("0/10 * * * * ?", func() {
CheckBannedWordSubstitutionUrlAlive() // 违禁词替换心跳
CheckMysqlAlive() // mysql 心跳
CheckRedisAlive() // redis 心跳
CheckPddAlive() // 拼多多心跳
CheckCreateTaskNoticeUrlAlive() // 创建任务通知心跳
CheckXyBannedWord() // 闲鱼违禁词
return
})
if heartbeatErr != nil {
logs.LoggingMiddleware("error", "定时任务 心跳检测 失败")
return
}
// 60秒钟检测一次
_, bErr := c.AddFunc("0/60 * * * * ?", func() {
B()
})
if bErr != nil {
logs.LoggingMiddleware("error", "定时任务 B 函数 启动失败")
return
}
// 每日执行删除过期日志文件
_, delLogErr := c.AddFunc("0 0 0 * * ?", func() {
DeleteOldLog("logs\\debug")
DeleteOldLog("logs\\info")
DeleteOldLog("logs\\warning")
DeleteOldLog("logs\\error")
DeleteOldLog("logs\\success")
})
if delLogErr != nil {
logs.LoggingMiddleware("error", "定时任务 删除过期日志文件 启动失败")
return
}
// 启动删除任务
_, executeDelTaskErr := c.AddFunc("0/10 * * * * ?", func() {
ExecuteDelTask()
})
if executeDelTaskErr != nil {
logs.LoggingMiddleware("error", "定时任务 启动删除任务 启动失败")
return
}
// 30分钟执行一次
_, verifyTokenErr := c.AddFunc("0 0/30 * * * ?", func() {
VerifyToken()
})
if verifyTokenErr != nil {
logs.LoggingMiddleware("error", "定时任务 30分钟执行一次 启动失败")
return
}
// 删除指定目录的文件夹
_, delDirFolderErr := c.AddFunc("0 0 0 * * ?", func() {
DeleteKfzTempImg()
})
if delDirFolderErr != nil {
logs.LoggingMiddleware("error", "定时任务 删除指定目录的文件夹 启动失败")
return
}
// 五秒执行一次
_, runFErr := c.AddFunc("0/5 * * * * ?", func() {
runFErr := RunF()
if runFErr != nil {
fmt.Println(runFErr)
logs.LoggingMiddleware("error", "定时任务 启动planF.exe 启动失败")
return
}
})
if runFErr != nil {
logs.LoggingMiddleware("error", "定时任务 启动planF.exe 启动失败")
return
}
//// 备份 body_backup到硬盘 - 每分钟执行一次使用锁防止并发挪到C.exe
//_, backupBodyBackupErr := c.AddFunc("0 * * * * ?", func() {
// BackupBodyBackup()
//})
//if backupBodyBackupErr != nil {
// logs.LoggingMiddleware("error", "定时任务 备份 body_backup到硬盘 启动失败")
// return
//}
//
//// 每天上午9点压缩昨天csv文件挪到C.exe
//_, zipBackupFileErr := c.AddFunc("0 0 9 * * ?", func() {
// ZipBackupFile()
//})
//if zipBackupFileErr != nil {
// logs.LoggingMiddleware("error", "定时任务 zipBackupFile 启动失败")
// return
//}
c.Start() // 启动调度器(非阻塞)
}

841
initialization/cron/task.go Normal file
View File

@ -0,0 +1,841 @@
package cron
import (
"archive/zip"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"planA/controlState/serviceAlive"
"planA/controller"
"planA/initialization/config"
"planA/initialization/golabl"
"planA/modules/logs"
"planA/modules/pdd"
"planA/rep"
"planA/service"
"planA/service/mysql"
"planA/tool"
toolPdd "planA/tool/pdd"
"planA/tool/process"
_type "planA/type"
"regexp"
"strings"
"time"
)
// DeleteOldExportFile 删除N天前的导出文件
func DeleteOldExportFile() {
read := rep.CreateDbFactoryRead()
lite, getTaskExportOldListErr := read.GetTaskExportOldList()
if getTaskExportOldListErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取SQLite中N天前的记录失败"+getTaskExportOldListErr.Error())
return
}
for _, v := range lite {
removeErr := os.Remove(v.FileUrl)
if removeErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "删除文件失败:"+removeErr.Error())
continue
}
}
}
// DeleteOldRecords 删除 task_records 表中N天前的记录
func DeleteOldRecords() {
mysqlWrite, sqliteWrite := rep.CreateDbFactoryWrite()
mysqlDeleteTaskRecordsOldDataErr := mysqlWrite.DeleteTaskRecordsOldData()
if mysqlDeleteTaskRecordsOldDataErr != nil {
errMsg := fmt.Sprintf("删除task_records表中N天前的记录失败: %v", mysqlDeleteTaskRecordsOldDataErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
sqLiteDeleteTaskRecordsOldDataErr := sqliteWrite.DeleteTaskRecordsOldData()
if sqLiteDeleteTaskRecordsOldDataErr != nil {
errMsg := fmt.Sprintf("删除task_records表中N天前的记录失败: %v", sqLiteDeleteTaskRecordsOldDataErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
}
// DeleteOldExport 删除 task_export 表中N天前的记录
func DeleteOldExport() {
mysqlWrite, sqliteWrite := rep.CreateDbFactoryWrite()
mysqlDeleteTaskExportOldDataErr := mysqlWrite.DeleteTaskExportOldData()
if mysqlDeleteTaskExportOldDataErr != nil {
errMsg := fmt.Sprintf("删除task_export表中N天前的记录失败: %v", mysqlDeleteTaskExportOldDataErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
sqliteDeleteTaskExportOldDataErr := sqliteWrite.DeleteTaskExportOldData()
if sqliteDeleteTaskExportOldDataErr != nil {
errMsg := fmt.Sprintf("删除task_export表中N天前的记录失败: %v", sqliteDeleteTaskExportOldDataErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
}
// CheckMysqlAlive mysql心跳
func CheckMysqlAlive() {
//计算心跳时间
start := time.Now()
mysql.GetTaskRecordsByTaskId("1")
elapsed := time.Since(start)
elapsedMs := int(elapsed.Milliseconds()) //将time.Duration类型转换为int类型的毫秒
//设置状态
serviceAlive.SetServiceAlive("mysql", elapsedMs)
}
// CheckRedisAlive redis心跳
func CheckRedisAlive() {
//计算心跳时间
start := time.Now()
service.GetTaskBookPing()
elapsed := time.Since(start)
elapsedMs := int(elapsed.Milliseconds()) //将time.Duration类型转换为int类型的毫秒
//设置状态
serviceAlive.SetServiceAlive("redis", elapsedMs)
}
// CheckPddAlive 拼多多心跳
func CheckPddAlive() {
token := ""
//获取系统规定拼多多 token
//urlConfig, _ := config.GetFileUrlConfig()
//_, token, HttpGetRequestErr := tool.HttpGetRequest(urlConfig.PddTokenUrl)
//if HttpGetRequestErr != nil {
// logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取系统规定拼多多 token失败"+HttpGetRequestErr.Error())
// return
//}
pddDll, initPddSOErr := pdd.InitPddDll()
if initPddSOErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "初始化拼多多dll文件失败"+initPddSOErr.Error())
return
}
client, err := config.GetPddClient()
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取拼多多配置失败:"+err.Error())
return
}
//计算心跳时间
start := time.Now()
_, pddTimeGetErr := pddDll.PddTimeGet(client.ClientId, client.ClientSecret, token)
if pddTimeGetErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取拼多多系统时间失败:"+pddTimeGetErr.Error())
return
}
elapsed := time.Since(start)
elapsedMs := int(elapsed.Milliseconds()) //将time.Duration类型转换为int类型的毫秒
//设置状态
serviceAlive.SetServiceAlive("pdd", elapsedMs)
}
// CheckCreateTaskNoticeUrlAlive 价软件提交数据通知接口心跳
func CheckCreateTaskNoticeUrlAlive() {
//计算心跳时间
start := time.Now()
controller.TaskNoticeRequest("ping")
elapsed := time.Since(start)
elapsedMs := int(elapsed.Milliseconds()) //将time.Duration类型转换为int类型的毫秒
//设置状态
serviceAlive.SetServiceAlive("通知取出bodyOver接口", elapsedMs)
}
// CheckBannedWordSubstitutionUrlAlive 违禁词接口心跳
func CheckBannedWordSubstitutionUrlAlive() {
urlConfig, _ := config.GetFileUrlConfig()
bannerWordDataReq := map[string]string{
"isbn": "9787508618388",
"bookName": "麦迪逊大道之王:大卫·奥格威转",
"author": "[美]肯尼斯·罗曼",
"publisher": "中信出版社",
"shopId": "2029141110649929729",
"replaceMark": "1",
}
//计算心跳时间
start := time.Now()
tool.HttpBannedWordSubstitution(urlConfig.BannedWordSubstitutionUrl, bannerWordDataReq)
elapsed := time.Since(start)
elapsedMs := int(elapsed.Milliseconds()) //将time.Duration类型转换为int类型的毫秒
//设置状态
serviceAlive.SetServiceAlive("违禁词替换接口", elapsedMs)
}
// CheckXyBannedWord 闲鱼违禁词
func CheckXyBannedWord() {
url := golabl.Config.FileUrl.XYBannedWordSubstitutionUrl
//计算心跳时间
start := time.Now()
// 发送 GET 请求
resp, err := http.Get(url)
if err != nil {
fmt.Printf("请求失败: %v\n", err)
return
}
defer resp.Body.Close()
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应失败: %v\n", err)
return
}
elapsed := time.Since(start)
elapsedMs := int(elapsed.Milliseconds()) //将time.Duration类型转换为int类型的毫秒
//设置状态
serviceAlive.SetServiceAlive("闲鱼违禁词", elapsedMs)
// 解析 JSON
var healthResp _type.HealthResponse
err = json.Unmarshal(body, &healthResp)
if err != nil {
fmt.Printf("解析 JSON 失败: %v\n", err)
return
}
// 判断 code 是否为 200
if healthResp.Code != 200 {
serviceAlive.SetServiceAliveWithMsg("闲鱼违禁词", elapsedMs, healthResp.Message)
}
}
// DeleteOldRedis 删除redisN天前的数据
func DeleteOldRedis() {
read := rep.CreateDbFactoryRead()
list, getTaskRecordsOldListtErr := read.GetTaskRecordsOldList()
if getTaskRecordsOldListtErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取task_export中N天前的记录失败"+getTaskRecordsOldListtErr.Error())
return
}
for _, v := range list {
err := service.DelTask(v.TaskId)
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "删除任务失败:"+err.Error())
continue
}
}
}
// B 程序守护
func B() {
read := rep.CreateDbFactorySqliteRead()
//查询task_records中24小时内的所有数据
records, getTaskRecords24HourErr := read.GetTaskRecords24Hour()
if getTaskRecords24HourErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取所有任务记录失败:"+getTaskRecords24HourErr.Error())
return
}
for _, v := range records {
//获取 header 信息
header, getTaskHeaderErr := service.GetTaskHeader(v.TaskId)
if getTaskHeaderErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取header 信息失败:"+getTaskHeaderErr.Error())
continue
}
// 不能是淘宝的
if header.Status != 0 && header.ShopType != "6" {
// 启动 B程序
_, runTaskWorkerErr := process.RunTaskWorker(v.TaskId)
if runTaskWorkerErr != nil {
//logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "启动B程序失败"+runTaskWorkerErr.Error())
continue
}
fmt.Println("守护进程成功启动任务B程序的窗口 任务ID" + v.TaskId)
}
}
}
// DeleteOldLog 删除日志N天以上的日志文件
func DeleteOldLog(dir string) {
// 配置参数
pattern := `^[^-]+-[a-z]+-(\d{4}-\d{2}-\d{2})(?:-\d{2})?\.log$` // 匹配两种格式ERROR-task-2026-03-23-04.log 和 ERROR-task-2026-03-23.log
retentionDays := 3 // 保留天数
// 计算截止时间
cutoffTime := time.Now().AddDate(0, 0, -retentionDays)
// 编译正则表达式
regex, err := regexp.Compile(pattern)
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("无效的正则表达式模式: %v", err))
return
}
// 遍历目录
entries, err := os.ReadDir(dir)
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("无法读取目录: %v", err))
return
}
var cleanedCount int
var errors []string
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := entry.Name()
// 检查文件名是否匹配模式并提取日期
matches := regex.FindStringSubmatch(filename)
if len(matches) != 2 {
continue
}
// 解析文件名中的日期
dateStr := matches[1] // 格式: 2026-03-23
fileDate, err := time.Parse("2006-01-02", dateStr)
if err != nil {
errors = append(errors, fmt.Sprintf("解析日期失败 %s: %v", filename, err))
continue
}
// 检查文件日期是否早于截止时间
if fileDate.Before(cutoffTime) {
filePath := filepath.Join(dir, filename)
// 删除文件
if err := os.Remove(filePath); err != nil {
errors = append(errors, fmt.Sprintf("删除失败 %s: %v", filename, err))
} else {
cleanedCount++
}
}
}
// 输出清理结果
if cleanedCount > 0 || len(errors) > 0 {
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("清理完成: 删除了 %d 个文件", cleanedCount))
}
if len(errors) > 0 {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("清理过程中遇到 %d 个错误", len(errors)))
for _, errMsg := range errors {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
}
}
}
// DeleteOldWatermarkImage 删除N天以上的水印图片
func DeleteOldWatermarkImage() {
// 目标根目录(你提供的目录)
rootDir := `img\watermark`
// 计算需要删除的截止时间:当前时间 - N天
days := golabl.Config.Server.DataDay
expireTime := time.Now().AddDate(0, 0, -days)
// 遍历根目录
err := filepath.Walk(rootDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("无法访问目录 %s: %v", path, err)
}
// 只处理一级子文件夹(不递归)
if path == rootDir {
return nil
}
if !f.IsDir() {
return nil
}
// 解析文件夹名称为日期格式2006-01-02
dirName := f.Name()
dirTime, err := time.Parse("2006-01-02", dirName)
if err != nil {
// 不是日期格式的文件夹跳过
return nil
}
// 判断是否超过N天
if dirTime.Before(expireTime) {
// 删除文件夹(包括里面所有内容)
err := os.RemoveAll(path)
if err != nil {
return fmt.Errorf("无法删除目录 %s: %v", path, err)
}
}
// 只处理一级子目录,不递归深入
return filepath.SkipDir
})
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("删除N天以上的水印图片失败: %v", err.Error()))
}
}
// DeleteOldSkuWatermarkImage 删除N天以上的sku水印图片
func DeleteOldSkuWatermarkImage() {
// 目标根目录(你提供的目录)
rootDir := `img\skuwatermark`
// 计算需要删除的截止时间:当前时间 - N天
days := golabl.Config.Server.DataDay
expireTime := time.Now().AddDate(0, 0, -days)
// 遍历根目录
err := filepath.Walk(rootDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("无法访问目录 %s: %v", path, err)
}
// 只处理一级子文件夹(不递归)
if path == rootDir {
return nil
}
if !f.IsDir() {
return nil
}
// 解析文件夹名称为日期格式2006-01-02
dirName := f.Name()
dirTime, err := time.Parse("2006-01-02", dirName)
if err != nil {
// 不是日期格式的文件夹跳过
return nil
}
// 判断是否超过N天
if dirTime.Before(expireTime) {
// 删除文件夹(包括里面所有内容)
err := os.RemoveAll(path)
if err != nil {
return fmt.Errorf("无法删除目录 %s: %v", path, err)
}
}
// 只处理一级子目录,不递归深入
return filepath.SkipDir
})
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("删除N天以上的水印图片失败: %v", err.Error()))
}
}
// BackupBodyBackup 备份 body_backup到硬盘
func BackupBodyBackup() {
// 定义导出目录
csvUrl := golabl.Config.FileUrl.BackupUrl
fmt.Println("路径:" + csvUrl)
// 获取所有任务数据
read := rep.CreateDbFactoryRead()
list, getAllTaskErr := read.GetAllTask()
if getAllTaskErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "获取所有任务数据失败:"+getAllTaskErr.Error())
return
}
for _, v := range list {
dateDir := v.CreateAt.Format("2006-01-02") // 按日期分组
// 构建完整的目录路径
taskCsvUrl := filepath.Join(csvUrl, dateDir)
// 获取 backup数据长度
backupLen, getBodyBackupLenErr := service.GetBodyBackupLen(v.TaskId)
if getBodyBackupLenErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("获取任务 %s 的backup长度失败%v", v.TaskId, getBodyBackupLenErr))
continue // 跳过当前任务,继续处理下一个
}
if backupLen == 0 {
// 如果 backup中没有数据则跳过
continue
}
csvFileName := fmt.Sprintf("%v.csv", v.TaskId)
// 检查并创建目录(如果不存在)
err := os.MkdirAll(taskCsvUrl, 0755)
if err != nil {
errMsg := fmt.Sprintf("创建目录失败: %v", err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return
}
// 拼接完整的文件路径
fullPath := filepath.Join(taskCsvUrl, csvFileName)
// 判断文件是否存在,决定是创建新文件还是追加写入
var file *os.File
if _, err := os.Stat(fullPath); err == nil {
// 文件存在,以追加模式打开
file, err = os.OpenFile(fullPath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
errMsg := fmt.Sprintf("打开文件失败: %v", err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
continue
}
} else if os.IsNotExist(err) {
// 文件不存在,创建新文件
file, err = os.Create(fullPath)
if err != nil {
errMsg := fmt.Sprintf("创建文件失败: %v", err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
continue
}
} else {
// 其他错误(如权限问题)
errMsg := fmt.Sprintf("检查文件状态失败: %v", err)
fmt.Println(errMsg)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
continue
}
defer file.Close()
// 创建 CSV写入器
writer := csv.NewWriter(file)
defer writer.Flush()
// 循环获取并写入数据
for i := 0; i < int(backupLen); i++ {
// 获取 backup数据
one, getBodyBackupOneErr := service.GetBodyBackupOne(v.TaskId)
if getBodyBackupOneErr != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("获取任务 %s 的body_backup数据失败%v", v.TaskId, getBodyBackupOneErr))
break // 跳出当前循环,继续下一个任务
}
// 将数据写入到CSV文件的一行A列
// 假设 one 是字符串类型,如果是结构体需要根据实际字段调整
record := []string{one} // 如果 one 不是字符串,需要转换,例如 fmt.Sprintf("%v", one)
if err := writer.Write(record); err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("写入CSV数据失败%v", err))
break
}
}
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("任务 %s 的数据已成功写入文件:%s", v.TaskId, fullPath))
}
}
// ZipBackupFile 压缩backup文件
func ZipBackupFile() {
csvUrl := golabl.Config.FileUrl.BackupUrl
// 获取昨天的日期
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
// 拼接完整的文件路径
taskCsvUrl := filepath.Join(csvUrl, yesterday)
// 检查源目录是否存在
srcInfo, err := os.Stat(taskCsvUrl)
if os.IsNotExist(err) {
log.Printf("目录不存在: %s", taskCsvUrl)
return
}
// 如果不是目录,则直接返回
if !srcInfo.IsDir() {
log.Printf("路径不是目录: %s", taskCsvUrl)
return
}
// 创建zip文件
zipFileName := taskCsvUrl + ".zip"
zipFile, err := os.Create(zipFileName)
if err != nil {
log.Printf("创建zip文件失败: %v", err)
return
}
defer zipFile.Close()
// 创建zip writer
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// 遍历目录并添加文件
err = filepath.Walk(taskCsvUrl, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 跳过目录本身
if info.IsDir() {
return nil
}
// 获取相对路径作为zip内的文件名
relPath, err := filepath.Rel(taskCsvUrl, path)
if err != nil {
return err
}
// 创建zip中的文件头
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.Join(yesterday, relPath) // 保留目录结构
header.Method = zip.Deflate
// 创建zip中的文件
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
// 打开并复制源文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
})
if err != nil {
log.Printf("压缩失败: %v", err)
os.Remove(zipFileName) // 删除不完整的zip文件
return
}
log.Printf("压缩成功: %s", zipFileName)
// 压缩成功后删除原目录
err = os.RemoveAll(taskCsvUrl)
if err != nil {
log.Printf("删除原目录失败: %v", err)
return
}
log.Printf("成功删除原目录: %s", taskCsvUrl)
}
// DeleteZipFile 删除zip文件
func DeleteZipFile() {
zipDir := golabl.Config.FileUrl.BackupUrl
day := golabl.Config.Server.DataDay
// 获取当前时间
now := time.Now()
// 计算截止时间当天0点减去指定天数
cutoffTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -day)
// 读取目录
entries, err := os.ReadDir(zipDir)
if err != nil {
fmt.Printf("读取目录失败: %v\n", err)
return
}
deletedCount := 0
for _, entry := range entries {
if entry.IsDir() {
continue
}
fileName := entry.Name()
if !strings.HasSuffix(fileName, ".zip") {
continue
}
// 从文件名解析日期格式2026-04-10.zip
dateStr := strings.TrimSuffix(fileName, ".zip")
fileDate, err := time.Parse("2006-01-02", dateStr)
if err != nil {
// 如果文件名不符合日期格式,跳过
continue
}
// 如果文件日期早于或等于截止时间,则删除
if !fileDate.After(cutoffTime) {
filePath := filepath.Join(zipDir, fileName)
err := os.Remove(filePath)
if err != nil {
fmt.Printf("删除文件失败 %s: %v\n", filePath, err)
} else {
fmt.Printf("已删除文件: %s\n", filePath)
deletedCount++
}
}
}
fmt.Printf("共删除 %d 个%d天前的zip文件\n", deletedCount, day)
}
// ExecuteDelTask 查询del_task 表中待执行
func ExecuteDelTask() {
delTask, err := mysql.GetDelTask()
if err != nil {
fmt.Println("查询del_task 表中待执行失败:", err)
}
for _, v := range delTask {
//如果是暂停中的任务,将任务状态修改为执行中
if *v.Status == 2 {
// 要求 v.PauseAt 不能等于 nil 并且 v.PauseAt 必须大于当前时间24小时
if v.PauseAt != nil && !time.Now().After(v.PauseAt.Add(24*time.Hour)) {
continue
}
//修改任务状态
err := mysql.UpdateDelTaskStatus(v.ID)
if err != nil {
fmt.Println("修改任务状态失败:", err)
continue
}
}
//启动任务
_, err := process.RunDprogram(*v.TaskID)
if err != nil {
fmt.Println("启动任务失败:", err)
continue
}
}
}
// DeleteDelTaskAndDelTaskDetails 清理删除任务与删除任务详情过期的数据
func DeleteDelTaskAndDelTaskDetails() {
defer func() {
if r := recover(); r != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("DeleteDelTaskAndDelTaskDetails panic: %v", r))
}
}()
task, getExpiredDelTaskErr := mysql.GetExpiredDelTask()
if getExpiredDelTaskErr != nil {
errMsg := fmt.Sprintf("查询过期的删除任务失败:%v", getExpiredDelTaskErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
fmt.Println(errMsg)
return
}
for _, v := range task {
// 检查必要字段是否为 nil
if v.TaskType == nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_WARNING, fmt.Sprintf("任务记录中 TaskType 为 nil跳过处理 ID: %d", v.ID))
continue
}
if v.TaskID == nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_WARNING, fmt.Sprintf("任务记录中 TaskID 为 nil跳过处理 ID: %d", v.ID))
continue
}
// 处理任务类型 2 或 3
if *v.TaskType == 2 || *v.TaskType == 3 {
// 删除 header 与 footer
delTaskErr := service.DelTask(*v.TaskID)
if delTaskErr != nil {
errMsg := fmt.Sprintf("删除任务失败 TaskID: %s, Error: %v", *v.TaskID, delTaskErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
fmt.Println(errMsg)
// 注意:这里用 continue 而不是 return避免一个任务失败影响其他任务
continue
}
}
// 删除删除任务明细表
deleteDelTaskDetailErr := mysql.DeleteDelTaskDetail(*v.TaskID)
if deleteDelTaskDetailErr != nil {
errMsg := fmt.Sprintf("删除删除任务明细表失败 TaskID: %s, Error: %v", *v.TaskID, deleteDelTaskDetailErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
fmt.Println(errMsg)
continue
}
// 删除任务
deleteDelTaskByIdErr := mysql.DeleteDelTaskById(v.ID)
if deleteDelTaskByIdErr != nil {
errMsg := fmt.Sprintf("删除任务失败 ID: %d, Error: %v", v.ID, deleteDelTaskByIdErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
fmt.Println(errMsg)
continue
}
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("成功清理任务 ID: %d, TaskID: %s", v.ID, *v.TaskID))
}
}
// VerifyToken 验证token过期
func VerifyToken() {
list, getPddTokenListErr := service.GetPddTokenList()
if getPddTokenListErr != nil {
fmt.Println("获取token列表失败", getPddTokenListErr)
return
}
pddDll, initPddSOErr := pdd.InitPddDll()
if initPddSOErr != nil {
fmt.Println("初始化pdd.so失败", initPddSOErr)
return
}
for _, v := range list {
//使用类目预测接口测试Token 是否有效
buildPddGoodsOuterCatMappingGetErr := toolPdd.BuildPddGoodsOuterCatMappingGet(pddDll, v.Token)
if buildPddGoodsOuterCatMappingGetErr != nil {
if buildPddGoodsOuterCatMappingGetErr.Error() == "拼多多Token已过期" {
//fmt.Printf("token 过期的店铺 %v 店铺id %v\n", v.ShopName, v.ID)
reqData := map[string]string{
"shopId": v.ID,
}
_, submitFormDataErr := tool.SubmitFormData(golabl.Config.FileUrl.UpdateTokenUrl, reqData)
if submitFormDataErr != nil {
fmt.Println("提交表单数据失败:", submitFormDataErr)
return
}
}
}
}
}
// DeleteKfzTempImg 删除本地临时的孔夫子图片
func DeleteKfzTempImg() {
err := tool.CleanOldFolders(golabl.Config.FileUrl.KfzImgTempUrl, 3)
if err != nil {
fmt.Println("删除本地临时的孔夫子图片失败:", err)
}
}
/////////////////*********检查F程序启动***********/////////////////////////
func RunF() error {
exeName := golabl.Config.FileUrl.FFileName
running, err := isProcessRunning(exeName)
if err != nil {
return fmt.Errorf("检查进程状态出错: %v\n", err)
}
if !running {
runFProgramErr := process.RunFProgram()
if runFProgramErr != nil {
return runFProgramErr
}
}
return nil
}
func isProcessRunning(exePath string) (bool, error) {
exeName := filepath.Base(exePath)
// 方法1使用 tasklist适用于Windows
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("IMAGENAME eq %s", exeName), "/NH", "/FO", "CSV")
output, err := cmd.Output()
if err != nil {
// 检查是否是"未找到进程"的错误
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return false, nil
}
}
return false, err
}
outputStr := string(output)
// 如果输出包含进程名且不包含"No tasks",则认为进程在运行
return strings.Contains(outputStr, exeName) &&
!strings.Contains(outputStr, "INFO: No tasks"), nil
}
/////////////////*********检查F程序启动***********/////////////////////////

View File

@ -0,0 +1,28 @@
package golabl
import (
"context"
"database/sql"
_type "planA/type"
"time"
"github.com/go-playground/validator/v10"
"github.com/go-redis/redis/v8"
"github.com/gorilla/mux"
"gorm.io/gorm"
)
var (
Ctx context.Context
Config _type.Config
MysqlDb *gorm.DB
PsiMysqlDb *gorm.DB
RedisDbA *redis.Client
RedisDbB *redis.Client
RedisDbC *redis.Client
RedisDbD *redis.Client
SqliteDb *sql.DB
Router = mux.NewRouter()
RedisExp = time.Duration(Config.Server.RedisExp) * time.Hour
Validator *validator.Validate
)

102
initialization/init.go Normal file
View File

@ -0,0 +1,102 @@
package initialization
import (
"context"
"fmt"
"log"
"net/http"
"planA/initialization/c"
"planA/initialization/config"
"planA/initialization/cron"
"planA/initialization/golabl"
"planA/initialization/middle"
"planA/initialization/mysql"
"planA/initialization/redis"
"planA/initialization/router"
"planA/initialization/sqLite"
"planA/initialization/validator"
)
func Init() error {
//初始化上下文
golabl.Ctx = context.Background()
// 初始化配置
configErr := config.Init("")
if configErr != nil {
return fmt.Errorf("初始化配置失败: %v", configErr)
}
// 初始化 mysql
mysqlErr := mysql.Init()
if mysqlErr != nil {
return fmt.Errorf("初始化mysql失败: %v", mysqlErr)
}
// 初始化 redis
redisErr := redis.Init()
if redisErr != nil {
return fmt.Errorf("初始化redis失败: %v", redisErr)
}
// 初始化 sqlite
sqliteErr := sqLite.Init()
if sqliteErr != nil {
return fmt.Errorf("初始化sqlite失败: %v", sqliteErr)
}
// 初始化验证器
validator.Init()
// 初始化定时任务(非阻塞,因此不需要返回错误)
cron.Init()
//初始化中间件
middle.Init()
//初始化路由
router.Init()
//运行 C程序
c.RunC()
return nil
}
// Server 启动服务
func Server() {
// 从配置获取端口并启动服务
port := ":" + golabl.Config.Server.Port
fmt.Printf("服务器启动在 http://localhost%s\n", port)
// 打印所有可用端点(控制台输出)
printAvailableEndpoints()
// 启动HTTP服务如果失败则记录致命错误
log.Fatal(http.ListenAndServe(port, golabl.Router))
}
// printAvailableEndpoints 打印所有可用的API端点
func printAvailableEndpoints() {
fmt.Println("\n========== 可用API端点 ==========")
fmt.Println("\n【任务管理】")
fmt.Println(" POST /task/create - 创建新任务")
fmt.Println(" GET /task/pause/{id} - 暂停任务")
fmt.Println(" GET /task/resume/{id} - 恢复任务")
fmt.Println(" GET /task/stop/{id} - 停止任务")
fmt.Println(" GET /task/over/{id} - 完成任务")
fmt.Println(" GET /task/get - 获取任务列表(支持查询参数)")
fmt.Println(" GET /task/getByUserId - 根据用户ID获取任务")
fmt.Println(" POST /task/setTaskBody - 设置任务内容")
fmt.Println(" GET /task/b - 运行B程序")
fmt.Println("\n【任务导出】")
fmt.Println(" GET /task/export/exportTaskDetail/{id} - 导出指定任务详情")
fmt.Println(" GET /task/export/exportTaskDetail/{userId}/{id} - 导出指定用户的指定任务详情")
fmt.Println(" GET /task/export/get - 获取所有导出任务列表")
fmt.Println(" GET /task/export/get/{userId} - 获取指定用户的导出任务列表")
fmt.Println("\n【商品任务】")
fmt.Println(" POST /task/goods/add - 添加商品任务")
fmt.Println(" GET /task/goods/get/{id} - 获取指定商品任务详情")
fmt.Println(" PUT /task/goods/set/{id} - 更新指定商品任务")
fmt.Println(" DELETE /task/goods/del/{id} - 删除指定商品任务")
fmt.Println("\n【系统工具】")
fmt.Println(" GET /alive/get - 获取服务存活状态列表")
fmt.Println(" GET /health - 健康检查")
fmt.Println(" GET /export/ - 导出文件下载服务")
fmt.Println(" GET / - 服务欢迎页")
fmt.Println("\n=====================================")
}

View File

@ -0,0 +1,25 @@
package middle
import (
"net/http"
)
// Cors 跨域中间件
func Cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置CORS头
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
// 处理OPTIONS请求
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent) // 204
return
}
// 处理正常请求
next.ServeHTTP(w, r)
})
}

View File

@ -0,0 +1,378 @@
package middle
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
_myLogs "planA/modules/logs"
"strings"
"time"
)
// LoggingMiddleware 中间自动记录
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 初始化日志
if err := _myLogs.InitializeLogger("logs"); err != nil {
return
}
if err := _myLogs.SetLogTaskType("task"); err != nil {
return
}
// 记录请求开始时间
startTime := time.Now()
// 收集基本信息
clientIP := getClientIP(r)
userAgent := r.UserAgent()
referer := r.Referer()
// 记录请求信息(但不立即打印,等待收集完数据)
baseMsg := fmt.Sprintf(
"Request: %s %s | ClientIP: %s | User-Agent: %s | Referer: %s",
r.Method,
r.URL.Path,
clientIP,
userAgent,
referer,
)
// 处理不同类型的请求数据
var requestData string
var requestBody []byte
// 如果是需要记录数据的请求方法
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
contentType := r.Header.Get("Content-Type")
contentLength := r.ContentLength
// 根据 Content-Type 处理不同的数据格式
if strings.Contains(contentType, "multipart/form-data") {
// 处理 multipart/form-data
requestData = processMultipartFormData(r, baseMsg)
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
// 处理表单数据
requestData = processFormData(r)
} else if strings.Contains(contentType, "application/json") ||
strings.Contains(contentType, "text/plain") ||
strings.Contains(contentType, "application/xml") {
// 处理 JSON、文本等
requestData = processBodyData(r, &requestBody)
} else {
// 其他类型
requestData = fmt.Sprintf("Content-Type: %s, Content-Length: %d", contentType, contentLength)
}
} else if r.Method == "GET" || r.Method == "HEAD" {
// GET 请求参数
requestData = fmt.Sprintf("Query: %s", r.URL.RawQuery)
}
// 组合完整的日志消息
fullMsg := baseMsg
if requestData != "" {
fullMsg += " | " + requestData
}
// 记录请求头信息(可选)
headers := []string{"Authorization", "Accept", "Accept-Encoding"}
for _, header := range headers {
if value := r.Header.Get(header); value != "" {
fullMsg += fmt.Sprintf(" | %s: %s", header, sanitizeHeaderValue(header, value))
}
}
// 记录请求信息
if err := _myLogs.LogInfo(fullMsg); err != nil {
return
}
// 如果读取了请求体,需要恢复它
if requestBody != nil {
// 重新设置请求体
r.Body = io.NopCloser(bytes.NewBuffer(requestBody))
}
// 使用 ResponseWriter 包装器
crw := &captureResponseWriter{
ResponseWriter: w,
statusCode: 200,
}
// 处理请求
next.ServeHTTP(crw, r)
// 记录响应信息
duration := time.Since(startTime)
responseMsg := fmt.Sprintf(
"Response: %s %s | Status: %d | Duration: %v | Size: %d",
r.Method,
r.URL.Path,
crw.statusCode,
duration,
crw.size,
)
_myLogs.LogInfo(responseMsg)
})
}
// 处理 multipart/form-data
func processMultipartFormData(r *http.Request, baseMsg string) string {
// 保存原始请求体
body, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Sprintf("Error reading body: %v", err)
}
// 恢复请求体供后续使用
r.Body = io.NopCloser(bytes.NewBuffer(body))
// 解析 multipart
reader := bytes.NewReader(body)
boundary := extractBoundary(r.Header.Get("Content-Type"))
if boundary == "" {
return fmt.Sprintf("Content-Type: multipart/form-data (no boundary)")
}
mr := multipart.NewReader(reader, boundary)
var formData []string
sensitiveFields := []string{"password", "token", "secret", "key", "file"}
for {
part, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
formData = append(formData, fmt.Sprintf("Parse error: %v", err))
break
}
fieldName := part.FormName()
fileName := part.FileName()
if fileName != "" {
// 这是文件上传
formData = append(formData, fmt.Sprintf("%s: [FILE] %s (size unknown)", fieldName, fileName))
} else {
// 这是普通字段
partData, _ := io.ReadAll(part)
value := string(partData)
// 检查是否是敏感字段
isSensitive := false
for _, sensitive := range sensitiveFields {
if strings.Contains(strings.ToLower(fieldName), sensitive) {
isSensitive = true
break
}
}
if isSensitive {
formData = append(formData, fmt.Sprintf("%s: [FILTERED]", fieldName))
} else {
// 限制长度,避免日志过大
if len(value) > 100 {
value = value[:100] + "...(truncated)"
}
formData = append(formData, fmt.Sprintf("%s: %s", fieldName, value))
}
}
part.Close()
}
if len(formData) > 0 {
return fmt.Sprintf("FormData: %s", strings.Join(formData, ", "))
}
return "FormData: (empty)"
}
// 从 Content-Type 提取 boundary
func extractBoundary(contentType string) string {
parts := strings.Split(contentType, "boundary=")
if len(parts) > 1 {
return strings.Trim(parts[1], "\" ;")
}
return ""
}
// 处理普通表单数据
func processFormData(r *http.Request) string {
// 复制请求以便解析
r2 := r.Clone(r.Context())
if err := r2.ParseForm(); err != nil {
return fmt.Sprintf("Form parse error: %v", err)
}
var formData []string
sensitiveFields := []string{"password", "token", "secret", "key"}
for key, values := range r2.Form {
isSensitive := false
for _, sensitive := range sensitiveFields {
if strings.Contains(strings.ToLower(key), sensitive) {
isSensitive = true
break
}
}
if isSensitive {
formData = append(formData, fmt.Sprintf("%s: [FILTERED]", key))
} else {
valueStr := strings.Join(values, ",")
if len(valueStr) > 100 {
valueStr = valueStr[:100] + "...(truncated)"
}
formData = append(formData, fmt.Sprintf("%s: %s", key, valueStr))
}
}
if len(formData) > 0 {
return fmt.Sprintf("Form: %s", strings.Join(formData, ", "))
}
return "Form: (empty)"
}
// 处理请求体数据JSON、文本等
func processBodyData(r *http.Request, bodyBuf *[]byte) string {
contentType := r.Header.Get("Content-Type")
contentLength := r.ContentLength
// 读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Sprintf("Content-Type: %s, Content-Length: %d, Error reading: %v",
contentType, contentLength, err)
}
// 保存到缓冲区,以便后续恢复
*bodyBuf = body
// 如果是 JSON尝试美化输出
if strings.Contains(contentType, "application/json") && len(body) > 0 {
var js map[string]interface{}
if err := json.Unmarshal(body, &js); err == nil {
// 过滤敏感字段
js = sanitizeJSON(js)
// 转换为字符串,限制长度
jsonStr, _ := json.Marshal(js)
if len(jsonStr) > 200 {
jsonStr = jsonStr[:200]
return fmt.Sprintf("JSON: %s...(truncated)", string(jsonStr))
}
return fmt.Sprintf("JSON: %s", string(jsonStr))
}
}
// 普通文本
if len(body) > 0 {
// 限制长度
bodyStr := string(body)
if len(bodyStr) > 200 {
bodyStr = bodyStr[:200] + "...(truncated)"
}
return fmt.Sprintf("Body: %s", bodyStr)
}
return fmt.Sprintf("Content-Type: %s, Content-Length: %d", contentType, contentLength)
}
// 过滤 JSON 中的敏感字段
func sanitizeJSON(data map[string]interface{}) map[string]interface{} {
sensitiveFields := []string{"password", "token", "secret", "key", "creditCard", "ssn"}
for key, value := range data {
keyLower := strings.ToLower(key)
// 检查是否是敏感字段
isSensitive := false
for _, sensitive := range sensitiveFields {
if strings.Contains(keyLower, sensitive) {
isSensitive = true
break
}
}
if isSensitive {
data[key] = "[FILTERED]"
} else if subMap, ok := value.(map[string]interface{}); ok {
// 递归处理嵌套对象
data[key] = sanitizeJSON(subMap)
} else if arr, ok := value.([]interface{}); ok {
// 处理数组
for i, item := range arr {
if subMap, ok := item.(map[string]interface{}); ok {
arr[i] = sanitizeJSON(subMap)
}
}
}
}
return data
}
// 获取客户端真实 IP保持不变
func getClientIP(r *http.Request) string {
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
return strings.Split(ip, ",")[0]
}
if ip := r.Header.Get("X-Real-IP"); ip != "" {
return ip
}
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return ip
}
// 清理头信息(保持不变)
func sanitizeHeaderValue(header, value string) string {
if strings.ToLower(header) == "authorization" {
parts := strings.Split(value, " ")
if len(parts) > 1 {
return parts[0] + " [FILTERED]"
}
return "[FILTERED]"
}
return value
}
// ResponseWriter 包装器(保持不变)
type captureResponseWriter struct {
http.ResponseWriter
statusCode int
size int64
wroteHeader bool
}
func (crw *captureResponseWriter) WriteHeader(statusCode int) {
if !crw.wroteHeader {
crw.statusCode = statusCode
crw.wroteHeader = true
crw.ResponseWriter.WriteHeader(statusCode)
}
}
func (crw *captureResponseWriter) Write(b []byte) (int, error) {
if !crw.wroteHeader {
crw.WriteHeader(http.StatusOK)
}
n, err := crw.ResponseWriter.Write(b)
crw.size += int64(n)
return n, err
}
func (crw *captureResponseWriter) Unwrap() http.ResponseWriter {
return crw.ResponseWriter
}

View File

@ -0,0 +1,11 @@
package middle
import (
"planA/initialization/golabl"
)
// Init 初始化中间件
func Init() {
golabl.Router.Use(Cors) //跨域
golabl.Router.Use(Response) //响应
}

View File

@ -0,0 +1,12 @@
package middle
import "net/http"
func Response(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 全局设置 Content-Type 为 application/json
w.Header().Set("Content-Type", "application/json")
// 调用下一个处理函数(核心业务逻辑)
next.ServeHTTP(w, r)
})
}

View File

@ -0,0 +1,66 @@
package middle
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"planA/tool"
"strings"
)
func Sign(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. 读取请求体并备份
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求失败", http.StatusBadRequest)
return
}
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 2. 获取 boundary
var boundary string
ct := r.Header.Get("Content-Type")
if strings.Contains(ct, "multipart/form-data") {
for _, p := range strings.Split(ct, ";") {
p := strings.TrimSpace(p)
if strings.HasPrefix(p, "boundary=") {
boundary = strings.TrimPrefix(p, "boundary=")
break
}
}
}
// 3. 解析所有字段,同名字段拼接成一个字符串(验签用)
paramMap := make(map[string]string)
if boundary != "" {
reader := multipart.NewReader(bytes.NewBuffer(bodyBytes), boundary)
for {
part, err := reader.NextPart()
if err != nil {
break
}
name := part.FormName()
val, _ := io.ReadAll(part)
// 同名字段拼接(关键!!!)
paramMap[name] += string(val)
part.Close()
}
}
// 4. 验签
sign := paramMap["sign"]
if sign == "" {
tool.Error(w, "签名不能为空", http.StatusBadRequest)
return
}
if !tool.VerifySign(paramMap) {
tool.Error(w, "签名失败", http.StatusBadRequest)
return
}
// 5. 放行
next.ServeHTTP(w, r)
})
}

View File

@ -0,0 +1,175 @@
package mysql
import (
"fmt"
"planA/initialization/golabl"
"time"
mysqlModle "planA/type/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// Init 初始化数据库连接
// @return error 错误信息
func Init() error {
//mysql
mysqlDBInitErr := mysqlDBInit()
if mysqlDBInitErr != nil {
return mysqlDBInitErr
}
//psiMysqlInit
psiMysqlInitErr := psiMysqlInit()
if psiMysqlInitErr != nil {
return psiMysqlInitErr
}
return nil
}
// mysqlDBInit
func mysqlDBInit() error {
// 1. 获取mysql配置
mysqlConfig := golabl.Config.MysqlConfig
// 2. 配置 DSN
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlConfig.User,
mysqlConfig.Password,
mysqlConfig.Host,
mysqlConfig.Port,
mysqlConfig.DBName,
)
// 3. 配置 GORM 连接选项
logLevel := logger.Silent
switch mysqlConfig.Loglevel {
case "info":
logLevel = logger.Info
case "warn":
logLevel = logger.Warn
case "error":
logLevel = logger.Error
case "silent":
logLevel = logger.Silent
}
gormConfig := &gorm.Config{
Logger: logger.Default.LogMode(logLevel), //日志级别
DisableForeignKeyConstraintWhenMigrating: true, //不创建外键约束
}
// 4. 连接数据库
db, openErr := gorm.Open(mysql.Open(dsn), gormConfig)
if openErr != nil {
return openErr
}
// 5. 获取底层 sql.DB配置连接池
sqlDB, dbErr := db.DB()
if dbErr != nil {
return dbErr
}
// 连接池优化 + 保活配置
sqlDB.SetMaxOpenConns(mysqlConfig.MaxOpenConns)
sqlDB.SetMaxIdleConns(mysqlConfig.MaxIdleConns)
sqlDB.SetConnMaxIdleTime(mysqlConfig.ConnMaxIdleTime * time.Minute)
sqlDB.SetConnMaxLifetime(mysqlConfig.ConnMaxLifetime * time.Hour)
// 5. 验证连接
if dbPingErr := sqlDB.Ping(); dbPingErr != nil {
return dbPingErr
}
// 6. 迁移表结构
if migrateErr := Migrate(db); migrateErr != nil {
return migrateErr
}
// 7. 保存db实例
golabl.MysqlDb = db
return nil
}
// Migrate 迁移表
func Migrate(db *gorm.DB) error {
// task_records表
if err := mysqlModle.MigrateTaskRecords(db); err != nil {
return err
}
// task_export表
if err := mysqlModle.MigrateTaskExport(db); err != nil {
return err
}
// del_task表
if err := mysqlModle.MigrateDelTask(db); err != nil {
return err
}
return nil
}
// psiMysql
func psiMysqlInit() error {
// 1. 获取mysql配置
mysqlConfig := golabl.Config.PsiMysqlConfig
// 2. 配置 DSN
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlConfig.User,
mysqlConfig.Password,
mysqlConfig.Host,
mysqlConfig.Port,
mysqlConfig.DBName,
)
// 3. 配置 GORM 连接选项
logLevel := logger.Silent
switch mysqlConfig.Loglevel {
case "info":
logLevel = logger.Info
case "warn":
logLevel = logger.Warn
case "error":
logLevel = logger.Error
case "silent":
logLevel = logger.Silent
}
gormConfig := &gorm.Config{
Logger: logger.Default.LogMode(logLevel), //日志级别
DisableForeignKeyConstraintWhenMigrating: true, //不创建外键约束
}
// 4. 连接数据库
db, openErr := gorm.Open(mysql.Open(dsn), gormConfig)
if openErr != nil {
return openErr
}
// 5. 获取底层 sql.DB配置连接池
sqlDB, dbErr := db.DB()
if dbErr != nil {
return dbErr
}
// 连接池优化 + 保活配置
sqlDB.SetMaxOpenConns(mysqlConfig.MaxOpenConns)
sqlDB.SetMaxIdleConns(mysqlConfig.MaxIdleConns)
sqlDB.SetConnMaxIdleTime(mysqlConfig.ConnMaxIdleTime * time.Minute)
sqlDB.SetConnMaxLifetime(mysqlConfig.ConnMaxLifetime * time.Hour)
// 5. 验证连接
if dbPingErr := sqlDB.Ping(); dbPingErr != nil {
return dbPingErr
}
// 7. 保存db实例
golabl.PsiMysqlDb = db
return nil
}

View File

@ -0,0 +1,78 @@
package redis
import (
"fmt"
"planA/initialization/golabl"
_type "planA/type"
"time"
"github.com/go-redis/redis/v8"
)
// Init 初始化Redis连接
// @return error 错误信息
func Init() error {
// 1. 获取redis配置
redisConfig := golabl.Config.RedisConfig
redisClientA, redisErr := NewRedisClient(redisConfig[0])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[0].Addr, redisConfig[0].DB, redisErr)
}
golabl.RedisDbA = redisClientA
// Redis B - Redis实例
redisClientB, redisErr := NewRedisClient(redisConfig[1])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[1].Addr, redisConfig[1].DB, redisErr)
}
golabl.RedisDbB = redisClientB
// Redis C - Redis实例
redisClientC, redisErr := NewRedisClient(redisConfig[2])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[2].Addr, redisConfig[2].DB, redisErr)
}
golabl.RedisDbC = redisClientC
// Redis D - Redis实例
redisClientD, redisErr := NewRedisClient(redisConfig[6])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[6].Addr, redisConfig[6].DB, redisErr)
}
golabl.RedisDbD = redisClientD
//设置默认过期时间
golabl.RedisExp = time.Duration(golabl.Config.Server.RedisExp) * time.Hour
return nil
}
// NewRedisClient 创建redis 客户端
// @param config redis配置
// @return *redis.Client redis客户端
// @return error 错误信息
func NewRedisClient(config _type.RedisConfig) (*redis.Client, error) {
ctx := golabl.Ctx
rdb := redis.NewClient(&redis.Options{
Addr: config.Addr, // 连接地址
Password: config.Password, // 密码
DB: config.DB, // 数据库
PoolSize: config.PoolSize, // 连接池大小
PoolTimeout: time.Duration(config.PoolTimeout), // 连接池超时时间
ReadTimeout: time.Duration(config.ReadTimeout), // 读取超时
WriteTimeout: time.Duration(config.WriteTimeout), // 写入超时
DialTimeout: time.Duration(config.DialTimeout), // 连接超时
IdleTimeout: time.Duration(config.IdleTimeout), // 空闲超时
MinIdleConns: config.MinIdleConns, // 最小空闲连接数
IdleCheckFrequency: time.Duration(config.IdleCheckFrequency), // 空闲检查频率
MaxRetries: config.MaxRetries, // 最大重试次数
MaxRetryBackoff: time.Duration(config.MaxRetryBackoff), // 最大重试间隔
MinRetryBackoff: time.Duration(config.MinRetryBackoff), // 最小重试间隔
})
// 测试连接
_, err := rdb.Ping(ctx).Result()
if err != nil {
return rdb, err
}
return rdb, nil
}

View File

@ -0,0 +1,17 @@
package router
import "planA/router"
// Init 初始化路由
func Init() {
router.DefaultInit()
router.TaskInit()
router.TaskExportInit()
router.StaticInit()
router.AdmiinInir()
router.Alive()
router.DelTaskInit()
router.UploadImgInit()
router.ShopInit()
router.BodyInit()
}

View File

@ -0,0 +1,46 @@
package sqLite
import (
"database/sql"
"errors"
"fmt"
"planA/initialization/golabl"
sqLiteServer "planA/service/sqLite"
_ "modernc.org/sqlite"
)
// Init 初始化sqlIte连接
// @return error 错误信息
func Init() error {
// 1. 打开数据库
db, err := sql.Open("sqlite", "./taskDb.db")
if err != nil {
return errors.New("打开sqLite数据库失败" + err.Error())
}
// 测试连接
err = db.Ping()
if err != nil {
return errors.New("无法连接到sqLite数据库" + err.Error())
}
golabl.SqliteDb = db
// 自动创建表
if err := CreateTable(); err != nil {
return err
}
return nil
}
// CreateTable 自动建表
func CreateTable() error {
createTaskIdTabErr := sqLiteServer.CreateTaskIdTab()
if createTaskIdTabErr != nil {
return fmt.Errorf("自动创建表失败: %v", createTaskIdTabErr)
}
createTaskExportTabErr := sqLiteServer.CreateTaskExportTab()
if createTaskExportTabErr != nil {
return fmt.Errorf("自动创建表失败: %v", createTaskExportTabErr)
}
return nil
}

View File

@ -0,0 +1,11 @@
package validator
import (
"planA/initialization/golabl"
"github.com/go-playground/validator/v10"
)
func Init() {
golabl.Validator = validator.New()
}

17
main.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"fmt"
"planA/initialization"
)
func main() {
// 初始化
err := initialization.Init()
if err != nil {
fmt.Println("初始化失败:", err)
return
}
//启动服务
initialization.Server()
}

BIN
modules/config/config.dll Normal file

Binary file not shown.

74
modules/config/conifg.go Normal file
View File

@ -0,0 +1,74 @@
package config
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// ConfigDLL 配置文件读取DLL结构
type ConfigDLL struct {
dll *syscall.DLL
readConfigFile *syscall.Proc // 读取配置文件
getVersion *syscall.Proc // 获取版本信息
freeCString *syscall.Proc // 释放C字符串
}
// InitConfigDLL 初始化ConfigDLL
func InitConfigDLL() (*ConfigDLL, error) {
dllPath := filepath.Join("modules/config/", "config.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("config DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载config DLL 失败: %s", err)
} else {
return &ConfigDLL{
dll: dll,
readConfigFile: dll.MustFindProc("ReadConfigFile"),
getVersion: dll.MustFindProc("GetVersion"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
// cStr 获取C字符串
func (m *ConfigDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
// ReadConfigFile 读取配置文件
func (m *ConfigDLL) ReadConfigFile(filePath, fileName string) (string, error) {
proc, err := m.dll.FindProc("ReadConfigFile")
if err != nil {
return "", fmt.Errorf("找不到函数 ReadConfigFile: %v", err)
}
filePathPtr, _ := syscall.BytePtrFromString(filePath)
fileNamePtr, _ := syscall.BytePtrFromString(fileName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(filePathPtr)),
uintptr(unsafe.Pointer(fileNamePtr)),
)
result := m.cStr(resultPtr)
return result, nil
}

BIN
modules/image/image.dll Normal file

Binary file not shown.

107
modules/image/image.go Normal file
View File

@ -0,0 +1,107 @@
package image
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"planA/planB/config"
"syscall"
"unsafe"
)
var (
gImageDll *ImageDLL
)
// ImageDLL 图片工具DLL结构
type ImageDLL struct {
Dll *syscall.DLL
AddWatermarkFromURLEx *syscall.Proc // 打水印
}
// InitImageDll 初始化 imageDLL
func InitImageDll() (*ImageDLL, error) {
fileConfig, getDllFileConfigErr := config.GetFileUrlConfig()
if getDllFileConfigErr != nil {
return nil, getDllFileConfigErr
}
dllPath := filepath.Join(fileConfig.ImageDll, "image.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("Image DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载Image DLL 失败: %s", err)
}
gImageDll = &ImageDLL{
Dll: dll,
AddWatermarkFromURLEx: dll.MustFindProc("AddWatermarkFromURLEx"),
}
return gImageDll, nil
}
// WatermarkConfig 添加水印
type WatermarkConfig struct {
SourceImageURL string // 源图片URL地址
WatermarkURL string // 水印图片URL地址
Opacity float64 // 不透明度 (0.0-1.0)
Position string // 位置: center, top-left, top-right, bottom-left, bottom-right, tile
TileSpacing int // 平铺时的间距
Scale float64 // 水印缩放比例 (0.0-1.0)
Rotation float64 // 旋转角度 (度数)
XOffset int // X轴偏移量
YOffset int // Y轴偏移量
Timeout int // 下载超时时间默认30秒
OutputFormat string // 输出格式: "jpeg", "png", "auto"默认auto根据源图片格式auto
JPEGQuality int // JPEG质量 (1-100)默认95
}
// AddWatermarkFromURLExs 添加水印
func (m *ImageDLL) AddWatermarkFromURLExs(sourceImageUrl, watermarkUrl string) (string, error) {
watermarkConfig := WatermarkConfig{
SourceImageURL: sourceImageUrl,
WatermarkURL: watermarkUrl,
Position: "center",
Opacity: 1.0,
Scale: 1.0,
TileSpacing: 50,
Timeout: 30,
OutputFormat: "jpeg",
JPEGQuality: 95,
}
watermarkConfigJson, err := json.Marshal(watermarkConfig)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
proc, err := m.Dll.FindProc("AddWatermarkFromURLEx")
if err != nil {
return "", fmt.Errorf("找不到函数 AddWatermarkFromURLEx: %v", err)
}
watermarkConfigJsonPtr, _ := syscall.BytePtrFromString(string(watermarkConfigJson))
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(watermarkConfigJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}

395
modules/logs/dll.go Normal file
View File

@ -0,0 +1,395 @@
package logs
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"planA/initialization/golabl"
"runtime"
"syscall"
"unsafe"
)
const (
LOG_LEVEL_DEBUG = "DEBUG"
LOG_LEVEL_INFO = "INFO"
LOG_LEVEL_WARNING = "WARNING"
LOG_LEVEL_ERROR = "ERROR"
LOG_LEVEL_SUCCESS = "SUCCESS"
)
// LoggerDLL 封装 logger.dll 操作
type LoggerDLL struct {
dll *syscall.LazyDLL
createLogger *syscall.LazyProc
createContext *syscall.LazyProc
logInfo *syscall.LazyProc
logError *syscall.LazyProc
logWarning *syscall.LazyProc
logSuccess *syscall.LazyProc
freeString *syscall.LazyProc
closeAllLoggers *syscall.LazyProc
}
// LoggerConfig logger配置结构
type LoggerConfig struct {
LogDir string `json:"log_dir"`
SplitType int `json:"split_type"`
RotateType int `json:"rotate_type"`
MaxSize int64 `json:"max_size"`
MaxCount int `json:"max_count"`
Level int `json:"level"`
EnableCaller bool `json:"enable_caller"`
DefaultTaskType string `json:"default_task_type"`
}
var loggerDLLInstance *LoggerDLL
var loggerHandle string
var loggerContextHandle string
// ensureLoggerDLL 确保logger DLL已加载
func ensureLoggerDLL() (*LoggerDLL, error) {
if loggerDLLInstance != nil {
return loggerDLLInstance, nil
}
// 检查是否在Windows平台
if runtime.GOOS != "windows" {
return nil, fmt.Errorf("logger DLL only supported on Windows platform")
}
dllPath := filepath.Join(golabl.Config.FileUrl.LogDll, "logger.dll")
// 检查文件是否存在
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
// 尝试从当前目录查找
if _, err := os.Stat("logger.dll"); err == nil {
dllPath = "logger.dll"
} else {
return nil, fmt.Errorf("logger DLL not found at %s", dllPath)
}
}
dll := syscall.NewLazyDLL(dllPath)
loggerDLLInstance = &LoggerDLL{
dll: dll,
createLogger: dll.NewProc("CreateLogger"),
createContext: dll.NewProc("CreateContextWithTaskType"),
logInfo: dll.NewProc("LogInfo"),
logError: dll.NewProc("LogError"),
logWarning: dll.NewProc("LogWarning"),
logSuccess: dll.NewProc("LogSuccess"),
freeString: dll.NewProc("FreeString"),
closeAllLoggers: dll.NewProc("CloseAllLoggers"),
}
return loggerDLLInstance, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
// InitializeLogger 初始化logger
func InitializeLogger(logDir string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
// 确保日志目录存在
if err := os.MkdirAll(logDir, 0755); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
// 创建logger配置
config := LoggerConfig{
LogDir: logDir,
SplitType: 2, // SplitByDay
RotateType: 0, // RotateBySize
MaxSize: 100 * 1024 * 1024, // 100MB
MaxCount: 10,
Level: 1, // LevelInfo - 只显示INFO及以上级别的日志
EnableCaller: true,
DefaultTaskType: "main",
}
configJSON, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("序列化配置失败: %v", err)
}
// 调用CreateLogger
configPtr, _ := syscall.BytePtrFromString(string(configJSON))
ret, _, _ := m.createLogger.Call(uintptr(unsafe.Pointer(configPtr)))
if ret == 0 {
return fmt.Errorf("创建logger失败")
}
// 获取logger句柄
handle := cStr(ret)
loggerHandle = handle
// 释放返回的字符串
m.freeString.Call(ret)
// 创建默认上下文
return createLoggerContext("main")
}
// createLoggerContext 创建带任务类型的logger上下文
func createLoggerContext(taskType string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerHandle == "" {
return fmt.Errorf("logger未初始化")
}
handlePtr, _ := syscall.BytePtrFromString(loggerHandle)
taskTypePtr, _ := syscall.BytePtrFromString(taskType)
ret, _, _ := m.createContext.Call(
uintptr(unsafe.Pointer(handlePtr)),
uintptr(unsafe.Pointer(taskTypePtr)),
)
if ret == 0 {
return fmt.Errorf("创建logger上下文失败")
}
// 获取上下文句柄
loggerContextHandle = cStr(ret)
// 释放返回的字符串
m.freeString.Call(ret)
return nil
}
// SetLogTaskType 设置当前日志任务类型
func SetLogTaskType(taskType string) error {
return createLoggerContext(taskType)
}
// LogInfo 记录信息日志
func LogInfo(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logInfo.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogError 记录错误日志
func LogError(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logError.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogWarning 记录警告日志
func LogWarning(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logWarning.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogSuccess 记录成功日志
func LogSuccess(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logSuccess.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// CloseLogger 关闭logger
func CloseLogger() error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
ret, _, _ := m.closeAllLoggers.Call()
if ret == 0 {
return fmt.Errorf("关闭logger失败")
}
m.freeString.Call(ret)
loggerHandle = ""
loggerContextHandle = ""
loggerDLLInstance = nil
return nil
}
// GetLoggerHandle 获取当前logger句柄用于外部调用
func GetLoggerHandle() string {
return loggerContextHandle
}
// IsLoggerInitialized 检查logger是否已初始化
func IsLoggerInitialized() bool {
return loggerHandle != "" && loggerContextHandle != ""
}
// SetConsoleOutput 设置控制台输出开关
func SetConsoleOutput(enabled bool) {
if enabled {
os.Setenv("LOG_CONSOLE", "true")
} else {
os.Setenv("LOG_CONSOLE", "false")
}
}
// LogWithLevel 带级别的日志记录,可以精确控制显示
func LogWithLevel(level, message string, showConsole bool) {
if !IsLoggerInitialized() {
return
}
switch level {
case "ERROR":
LogError(message)
case "WARNING":
LogWarning(message)
case "SUCCESS":
LogSuccess(message)
case "INFO":
LogInfo(message)
default:
LogInfo(message)
}
}
// LogOnlyFile 仅写入文件,不输出到控制台
func LogOnlyFile(level, message string) {
// 临时禁用控制台输出
os.Setenv("LOG_CONSOLE", "false")
LogWithLevel(level, message, false)
}
// LogConsoleAndFile 同时输出到控制台和文件
func LogConsoleAndFile(level, message string) {
// 临时启用控制台输出
os.Setenv("LOG_CONSOLE", "true")
LogWithLevel(level, message, true)
}
// LoggingMiddleware 记录日志
func LoggingMiddleware(level string, str string) {
initializeLoggerErr := InitializeLogger("logs")
if initializeLoggerErr != nil {
fmt.Println("初始化日志失败:", initializeLoggerErr)
return
}
setLogTaskTypeErr := SetLogTaskType("task")
if setLogTaskTypeErr != nil {
fmt.Println("设置日志任务类型失败:", setLogTaskTypeErr)
return
}
switch {
case level == LOG_LEVEL_ERROR:
fmt.Println(str)
logErrorErr := LogError(str)
if logErrorErr != nil {
fmt.Println("记录错误日志失败:", logErrorErr)
return
}
case level == LOG_LEVEL_WARNING:
logWarningErr := LogWarning(str)
if logWarningErr != nil {
fmt.Println("记录警告日志失败:", logWarningErr)
return
}
case level == LOG_LEVEL_SUCCESS:
logSuccessErr := LogSuccess(str)
if logSuccessErr != nil {
fmt.Println("记录成功日志失败:", logSuccessErr)
return
}
default:
logInfoErr := LogInfo(str)
if logInfoErr != nil {
fmt.Println("记录信息日志失败:", logInfoErr)
return
}
}
}

BIN
modules/logs/logger.dll Normal file

Binary file not shown.

602
modules/logs/logger.md Normal file
View File

@ -0,0 +1,602 @@
# logger.dll 使用教程
## 1. 创建DLL工具实例
### 加载DLL文件
```gotemplate
package logs
import (
"encoding/json"
"fmt"
"os"
"runtime"
"syscall"
"unsafe"
)
// LoggerDLL 封装 logger.dll 操作
type LoggerDLL struct {
dll *syscall.LazyDLL
createLogger *syscall.LazyProc
createContext *syscall.LazyProc
logInfo *syscall.LazyProc
logError *syscall.LazyProc
logWarning *syscall.LazyProc
logSuccess *syscall.LazyProc
freeString *syscall.LazyProc
closeAllLoggers *syscall.LazyProc
}
// LoggerConfig logger配置结构
type LoggerConfig struct {
LogDir string `json:"log_dir"`
SplitType int `json:"split_type"`
RotateType int `json:"rotate_type"`
MaxSize int64 `json:"max_size"`
MaxCount int `json:"max_count"`
Level int `json:"level"`
EnableCaller bool `json:"enable_caller"`
DefaultTaskType string `json:"default_task_type"`
}
var loggerDLLInstance *LoggerDLL
var loggerHandle string
var loggerContextHandle string
// ensureLoggerDLL 确保logger DLL已加载
func ensureLoggerDLL() (*LoggerDLL, error) {
if loggerDLLInstance != nil {
return loggerDLLInstance, nil
}
// 检查是否在Windows平台
if runtime.GOOS != "windows" {
return nil, fmt.Errorf("logger DLL only supported on Windows platform")
}
// logger.dll 位于 dll/logger.dll
//dllPath := filepath.Join("modules", "logs", "logger.dll")
dllPath := "D:\\www\\wwwroot\\planA\\modules\\logs\\logger.dll"
// 检查文件是否存在
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
// 尝试从当前目录查找
if _, err := os.Stat("logger.dll"); err == nil {
dllPath = "logger.dll"
} else {
return nil, fmt.Errorf("logger DLL not found at %s", dllPath)
}
}
dll := syscall.NewLazyDLL(dllPath)
loggerDLLInstance = &LoggerDLL{
dll: dll,
createLogger: dll.NewProc("CreateLogger"),
createContext: dll.NewProc("CreateContextWithTaskType"),
logInfo: dll.NewProc("LogInfo"),
logError: dll.NewProc("LogError"),
logWarning: dll.NewProc("LogWarning"),
logSuccess: dll.NewProc("LogSuccess"),
freeString: dll.NewProc("FreeString"),
closeAllLoggers: dll.NewProc("CloseAllLoggers"),
}
return loggerDLLInstance, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
// InitializeLogger 初始化logger
func InitializeLogger(logDir string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
// 确保日志目录存在
if err := os.MkdirAll(logDir, 0755); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
// 创建logger配置
config := LoggerConfig{
LogDir: logDir,
SplitType: 1, // SplitByDay
RotateType: 0, // RotateBySize
MaxSize: 100 * 1024 * 1024, // 100MB
MaxCount: 10,
Level: 1, // LevelInfo - 只显示INFO及以上级别的日志
EnableCaller: true,
DefaultTaskType: "main",
}
configJSON, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("序列化配置失败: %v", err)
}
// 调用CreateLogger
configPtr, _ := syscall.BytePtrFromString(string(configJSON))
ret, _, _ := m.createLogger.Call(uintptr(unsafe.Pointer(configPtr)))
if ret == 0 {
return fmt.Errorf("创建logger失败")
}
// 获取logger句柄
handle := cStr(ret)
loggerHandle = handle
// 释放返回的字符串
m.freeString.Call(ret)
// 创建默认上下文
return createLoggerContext("main")
}
// createLoggerContext 创建带任务类型的logger上下文
func createLoggerContext(taskType string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerHandle == "" {
return fmt.Errorf("logger未初始化")
}
handlePtr, _ := syscall.BytePtrFromString(loggerHandle)
taskTypePtr, _ := syscall.BytePtrFromString(taskType)
ret, _, _ := m.createContext.Call(
uintptr(unsafe.Pointer(handlePtr)),
uintptr(unsafe.Pointer(taskTypePtr)),
)
if ret == 0 {
return fmt.Errorf("创建logger上下文失败")
}
// 获取上下文句柄
loggerContextHandle = cStr(ret)
// 释放返回的字符串
m.freeString.Call(ret)
return nil
}
// SetLogTaskType 设置当前日志任务类型
func SetLogTaskType(taskType string) error {
return createLoggerContext(taskType)
}
// LogInfo 记录信息日志
func LogInfo(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logInfo.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogError 记录错误日志
func LogError(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logError.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogWarning 记录警告日志
func LogWarning(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logWarning.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogSuccess 记录成功日志
func LogSuccess(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logSuccess.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// CloseLogger 关闭logger
func CloseLogger() error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
ret, _, _ := m.closeAllLoggers.Call()
if ret == 0 {
return fmt.Errorf("关闭logger失败")
}
m.freeString.Call(ret)
loggerHandle = ""
loggerContextHandle = ""
loggerDLLInstance = nil
return nil
}
// GetLoggerHandle 获取当前logger句柄用于外部调用
func GetLoggerHandle() string {
return loggerContextHandle
}
// IsLoggerInitialized 检查logger是否已初始化
func IsLoggerInitialized() bool {
return loggerHandle != "" && loggerContextHandle != ""
}
// SetConsoleOutput 设置控制台输出开关
func SetConsoleOutput(enabled bool) {
if enabled {
os.Setenv("LOG_CONSOLE", "true")
} else {
os.Setenv("LOG_CONSOLE", "false")
}
}
// LogWithLevel 带级别的日志记录,可以精确控制显示
func LogWithLevel(level, message string, showConsole bool) {
if !IsLoggerInitialized() {
return
}
switch level {
case "ERROR":
LogError(message)
case "WARNING":
LogWarning(message)
case "SUCCESS":
LogSuccess(message)
case "INFO":
LogInfo(message)
default:
LogInfo(message)
}
}
// LogOnlyFile 仅写入文件,不输出到控制台
func LogOnlyFile(level, message string) {
// 临时禁用控制台输出
os.Setenv("LOG_CONSOLE", "false")
LogWithLevel(level, message, false)
}
// LogConsoleAndFile 同时输出到控制台和文件
func LogConsoleAndFile(level, message string) {
// 临时启用控制台输出
os.Setenv("LOG_CONSOLE", "true")
LogWithLevel(level, message, true)
}
```
# 接口详情
## 创建日志器--CreateLogger
### 请求信息
```gotemplate
dll.CreateLogger(configJSON)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| configJSON | string | 是 | 配置信息JSON字符串 |
#### 配置JSON结构
```json
{
"log_dir": "/path/to/logs",
"split_type": 0,
"rotate_type": 0,
"max_size": 104857600,
"max_count": 30,
"level": 1,
"enable_caller": true,
"default_task_type": "main"
}
```
#### 参数说明:
```text
log_dir: 日志目录路径
split_type: 分片方式0=按月1=按天2=按小时3=按分钟4=按秒)
rotate_type: 轮转方式0=按大小1=按数量)
max_size: 最大文件大小字节仅在rotate_type=0时有效
max_count: 最大文件数量仅在rotate_type=1时有效
level: 日志级别0=SUCCESS1=INFO2=WARNING3=ERROR
enable_caller: 是否启用调用者信息
default_task_type: 默认任务类型
```
### 响应示例
```json
"错误: 创建日志目录失败: permission denied"
```
## 创建带任务类型的上下文--CreateContextWithTaskType
### 请求信息
```gotemplate
dll.CreateContextWithTaskType(loggerHandle, taskType)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| loggerHandle | string | 是 | 日志器句柄 |
| taskType | string | 是 | 任务类型 |
### 响应示例
```json
"ctx_1645497600000000000"
```
#### 错误响应示例
```json
"错误: 无效的logger句柄"
```
## 记录信息日志--LogInfo
### 请求信息
```gotemplate
dll.LogInfo(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 记录错误日志--LogError
### 请求信息
```gotemplate
dll.LogError(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 记录警告日志--LogWarning
### 请求信息
```gotemplate
dll.LogWarning(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 记录成功日志--LogSuccess
### 请求信息
```gotemplate
dll.LogSuccess(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 获取日志条目--GetLogs
### 请求信息
```gotemplate
dll.GetLogs(loggerHandle, configJSON)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------|
| loggerHandle | string | 是 | 日志器句柄 |
| configJSON | string | 是 | 查询配置JSON |
#### 查询配置JSON结构
```json
{
"level": 1,
"task_type": "main",
"start_time": "2024-01-01 00:00:00",
"end_time": "2024-01-31 23:59:59",
"max_entries": 1000
}
```
#### 参数说明:
```text
level: 日志级别(-1表示所有级别
task_type: 任务类型(空字符串表示所有任务类型)
start_time: 开始时间(格式: 2006-01-02 15:04:05
end_time: 结束时间(格式: 2006-01-02 15:04:05
max_entries: 最大返回条目数0表示使用默认值1000
```
### 响应示例
```json
{
"count": 125,
"entries": [
{
"timestamp": "2024-01-15 10:30:45.123",
"level": "INFO",
"task_type": "main",
"caller": "logger.go:256",
"message": "系统启动完成"
},
{
"timestamp": "2024-01-15 10:31:15.456",
"level": "ERROR",
"task_type": "backup",
"caller": "backup.go:89",
"message": "备份文件失败: 磁盘空间不足"
}
]
}
```
### 错误响应示例
```json
{"error": "无效的logger句柄"}
```
## 获取日志文件列表--GetLogFiles
### 请求信息
```gotemplate
dll.GetLogFiles(loggerHandle)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------|
| loggerHandle | string | 是 | 日志器句柄 |
### 响应示例
```json
{
"count": 8,
"files": [
{
"level": "INFO",
"task_type": "main",
"file_name": "INFO-main-2024-01.logs",
"file_size": 1048576,
"mod_time": "2024-01-15 10:30:45"
},
{
"level": "ERROR",
"task_type": "backup",
"file_name": "ERROR-backup-2024-01.logs",
"file_size": 51200,
"mod_time": "2024-01-15 10:31:15"
}
]
}
```
### 错误响应示例
```json
{"error": "无效的logger句柄"}
```
## 获取版本信息--GetVersion
### 请求信息
```gotemplate
dll.GetVersion()
```
### 请求参数
```text
无参数
```
### 响应示例
```json
"v1"
```
## 关闭所有日志器--CloseAllLoggers
### 请求信息
```gotemplate
dll.CloseAllLoggers()
```
### 请求参数
```text
无参数
```
### 响应示例
```json
"成功关闭所有logger"
```
### 错误响应示例
```json
"关闭了5个logger其中1个出错最后错误: close file error"
```
## 释放C字符串内存--FreeString
### 请求信息
```gotemplate
dll.FreeString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------|
| str | string | 是 | 需要释放的字符串 |

BIN
modules/pdd/pdd.dll Normal file

Binary file not shown.

223
modules/pdd/pdd.go Normal file
View File

@ -0,0 +1,223 @@
package pdd
import (
"fmt"
"os"
"path/filepath"
"planA/initialization/golabl"
"syscall"
"unsafe"
)
var (
gPddDll *PddDLL
)
// PddResponse 定义完整的响应结构(包含成功和失败两种情况)
type PddResponse struct {
SuccessResponse *PddSuccessResponse `json:"outer_cat_mapping_get_response,omitempty"`
ErrorResponse *PddErrorResponse `json:"error_response,omitempty"`
}
type PddSuccessResponse struct {
OuterCatMappingGetResponse PddCategoryMappingResponse `json:"outer_cat_mapping_get_response"`
}
// PddCategoryMappingResponse 定义拼多多API响应结构根据文档规范
type PddCategoryMappingResponse struct {
CatID1 int64 `json:"cat_id1"` // 一级类目 ID
CatID2 int64 `json:"cat_id2"` // 二级类目 ID
CatID3 int64 `json:"cat_id3"` // 三级类目 ID
CatID4 int64 `json:"cat_id4"` // 四级类目 ID
RequestID string `json:"request_id"` // 请求 ID
}
// PddDLL 拼多多工具DLL结构
type PddDLL struct {
Dll *syscall.DLL
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
freeCString *syscall.Proc // 释放C字符串
}
type PddErrorResponse struct {
ErrorCode int64 `json:"error_code"` // 错误码
ErrorMsg string `json:"error_msg"` // 错误信息
SubCode *string `json:"sub_code"` // 子错误码
SubMsg string `json:"sub_msg"` // 子错误信息
RequestID string `json:"request_id"` // 请求ID
}
// InitPddDll 初始化 pddDLL
func InitPddDll() (*PddDLL, error) {
dllPath := filepath.Join(golabl.Config.FileUrl.PddDll, "pdd.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
}
gPddDll = &PddDLL{
Dll: dll,
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
freeCString: dll.MustFindProc("FreeCString"),
}
return gPddDll, nil
}
// PddGoodsOuterCatMappingGet 类目预测
func (m *PddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsOuterCatMappingGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(outerCatIdPtr)),
uintptr(unsafe.Pointer(outerCatNamePtr)),
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsAdd 商品新增
func (m *PddDLL) PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsAdd")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsAdd: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
goodsAddJsonPtr, _ := syscall.BytePtrFromString(goodsAddJson)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(goodsAddJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
// PddGoodsSpecIdGet 生成商家自定义的规格
func (m *PddDLL) PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsSpecIdGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsSpecIdGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
parentSpecIdPtr, _ := syscall.BytePtrFromString(parentSpecId)
specNamePtr, _ := syscall.BytePtrFromString(specName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(parentSpecIdPtr)),
uintptr(unsafe.Pointer(specNamePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsCommitDetailGet 获取商品提交的商品详情
func (m *PddDLL) PddGoodsCommitDetailGet(clientId, clientSecret, accessToken, goodsCommitId, goodsId string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsCommitDetailGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsCommitDetailGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
goodsCommitIdPtr, _ := syscall.BytePtrFromString(goodsCommitId)
goodsIdPtr, _ := syscall.BytePtrFromString(goodsId)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(goodsCommitIdPtr)),
uintptr(unsafe.Pointer(goodsIdPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddTimeGet 获取拼多多系统时间
func (m *PddDLL) PddTimeGet(clientId, clientSecret, accessToken string) (string, error) {
proc, err := m.Dll.FindProc("PddTimeGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsCommitDetailGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsImageUpload 上传图片
func (m *PddDLL) PddGoodsImageUpload(clientId, clientSecret, accessToken, imgBase64 string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsImageUpload")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsImageUpload: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
imgBase64Ptr, _ := syscall.BytePtrFromString(imgBase64)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(imgBase64Ptr)),
)
result := cStr(resultPtr)
return result, nil
}

863
modules/pdd/pdd.md Normal file
View File

@ -0,0 +1,863 @@
# pdd.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
```gotemplate
// PddDLL 拼多多工具DLL结构
type pddDLL struct {
dll *syscall.DLL
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
freeCString *syscall.Proc // 释放C字符串
}
// <初始化pddDLL></初始化pddDLL>
func InitPddDLL() (*pddDLL, error) {
dllPath := filepath.Join("dll", "pdd.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
} else {
return &pddDLL{
dll: dll,
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
dll, err := InitPddDLL()
```
### 获取C字符串
```gotemplate
// cStr 获取C字符串
func (m *pddDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
```
## 2. 使用dll函数示例
```gotemplate
// 类目预测
func (m *pddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
proc, err := m.dll.FindProc("PddGoodsOuterCatMappingGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(outerCatIdPtr)),
uintptr(unsafe.Pointer(outerCatNamePtr)),
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
)
result := m.cStr(resultPtr)
return result, nil
}
```
# 接口详情
## 1. 类目预测--PddGoodsOuterCatMappingGet
### 请求信息
```gotemplate
dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| outerCatId | string | 是 | 外部平台类目ID |
| outerCatName | string | 是 | 外部平台类目名称 |
| outerGoodsName | string | 是 | 外部商品名称 |
### 响应示例
```json
{
"outer_cat_mapping_get_response": {
"cat_id2": 16028,
"cat_id3": 16031,
"cat_id1": 15543,
"request_id": "17666480184871649",
"cat_id4": 0
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 2. 快递公司查看--PddLogisticsCompaniesGet
### 请求信息
```gotemplate
dll.PddLogisticsCompaniesGet(clientId, clientSecret)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
### 响应示例
```json
{
"logistics_companies_get_response": {
"logistics_companies": [
{
"available": 1,
"code": "SF",
"id": 1,
"logistics_company": "顺丰速运"
},
{
"available": 1,
"code": "STO",
"id": 2,
"logistics_company": "申通快递"
}
]
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 3. erp打单信息同步--PddErpOrderSync
### 请求信息
```gotemplate
dll.PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
orderSn, orderState, waybillNo)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| logisticsId | string | 是 | 物流公司ID |
| orderSn | string | 是 | 拼多多订单号 |
| orderState | string | 是 | 订单状态 |
| waybillNo | string | 是 | 运单号 |
### 响应示例
```json
{
"erp_order_sync_response": {
"is_success": true,
"request_id": "17666480184871650"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 4. 拼多多订单同步--PddOrderSynchronization
### 请求信息
```gotemplate
dll.PddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsOnlineSendJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| logisticsCompany | string | 是 | 物流公司名称 |
| logisticsOnlineSendJson | string | 是 | 拼多多订单同步json字符串 |
### 响应示例
```json
{
"erp_order_sync_response": {
"is_success": true,
"request_id": "17666480184871651"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 5. 商品图片上传接口--PddGoodsImgUpload
### 请求信息
```gotemplate
dll.PddGoodsImgUpload(clientId, clientSecret, accessToken, filePath)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| filePath | string | 是 | 图片文件路径 |
### 响应示例
```json
{
"goods_img_upload_response": {
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"request_id": "17666480184871652"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 6. 商品新增接口--PddGoodsAdd
### 请求信息
```gotemplate
dll.PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| goodsAddJson | string | 是 | 商品信息JSON字符串 |
#### 商品信息JSON结构示例
```json
{
"goods_name": "测试商品",
"goods_desc": "商品描述",
"cat_id": 20111,
"goods_type": 1,
"market_price": 9900,
"is_folt": false,
"is_pre_sale": false,
"is_refundable": true,
"shipment_limit_second": 86400,
"cost_template_id": 10001,
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"carousel_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"detail_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"sku_list": [
{
"out_sku_sn": "SKU001",
"price": 8900,
"quantity": 100,
"spec_id_list": "1001:10001",
"sku_properties": [
{
"ref_pid": 1001,
"value": "红色",
"vid": 10001,
"punit": "个"
}
],
"is_onsale": 1,
"limit_quantity": 10,
"multi_price": 8500,
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"weight": 500
}
]
}
```
### 响应示例
```json
{
"goods_add_response": {
"goods_id": 123456789,
"goods_name": "测试商品",
"goods_sn": "G202501200001",
"request_id": "17666480184871653"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 7. 联合拼多多图片上传的商品新增--SelfPddGoodsAdd
### 请求信息
```gotemplate
dll.SelfPddGoodsAdd(clientId, clientSecret, accessToken, filePath, goodsAddJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| filePath | string | 是 | 图片文件路径 |
| goodsAddJson | string | 是 | 商品信息JSON字符串不需包含image_url|
#### 接口说明
此接口为组合接口,内部执行以下步骤:
1.上传商品主图文件到拼多多服务器
2.获取图片URL并自动填充到商品信息中
3.调用商品新增接口创建商品
#### 商品信息JSON结构示例
```json
{
"goods_name": "测试商品",
"goods_desc": "商品描述",
"cat_id": 20111,
"goods_type": 1,
"market_price": 9900,
"is_folt": false,
"is_pre_sale": false,
"is_refundable": true,
"shipment_limit_second": 86400,
"cost_template_id": 10001,
"image_url": "",
"carousel_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"detail_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"sku_list": [
{
"out_sku_sn": "SKU001",
"price": 8900,
"quantity": 100,
"spec_id_list": "1001:10001",
"sku_properties": [
{
"ref_pid": 1001,
"value": "红色",
"vid": 10001,
"punit": "个"
}
],
"is_onsale": 1,
"limit_quantity": 10,
"multi_price": 8500,
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"weight": 500
}
]
}
```
### 响应示例
```json
{
"goods_add_response": {
"goods_id": 123456790,
"goods_name": "测试商品",
"goods_sn": "G202501200002",
"request_id": "17666480184871654"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 8. 批量数据解密脱敏接口--PddOpenDecryptMaskBatch
### 请求信息
```gotemplate
dll.PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| reqJson | string | 是 | 信息JSON字符串 |
#### 信息JSON结构示例
```json
[
{
"data_tag": "251229-272441044622514",
"encrypted_data": "~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"
}
]
```
### 响应示例
```json
{
"open_decrypt_mask_batch_response": {
"data_decrypt_list": [
{
"data_tag": "str",
"data_type": 0,
"decrypted_data": "str",
"encrypted_data": "str",
"error_code": 0,
"error_msg": "str"
}
]
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 生成商家自定义的规格--PddGoodsSpecIdGet
### 请求信息
```gotemplate
dll.PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| parentSpecId | string | 是 | 拼多多标准规格ID |
| specName | string | 是 | 商家编辑的规格值,如颜色规格下设置白色属性 |
### 响应参数
```json
{
"goods_spec_id_get_response": {
"parent_spec_id": 0,
"spec_id": 0,
"spec_name": "str"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 修改商品SKU价格--PddGoodsSkuPriceUpdate
### 请求信息
```gotemplate
dll.PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, request)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| request | string | 是 | 价格更新请求JSON字符串 |
#### 请求JSON结构
```json
{
"goods_id": "必填商品id类型为LONG",
"ignore_edit_warn": "非必填是否获取商品发布警告信息默认为忽略类型为BOOLEAN",
"market_price": "非必填参考价单位分类型为LONG",
"market_price_in_yuan": "非必填参考价单位元类型为STRING",
"sku_price_list": [
{
"group_price": "非必填拼团购买价格单位分类型为LONG",
"is_onsale": "非必填sku上架状态0-已下架1-上架中类型为INTEGER",
"single_price": "非必填单独购买价格单位分类型为LONG",
"sku_id": "必填sku标识类型为LONG"
}
],
"sync_goods_operate": "非必填提交后上架状态0:上架,1:保持原样类型为INTEGER",
"two_pieces_discount": "非必填满2件折扣可选范围0-1000表示取消95表示95折设置需先查询规则接口获取实际可填范围类型为INTEGER"
}
```
### 响应参数
```json
{
"goods_update_sku_price_response": {
"goods_commit_id": 0,
"is_success": true
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 商品库存更新接口--PddGoodsQuantityUpdate
### 请求信息
```gotemplate
dll.PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, request)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| request | string | 是 | 库存更新请求JSON字符串 |
#### 请求JSON结构 request 字符串
```json
{
"force_update": "非必填是否强制更新仅update_type=1(全量更新)时有效默认值falseforce_update=false时quantity不能小于预扣库存force_update=true时代表强制更新当quantity<预扣库存时不报错直接将quantity清0类型为BOOLEAN",
"goods_id": "必填商品id类型为LONG",
"outer_id": "非必填sku商家编码类型为STRING",
"quantity": "必填库存修改值。当全量更新库存时quantity必须为大于等于0的正整数当增量更新库存时quantity为整数可小于等于0。若增量更新时传入的库存为负数则负数与实际库存之和不能小于0。比如当前实际库存为1传入增量更新quantity=-1库存改为0类型为LONG",
"sku_id": "非必填sku_id和outer_id必填一个类型为LONG",
"update_type": "非必填库存更新方式可选。1为全量更新2为增量更新。如果不填默认为全量更新类型为INTEGER"
}
```
### 响应参数
```json
{
"goods_quantity_update_response": {
"is_success": false
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 获取商品信息接口 -- OutPddAuthGetCommitDetailt
### 请求信息
```gotemplate
dll.OutPddAuthGetCommitDetailt(goodsCommitId, goodsId, accessToken)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| goodsCommitId | string | 是 | 商品提交ID |
| goodsId | string | 是 | 商品ID |
| accessToken | string | 是 | 授权令牌 |
### 响应参数
```json
```
## 获取商品详情信息接口 -- OutPddAuthGetGoodsDetail
### 请求信息
```gotemplate
dll.OutPddAuthGetGoodsDetail(goodsId, accessToken)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| goodsId | string | 是 | 商品ID |
| accessToken | string | 是 | 授权令牌 |
### 响应参数
```json
{
"bad_fruit_claim": 0,
"buy_limit": 999999,
"carousel_gallery_list": [
"https://img.pddpic.com/open-gw/2025-06-30/59c30d4c-193f-40a3-a639-7af59a381ec5.jpeg",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2025-06-30/4539f740-331b-4687-aa00-5c96855de6cd.jpeg",
"https://img.pddpic.com/open-gw/2025-06-30/b0e89e39-c97b-475d-9be2-f1909e30acb5.jpeg"
],
"cat_id": 15678,
"cost_template_id": 655688447565777,
"country_id": 0,
"customer_num": 2,
"customs": "",
"detail_gallery_list": [
"https://img.pddpic.com/open-gw/2025-06-30/b691c104-baf8-42b2-97e2-b7258113114b.jpeg",
"https://img.pddpic.com/open-gw/2023-09-07/53e6f7ff-d15e-4e8f-8625-e293717ca1e4.jpeg",
"https://img.pddpic.com/open-gw/2023-09-07/ecff591d-32a6-42c9-ba5a-6a42829092a8.jpeg",
"https://img.pddpic.com/open-gw/2023-10-16/7034f8a0-5d88-49f8-a96f-608abb8cac80.jpeg",
"https://img.pddpic.com/open-gw/2023-10-16/e10c2b6c-d4de-4fdd-8d48-f0a334735e9a.jpeg",
"https://img.pddpic.com/open-gw/2023-10-16/c19358fb-0a4d-49ad-bcc8-b2980e938064.jpeg",
"https://img.pddpic.com/open-gw/2025-06-30/1deeb9c0-7212-432b-a309-f774db6e1adb.jpeg"
],
"goods_desc": "书名:金属工艺学 下 第6版作者'邓文英,宋力宏主编'ISBN9787040456295出版社高等教育出版社",
"goods_id": 770621582375,
"goods_name": "金属工艺学 下 第6版 邓文英,宋力宏主编 高等教育出版社 978",
"goods_property_list": [
{
"punit": "",
"ref_pid": 425,
"template_pid": 401030,
"vid": 0,
"vvalue": "9787040456295"
},
{
"punit": "",
"ref_pid": 876,
"template_pid": 401029,
"vid": 0,
"vvalue": "金属工艺学 下 第6版"
},
{
"punit": "页",
"ref_pid": 692,
"template_pid": 401032,
"vid": 0,
"vvalue": "157"
},
{
"punit": "元",
"ref_pid": 879,
"template_pid": 401034,
"vid": 0,
"vvalue": "24.70"
},
{
"punit": "",
"ref_pid": 882,
"template_pid": 401037,
"vid": 0,
"vvalue": "邓文英,宋力宏主编"
},
{
"punit": "",
"ref_pid": 880,
"template_pid": 401035,
"vid": 483761,
"vvalue": "高等教育出版社"
},
{
"punit": "",
"ref_pid": 888,
"template_pid": 401043,
"vid": 0,
"vvalue": "平装"
}
],
"goods_type": 1,
"image_url": "",
"invoice_status": 0,
"is_customs": 0,
"is_folt": 0,
"is_group_pre_sale": 0,
"is_pre_sale": 0,
"is_refundable": 1,
"is_sku_pre_sale": 0,
"market_price": 5948,
"order_limit": 999999,
"outer_goods_id": "9787040456295",
"oversea_type": 0,
"pre_sale_time": 0,
"privacy_delivery": 0,
"quan_guo_lian_bao": 0,
"second_hand": 1,
"shipment_limit_second": 172800,
"sku_list": [
{
"is_onsale": 1,
"limit_quantity": 999999,
"multi_price": 1487,
"out_sku_sn": "9787040456295",
"price": 1587,
"quantity": 0,
"reserve_quantity": 0,
"sku_id": 1753931570290,
"sku_pre_sale_time": 0,
"spec": [
{
"parent_id": 1216,
"parent_name": "尺寸",
"spec_id": 27632894279,
"spec_name": "单本 无附赠 超七天不退换"
}
],
"thumb_url": "https://img.pddpic.com/open-gw/2025-06-30/59c30d4c-193f-40a3-a639-7af59a381ec5.jpeg",
"weight": 500
}
],
"status": 4,
"tiny_name": "金属工艺学 下 第6",
"two_pieces_discount": 96,
"video_gallery": [],
"warehouse": "",
"warm_tips": "",
"zhi_huan_bu_xiu": 0
}
```
## 生成自定义规格接口 -- OutPddAuthSetSpec
### 请求信息
```gotemplate
dll.OutPddAuthSetSpec(specTypeId, specName, accessToken)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| specTypeId | int | 是 | 规格类型ID |
| specName | string | 是 | 规格名称 |
| accessToken | string | 是 | 授权令牌 |
### 响应参数
```json
{
"parentSpecId": 3820,
"specName": "全新",
"specId": 1080396526
}
```
## 修改价格接口 -- OutPddAuthUpdatePrice
### 请求信息
```gotemplate
dll.OutPddAuthUpdatePrice(jsonData)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------|
| jsonData | int | 是 | 价格修改信息JSON字符串 |
### 响应参数
```json
[
{
"success": true,
"msg": "操作成功"
},
{
"success": false,
"msg": "操作失败"
}
]
```
## 修改库存接口 -- OutPddAuthUpdateStock
### 请求信息
```gotemplate
dll.OutPddAuthUpdateStock(jsonData)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------|
| jsonData | int | 是 | 价格修改信息JSON字符串 |
### 响应参数
```json
[
{
"success": true,
"msg": "操作成功"
},
{
"success": false,
"msg": "操作失败"
}
]
```
## 12.释放C字符串内存--FreeCString
### 请求信息
```gotemplate
dll.FreeCString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| str | string | 是 | 需要释放的字符串 |

BIN
modules/xianYu/address.xlsx Normal file

Binary file not shown.

25
modules/xianYu/config.ini Normal file
View File

@ -0,0 +1,25 @@
[app]
AppId = 1228288260261189
AppSecret = aq9gAwrwp6WGZkMRqKIXmnu2c2uCm82k
Domain = https://open.goofish.pro
[http]
Addr = 127.0.0.1:53368
[categoryListRequest]
Path = /api/open/product/category/list
ItemBizType: 2
SpBizType: 24
[batchCreatRequest]
Path = /api/open/product/batchCreate
[file]
TxtPath = modules/xianYu/productCategory.txt
ExcelPath = modules/xianYu/address.xlsx
SheetName = Result
[redis]
Password = Long6166@@
Addr = 127.0.0.1:6379
Db = 5
[tokenBucket]
BucketKeyPrefix = "token_bucket_"
TokenPerSecond = 10
BucketSize = 100
Delay = 100

View File

@ -0,0 +1,284 @@
cbf4e2ec8f2013d31921b9e373cead75电视剧
cbf4e2ec8f2013d3267e0a01017d9f44电影
cbf4e2ec8f2013d36f38848189966e7d生活
cbf4e2ec8f2013d3ac899d2620c5df2b成人教育音像
cbf4e2ec8f2013d3acde29f76907b07f动画
cbf4e2ec8f2013d3e10cfa39bf43dc0f儿童教育音像
d14d229692616168b108d382c4e6ea42废品回收
d816d18aa66dfb3d1921b9e373cead75励志成长
dbaba36adf47af96b108d382c4e6ea42不干胶标签
e59460ef9961e2bd28d88a08a19453dc古典吉他
e59460ef9961e2bda7f7e02f36b0b49a电箱吉他
86cddebb2de0815c1921b9e373cead75桌面文件柜
86cddebb2de0815c267e0a01017d9f44资料册
86cddebb2de0815c6f38848189966e7d镇纸
86cddebb2de0815ca7f7e02f36b0b49a文件袋
86cddebb2de0815cacde29f76907b07f文房墨汁
86cddebb2de0815ce10cfa39bf43dc0f文房四宝套装
879b743300e7a58137b3d33c282f2081古筝
8bd8d9724880b84d28d88a08a19453dc学习笔记
a457d6fc43c609bdac899d2620c5df2b单据收据
a457d6fc43c609bdacde29f76907b07f印台
a9ef3505c7fe4b661921b9e373cead75勾线笔
a9ef3505c7fe4b66a7f7e02f36b0b49a电子阅览器/电纸书
ab78823bfd3c7134b108d382c4e6ea42经济管理
ac69f9982deabde1acde29f76907b07f民谣吉他
ac69f9982deabde1e10cfa39bf43dc0f架子鼓
b12c1c13a8dc3b2b6f38848189966e7dPOP广告纸
b12c1c13a8dc3b2ba7f7e02f36b0b49a修正贴
b12c1c13a8dc3b2bac899d2620c5df2b学生用印
b12c1c13a8dc3b2bacde29f76907b07f名片
b2b61c32fc4c904428d88a08a19453dc背胶证件照
b3b713b29220947237b3d33c282f2081台历
4c49139fe1b6ae4aac899d2620c5df2b童书育儿
4fecb084c468ed626f38848189966e7d黑板
5042edcbd2cc4b94ac899d2620c5df2b生活百科
621bd460d751e0fc37b3d33c282f2081订书机
701ed8603d74ee60b108d382c4e6ea42报纸
722d38201b9c8cba267e0a01017d9f44社科心理
7912befd7e1215d11921b9e373cead75挂历
7dba397e41d08d4937b3d33c282f2081拆信刀
7eb776b01814cc6e1921b9e373cead75教材教辅
22e1d81dc4cf3a25a7f7e02f36b0b49a图书
2dfa3034d88aedcc1921b9e373cead75期刊/杂志
31329c43789fae0437b3d33c282f2081戏曲综艺
31329c43789fae04a7f7e02f36b0b49a音乐唱片/专辑
322a73805c38995f6f38848189966e7d宝珠笔
3cdbae6d47df9251a7f7e02f36b0b49a电子资料
22d3cfff678abab1e10cfa39bf43dc0f握笔器
b7fd03d456abe3011921b9e373cead75活页替芯
b7fd03d456abe301b108d382c4e6ea42索引纸
b7fd03d456abe301e10cfa39bf43dc0f拍纸本
c230ba4ca293f3b528d88a08a19453dc马克笔
c230ba4ca293f3b5a7f7e02f36b0b49a钢笔
c230ba4ca293f3b5ac899d2620c5df2b铅笔
c3c6e8d1d63c0618b108d382c4e6ea42文学/小说
c58d3dbcff05e404acde29f76907b07f笔筒
eac1d67ece5fa9b16f38848189966e7d钢琴
ee8603696d446e931921b9e373cead75电钢琴
06d80b131d7b0b616f38848189966e7d毛笔
0e28c0f1f1e57eb1ac899d2620c5df2b地图
0f75076039b85f74267e0a01017d9f44计算器
0f75076039b85f7428d88a08a19453dc
0f75076039b85f746f38848189966e7d板擦
0f75076039b85f74b108d382c4e6ea42算盘
11c38799bd389b3828d88a08a19453dc漫画书籍
ac69f9982deabde1a7f7e02f36b0b49a上弦器
83f9286d1ea41056ac899d2620c5df2b其他吉他配件
e59460ef9961e2bd1921b9e373cead75变调夹
83f9286d1ea4105637b3d33c282f2081古典吉他弦
e59460ef9961e2bdacde29f76907b07f吉他单块效果器
83f9286d1ea41056267e0a01017d9f44吉他效果器配件
83f9286d1ea41056b108d382c4e6ea42吉他电源
ac69f9982deabde1267e0a01017d9f44吉他综合效果器
e59460ef9961e2bdb108d382c4e6ea42吉他背包琴盒
83f9286d1ea4105628d88a08a19453dc吉他背带
83f9286d1ea410561921b9e373cead75吉他连接线
e59460ef9961e2bd6f38848189966e7d吊架
ac69f9982deabde1ac899d2620c5df2b弦枕
ac69f9982deabde11921b9e373cead75弦柱
e59460ef9961e2bd267e0a01017d9f44拨片
ac69f9982deabde128d88a08a19453dc拾音器
83f9286d1ea41056e10cfa39bf43dc0f曼陀铃弦
83f9286d1ea410566f38848189966e7d民谣吉他弦
ac69f9982deabde137b3d33c282f2081清洁保护品
83f9286d1ea41056a7f7e02f36b0b49a滑棒指套
e59460ef9961e2bde10cfa39bf43dc0f电吉他弦
e59460ef9961e2bdac899d2620c5df2b背带钮
83f9286d1ea41056acde29f76907b07f脚凳
ac69f9982deabde1b108d382c4e6ea42调音器
e59460ef9961e2bd37b3d33c282f2081电吉他
c6d5c9e68467b108ac899d2620c5df2b哑鼓垫
c6d5c9e68467b10837b3d33c282f2081镲片
c6d5c9e68467b10828d88a08a19453dc鼓凳
ac69f9982deabde16f38848189966e7d鼓刷
c6d5c9e68467b108b108d382c4e6ea42鼓架镲架
c6d5c9e68467b108a7f7e02f36b0b49a鼓棒鼓锤
1cac27c660d7b098b108d382c4e6ea42唢呐
1cac27c660d7b098267e0a01017d9f44
f22578f0c6a8eaa5267e0a01017d9f44尺八
f22578f0c6a8eaa51921b9e373cead75巴乌
1cac27c660d7b09828d88a08a19453dc
f22578f0c6a8eaa5acde29f76907b07f笛子
f22578f0c6a8eaa5e10cfa39bf43dc0f管子
1cac27c660d7b0981921b9e373cead75
1cac27c660d7b098a7f7e02f36b0b49a芦笙
1cac27c660d7b09837b3d33c282f2081葫芦丝
f22578f0c6a8eaa56f38848189966e7d葫芦笙
1cac27c660d7b098ac899d2620c5df2b陶笛
879b743300e7a581acde29f76907b07f三弦
1cac27c660d7b098e10cfa39bf43dc0f冬不拉
1cac27c660d7b0986f38848189966e7d古琴
1cac27c660d7b098acde29f76907b07f弹布尔
879b743300e7a581e10cfa39bf43dc0f扬琴
879b743300e7a5816f38848189966e7d月琴
879b743300e7a58128d88a08a19453dc柳琴
879b743300e7a5811921b9e373cead75热瓦普
879b743300e7a581b108d382c4e6ea42琵琶
879b743300e7a581ac899d2620c5df2b秦琴
879b743300e7a581a7f7e02f36b0b49a箜篌
879b743300e7a581267e0a01017d9f44
a2eba09f5b889a7c28d88a08a19453dc中胡
7d61e938542f6790b108d382c4e6ea42二胡
7d61e938542f6790267e0a01017d9f44京二胡
7d61e938542f6790acde29f76907b07f京胡
7d61e938542f679028d88a08a19453dc低音胡
a2eba09f5b889a7c37b3d33c282f2081四胡
a2eba09f5b889a7cb108d382c4e6ea42坠琴
7d61e938542f6790a7f7e02f36b0b49a板胡
a2eba09f5b889a7ca7f7e02f36b0b49a椰胡
7d61e938542f679037b3d33c282f2081艾捷克
7d61e938542f67901921b9e373cead75革胡
7d61e938542f67906f38848189966e7d马头琴
7d61e938542f6790e10cfa39bf43dc0f马骨胡
7d61e938542f6790ac899d2620c5df2b高胡
882b39ff0db2dd0037b3d33c282f2081军镲
00a32e7ff35aaf9e267e0a01017d9f44大钹
00a32e7ff35aaf9ee10cfa39bf43dc0f大铙
00a32e7ff35aaf9eacde29f76907b07f大顶钹
00a32e7ff35aaf9e1921b9e373cead75川钹
00a32e7ff35aaf9e6f38848189966e7d广钹
882b39ff0db2dd00a7f7e02f36b0b49a快板
882b39ff0db2dd0028d88a08a19453dc拍板
00a32e7ff35aaf9eb108d382c4e6ea42梆子
882b39ff0db2dd001921b9e373cead75水镲
882b39ff0db2dd00b108d382c4e6ea42碰钟
882b39ff0db2dd00acde29f76907b07f秧歌镲
882b39ff0db2dd00e10cfa39bf43dc0f腰鼓镲
882b39ff0db2dd00ac899d2620c5df2b萨巴依
882b39ff0db2dd00267e0a01017d9f44铜书板
00a32e7ff35aaf9eac899d2620c5df2b镲锅
0ea61a801ba323c1267e0a01017d9f44堂鼓
00a32e7ff35aaf9e28d88a08a19453dc战鼓
0ea61a801ba323c11921b9e373cead75排鼓
0ea61a801ba323c1b108d382c4e6ea42板鼓
00a32e7ff35aaf9e37b3d33c282f2081秧歌鼓
0ea61a801ba323c1e10cfa39bf43dc0f细腰鼓
00a32e7ff35aaf9ea7f7e02f36b0b49a腰鼓
0ea61a801ba323c1ac899d2620c5df2b花盆鼓
0ea61a801ba323c16f38848189966e7d象脚鼓
0ea61a801ba323c1acde29f76907b07f铜鼓
a7133eb411b587cf1921b9e373cead75空灵鼓/无忧鼓
0ea61a801ba323c1a7f7e02f36b0b49a云锣
a2eba09f5b889a7c267e0a01017d9f44京锣
a2eba09f5b889a7cac899d2620c5df2b低音锣
a2eba09f5b889a7cacde29f76907b07f开道锣
a2eba09f5b889a7ce10cfa39bf43dc0f手锣
0ea61a801ba323c137b3d33c282f2081武锣
0ea61a801ba323c128d88a08a19453dc舟山锣
a2eba09f5b889a7c6f38848189966e7d苏锣
a2eba09f5b889a7c1921b9e373cead75虎音锣
33a0daa5d89d68fa1921b9e373cead75宣纸
b12c1c13a8dc3b2b1921b9e373cead75吊牌
b12c1c13a8dc3b2be10cfa39bf43dc0f自封袋
b12c1c13a8dc3b2b267e0a01017d9f44贺卡明信片
22d3cfff678abab11921b9e373cead75书皮
b12c1c13a8dc3b2b37b3d33c282f2081修正带
b12c1c13a8dc3b2b28d88a08a19453dc修正液
22d3cfff678abab1a7f7e02f36b0b49a削笔器
22d3cfff678abab128d88a08a19453dc可爱印泥
b12c1c13a8dc3b2bb108d382c4e6ea42学生书包
22d3cfff678abab1acde29f76907b07f文具套装
22d3cfff678abab1267e0a01017d9f44文具盒
22d3cfff678abab16f38848189966e7d橡皮
22d3cfff678abab1b108d382c4e6ea42练字帖
22d3cfff678abab1ac899d2620c5df2b视力保护器
dbaba36adf47af9637b3d33c282f2081笔袋
54e552aa1c9b2cbcacde29f76907b07f彩泥橡皮泥
bf164bd2e8dd8cebb108d382c4e6ea42便条照片夹
bf164bd2e8dd8ceb28d88a08a19453dc便签盒座
bf164bd2e8dd8cebe10cfa39bf43dc0f卡套证件套
bf164bd2e8dd8ceb6f38848189966e7d名片册
86cddebb2de0815c37b3d33c282f2081名片盒
bf164bd2e8dd8cebacde29f76907b07f快劳夹
86cddebb2de0815c28d88a08a19453dc文件夹
86cddebb2de0815cb108d382c4e6ea42文件架
bf164bd2e8dd8ceb1921b9e373cead75档案盒
bf164bd2e8dd8cebac899d2620c5df2b档案袋
86cddebb2de0815cac899d2620c5df2b相册
1ad9ac4511bbb8646f38848189966e7d笔插
bf164bd2e8dd8ceba7f7e02f36b0b49a笔架
bf164bd2e8dd8ceb267e0a01017d9f44风琴包
d665d5e1347fa192a7f7e02f36b0b49a地球仪
bf164bd2e8dd8ceb37b3d33c282f2081展板
d665d5e1347fa192267e0a01017d9f44教学仪器器材
d665d5e1347fa1921921b9e373cead75教鞭
bb9bba251ee78e59267e0a01017d9f44旗帜
d665d5e1347fa19237b3d33c282f2081提示牌
d665d5e1347fa192b108d382c4e6ea42激光笔
0f75076039b85f74acde29f76907b07f白板
0f75076039b85f74e10cfa39bf43dc0f白板笔
d665d5e1347fa19228d88a08a19453dc粉笔
d665d5e1347fa192acde29f76907b07f绿板
d665d5e1347fa1926f38848189966e7d荧光板
d665d5e1347fa192ac899d2620c5df2b计划表
d665d5e1347fa192e10cfa39bf43dc0f软木板
a457d6fc43c609bda7f7e02f36b0b49a中性笔
c230ba4ca293f3b5e10cfa39bf43dc0f圆珠笔
c230ba4ca293f3b51921b9e373cead75铅芯
f9910185f1984f2937b3d33c282f2081正姿笔
c230ba4ca293f3b5acde29f76907b07f油漆笔
c230ba4ca293f3b5b108d382c4e6ea42泡泡笔
c230ba4ca293f3b537b3d33c282f2081墨水墨囊
c230ba4ca293f3b5267e0a01017d9f44荧光笔
f4a071d4dba28eccac899d2620c5df2b记号笔
c230ba4ca293f3b56f38848189966e7d针管笔
dfdbd3409fadcd3f6f38848189966e7d其他笔
58e84885c426409e267e0a01017d9f44书签
b7fd03d456abe30128d88a08a19453dc便签
e9fa1ad466b79d97b108d382c4e6ea42信封
af2cf5b1faa3537a1921b9e373cead75信纸
b7fd03d456abe30137b3d33c282f2081包装纸
e9fa1ad466b79d97a7f7e02f36b0b49a纪念册
b7fd03d456abe301ac899d2620c5df2b复写纸
b7fd03d456abe301267e0a01017d9f44奖状证书
e9fa1ad466b79d971921b9e373cead75手工纸
e9fa1ad466b79d9728d88a08a19453dc草稿纸
b7fd03d456abe3016f38848189966e7d日记本
e9fa1ad466b79d97ac899d2620c5df2b硬面抄
b7fd03d456abe301a7f7e02f36b0b49a记事本
b7fd03d456abe301acde29f76907b07f课业本
e9fa1ad466b79d9737b3d33c282f2081通讯录
dbaba36adf47af966f38848189966e7d磁性贴
6c0543ec11db7e61267e0a01017d9f44贴纸/标签
0f75076039b85f741921b9e373cead75圆规
0f75076039b85f74ac899d2620c5df2b显微镜
1c75d8021bacf61e267e0a01017d9f44放大镜
a9ef3505c7fe4b66b108d382c4e6ea42丙烯颜料
823f8d7bd96780d0ac899d2620c5df2b书法用纸
a9ef3505c7fe4b66ac899d2620c5df2b儿童填色本
a9ef3505c7fe4b66267e0a01017d9f44国画颜料
823f8d7bd96780d037b3d33c282f2081描图硫酸纸
5fd3299edc3ff44a37b3d33c282f2081毛边纸
823f8d7bd96780d01921b9e373cead75水彩笔
823f8d7bd96780d0267e0a01017d9f44水彩颜料
823f8d7bd96780d0acde29f76907b07f水粉水彩油画笔
823f8d7bd96780d0e10cfa39bf43dc0f水粉颜料
0f75076039b85f7437b3d33c282f2081油画棒
0f75076039b85f74a7f7e02f36b0b49a油画颜料
a9ef3505c7fe4b66acde29f76907b07f画板画架
823f8d7bd96780d0b108d382c4e6ea42石膏像
823f8d7bd96780d06f38848189966e7d素描本
a9ef3505c7fe4b66e10cfa39bf43dc0f绘图纸
823f8d7bd96780d028d88a08a19453dc色卡
a9ef3505c7fe4b666f38848189966e7d蜡笔
823f8d7bd96780d0a7f7e02f36b0b49a铅画纸
7dba397e41d08d49a7f7e02f36b0b49a裁剪刀片
7dba397e41d08d49b108d382c4e6ea42雕刻垫板
7dba397e41d08d49ac899d2620c5df2b切纸刀
7dba397e41d08d4928d88a08a19453dc美工刀
356e5d8126d3aefaa7f7e02f36b0b49a裁剪剪刀
e9fa1ad466b79d97267e0a01017d9f44回形针
621bd460d751e0fca7f7e02f36b0b49a回形针盒
621bd460d751e0fcb108d382c4e6ea42图钉工字钉
e9fa1ad466b79d97e10cfa39bf43dc0f大头针
e9fa1ad466b79d97acde29f76907b07f打孔机
621bd460d751e0fc28d88a08a19453dc票夹长尾夹
e9fa1ad466b79d976f38848189966e7d订书钉
a457d6fc43c609bd1921b9e373cead75凭证
a457d6fc43c609bde10cfa39bf43dc0f印油印泥
a457d6fc43c609bd28d88a08a19453dc报表
a457d6fc43c609bd267e0a01017d9f44湿手器
a457d6fc43c609bdb108d382c4e6ea42财务证明用品
a457d6fc43c609bd6f38848189966e7d账本账册
740736cf215b7509a7f7e02f36b0b49a电子壁纸

94
modules/xianYu/xianYu.go Normal file
View File

@ -0,0 +1,94 @@
package xianYu
import (
"fmt"
"os"
"path/filepath"
"planA/initialization/golabl"
"syscall"
"unsafe"
)
var (
gXianYuDll *XianYuDLL
)
// XianYuDLL 闲鱼工具DLL结构
type XianYuDLL struct {
Dll *syscall.DLL
freeCString *syscall.Proc // 释放C字符串
}
// InitXianYuDll 初始化 XianYuDLL
func InitXianYuDll() (*XianYuDLL, error) {
if gXianYuDll != nil {
return gXianYuDll, nil
}
dllPath := filepath.Join(golabl.Config.FileUrl.XianYuDll, "xy.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("XianYu DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载XianYu DLL 失败: %s", err)
}
gXianYuDll = &XianYuDLL{
Dll: dll,
freeCString: dll.MustFindProc("FreeCString"),
}
return gXianYuDll, nil
}
// XianYuGoodsAdd 商品新增
func (m *XianYuDLL) XianYuGoodsAdd(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsCreat")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsCreat: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuLaunchGoods 商品上架
func (m *XianYuDLL) XianYuLaunchGoods(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsPublish")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsPublish: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}

BIN
modules/xianYu/xy.dll Normal file

Binary file not shown.

View File

@ -0,0 +1,239 @@
##### FreeCString(str *C.char)
接收其他函数返回值之后,释放内存,参考示例
##### 内存释放示例
```go
func example () {
// ...其他逻辑
var res = StartServer (configFile *C.char)
FreeCString(res) //释放内存
}
```
##### StartServer (configFile *C.char)
启动http服务器参数配置文件路径不提供默认使用工程根目录config.ini
返回C字符串启动消息接收后使用FreeCString进行内存释放
##### StopServer
停止HTTP服务器
返回C字符串停止消息接收后使用FreeCString进行内存释放
##### GetServerStatus
获取服务器当前状态
返回C字符串指针消息running/stopped接收后使用FreeCString进行内存释放
##### GetServerAddress
获取服务器监听地址
返回C字符串指针服务器地址消息未运行返回空串接收后使用FreeCString进行内存释放
##### ReloadConfig(configFile *C.char)
重新加载配置文件参数配置文件路径不提供默认使用根目录config.ini
返回C字符串加载结果消息接收后使用FreeCString进行内存释放
### 以下都需要传递appid和appSecret ###
##### ExecuteGoodsCreat(bodyJson *C.char, configFile *C.char)
*管道通信直接调用此函数*
执行商品创建操作,参数商品信息,参考示例
返回C字符串指针创建商品结果信息接收后使用FreeCString进行内存释放
##### 商品信息参考示例
```json
{
"appId": 1228288260261189,
"appSecret": "aq9gAwrwp6WGZkMRqKIXmnu2c2uCm82k",
"token": "",
"apiShopId": 0,
"typePlatform": 4,
"shopId": 0,
"shopToken": "",
"shopName": "",
"province": 210000,
"city": 210100,
"district": 210101,
"typeClass": "",
"typeGoods": "",
"catIds": "d14d229692616168b108d382c4e6ea42",
"shop": [
{
"userName": "xy938400231518",
"province": 210000,
"city": 210100,
"district": 210101,
"title": "牧羊少年奇幻之旅",
"content": "牧羊少年奇幻之旅",
"mainImgs": ["https://img.cdn1.vip/i/68cf5cb4e5840_1758420148.webp"],
"contentImgs": []
}
],
"stuffStatus": 90,
"bookData": [
{
"ISBN": "9787530217054",
"Title": "牧羊少年奇幻之旅",
"Author": "保罗·柯艾略",
"Publisher": "北京十月文艺出版",
"itemBizType": 2,
"spBizType": 24,
"prices": [199999, 299999],
"stock": 100,
"catIds": "22e1d81dc4cf3a25a7f7e02f36b0b49a"
}
],
"itemKey": "itemAAAAA1111"
}
```
##### ExecuteGoodsPublish(bodyJson *C.char, configFile *C.char)
*管道通信直接调用此函数*
执行商品上架操作,参数上架信息,参考示例
返回C字符串指针行商品上架结果信息接收后使用FreeCString进行内存释放
##### 上架信息参考示例
```json
{
"product_id": 1250927879325125,
"user_name": ["xy938400231518"],
"specify_publish_time": "",
"notify_url": ""
}
```
#### 追加下架,改价,擦亮 ####
##### ExecuteGoodsDownShelf(bodyJson *C.char, configFile *C.char) ######
*管道通信直接调用此函数*
执行商品下架操作参数管家商品ID参考示例
返回C字符串指针行商品下架结果信息接收后使用FreeCString进行内存释放
##### 下架信息参考示例 #####
```json
{
"product_id": 1250927879325125
}
```
##### ExecuteGoodsFlash(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
执行商品擦亮操作参数管家商品ID参考示例
返回C字符串指针行商品擦亮结果信息接收后使用FreeCString进行内存释放
##### 擦亮信息参考示例 #####
```json
{
"product_id": 1250927879325125
}
```
##### ExecuteGoodsEditPrice(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
执行商品改价操作参数管家商品ID参考示例
返回C字符串指针行商品改价结果信息接收后使用FreeCString进行内存释放
##### 改价信息参考示例(单位:分) #####
```json
{
"product_id": 1250927879325125,
"price": 550000,
"originalPrice": 770000
}
```
##### ExecuteGoodsEditStock(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
执行商品改库存操作参数管家商品ID参考示例
返回C字符串指针行商品改价结果信息接收后使用FreeCString进行内存释放
##### 改库存信息参考示例(单位:分) #####
```json
{
"product_id": 1250927879325125,
"stock": 10
}
```
##### ExecuteSelectGoodsListPrice(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
查询店铺列表操作参数管家商品ID参考示例
返回C字符串指针行商品改价结果信息接收后使用FreeCString进行内存释放
##### 查询参考示例(单位:分) #####
```json
{
//online_time 字段可传空
"online_time": [
1690300800,
1690366883
],
"product_status": 22
}
```

46
planA.md Normal file
View File

@ -0,0 +1,46 @@
# Plan A
## 目录结构
```gotemplate
controller 逻辑控制
controlState 全局状态控制
|-lock 状态锁
|-serviceAlive 服务存活状态
export 导出的csv文件
initialization 初始化
|-config 初始化配置文件
|-cron 初始化定时任务
|-golabl 初始化全局变量
|-middle 初始化中间件
|-mysql 初始化mysql数据库
|-redis 初始化redis数据库
|-router 初始化路由
|-sqlite 初始化sqlite数据库
|-validator 初始化验证器
|-init.go 初始化文件
logs 日志
modules DLL模块
planB 模块B
rep 工厂模式接口
router 路由
service 服务(针对数据库相关操作)
tool 工具
type 结构体
|-mysql mysql结构体
|-redis redis结构体
|-sqlite sqlite结构体
|-validator 验证器结构体
validator 验证器
config.yaml 配置文件
taskDb.db sqlite数据库自动创建
```
## 目录结构
```
planA web服务器
planB 任务执行器
planC 同步redis数据到硬盘
planD 删除任务
planE 图片上传到拼多多图片空间(未使用)
planF 获取12个商品信息主要转发小军的接口信息
```

128
planB/config.yaml Normal file
View File

@ -0,0 +1,128 @@
server:
port: "8080" #服务器端口
f_port : "8284" #F程序端口
filter: 1 #是否开启违禁词过滤器 0=关闭 1=开启
replace_mark: "0" #标题违规词是否替换* 0 不替换 1 替换(替换会继续发布,不替换则不发布)
redis_exp: 192 #redis过期时间 192小时8天
read_db: "sqlite" #读数据库 mysql sqlite
err_pause_time: 3000 #错误暂停时间(毫秒)
sign_key: "jRQdCh52Z55Kzh1hADaA2ZtdTKetj2PXk60Tz5Yc0iz9aD8Wafbk7CwAZ8cz69A9zb9caZ3k9dnR3Ys06J5nYFPrZ0xE9p6TY8DCD538ryiRjW81YTPmk41tCEnXizPh" #签名密钥
data_day: 2 #数据保存时间(天)
is_c: true #是否启动 C程序
speed: #限速器
pdd_speed: 18 #拼多多 每秒多少个任务
xianyu_speed: 5 #闲鱼 每秒多少个任务
watermark: 15 #打水印速率的个数
minio: #minio 图片空间
url: "shxy.image.yushutx.com" #minio地址
access_key_id: "minioadmin" #minio keyId
secret_access_key: "minioadmin" #minio key
bucket_name: "task-xianyu" #存储桶
target_dir: "test/2025" #目标目录
use_ssl: true #是否使用 SSL
alive:
fluent: 50 #存活状态-流畅时间(毫秒)
slow: 200 #存活状态-缓慢时间(毫秒)
pool_config:
size: 6 #协程数量
with_expiry_duration: 10 #过期时间
with_pre_alloc: true #预分配
with_max_blocking_tasks: 600 #阻塞任务数
with_nonblocking : true #非阻塞
mysql_config:
db_name: "task_user" #数据库名称
user: "root" #数据库用户名
password: "root" #数据库密码
host: "127.0.0.1" #数据库地址
port: 3306 #数据库端口
loglevel: "info" #数据库日志级别 info=开发环境输出所有日志SQL、执行时间等 warn=预发布:输出警告+错误 error=生产环境:只输出错误日志 silent=生产环境:关闭所有日志
max_retry_times: 3 #数据库重试次数
base_retry_interval: 100 #数据库重试间隔(毫秒)
max_retry_interval: 3 #数据库最大重试间隔(秒)
max_open_conns: 100 #数据库最大打开连接数
max_idle_conns: 20 #数据库最大空闲连接数
conn_max_idle_time: 30 #数据库连接最大空闲时间(分钟)
conn_max_lifetime: 1 #数据库连接最大生命周期(小数)
redis_config:
- db_name: "任务池"
db: 0
addr: "127.0.0.1:6379"
password: "123456"
- db_name: "书品库"
db: 7
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "店铺信息"
db: 8
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "出版社信息列表"
db: 3
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "省市区列表"
db: 4
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "没有图片的 isbn"
db: 5
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "没有书籍的 isbn"
db: 6
addr: "36.212.12.247:6379"
password: "long6166@@"
- db_name: "拼多多操作回调数据库"
db: 14
addr: "36.212.20.113:7963"
password: "j8nZ4jra2E"
pdd_config:
client_id: "203c5a7ba8bd4b8488d5e26f93052642" #拼多多clientId
client_secret: "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" #拼多多clientSecret
kfz_config:
app_id: 576 #孔夫子appid
app_secret: "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8" #孔夫子appsecret
taobao_config:
app_key: 852496 #平台分配的 App Key
app_secret: "6876bd91e93840wet8d264255a24d892" #App 密钥,用于 HMAC-MD5 签名计算
token: "f614764edd33345a41acd10ed3edaa9d" #App 密钥,用于 HMAC-MD5 签名计算
ati: "455224047430" #设备/用户标识
user_id: "1779953588275" #当前操作用户 ID
company_id: "1779953588275" #公司/店铺所属 ID
base_url: "https://fxzs.yulinkai.com" #API 基础域名
local_img_dir: "D:\\file\\taobao" #图片本地缓存目录
request_timeout: 20 #请求超时时间(秒)
http_url:
task_url: "http://127.0.0.1:8080" #A 程序接口地址
file_url:
xian_yu_dll: "D:\\source\\planA\\planB\\modules\\xianYu" #闲鱼 DLL库路径
pdd_dll: "D:\\source\\planA\\planB\\modules\\pdd" #拼多多 DLL库路径
kfz_dll: "D:\\source\\planA\\planB\\modules\\kfz" #孔夫子 DLL库路径
log_dll: "D:\\source\\planA\\planB\\modules\\logs" #日志 DLL库路径
image_dll: "D:\\source\\planA\\planB\\modules\\image" #水印 DLL库路径
b_file_name: "D:\\source\\planA\\planB\\planB.exe" #B 程序文件路径
c_file_name: "D:\\source\\planA\\planC\\planC.exe" #C 程序文件路径
d_file_name: "D:\\source\\planA\\planD\\planD.exe" #D 程序文件路径
e_file_name: "D:\\source\\planA\\planE\\planE.exe" #E 程序文件路径
f_file_name: "D:\\source\\planA\\planE\\planF.exe" #F 程序文件路径
create_task_url: "https://api.buzhiyushu.cn/zhishu/baseInfo/addNewTask" #新增任务接口
create_task_notice_url: "http://36.212.12.92:8055/task" #核价软件提交数据通知接口
create_operation_task_notice_url: "http://36.212.12.92:8055/taskV2" #操作商品任务核价软件提交数据通知接口
banned_word_substitution_url : "http://36.212.12.247:13001/task/getFilterSetNew" #违禁词替换接口
pdd_token_url: "https://api.buzhiyushu.cn/huidiao/pdd/getToken" #获取系统规定拼多多 token
deduction_url: "https://api.buzhiyushu.cn/zhishu/userRecharge/apiBalancePayment" #扣费接口
pdd_get_goods_url: "http://pdd.buzhiyushu.cn/api/pdd/auth/getShopGoodsList" #查询拼多多商品接口
pdd_get_goods_detail_url: "http://pdd.buzhiyushu.cn/api/pdd/auth/newGetShopGoodsDetailList" #查询拼多多商品详情列表接口
pdd_add_goods_url: "http://119.45.237.193:14003/task/putShopGoods" #添加拼多多商品接口
pdd_get_sku_id: "http://192.168.101.127:18099/shopGoods/getShopGoods" #批量获取 skuId接口
xianyu_add_goods_url: "http://119.45.237.193:14008/task/putShopGoods" #添加闲鱼商品接口
kfz_add_goods_url: "http://119.45.237.193:14009/task/kfzverifyPricePublishGoods" #添加孔夫子商品接口
del_task_url: "http://119.45.237.193:14008/shopGoods/delShopGoodsk" #删除任务通知接口
backup_url: "D:\\file\\backup" #备份文件路径
pdd_goods_details_url: "D:\\file\\pdd_goods_details" #保存拼多多详情路径
update_token_url: "http://146.56.227.42:9099/api/updateToken" #更新拼多多 token 到redis
kfz_img_temp_url: "D:\\file\\kfzImg" #孔夫子图片临时路径
kfz_img_http_url: "https://www0.kfzimg.com/" #孔夫子图片 http 路径
get_pdd_goods_shopid_isbn_url : "http://119.45.237.193:14008/shopGoods/selectTrilateralIds" #获取拼多多商品 shopId 和 isbn
get_subscription_expiration_date_url : "http://119.45.237.193:9096/api/user/getKfzUserRecbusiness" #获取订阅到期时间
pdd_img_temp_url: "D:\\file\\pddImg" #拼多多图片临时路径

View File

@ -0,0 +1,36 @@
package dispatcher
import (
"fmt"
"planA/planB/initialization/golabl"
planAType "planA/type"
)
// Go 调度任务
// @param bodyWait 任务体
// @return string 任务ID
// @return error 错误信息
func Go(bodyWait planAType.TaskBody) (string, error) {
switch golabl.TaskType {
case golabl.TaskTypeAddGoodsTask:
return golabl.Platform.AddGoodsTask(bodyWait) // 添加商品
//挪到了main方法中执行
//case "GetGoodsTask":
// return golabl.Platform.GetGoodsTask() // 获取商品
case golabl.TaskTypeSetGoodsTask:
return golabl.Platform.SetGoodsTask(), nil // 修改商品
case golabl.TaskTypeOperationGoodsTask:
return golabl.Platform.OperationGoodsTask(bodyWait) // 操作商品
case golabl.TaskTypeIncStock:
return golabl.Platform.IncStockTask(bodyWait) // 增量库存
default:
return "", fmt.Errorf("没有此任务类型")
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

40
planB/go.sum Normal file
View File

@ -0,0 +1,40 @@
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/panjf2000/ants/v2 v2.12.0 h1:u9JhESo83i/GkZnhfTNuFMMWcNt7mnV1bGJ6FT4wXH8=
github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,46 @@
package config
import (
"encoding/json"
"fmt"
"planA/planB/initialization/golabl"
planBConfig "planA/planB/modules/config"
"planA/tool"
planAtype "planA/type"
)
// GetConfigSetToG 获取配置文件并保存到全局变量中
// @return error 错误信息
func GetConfigSetToG() error {
// 检查全局 CTX 是否失效 以防止重复初始化和 ctx 失效 导致程序崩溃
checkContextErr := tool.CheckContext(golabl.Ctx)
if checkContextErr != nil {
// 返回 且 返回错误
return checkContextErr
}
//读取配置文件
var config planAtype.Config
// 加载 config.dll
dll, initConfigDLLErr := planBConfig.InitConfigDLL()
if initConfigDLLErr != nil {
return initConfigDLLErr
}
// 读取配置文件
configJson, ReadConfigFileErr := dll.ReadConfigFile("", "config.yaml")
if ReadConfigFileErr != nil {
return fmt.Errorf("读取配置文件失败:%v", ReadConfigFileErr)
}
// 转换配置文件到 JSON
jsonUnmarshalErr := json.Unmarshal([]byte(configJson), &config)
if jsonUnmarshalErr != nil {
return fmt.Errorf("解析配置文件失败:%v", jsonUnmarshalErr)
}
// 保存到全局变量
golabl.Config = config
// 返回
return nil
}

View File

@ -0,0 +1,39 @@
package dll
import (
"planA/planB/initialization/dll/image"
"planA/planB/initialization/dll/kfz"
"planA/planB/initialization/dll/logs"
"planA/planB/initialization/dll/pdd"
"planA/planB/initialization/dll/xianYu"
)
// GetDllSetToG 获取DLL
func GetDllSetToG() error {
// 初始化 PddDll
getPddDllSetToGErr := pdd.GetPddDllSetToG()
if getPddDllSetToGErr != nil {
return getPddDllSetToGErr
}
// 初始化 ImageDll
getImageDllSetToGErr := image.GetImageDllSetToG()
if getImageDllSetToGErr != nil {
return getImageDllSetToGErr
}
// 初始化 XianYuDll
getXianYuDllSetToGErr := xianYu.GetXianYuDllSetToG()
if getXianYuDllSetToGErr != nil {
return getXianYuDllSetToGErr
}
// 初始化 LogrDll
getLogrDllSetToGErr := logs.GetLogrDllSetToG()
if getLogrDllSetToGErr != nil {
return getLogrDllSetToGErr
}
// 获取KfzDll
getKfzDllSetToGErr := kfz.GetKfzDllSetToG()
if getKfzDllSetToGErr != nil {
return getKfzDllSetToGErr
}
return nil
}

View File

@ -0,0 +1,16 @@
package image
import (
"planA/planB/initialization/golabl"
"planA/planB/modules/image"
)
// GetImageDllSetToG 获取图片DLL
func GetImageDllSetToG() error {
imageDll, imageDllErr := image.InitImageDll(golabl.Config.FileUrl.ImageDll)
if imageDllErr != nil {
return imageDllErr
}
golabl.ImageDll = imageDll
return nil
}

View File

@ -0,0 +1,17 @@
package kfz
import (
"planA/planB/initialization/golabl"
"planA/planB/modules/kfz"
)
// GetKfzDllSetToG 获取孔夫子DLL
func GetKfzDllSetToG() error {
// 初始化 KfzDll
kfzDll, err := kfz.InitKfzDll(golabl.Config.FileUrl.KfzDll)
if err != nil {
return err
}
golabl.KfzDll = kfzDll
return nil
}

View File

@ -0,0 +1,16 @@
package logs
import (
"planA/planB/initialization/golabl"
"planA/planB/modules/logs"
)
// GetLogrDllSetToG 获取日志DLL
func GetLogrDllSetToG() error {
dll, ensureLoggerDLLErr := logs.EnsureLoggerDLL(golabl.Config.FileUrl.LogDll)
if ensureLoggerDLLErr != nil {
return ensureLoggerDLLErr
}
golabl.LogDll = dll
return nil
}

View File

@ -0,0 +1,17 @@
package pdd
import (
"planA/planB/initialization/golabl"
"planA/planB/modules/pdd"
)
// GetPddDllSetToG 获取拼多多DLL
func GetPddDllSetToG() error {
// 初始化 PddDll
pddDll, err := pdd.InitPddDll(golabl.Config.FileUrl.PddDll)
if err != nil {
return err
}
golabl.PddDll = pddDll
return nil
}

View File

@ -0,0 +1,16 @@
package xianYu
import (
"planA/planB/initialization/golabl"
"planA/planB/modules/xianYu"
)
// GetXianYuDllSetToG 获取闲鱼DLL
func GetXianYuDllSetToG() error {
xianYuDll, xianYuDllErr := xianYu.InitXianYuDll(golabl.Config.FileUrl.XianYuDll)
if xianYuDllErr != nil {
return xianYuDllErr
}
golabl.XianYuDll = xianYuDll
return nil
}

View File

@ -0,0 +1,59 @@
package golabl
import (
"context"
"planA/planB/interfaces"
"planA/planB/modules/image"
"planA/planB/modules/kfz"
"planA/planB/modules/logs"
"planA/planB/modules/pdd"
xianYuDll "planA/planB/modules/xianYu"
planBType "planA/planB/type"
planAType "planA/type"
"golang.org/x/time/rate"
"gorm.io/gorm"
)
var (
Ctx context.Context // 全局上下文
Speed *rate.Limiter // 全局令牌桶限速器
Config planAType.Config // 全局配置
Redis planBType.Redis // 全局 Redis
Task *planBType.Task // 全局任务
Pool planBType.Pool // 全局线程池
Logic planBType.Logic // 全局逻辑控制
Platform interfaces.GoodsTask // 全局平台对象
TaskType string // 全局任务类型
MinIo *planBType.MinIOClient // 全局 MinIO
PddDll *pdd.PddDLL // 全局拼多多 DLL
ImageDll *image.ImageDLL // 全局 ImageDll
XianYuDll *xianYuDll.XianYuDLL // 全局 闲鱼 DLL
LogDll *logs.LoggerDLL // 全局日志 DLL
KfzDll *kfz.KfzDLL // 全局孔夫子 DLL
MysqlDb *gorm.DB // 全局 mysql
KfzGetCommonCategory map[string]string // 孔夫子商品分类列表
)
// 任务 body 状态
const (
BodyStatusSuccess int64 = 1 // 正常
BodyStatusError int64 = 2 // 错误
)
// 任务类型
const (
TaskTypeAddGoodsTask string = "AddGoodsTask" // 添加商品
TaskTypeGetGoodsTask string = "GetGoodsTask" // 获取商品
TaskTypeSetGoodsTask string = "SetGoodsTask" // 修改商品
TaskTypeOperationGoodsTask string = "OperationGoodsTask" // 操作商品
TaskTypeIncStock string = "IncStock" // 增量库存
)
// 错误集
const (
LastIndexRedisNil int64 = 10001 // redis 多次读Nil
LastIndexGoodsMaxRestriction int64 = 11002 // 店铺已达到最大商品限制
LastIndexFilteWordErr int64 = 10003 // 过滤关键词异常
)

View File

@ -0,0 +1,95 @@
package initialization
import (
"context"
"fmt"
"planA/planB/initialization/config"
"planA/planB/initialization/dll"
"planA/planB/initialization/golabl"
"planA/planB/initialization/kfz"
"planA/planB/initialization/minIo"
"planA/planB/initialization/mysql"
"planA/planB/initialization/platform"
"planA/planB/initialization/pool"
"planA/planB/initialization/redis"
"planA/planB/initialization/speed"
"planA/planB/initialization/task"
"planA/planB/initialization/taskType"
"planA/planB/initialization/title"
planBType "planA/planB/type"
planAType "planA/type"
)
// Init 初始化
func Init(taskId string) error {
//初始化上下文
if golabl.Ctx == nil {
golabl.Ctx = context.Background()
}
// 初始化配置文件
if configErr := config.GetConfigSetToG(); configErr != nil {
return fmt.Errorf("初始化配置文件失败:%v", configErr)
}
// 初始化 redis
if redisErr := redis.LinkRedisSetToG(); redisErr != nil {
return fmt.Errorf("初始化redis失败: %v", redisErr)
}
// 初始化 mysql
if mysqlErr := mysql.LikeMysqlSetToG(); mysqlErr != nil {
return fmt.Errorf("初始化mysql失败: %v", mysqlErr)
}
// 初始化 task
golabl.Task = &planBType.Task{
TaskId: taskId,
Header: &planAType.TaskHeader{},
Footer: &planAType.TaskFooter{},
BodyWait: &planAType.TaskBody{},
BodyOver: &planAType.TaskBody{},
BodyBackup: &planAType.TaskBody{},
}
if taskErr := task.GetTaskHeaderAndFooterSetToG(); taskErr != nil {
return fmt.Errorf("初始化任务失败: %v", taskErr)
}
// 初始化限速器
speed.Init()
// 初始化 协程池
if poolErr := pool.CreatePoolToG(); poolErr != nil {
return fmt.Errorf("初始化协程池失败: %v", poolErr)
}
// 初始化平台
if platformErr := platform.GetPlatformSetToG(); platformErr != nil {
return fmt.Errorf("初始化平台失败: %v", platformErr)
}
// 初始化任务类型
if taskTypeErr := taskType.GetTaskTypeSetToG(); taskTypeErr != nil {
return fmt.Errorf("初始化任务类型失败: %v", taskTypeErr)
}
// 初始化图片空间
if newMinIOClientErr := minIo.NewMinIOClient(); newMinIOClientErr != nil {
return fmt.Errorf("初始化图片空间失败: %v", newMinIOClientErr)
}
// 初始化 DLL
if dllErr := dll.GetDllSetToG(); dllErr != nil {
return fmt.Errorf("初始化DLL失败: %v", dllErr)
}
// 初始化 孔夫子公共分类
if getKfzGoodsCategorySetToGErr := kfz.GetKfzGetCommonCategorySetToG(); getKfzGoodsCategorySetToGErr != nil {
return fmt.Errorf("初始化孔夫子公共分类失败: %v", getKfzGoodsCategorySetToGErr)
}
//设置窗口标题
title.SetWinTitle()
return nil
}

View File

@ -0,0 +1,95 @@
package kfz
import (
"encoding/json"
"fmt"
"planA/planB/initialization/golabl"
planBTypeKfz "planA/planB/type/kfz"
)
func GetKfzGetCommonCategorySetToG() error {
if golabl.Task.Header.ShopType == "2" {
//获取孔夫子商品分类
goodsCategoryList, getGoodsCategoryListErr := golabl.KfzDll.GetCommonCategory(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token)
if getGoodsCategoryListErr != nil {
return getGoodsCategoryListErr
}
//转为结构体
var kfzGoodsCategoryList planBTypeKfz.KfzCategoryRet
unmarshalErr := json.Unmarshal([]byte(goodsCategoryList), &kfzGoodsCategoryList)
if unmarshalErr != nil {
return unmarshalErr
}
//判断是否错误
if kfzGoodsCategoryList.ErrorResponse != nil {
return fmt.Errorf("获取商品公共分类失败 %v", kfzGoodsCategoryList.ErrorResponse)
}
//设置为全局
golabl.KfzGetCommonCategory = make(map[string]string)
// 使用递归函数遍历所有分类,传入路径前缀
for _, level1 := range kfzGoodsCategoryList.SuccessResponse {
collectCategoriesWithPath(level1, "")
}
}
return nil
}
// 递归收集分类,带路径
func collectCategoriesWithPath(category interface{}, parentPath string) {
switch v := category.(type) {
case planBTypeKfz.CategoryLevel1:
currentPath := v.Name
if v.Name != "" && v.Id != "" {
golabl.KfzGetCommonCategory[currentPath] = v.Id
}
for _, child := range v.Children {
collectCategoriesWithPath(child, currentPath)
}
case planBTypeKfz.CategoryLevel2:
currentPath := buildPath(parentPath, v.Name)
if v.Name != "" && v.Id != "" {
golabl.KfzGetCommonCategory[currentPath] = v.Id
}
for _, child := range v.Children {
collectCategoriesWithPath(child, currentPath)
}
case planBTypeKfz.CategoryLevel3:
currentPath := buildPath(parentPath, v.Name)
if v.Name != "" && v.Id != "" {
golabl.KfzGetCommonCategory[currentPath] = v.Id
}
for _, child := range v.Children {
collectCategoriesWithPath(child, currentPath)
}
case planBTypeKfz.CategoryLevel4:
currentPath := buildPath(parentPath, v.Name)
if v.Name != "" && v.Id != "" {
golabl.KfzGetCommonCategory[currentPath] = v.Id
}
for _, child := range v.Children {
collectCategoriesWithPath(child, currentPath)
}
case planBTypeKfz.CategoryLevel5:
currentPath := buildPath(parentPath, v.Name)
if v.Name != "" && v.Id != "" {
golabl.KfzGetCommonCategory[currentPath] = v.Id
}
for _, child := range v.Children {
collectCategoriesWithPath(child, currentPath)
}
case planBTypeKfz.CategoryLevel6:
currentPath := buildPath(parentPath, v.Name)
if v.Name != "" && v.Id != "" {
golabl.KfzGetCommonCategory[currentPath] = v.Id
}
}
}
// 构建路径,用 / 连接
func buildPath(parentPath, currentName string) string {
if parentPath == "" {
return currentName
}
return parentPath + "/" + currentName
}

View File

@ -0,0 +1,30 @@
package minIo
import (
"planA/planB/initialization/golabl"
PlanBType "planA/planB/type"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// NewMinIOClient 创建 MinIO 客户端实例
func NewMinIOClient() error {
client, newMinIoErr := minio.New(golabl.Config.Minio.Url, &minio.Options{
Creds: credentials.NewStaticV4(golabl.Config.Minio.AccessKeyID, golabl.Config.Minio.SecretAccessKey, ""),
Secure: false,
})
if newMinIoErr != nil {
return newMinIoErr
}
golabl.MinIo = &PlanBType.MinIOClient{
Client: client,
Endpoint: golabl.Config.Minio.Url,
AccessKey: golabl.Config.Minio.AccessKeyID,
SecretKey: golabl.Config.Minio.SecretAccessKey,
UseSSL: golabl.Config.Minio.UseSSL,
BucketName: golabl.Config.Minio.BucketName,
}
return nil
}

View File

@ -0,0 +1,73 @@
package mysql
import (
"fmt"
"planA/planB/initialization/golabl"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// LikeMysqlSetToG 链接mysql并保留到全局变量中
func LikeMysqlSetToG() error {
// 1. 获取mysql配置
mysqlConfig := golabl.Config.MysqlConfig
// 2. 配置 DSN
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlConfig.User,
mysqlConfig.Password,
mysqlConfig.Host,
mysqlConfig.Port,
mysqlConfig.DBName,
)
// 3. 配置 GORM 连接选项
logLevel := logger.Silent
switch mysqlConfig.Loglevel {
case "info":
logLevel = logger.Info
case "warn":
logLevel = logger.Warn
case "error":
logLevel = logger.Error
case "silent":
logLevel = logger.Silent
}
gormConfig := &gorm.Config{
Logger: logger.Default.LogMode(logLevel), //日志级别
DisableForeignKeyConstraintWhenMigrating: true, //不创建外键约束
}
// 4. 连接数据库
db, openErr := gorm.Open(mysql.Open(dsn), gormConfig)
if openErr != nil {
return openErr
}
// 5. 获取底层 sql.DB配置连接池
sqlDB, dbErr := db.DB()
if dbErr != nil {
return dbErr
}
// 连接池优化 + 保活配置
sqlDB.SetMaxOpenConns(mysqlConfig.MaxOpenConns)
sqlDB.SetMaxIdleConns(mysqlConfig.MaxIdleConns)
sqlDB.SetConnMaxIdleTime(mysqlConfig.ConnMaxIdleTime * time.Minute)
sqlDB.SetConnMaxLifetime(mysqlConfig.ConnMaxLifetime * time.Hour)
// 5. 验证连接
if dbPingErr := sqlDB.Ping(); dbPingErr != nil {
return dbPingErr
}
// 7. 保存db实例
golabl.MysqlDb = db
return nil
}

View File

@ -0,0 +1,30 @@
package platform
import (
"errors"
"planA/planB/dispatcher/kongfuzi"
pinDuoDuo "planA/planB/dispatcher/pinduoduo"
"planA/planB/dispatcher/taobao"
"planA/planB/dispatcher/xianyu"
"planA/planB/initialization/golabl"
)
// GetPlatformSetToG 获取平台并保存到全局变量中
func GetPlatformSetToG() error {
switch golabl.Task.Header.ShopType {
case "1":
golabl.Platform = pinDuoDuo.NewPinDuoDuo()
return nil
case "2":
golabl.Platform = kongfuzi.NewKongFuZi()
return nil
case "5":
golabl.Platform = xianyu.NewXianYu()
return nil
case "6":
golabl.Platform = taobao.NewTaobao()
return nil
default:
return errors.New("错误!")
}
}

View File

@ -0,0 +1,30 @@
package pool
import (
"fmt"
"planA/planB/initialization/golabl"
"sync"
"time"
"github.com/panjf2000/ants/v2"
)
// CreatePoolToG 创建协程池到全局变量中
// @return error 错误信息
func CreatePoolToG() error {
// 创建协程池
pool, err := ants.NewPool(
golabl.Config.PoolConfig.Size,
ants.WithExpiryDuration(time.Duration(golabl.Config.PoolConfig.WithExpiryDuration)*time.Second), // 过期时间
ants.WithPreAlloc(golabl.Config.PoolConfig.WithPreAlloc), // 预分配
ants.WithMaxBlockingTasks(golabl.Config.PoolConfig.WithMaxBlockingTasks), // 最大阻塞任务数
ants.WithNonblocking(golabl.Config.PoolConfig.WithNonblocking), // 非阻塞
ants.WithPanicHandler(func(p interface{}) { fmt.Printf("panic: %v", p) }), // panic 处理
)
if err != nil {
return err
}
golabl.Pool.Pool = pool
golabl.Pool.Wg = &sync.WaitGroup{}
return nil
}

View File

@ -0,0 +1,82 @@
package redis
import (
"fmt"
"planA/planB/initialization/golabl"
planAType "planA/type"
"time"
"github.com/go-redis/redis/v8"
)
// LinkRedisSetToG 链接redis并保留到全局变量中
// @return error 错误信息
func LinkRedisSetToG() error {
// 1. 获取redis配置
redisConfig := golabl.Config.RedisConfig
redisClientA, redisErr := NewRedisClient(redisConfig[0])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[0].Addr, redisConfig[0].DB, redisErr)
}
golabl.Redis.RedisDbA = redisClientA
// Redis B - Redis实例
redisClientB, redisErr := NewRedisClient(redisConfig[3])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[3].Addr, redisConfig[3].DB, redisErr)
}
golabl.Redis.RedisDbB = redisClientB
// Redis C - Redis实例
redisClientC, redisErr := NewRedisClient(redisConfig[4])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[4].Addr, redisConfig[4].DB, redisErr)
}
golabl.Redis.RedisDbC = redisClientC
// Redis D - Redis实例
redisClientD, redisErr := NewRedisClient(redisConfig[5])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[5].Addr, redisConfig[5].DB, redisErr)
}
golabl.Redis.RedisDbD = redisClientD
// Redis E - Redis实例
redisClientE, redisErr := NewRedisClient(redisConfig[2])
if redisErr != nil {
return fmt.Errorf("初始化 redis %v db%v 失败: %v\n", redisConfig[2].Addr, redisConfig[2].DB, redisErr)
}
golabl.Redis.RedisDbE = redisClientE
return nil
}
// NewRedisClient 创建redis 客户端
// @param config redis配置
// @return *redis.Client redis客户端
// @return error 错误信息
func NewRedisClient(config planAType.RedisConfig) (*redis.Client, error) {
ctx := golabl.Ctx
rdb := redis.NewClient(&redis.Options{
Addr: config.Addr, // 连接地址
Password: config.Password, // 密码
DB: config.DB, // 数据库
PoolSize: config.PoolSize, // 连接池大小
PoolTimeout: time.Duration(config.PoolTimeout), // 连接池超时时间
ReadTimeout: time.Duration(config.ReadTimeout), // 读取超时
WriteTimeout: time.Duration(config.WriteTimeout), // 写入超时
DialTimeout: time.Duration(config.DialTimeout), // 连接超时
IdleTimeout: time.Duration(config.IdleTimeout), // 空闲超时
MinIdleConns: config.MinIdleConns, // 最小空闲连接数
IdleCheckFrequency: time.Duration(config.IdleCheckFrequency), // 空闲检查频率
MaxRetries: config.MaxRetries, // 最大重试次数
MaxRetryBackoff: time.Duration(config.MaxRetryBackoff), // 最大重试间隔
MinRetryBackoff: time.Duration(config.MinRetryBackoff), // 最小重试间隔
})
// 测试连接
_, err := rdb.Ping(ctx).Result()
if err != nil {
return rdb, err
}
return rdb, nil
}

View File

@ -0,0 +1,32 @@
package speed
import (
"planA/planB/initialization/golabl"
"golang.org/x/time/rate"
)
// Init 初始化 限速器
func Init() {
//默认为18
speed := 18
//根据平台设置速率
switch golabl.Task.Header.ShopType {
case "1":
speed = golabl.Config.Speed.PddSpeed
//case 2:
case "5":
speed = golabl.Config.Speed.XianyuSpeed
default:
speed = 18
}
//如果需要打水印则速率下降为10
if golabl.Task.Header.ShopMsg.WatermarkImgUrl != "" && golabl.Task.Header.ShopType == "1" {
speed = golabl.Config.Speed.Watermark
if speed == 0 {
speed = 10
}
}
//初始化限速器
golabl.Speed = rate.NewLimiter(rate.Limit(speed), 1)
}

View File

@ -0,0 +1,20 @@
package task
import (
"fmt"
"planA/planB/service"
)
// GetTaskHeaderAndFooterSetToG 获取任务头和尾并保存到全局变量中
// @return error 错误信息
func GetTaskHeaderAndFooterSetToG() error {
// 获取任务头
if err := service.GetTaskHeader(); err != nil {
return fmt.Errorf("获取任务头失败 %v", err)
}
// 获取任务尾
if err := service.GetTaskFooter(); err != nil {
return fmt.Errorf("获取任务尾失败 %v", err)
}
return nil
}

View File

@ -0,0 +1,44 @@
package taskType
import (
"errors"
"fmt"
"planA/planB/initialization/golabl"
)
// GetTaskTypeSetToG 获取任务类型并保存到全局变量中
// @return error 错误信息
func GetTaskTypeSetToG() error {
switch golabl.Task.Header.TaskType {
case 1: //核价发布
golabl.TaskType = golabl.TaskTypeAddGoodsTask
return nil
case 2: //表格发布
golabl.TaskType = golabl.TaskTypeAddGoodsTask
return nil
case 3: //获取商品
golabl.TaskType = golabl.TaskTypeGetGoodsTask
return nil
case 4: //获取拼多多详情商品
golabl.TaskType = golabl.TaskTypeGetGoodsTask
return nil
case 5: //操作商品
golabl.TaskType = golabl.TaskTypeOperationGoodsTask
return nil
case 6: //核价表格发布
golabl.TaskType = golabl.TaskTypeAddGoodsTask
return nil
case 7: //增量库存
golabl.TaskType = golabl.TaskTypeIncStock
return nil
case 8: //自营书品发布
golabl.TaskType = golabl.TaskTypeAddGoodsTask
return nil
case 9: //核价改价
golabl.TaskType = golabl.TaskTypeOperationGoodsTask
return nil
default:
fmt.Println(golabl.Task.Header.TaskType)
return errors.New("错误!")
}
}

View File

@ -0,0 +1,82 @@
package title
import (
"fmt"
"planA/planB/initialization/golabl"
"syscall"
"time"
"unsafe"
)
// SetWinTitle 设置窗口标题
func SetWinTitle() {
title := ""
//平台
switch golabl.Task.Header.ShopType {
case "1":
title = title + "【拼多多】"
case "2":
title = title + "【孔夫子】"
case "5":
title = title + "【闲鱼】"
default:
title = title + "【其他平台 " + golabl.Task.Header.ShopType + "】"
}
//店铺名称
title = title + "【" + golabl.Task.Header.ShopName + "】"
//任务类型
switch golabl.Task.Header.TaskType {
case 1:
title = title + "【核价发布】"
case 2:
title = title + "【表格发布】"
case 3:
title = title + "【拉取商品】"
case 4:
title = title + "【拉取商品详情】"
case 5:
title = title + "【操作商品】"
case 6:
title = title + "【核价表格发布】"
case 7:
title = title + "【增量库存】"
default:
title = title + "【其他任务类型 " + fmt.Sprint(golabl.Task.Header.TaskType) + "】"
}
//图片类型
switch golabl.Task.Header.ImgType {
case 1:
title = title + "【仅官图】"
case 2:
title = title + "【实拍图】"
case 3:
title = title + "【优先官图】"
case 4:
title = title + "【优先实拍图】"
default:
title = title + "【其他图片类型 " + fmt.Sprint(golabl.Task.Header.ImgType) + "】"
}
//创建时间
createTime := time.Unix(golabl.Task.Header.TaskCreateAt, 0)
timeStr := createTime.Format("2006-01-02 15:04:05")
title = title + "【创建时间 " + timeStr + "】"
//任务 id
title = title + golabl.Task.Header.TaskId
setConsoleTitle(title)
}
// SetConsoleTitle 设置窗口标题
// @param title 标题
func setConsoleTitle(title string) {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
procSetConsoleTitle := kernel32.NewProc("SetConsoleTitleW")
// 将字符串转换为UTF-16指针
titlePtr, _ := syscall.UTF16PtrFromString(title)
procSetConsoleTitle.Call(uintptr(unsafe.Pointer(titlePtr)))
}

View File

@ -0,0 +1,23 @@
package interfaces
import (
planAType "planA/type"
)
// GoodsTask 商品任务接口
type GoodsTask interface {
// AddGoodsTask 添加商品任务
AddGoodsTask(bodyWait planAType.TaskBody) (string, error)
// SetGoodsTask 设置商品任务
SetGoodsTask() string
// GetGoodsTask 获取商品任务
GetGoodsTask() (string, error)
// OperationGoodsTask 操作商品任务
OperationGoodsTask(bodyWait planAType.TaskBody) (string, error)
// IncStockTask 增量库存
IncStockTask(bodyWait planAType.TaskBody) (string, error)
}

410
planB/logic/logic.go Normal file
View File

@ -0,0 +1,410 @@
package logic
import (
"encoding/json"
"errors"
"fmt"
"planA/planB/dispatcher"
"planA/planB/initialization/config"
"planA/planB/initialization/golabl"
"planA/planB/initialization/task"
"planA/planB/modules/logs"
"planA/planB/service"
"planA/planB/tool"
planAType "planA/type"
planATypeMysql "planA/type/mysql"
redisType "planA/type/redis"
"strings"
"sync/atomic"
"time"
"github.com/go-redis/redis/v8"
)
var Goto bool = false
// Logic 执行任务
func Logic() {
//loop:
// 开始读取待处理任务 等待任务数必须大于0
for golabl.Task.Footer.TaskCountWait.Load() > 0 {
// 任务索引
atomic.AddInt64(&golabl.Logic.TaskIndex, 1)
//TODO 在更新config方法出去后应该去除该代码 每次重新获取配置文件
if configErr := config.GetConfigSetToG(); configErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, configErr.Error())
return
}
// 使用令牌桶进行速率控制每秒20个
if err := golabl.Speed.Wait(golabl.Ctx); err != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("令牌桶等待失败-原因来自于:%v", err))
continue
}
//TODO 重新获取任务头尾
if taskErr := task.GetTaskHeaderAndFooterSetToG(); taskErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, taskErr.Error())
continue
}
// 如果连续读出 redisNil 的次数大于100
if atomic.LoadInt64(&golabl.Logic.RedisNilCon) > 100 {
//Goto = true
// 等待所有任务完成 暂停 5 秒
golabl.Pool.Wg.Wait()
fmt.Println("等待当前所有协程完成后 暂停5秒如果等待的任务真的是0的话则通知A完成任务")
time.Sleep(5 * time.Second)
//获取任务真实的 wait数量
count, getTaskBodyWaitCountErr := service.GetTaskBodyWaitCount()
if getTaskBodyWaitCountErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("获取任务任务真实的 wait数量失败-原因来自:%v", getTaskBodyWaitCountErr))
return
}
// 如果数量真的是0则完成任务
if count == 0 {
break
}
atomic.StoreInt64(&golabl.Logic.RedisNilCon, 0)
}
// 创建等待
golabl.Pool.Wg.Add(1)
//协程池 提交
if golabl.Task.Header.TaskType == 7 {
// 单线程执行
taskExecute()
if taskPoolErr := golabl.Pool.Pool.Submit(taskExecute); taskPoolErr != nil {
golabl.Pool.Wg.Done()
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("协程池意外-原因来自:%d", taskPoolErr))
} else {
golabl.Pool.Wg.Done()
}
} else {
// 多线程执行
if taskPoolErr := golabl.Pool.Pool.Submit(func() {
defer golabl.Pool.Wg.Done()
taskExecute()
}); taskPoolErr != nil {
golabl.Pool.Wg.Done()
}
}
// 判断 任务数是否超过1000 并且 判断是否执行到了1000的倍数
if golabl.Task.Header.TaskCountTrue > 1000 && golabl.Logic.TaskIndex%1000 == 0 {
// 更新任务头部信息
updateTaskHeaderErr := tool.UpdateTaskHeader()
if updateTaskHeaderErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("更新任务头信息失败-原因来自:%v", updateTaskHeaderErr))
}
}
}
// 等待所有任务完成
golabl.Pool.Wg.Wait()
//等待指定时间后重新执行循环
//if Goto == true {
// golabl.Logic.RedisNilCon = 0
// golabl.Logic.LastIndex = golabl.LastIndexRedisNil
// fmt.Printf("连续读出 redisNil 的次数 %v 暂停%v毫秒", golabl.Logic.RedisNilCon, golabl.Config.Server.ErrPauseTime)
// time.Sleep(time.Duration(golabl.Config.Server.ErrPauseTime) * time.Millisecond)
// goto loop
//}
// 更新任务头部信息
if updateTaskHeaderErr := tool.UpdateTaskHeader(); updateTaskHeaderErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("更新任务头信息失败-原因来自:%v", updateTaskHeaderErr))
}
// 通知 A程序任务完成
httpTaskStatusOverErr := tool.NotifyA()
if httpTaskStatusOverErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, httpTaskStatusOverErr.Error())
}
// 延迟2分钟
time.Sleep(2 * time.Minute)
}
// 任务执行
func taskExecute() {
//初始化 变量
status := golabl.BodyStatusSuccess //默认的书籍执行状态·
errorStr := "执行成功" //默认的书籍执行描述
// 获取任务信息
taskMsg, taskMsgErr := service.GetTaskToPopFromBodyWait()
if errors.Is(taskMsgErr, redis.Nil) {
//redis 读nil空+1
fmt.Printf("第 %v 次读出 Redis Nil \n", atomic.LoadInt64(&golabl.Logic.RedisNilCon))
atomic.AddInt64(&golabl.Logic.RedisNilCon, 1)
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("获取任务信息失败-原因来自:%v", taskMsgErr))
return
} else if taskMsgErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("获取任务信息失败-原因来自:%v", taskMsgErr))
return
}
//设置混合任务成功状态
if golabl.Task.Header.TaskType == 5 || golabl.Task.Header.TaskType == 9 {
switch taskMsg.Detail.Status {
case 1:
errorStr = "设置商品上架 " + errorStr
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
case 2:
errorStr = "设置商品下架 " + errorStr
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
case 3:
//删除商品的任务存储到 mysql中
//删除商品 {"book_info":{"isbn":"9787543982888"},"detail":{"goods_id":935670364385,"status":3}}
DelTask(taskMsg)
errorStr = "删除商品 已转转移至删除中心"
case 4:
errorStr = "修改商品库存 " + errorStr
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
case 5:
errorStr = "修改商品价格 " + errorStr
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
case 6:
errorStr = "发布商品 " + errorStr
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
case 7:
errorStr = "删除并重新发布 " + errorStr
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
default:
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
}
// 更新任务信息
taskMsg.Detail.Status = status
taskMsg.Detail.Error = errorStr
} else if golabl.Task.Header.TaskType == 7 {
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
taskMsg.Detail.Status = status
if status != 1 {
taskMsg.Detail.Error = errorStr
}
} else {
//执行任务
status, errorStr, taskMsg = exeTask(taskMsg, status, errorStr)
// 更新任务信息
taskMsg.Detail.Status = status
taskMsg.Detail.Error = errorStr
}
//isbn 不为空的添加到body中比如拉取店铺商品信息isbn可以返回空的
if taskMsg.BookInfo.Isbn != "" && (golabl.TaskType == "3" || golabl.TaskType == "4") {
// 添加任务到bodyOver、bodyData、bodyBackup
if addTaskToBodyOverErr := service.AddTaskToBodyOver(taskMsg, []string{}); addTaskToBodyOverErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("任务失败 添加到BodyOver失败-原因:%v", addTaskToBodyOverErr))
}
} else {
if taskMsg.BookInfo.Isbn == "" && taskMsg.BookInfo.BookName == "" {
taskMsg.BookInfo.BookName = "暂无书品信息"
}
// 添加任务到bodyOver、bodyData、bodyBackup
if addTaskToBodyOverErr := service.AddTaskToBodyOver(taskMsg, []string{}); addTaskToBodyOverErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("任务失败 添加到BodyOver失败-原因:%v", addTaskToBodyOverErr))
}
}
// 更新 footer信息
if updateTaskFooterErr := service.UpdateTaskFooter(status, 1); updateTaskFooterErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("任务失败 添加到BodyOver失败-原因:%v", updateTaskFooterErr))
}
// 如果错误是 店铺商品发布达到上限则暂停程序
if strings.Contains(errorStr, "店铺内发布商品总数已达到上限") {
golabl.Task.Header.LastIndex = golabl.LastIndexGoodsMaxRestriction
//暂停 B程序运行
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "任务失败 添加到BodyOver失败-原因:店铺内发布商品总数已达到上限")
pauseTaskErr := tool.PauseTask()
if pauseTaskErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, "任务失败 添加到BodyOver失败-原因:店铺内发布商品总数已达到上限")
}
}
fmt.Println(errorStr)
}
//****************************工具**************************************//
// parseShopData 解析店铺数据
// @param shopData 店铺数据
// @return *_type.ShopInfo 店铺信息
func parseShopData(shopData string) (*planAType.ShopInfo, error) {
shopData = strings.TrimSpace(shopData)
// 直接解析为 RedisData数组
var redisData []redisType.RedisData
err := json.Unmarshal([]byte(shopData), &redisData)
if err != nil {
// 尝试另一种格式:可能是单对象而不是数组
var singleData redisType.RedisData
if singleErr := json.Unmarshal([]byte(shopData), &singleData); singleErr == nil {
redisData = []redisType.RedisData{singleData}
} else {
return nil, fmt.Errorf("JSON解析失败: %v, 原始数据: %s", err, shopData[:min(100, len(shopData))])
}
}
shopInfo := &planAType.ShopInfo{}
// 遍历所有数据根据source_table分类
for _, item := range redisData {
switch item.SourceTable {
case "t_shop":
var shop planAType.Shop
if err := json.Unmarshal(item.Data, &shop); err == nil {
shopInfo.Shop = &shop
} else {
fmt.Printf("解析t_shop失败: %v\n", err)
}
case "t_shop_detail":
var detail planAType.ShopDetail
if err := json.Unmarshal(item.Data, &detail); err == nil {
shopInfo.ShopDetail = &detail
} else {
fmt.Printf("解析t_shop_detail失败: %v\n", err)
}
case "t_shop_context":
var context planAType.ShopContext
if err := json.Unmarshal(item.Data, &context); err == nil {
shopInfo.ShopContext = &context
} else {
fmt.Printf("解析t_shop_context失败: %v\n", err)
}
case "t_spec":
var spec planAType.Spec
if err := json.Unmarshal(item.Data, &spec); err == nil {
shopInfo.Spec = &spec
} else {
fmt.Printf("解析t_spec失败: %v\n", err)
}
case "t_price_template":
var template planAType.PriceTemplate
if err := json.Unmarshal(item.Data, &template); err == nil {
shopInfo.PriceTemplate = &template
} else {
fmt.Printf("解析t_price_template失败: %v\n", err)
}
default:
fmt.Printf("未知的source_table: %s\n", item.SourceTable)
}
}
return shopInfo, nil
}
// 调度任务
func exeTask(taskMsg planAType.TaskBody, status int64, errorStr string) (int64, string, planAType.TaskBody) {
// 任务调度
bodyOverJson, err := dispatcher.Go(taskMsg)
if err != nil {
//任务调度失败
status = golabl.BodyStatusError
errorStr = fmt.Sprintf("任务调度失败-原因来自:%v", err)
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("任务调度失败-原因来自:%v", err))
} else {
//任务调度成功
var bodyOver planAType.TaskBody
unmarshalErr := json.Unmarshal([]byte(bodyOverJson), &bodyOver)
if unmarshalErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("bodyOver json.Unmarshal错误-原因:%v", unmarshalErr))
}
//更新 taskMsg
taskMsg = bodyOver
}
return status, errorStr, taskMsg
}
// DelTask 删除任务
func DelTask(taskMsg planAType.TaskBody) {
//删除商品的任务存储到 mysql中
//删除商品 {"book_info":{"isbn":"9787543982888"},"detail":{"goods_id":935670364385,"status":3}}
delTask, isExistDelTask, delTaskErr := service.GetDelTaskByTaskId()
if !isExistDelTask && delTaskErr == nil {
taskCount := 0
taskCountOver := 0
sta := 0
//将header 转为json
headerByte, headerJsonErr := json.Marshal(golabl.Task.Header)
if headerJsonErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("将header 转为json失败-原因来自:%v", headerJsonErr))
return
}
headerJson := string(headerByte)
currentTime := time.Now()
// 查询店铺数据
shopDataStr, getTaskShopErr := service.GetTaskShop(golabl.Task.Header.ShopId)
if getTaskShopErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("查询店铺数据失败:%v", headerJsonErr))
return
}
// 解析 json数据
shopData, parseShopDataErr := parseShopData(shopDataStr)
if parseShopDataErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("解析店铺数据失败:%v", parseShopDataErr))
return
}
userId := shopData.Shop.CreateBy
taskType := 1
//不存在 mysql任务则创建
createDelTask := planATypeMysql.DelTask{
UserID: &userId,
ShopID: &golabl.Task.Header.ShopId,
TaskID: &golabl.Task.Header.TaskId,
ShopName: &golabl.Task.Header.ShopName,
ShopType: &shopData.Shop.ShopType,
TaskCount: &taskCount,
TaskCountOver: &taskCountOver,
Status: &sta,
TaskType: &taskType,
Header: &headerJson,
CreateAt: &currentTime,
}
var err error
delTask, err = service.CreateDelTask(createDelTask)
if err != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("创建删除任务失败-原因来自:%v", err))
return
}
} else if delTaskErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("获取删除任务失败-原因来自:%v", delTaskErr))
return
}
//将任务状态修改为执行中
updateDelTaskStatusToDoingErr := service.UpdateDelTaskStatusToDoing()
if updateDelTaskStatusToDoingErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("将删除任务状态修改为执行中失败-原因来自:%v", updateDelTaskStatusToDoingErr))
return
}
// 将明细的删除任务转移到 mysql中
insertDelTaskDetailErr := service.InsertDelTaskDetail(delTask.ID, taskMsg)
if insertDelTaskDetailErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("将明细的删除任务转移到 mysql中失败-原因来自:%v", insertDelTaskDetailErr))
return
}
// 添加删除任务数量
addDelTaskDetailCountErr := service.AddDelTaskDetailCount()
if addDelTaskDetailCountErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("添加删除任务数量失败-原因来自:%v", addDelTaskDetailCountErr))
return
}
}

76
planB/main.go Normal file
View File

@ -0,0 +1,76 @@
package main
import (
"fmt"
"planA/planB/initialization"
"planA/planB/initialization/golabl"
"planA/planB/logic"
"planA/planB/modules/logs"
"planA/planB/tool"
"planA/planB/validation"
//"planA/planB/initialization"
//"planA/planB/initialization/golabl"
//"planA/planB/logic"
//"planA/planB/modules/logs"
//"planA/planB/tool"
//"planA/planB/validation"
"time"
)
func main() {
//校验参数
taskId, validationErr := validation.Validation()
if validationErr != nil {
fmt.Println(validationErr)
return
}
// 是否测试模式
if taskId == "111" {
//test()
return
}
// 初始化配置
err := initialization.Init(taskId)
if err != nil {
fmt.Println(err)
return
}
// 拉取商品列表与拼多多商品详情列表
if golabl.Task.Header.TaskType == 3 || (golabl.Task.Header.TaskType == 4 && golabl.Task.Header.ShopType == "1") {
_, getGoodsTask := golabl.Platform.GetGoodsTask()
if getGoodsTask != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, getGoodsTask.Error())
}
// 通知 A程序任务完成
httpTaskStatusOverErr := tool.NotifyA()
if httpTaskStatusOverErr != nil {
tool.LoggingMiddleware(logs.LOG_LEVEL_ERROR, httpTaskStatusOverErr.Error())
}
//延迟3分钟,并且循环打印每秒倒计时
totalSeconds := 180 // 3分钟 = 180秒
for i := totalSeconds; i >= 0; i-- {
minutes := i / 60
seconds := i % 60
fmt.Printf("\r剩余时间: %02d:%02d", minutes, seconds)
if i > 0 {
time.Sleep(1 * time.Second)
}
}
} else {
// 执行任务
logic.Logic()
}
}
// 测试模式
func test() {
//循环1000次
for i := 0; i < 1000; i++ {
//每秒打印 i
fmt.Printf("i:%v\n", i)
time.Sleep(time.Second)
}
}

Binary file not shown.

View File

@ -0,0 +1,74 @@
package config
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// ConfigDLL 配置文件读取DLL结构
type ConfigDLL struct {
dll *syscall.DLL
readConfigFile *syscall.Proc // 读取配置文件
getVersion *syscall.Proc // 获取版本信息
freeCString *syscall.Proc // 释放C字符串
}
// InitConfigDLL 初始化ConfigDLL
func InitConfigDLL() (*ConfigDLL, error) {
dllPath := filepath.Join("modules/config/", "config.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("config DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载config DLL 失败: %s", err)
} else {
return &ConfigDLL{
dll: dll,
readConfigFile: dll.MustFindProc("ReadConfigFile"),
getVersion: dll.MustFindProc("GetVersion"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
// cStr 获取C字符串
func (m *ConfigDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
// ReadConfigFile 读取配置文件
func (m *ConfigDLL) ReadConfigFile(filePath, fileName string) (string, error) {
proc, err := m.dll.FindProc("ReadConfigFile")
if err != nil {
return "", fmt.Errorf("找不到函数 ReadConfigFile: %v", err)
}
filePathPtr, _ := syscall.BytePtrFromString(filePath)
fileNamePtr, _ := syscall.BytePtrFromString(fileName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(filePathPtr)),
uintptr(unsafe.Pointer(fileNamePtr)),
)
result := m.cStr(resultPtr)
return result, nil
}

Binary file not shown.

View File

@ -0,0 +1,141 @@
package image
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
var (
gImageDll *ImageDLL
// Windows API - 使用 C 运行时库
libc = syscall.NewLazyDLL("msvcrt.dll")
procFree = libc.NewProc("free")
procMalloc = libc.NewProc("malloc")
)
// ImageDLL 图片工具DLL结构
type ImageDLL struct {
Dll *syscall.DLL
AddWatermarkFromURLEx *syscall.Proc // 打水印
}
// InitImageDll 初始化 imageDLL
func InitImageDll(url string) (*ImageDLL, error) {
dllPath := filepath.Join(url, "image.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("Image DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载Image DLL 失败: %s", err)
}
gImageDll = &ImageDLL{
Dll: dll,
AddWatermarkFromURLEx: dll.MustFindProc("AddWatermarkFromURLEx"),
}
return gImageDll, nil
}
// WatermarkConfig 添加水印
type WatermarkConfig struct {
SourceImageURL string // 源图片URL地址
WatermarkURL string // 水印图片URL地址
WatermarkBase64 string // 水印图片base64编码字符串新增优先使用
Opacity float64 // 不透明度 (0.0-1.0)
Position string // 位置: center, top-left, top-right, bottom-left, bottom-right, tile
TileSpacing int // 平铺时的间距
Scale float64 // 水印缩放比例 (0.0-1.0)
Rotation float64 // 旋转角度 (度数)
XOffset int // X轴偏移量
YOffset int // Y轴偏移量
Timeout int // 下载超时时间默认30秒
OutputFormat string // 输出格式: "jpeg", "png", "auto"默认auto根据源图片格式auto
JPEGQuality int // JPEG质量 (1-100)默认95
TargetWidth int // 目标宽度0表示不缩放
TargetHeight int // 目标高度0表示不缩放
ResizeMode string // 缩放模式: "fit"(适应,保持比例,可能有黑边), "fill"(填充,裁剪), "stretch"(拉伸)
}
// AddWatermarkFromURLExs 添加水印
func (m *ImageDLL) AddWatermarkFromURLExs(sourceImageUrl, watermarkUrl string) (string, error) {
watermarkConfig := WatermarkConfig{
SourceImageURL: sourceImageUrl,
WatermarkBase64: watermarkUrl,
Position: "center",
Opacity: 1.0,
Scale: 1.0,
TileSpacing: 50,
Timeout: 30,
OutputFormat: "jpeg",
JPEGQuality: 95,
TargetWidth: 800,
TargetHeight: 800,
ResizeMode: "fit",
}
watermarkConfigJson, err := json.Marshal(watermarkConfig)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
proc, err := m.Dll.FindProc("AddWatermarkFromURLEx")
if err != nil {
return "", fmt.Errorf("找不到函数 AddWatermarkFromURLEx: %v", err)
}
// 分配内存并确保释放
jsonStr := string(watermarkConfigJson)
jsonPtr := cString(jsonStr)
defer freeCString(jsonPtr)
// 调用 DLL 函数
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(jsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cString 分配 C 字符串(使用 malloc
func cString(str string) unsafe.Pointer {
// 计算需要的内存大小
size := len(str) + 1
ptr, _, _ := procMalloc.Call(uintptr(size))
if ptr == 0 {
return nil
}
// 复制字符串内容
for i := 0; i < len(str); i++ {
*(*byte)(unsafe.Pointer(ptr + uintptr(i))) = str[i]
}
*(*byte)(unsafe.Pointer(ptr + uintptr(len(str)))) = 0 // 结尾加 \0
return unsafe.Pointer(ptr)
}
// freeCString 释放 C 字符串
func freeCString(ptr unsafe.Pointer) {
if ptr != nil {
procFree.Call(uintptr(ptr))
}
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}

BIN
planB/modules/kfz/kfz.dll Normal file

Binary file not shown.

248
planB/modules/kfz/kfz.go Normal file
View File

@ -0,0 +1,248 @@
package kfz
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// KfzDLL 孔夫子工具DLL结构
type KfzDLL struct {
Dll *syscall.DLL
freeCString *syscall.Proc // 释放C字符串
}
// InitKfzDll 初始化 kfzDLL
func InitKfzDll(url string) (*KfzDLL, error) {
dllPath := filepath.Join(url, "kfz.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("kfz DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
}
gKfzDll := &KfzDLL{
Dll: dll,
freeCString: dll.MustFindProc("FreeCString"),
}
return gKfzDll, nil
}
// PublishGoods 发布商品
func (m *KfzDLL) PublishGoods(appId int, clientSecret, accessToken, goodsAddJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemAdd")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemAdd: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
goodsAddJsonPtr, _ := syscall.BytePtrFromString(goodsAddJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(goodsAddJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// KfzGoodsImageUpload 将图片上传到孔夫子图片空间
func (m *KfzDLL) KfzGoodsImageUpload(appId int, clientSecret, accessToken, filePath string) (string, error) {
proc, err := m.Dll.FindProc("KongfzImageUpload")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzImageUpload: %v", err)
}
//appIdPtr, _ := syscall.BytePtrFromString(appId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
goodsAddJsonPtr, _ := syscall.BytePtrFromString(filePath)
savePathPtr, _ := syscall.BytePtrFromString("")
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(goodsAddJsonPtr)),
uintptr(unsafe.Pointer(savePathPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// GetGoodsCategoryList 获取本店商品分类列表
func (m *KfzDLL) GetGoodsCategoryList(appId int, clientSecret, accessToken string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopCategoryNameList")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopCategoryNameList: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// GetCommonCategory 获取公用分类数据
func (m *KfzDLL) GetCommonCategory(appId int, clientSecret, accessToken string) (string, error) {
proc, err := m.Dll.FindProc("KongfzCommonCategory")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzCommonCategory: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// GetGoodsList 获取商品列表
func (m *KfzDLL) GetGoodsList(appId int, clientSecret, accessToken, getGoodsListReqJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemList")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemList: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
getGoodsListReqJsonPtr, _ := syscall.BytePtrFromString(getGoodsListReqJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(getGoodsListReqJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PutOnSale 上架
func (m *KfzDLL) PutOnSale(appId int, clientSecret, accessToken, putOnSaleJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemListing")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemListing: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
putOnSaleJsonPtr, _ := syscall.BytePtrFromString(putOnSaleJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(putOnSaleJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PutOffSale 下架
func (m *KfzDLL) PutOffSale(appId int, clientSecret, accessToken, putOffSaleJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemDelisting")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemDelisting: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
putOffSaleJsonPtr, _ := syscall.BytePtrFromString(putOffSaleJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(putOffSaleJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// UpdateGoodsStock 修改商品库存
func (m *KfzDLL) UpdateGoodsStock(appId int, clientSecret, accessToken, updateGoodsStockJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemNumberUpdate")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemNumberUpdate: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
updateGoodsStockJsonPtr, _ := syscall.BytePtrFromString(updateGoodsStockJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(updateGoodsStockJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// UpdateGoodsPrice 修改商品价格
func (m *KfzDLL) UpdateGoodsPrice(appId int, clientSecret, accessToken, updateGoodsPriceJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemPriceUpdate")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemPriceUpdate: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
updateGoodsPriceJsonPtr, _ := syscall.BytePtrFromString(updateGoodsPriceJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(updateGoodsPriceJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// DeleteGoods 删除商品
func (m *KfzDLL) DeleteGoods(appId int, clientSecret, accessToken, deleteGoodsJson string) (string, error) {
proc, err := m.Dll.FindProc("KongfzShopItemDelete")
if err != nil {
return "", fmt.Errorf("找不到函数 KongfzShopItemDelete: %v", err)
}
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
deleteGoodsJsonPtr, _ := syscall.BytePtrFromString(deleteGoodsJson)
resultPtr, _, _ := proc.Call(
uintptr(appId),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(deleteGoodsJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}

322
planB/modules/logs/dll.go Normal file
View File

@ -0,0 +1,322 @@
package logs
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"syscall"
"unsafe"
)
const (
LOG_LEVEL_DEBUG = "DEBUG"
LOG_LEVEL_INFO = "INFO"
LOG_LEVEL_WARNING = "WARNING"
LOG_LEVEL_ERROR = "ERROR"
LOG_LEVEL_SUCCESS = "SUCCESS"
)
// LoggerDLL 封装 logger.dll 操作
type LoggerDLL struct {
dll *syscall.LazyDLL
createLogger *syscall.LazyProc
createContext *syscall.LazyProc
logInfo *syscall.LazyProc
logError *syscall.LazyProc
logWarning *syscall.LazyProc
logSuccess *syscall.LazyProc
freeString *syscall.LazyProc
closeAllLoggers *syscall.LazyProc
}
// LoggerConfig logger配置结构
type LoggerConfig struct {
LogDir string `json:"log_dir"`
SplitType int `json:"split_type"`
RotateType int `json:"rotate_type"`
MaxSize int64 `json:"max_size"`
MaxCount int `json:"max_count"`
Level int `json:"level"`
EnableCaller bool `json:"enable_caller"`
DefaultTaskType string `json:"default_task_type"`
}
var loggerDLLInstance *LoggerDLL
var loggerHandle string
var loggerContextHandle string
// EnsureLoggerDLL 确保logger DLL已加载
func EnsureLoggerDLL(url string) (*LoggerDLL, error) {
if loggerDLLInstance != nil {
return loggerDLLInstance, nil
}
// 检查是否在Windows平台
if runtime.GOOS != "windows" {
return nil, fmt.Errorf("logger DLL only supported on Windows platform")
}
dllPath := filepath.Join(url, "logger.dll")
// 检查文件是否存在
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
// 尝试从当前目录查找
if _, err := os.Stat("logger.dll"); err == nil {
dllPath = "logger.dll"
} else {
return nil, fmt.Errorf("logger DLL not found at %s", dllPath)
}
}
dll := syscall.NewLazyDLL(dllPath)
loggerDLLInstance = &LoggerDLL{
dll: dll,
createLogger: dll.NewProc("CreateLogger"),
createContext: dll.NewProc("CreateContextWithTaskType"),
logInfo: dll.NewProc("LogInfo"),
logError: dll.NewProc("LogError"),
logWarning: dll.NewProc("LogWarning"),
logSuccess: dll.NewProc("LogSuccess"),
freeString: dll.NewProc("FreeString"),
closeAllLoggers: dll.NewProc("CloseAllLoggers"),
}
return loggerDLLInstance, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
// InitializeLogger 初始化logger
func InitializeLogger(m *LoggerDLL, logDir string) error {
// 确保日志目录存在
if err := os.MkdirAll(logDir, 0755); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
// 创建 logger配置
config := LoggerConfig{
LogDir: logDir,
SplitType: 2, // SplitByDay
RotateType: 0, // RotateBySize
MaxSize: 100 * 1024 * 1024, // 100MB
MaxCount: 10,
Level: 1, // LevelInfo - 只显示INFO及以上级别的日志
EnableCaller: true,
DefaultTaskType: "main",
}
configJSON, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("序列化配置失败: %v", err)
}
// 调用CreateLogger
configPtr, _ := syscall.BytePtrFromString(string(configJSON))
ret, _, _ := m.createLogger.Call(uintptr(unsafe.Pointer(configPtr)))
if ret == 0 {
return fmt.Errorf("创建logger失败")
}
// 获取logger句柄
handle := cStr(ret)
loggerHandle = handle
// 释放返回的字符串
m.freeString.Call(ret)
// 创建默认上下文
return createLoggerContext(m, "main")
}
// createLoggerContext 创建带任务类型的logger上下文
func createLoggerContext(m *LoggerDLL, taskType string) error {
if loggerHandle == "" {
return fmt.Errorf("logger未初始化")
}
handlePtr, _ := syscall.BytePtrFromString(loggerHandle)
taskTypePtr, _ := syscall.BytePtrFromString(taskType)
ret, _, _ := m.createContext.Call(
uintptr(unsafe.Pointer(handlePtr)),
uintptr(unsafe.Pointer(taskTypePtr)),
)
if ret == 0 {
return fmt.Errorf("创建logger上下文失败")
}
// 获取上下文句柄
loggerContextHandle = cStr(ret)
// 释放返回的字符串
m.freeString.Call(ret)
return nil
}
// SetLogTaskType 设置当前日志任务类型
func SetLogTaskType(m *LoggerDLL, taskType string) error {
return createLoggerContext(m, taskType)
}
// LogInfo 记录信息日志
func LogInfo(m *LoggerDLL, message string) error {
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logInfo.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogError 记录错误日志
func LogError(m *LoggerDLL, message string) error {
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logError.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogWarning 记录警告日志
func LogWarning(m *LoggerDLL, message string) error {
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logWarning.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogSuccess 记录成功日志
func LogSuccess(m *LoggerDLL, message string) error {
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logSuccess.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// CloseLogger 关闭logger
func CloseLogger(m *LoggerDLL) error {
ret, _, _ := m.closeAllLoggers.Call()
if ret == 0 {
return fmt.Errorf("关闭logger失败")
}
m.freeString.Call(ret)
loggerHandle = ""
loggerContextHandle = ""
loggerDLLInstance = nil
return nil
}
// GetLoggerHandle 获取当前logger句柄用于外部调用
func GetLoggerHandle() string {
return loggerContextHandle
}
// IsLoggerInitialized 检查logger是否已初始化
func IsLoggerInitialized() bool {
return loggerHandle != "" && loggerContextHandle != ""
}
// SetConsoleOutput 设置控制台输出开关
func SetConsoleOutput(enabled bool) {
if enabled {
os.Setenv("LOG_CONSOLE", "true")
} else {
os.Setenv("LOG_CONSOLE", "false")
}
}
// LogWithLevel 带级别的日志记录,可以精确控制显示
func LogWithLevel(m *LoggerDLL, level, message string, showConsole bool) {
if !IsLoggerInitialized() {
return
}
switch level {
case "ERROR":
LogError(m, message)
case "WARNING":
LogWarning(m, message)
case "SUCCESS":
LogSuccess(m, message)
case "INFO":
LogInfo(m, message)
default:
LogInfo(m, message)
}
}
// LogOnlyFile 仅写入文件,不输出到控制台
func LogOnlyFile(m *LoggerDLL, level, message string) {
// 临时禁用控制台输出
os.Setenv("LOG_CONSOLE", "false")
LogWithLevel(m, level, message, false)
}
// LogConsoleAndFile 同时输出到控制台和文件
func LogConsoleAndFile(m *LoggerDLL, level, message string) {
// 临时启用控制台输出
os.Setenv("LOG_CONSOLE", "true")
LogWithLevel(m, level, message, true)
}

Binary file not shown.

View File

@ -0,0 +1,602 @@
# logger.dll 使用教程
## 1. 创建DLL工具实例
### 加载DLL文件
```gotemplate
package logs
import (
"encoding/json"
"fmt"
"os"
"runtime"
"syscall"
"unsafe"
)
// LoggerDLL 封装 logger.dll 操作
type LoggerDLL struct {
dll *syscall.LazyDLL
createLogger *syscall.LazyProc
createContext *syscall.LazyProc
logInfo *syscall.LazyProc
logError *syscall.LazyProc
logWarning *syscall.LazyProc
logSuccess *syscall.LazyProc
freeString *syscall.LazyProc
closeAllLoggers *syscall.LazyProc
}
// LoggerConfig logger配置结构
type LoggerConfig struct {
LogDir string `json:"log_dir"`
SplitType int `json:"split_type"`
RotateType int `json:"rotate_type"`
MaxSize int64 `json:"max_size"`
MaxCount int `json:"max_count"`
Level int `json:"level"`
EnableCaller bool `json:"enable_caller"`
DefaultTaskType string `json:"default_task_type"`
}
var loggerDLLInstance *LoggerDLL
var loggerHandle string
var loggerContextHandle string
// ensureLoggerDLL 确保logger DLL已加载
func ensureLoggerDLL() (*LoggerDLL, error) {
if loggerDLLInstance != nil {
return loggerDLLInstance, nil
}
// 检查是否在Windows平台
if runtime.GOOS != "windows" {
return nil, fmt.Errorf("logger DLL only supported on Windows platform")
}
// logger.dll 位于 dll/logger.dll
//dllPath := filepath.Join("modules", "logs", "logger.dll")
dllPath := "D:\\www\\wwwroot\\planA\\modules\\logs\\logger.dll"
// 检查文件是否存在
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
// 尝试从当前目录查找
if _, err := os.Stat("logger.dll"); err == nil {
dllPath = "logger.dll"
} else {
return nil, fmt.Errorf("logger DLL not found at %s", dllPath)
}
}
dll := syscall.NewLazyDLL(dllPath)
loggerDLLInstance = &LoggerDLL{
dll: dll,
createLogger: dll.NewProc("CreateLogger"),
createContext: dll.NewProc("CreateContextWithTaskType"),
logInfo: dll.NewProc("LogInfo"),
logError: dll.NewProc("LogError"),
logWarning: dll.NewProc("LogWarning"),
logSuccess: dll.NewProc("LogSuccess"),
freeString: dll.NewProc("FreeString"),
closeAllLoggers: dll.NewProc("CloseAllLoggers"),
}
return loggerDLLInstance, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
// InitializeLogger 初始化logger
func InitializeLogger(logDir string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
// 确保日志目录存在
if err := os.MkdirAll(logDir, 0755); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
// 创建logger配置
config := LoggerConfig{
LogDir: logDir,
SplitType: 1, // SplitByDay
RotateType: 0, // RotateBySize
MaxSize: 100 * 1024 * 1024, // 100MB
MaxCount: 10,
Level: 1, // LevelInfo - 只显示INFO及以上级别的日志
EnableCaller: true,
DefaultTaskType: "main",
}
configJSON, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("序列化配置失败: %v", err)
}
// 调用CreateLogger
configPtr, _ := syscall.BytePtrFromString(string(configJSON))
ret, _, _ := m.createLogger.Call(uintptr(unsafe.Pointer(configPtr)))
if ret == 0 {
return fmt.Errorf("创建logger失败")
}
// 获取logger句柄
handle := cStr(ret)
loggerHandle = handle
// 释放返回的字符串
m.freeString.Call(ret)
// 创建默认上下文
return createLoggerContext("main")
}
// createLoggerContext 创建带任务类型的logger上下文
func createLoggerContext(taskType string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerHandle == "" {
return fmt.Errorf("logger未初始化")
}
handlePtr, _ := syscall.BytePtrFromString(loggerHandle)
taskTypePtr, _ := syscall.BytePtrFromString(taskType)
ret, _, _ := m.createContext.Call(
uintptr(unsafe.Pointer(handlePtr)),
uintptr(unsafe.Pointer(taskTypePtr)),
)
if ret == 0 {
return fmt.Errorf("创建logger上下文失败")
}
// 获取上下文句柄
loggerContextHandle = cStr(ret)
// 释放返回的字符串
m.freeString.Call(ret)
return nil
}
// SetLogTaskType 设置当前日志任务类型
func SetLogTaskType(taskType string) error {
return createLoggerContext(taskType)
}
// LogInfo 记录信息日志
func LogInfo(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logInfo.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogError 记录错误日志
func LogError(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logError.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogWarning 记录警告日志
func LogWarning(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logWarning.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// LogSuccess 记录成功日志
func LogSuccess(message string) error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
if loggerContextHandle == "" {
return fmt.Errorf("logger上下文未初始化")
}
ctxPtr, _ := syscall.BytePtrFromString(loggerContextHandle)
msgPtr, _ := syscall.BytePtrFromString(message)
m.logSuccess.Call(
uintptr(unsafe.Pointer(ctxPtr)),
uintptr(unsafe.Pointer(msgPtr)),
)
return nil
}
// CloseLogger 关闭logger
func CloseLogger() error {
m, err := ensureLoggerDLL()
if err != nil {
return err
}
ret, _, _ := m.closeAllLoggers.Call()
if ret == 0 {
return fmt.Errorf("关闭logger失败")
}
m.freeString.Call(ret)
loggerHandle = ""
loggerContextHandle = ""
loggerDLLInstance = nil
return nil
}
// GetLoggerHandle 获取当前logger句柄用于外部调用
func GetLoggerHandle() string {
return loggerContextHandle
}
// IsLoggerInitialized 检查logger是否已初始化
func IsLoggerInitialized() bool {
return loggerHandle != "" && loggerContextHandle != ""
}
// SetConsoleOutput 设置控制台输出开关
func SetConsoleOutput(enabled bool) {
if enabled {
os.Setenv("LOG_CONSOLE", "true")
} else {
os.Setenv("LOG_CONSOLE", "false")
}
}
// LogWithLevel 带级别的日志记录,可以精确控制显示
func LogWithLevel(level, message string, showConsole bool) {
if !IsLoggerInitialized() {
return
}
switch level {
case "ERROR":
LogError(message)
case "WARNING":
LogWarning(message)
case "SUCCESS":
LogSuccess(message)
case "INFO":
LogInfo(message)
default:
LogInfo(message)
}
}
// LogOnlyFile 仅写入文件,不输出到控制台
func LogOnlyFile(level, message string) {
// 临时禁用控制台输出
os.Setenv("LOG_CONSOLE", "false")
LogWithLevel(level, message, false)
}
// LogConsoleAndFile 同时输出到控制台和文件
func LogConsoleAndFile(level, message string) {
// 临时启用控制台输出
os.Setenv("LOG_CONSOLE", "true")
LogWithLevel(level, message, true)
}
```
# 接口详情
## 创建日志器--CreateLogger
### 请求信息
```gotemplate
dll.CreateLogger(configJSON)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| configJSON | string | 是 | 配置信息JSON字符串 |
#### 配置JSON结构
```json
{
"log_dir": "/path/to/logs",
"split_type": 0,
"rotate_type": 0,
"max_size": 104857600,
"max_count": 30,
"level": 1,
"enable_caller": true,
"default_task_type": "main"
}
```
#### 参数说明:
```text
log_dir: 日志目录路径
split_type: 分片方式0=按月1=按天2=按小时3=按分钟4=按秒)
rotate_type: 轮转方式0=按大小1=按数量)
max_size: 最大文件大小字节仅在rotate_type=0时有效
max_count: 最大文件数量仅在rotate_type=1时有效
level: 日志级别0=SUCCESS1=INFO2=WARNING3=ERROR
enable_caller: 是否启用调用者信息
default_task_type: 默认任务类型
```
### 响应示例
```json
"错误: 创建日志目录失败: permission denied"
```
## 创建带任务类型的上下文--CreateContextWithTaskType
### 请求信息
```gotemplate
dll.CreateContextWithTaskType(loggerHandle, taskType)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| loggerHandle | string | 是 | 日志器句柄 |
| taskType | string | 是 | 任务类型 |
### 响应示例
```json
"ctx_1645497600000000000"
```
#### 错误响应示例
```json
"错误: 无效的logger句柄"
```
## 记录信息日志--LogInfo
### 请求信息
```gotemplate
dll.LogInfo(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 记录错误日志--LogError
### 请求信息
```gotemplate
dll.LogError(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 记录警告日志--LogWarning
### 请求信息
```gotemplate
dll.LogWarning(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 记录成功日志--LogSuccess
### 请求信息
```gotemplate
dll.LogSuccess(ctxHandle, message)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| ctxHandle | string | 是 | 上下文句柄 |
| message | string | 是 | 日志消息 |
### 响应示例
```text
无返回值,日志将写入到对应的日志文件中。
```
## 获取日志条目--GetLogs
### 请求信息
```gotemplate
dll.GetLogs(loggerHandle, configJSON)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------|
| loggerHandle | string | 是 | 日志器句柄 |
| configJSON | string | 是 | 查询配置JSON |
#### 查询配置JSON结构
```json
{
"level": 1,
"task_type": "main",
"start_time": "2024-01-01 00:00:00",
"end_time": "2024-01-31 23:59:59",
"max_entries": 1000
}
```
#### 参数说明:
```text
level: 日志级别(-1表示所有级别
task_type: 任务类型(空字符串表示所有任务类型)
start_time: 开始时间(格式: 2006-01-02 15:04:05
end_time: 结束时间(格式: 2006-01-02 15:04:05
max_entries: 最大返回条目数0表示使用默认值1000
```
### 响应示例
```json
{
"count": 125,
"entries": [
{
"timestamp": "2024-01-15 10:30:45.123",
"level": "INFO",
"task_type": "main",
"caller": "logger.go:256",
"message": "系统启动完成"
},
{
"timestamp": "2024-01-15 10:31:15.456",
"level": "ERROR",
"task_type": "backup",
"caller": "backup.go:89",
"message": "备份文件失败: 磁盘空间不足"
}
]
}
```
### 错误响应示例
```json
{"error": "无效的logger句柄"}
```
## 获取日志文件列表--GetLogFiles
### 请求信息
```gotemplate
dll.GetLogFiles(loggerHandle)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------|
| loggerHandle | string | 是 | 日志器句柄 |
### 响应示例
```json
{
"count": 8,
"files": [
{
"level": "INFO",
"task_type": "main",
"file_name": "INFO-main-2024-01.logs",
"file_size": 1048576,
"mod_time": "2024-01-15 10:30:45"
},
{
"level": "ERROR",
"task_type": "backup",
"file_name": "ERROR-backup-2024-01.logs",
"file_size": 51200,
"mod_time": "2024-01-15 10:31:15"
}
]
}
```
### 错误响应示例
```json
{"error": "无效的logger句柄"}
```
## 获取版本信息--GetVersion
### 请求信息
```gotemplate
dll.GetVersion()
```
### 请求参数
```text
无参数
```
### 响应示例
```json
"v1"
```
## 关闭所有日志器--CloseAllLoggers
### 请求信息
```gotemplate
dll.CloseAllLoggers()
```
### 请求参数
```text
无参数
```
### 响应示例
```json
"成功关闭所有logger"
```
### 错误响应示例
```json
"关闭了5个logger其中1个出错最后错误: close file error"
```
## 释放C字符串内存--FreeString
### 请求信息
```gotemplate
dll.FreeString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------|
| str | string | 是 | 需要释放的字符串 |

BIN
planB/modules/pdd/pdd.dll Normal file

Binary file not shown.

337
planB/modules/pdd/pdd.go Normal file
View File

@ -0,0 +1,337 @@
package pdd
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
var (
gPddDll *PddDLL
)
// PddResponse 定义完整的响应结构(包含成功和失败两种情况)
type PddResponse struct {
SuccessResponse *PddSuccessResponse `json:"outer_cat_mapping_get_response,omitempty"`
ErrorResponse *PddErrorResponse `json:"error_response,omitempty"`
}
type PddSuccessResponse struct {
OuterCatMappingGetResponse PddCategoryMappingResponse `json:"outer_cat_mapping_get_response"`
}
// PddCategoryMappingResponse 定义拼多多API响应结构根据文档规范
type PddCategoryMappingResponse struct {
CatID1 int64 `json:"cat_id1"` // 一级类目 ID
CatID2 int64 `json:"cat_id2"` // 二级类目 ID
CatID3 int64 `json:"cat_id3"` // 三级类目 ID
CatID4 int64 `json:"cat_id4"` // 四级类目 ID
RequestID string `json:"request_id"` // 请求 ID
}
// PddDLL 拼多多工具DLL结构
type PddDLL struct {
Dll *syscall.DLL
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
freeCString *syscall.Proc // 释放C字符串
}
type PddErrorResponse struct {
ErrorCode int64 `json:"error_code"` // 错误码
ErrorMsg string `json:"error_msg"` // 错误信息
SubCode *string `json:"sub_code"` // 子错误码
SubMsg string `json:"sub_msg"` // 子错误信息
RequestID string `json:"request_id"` // 请求ID
}
// InitPddDll 初始化 pddDLL
func InitPddDll(url string) (*PddDLL, error) {
dllPath := filepath.Join(url, "pdd.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
}
gPddDll = &PddDLL{
Dll: dll,
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
freeCString: dll.MustFindProc("FreeCString"),
}
return gPddDll, nil
}
// PddGoodsOuterCatMappingGet 类目预测
func (m *PddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsOuterCatMappingGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(outerCatIdPtr)),
uintptr(unsafe.Pointer(outerCatNamePtr)),
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsAdd 商品新增
func (m *PddDLL) PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsAdd")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsAdd: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
goodsAddJsonPtr, _ := syscall.BytePtrFromString(goodsAddJson)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(goodsAddJsonPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
// PddGoodsSpecIdGet 生成商家自定义的规格
func (m *PddDLL) PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsSpecIdGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsSpecIdGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
parentSpecIdPtr, _ := syscall.BytePtrFromString(parentSpecId)
specNamePtr, _ := syscall.BytePtrFromString(specName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(parentSpecIdPtr)),
uintptr(unsafe.Pointer(specNamePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsCommitDetailGet 获取商品提交的商品详情
func (m *PddDLL) PddGoodsCommitDetailGet(clientId, clientSecret, accessToken, goodsCommitId, goodsId string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsCommitDetailGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsCommitDetailGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
goodsCommitIdPtr, _ := syscall.BytePtrFromString(goodsCommitId)
goodsIdPtr, _ := syscall.BytePtrFromString(goodsId)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(goodsCommitIdPtr)),
uintptr(unsafe.Pointer(goodsIdPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddTimeGet 获取拼多多系统时间
func (m *PddDLL) PddTimeGet(clientId, clientSecret, accessToken string) (string, error) {
proc, err := m.Dll.FindProc("PddTimeGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsCommitDetailGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsImageUpload 上传图片
func (m *PddDLL) PddGoodsImageUpload(clientId, clientSecret, accessToken, imgBase64 string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsImageUpload")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsImageUpload: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
imgBase64Ptr, _ := syscall.BytePtrFromString(imgBase64)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(imgBase64Ptr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsListGet 获取店铺商品
func (m *PddDLL) PddGoodsListGet(clientId, clientSecret, accessToken string, params string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsListGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsListGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
paramsPtr, _ := syscall.BytePtrFromString(params)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(paramsPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsSaleStatusSet 设置上下架状态
func (m *PddDLL) PddGoodsSaleStatusSet(clientId, clientSecret, accessToken, params string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsSaleStatusSet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsSaleStatusSet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
paramsPtr, _ := syscall.BytePtrFromString(params)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(paramsPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddDeleteGoodsCommit 删除商品
func (m *PddDLL) PddDeleteGoodsCommit(clientId, clientSecret, accessToken, params string) (string, error) {
proc, err := m.Dll.FindProc("PddDeleteGoodsCommit")
if err != nil {
return "", fmt.Errorf("找不到函数 PddDeleteGoodsCommit: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
paramsPtr, _ := syscall.BytePtrFromString(params)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(paramsPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsQuantityUpdate 更新库存
func (m *PddDLL) PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, params string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsQuantityUpdate")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsQuantityUpdate: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
paramsPtr, _ := syscall.BytePtrFromString(params)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(paramsPtr)),
)
result := cStr(resultPtr)
return result, nil
}
// PddGoodsSkuPriceUpdate 更新价格
func (m *PddDLL) PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, params string) (string, error) {
proc, err := m.Dll.FindProc("PddGoodsSkuPriceUpdate")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsSkuPriceUpdate: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
paramsPtr, _ := syscall.BytePtrFromString(params)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(paramsPtr)),
)
result := cStr(resultPtr)
return result, nil
}

863
planB/modules/pdd/pdd.md Normal file
View File

@ -0,0 +1,863 @@
# pdd.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
```gotemplate
// PddDLL 拼多多工具DLL结构
type pddDLL struct {
dll *syscall.DLL
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
freeCString *syscall.Proc // 释放C字符串
}
// <初始化pddDLL></初始化pddDLL>
func InitPddDLL() (*pddDLL, error) {
dllPath := filepath.Join("dll", "pdd.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
} else {
return &pddDLL{
dll: dll,
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
dll, err := InitPddDLL()
```
### 获取C字符串
```gotemplate
// cStr 获取C字符串
func (m *pddDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
```
## 2. 使用dll函数示例
```gotemplate
// 类目预测
func (m *pddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
proc, err := m.dll.FindProc("PddGoodsOuterCatMappingGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(outerCatIdPtr)),
uintptr(unsafe.Pointer(outerCatNamePtr)),
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
)
result := m.cStr(resultPtr)
return result, nil
}
```
# 接口详情
## 1. 类目预测--PddGoodsOuterCatMappingGet
### 请求信息
```gotemplate
dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| outerCatId | string | 是 | 外部平台类目ID |
| outerCatName | string | 是 | 外部平台类目名称 |
| outerGoodsName | string | 是 | 外部商品名称 |
### 响应示例
```json
{
"outer_cat_mapping_get_response": {
"cat_id2": 16028,
"cat_id3": 16031,
"cat_id1": 15543,
"request_id": "17666480184871649",
"cat_id4": 0
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 2. 快递公司查看--PddLogisticsCompaniesGet
### 请求信息
```gotemplate
dll.PddLogisticsCompaniesGet(clientId, clientSecret)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
### 响应示例
```json
{
"logistics_companies_get_response": {
"logistics_companies": [
{
"available": 1,
"code": "SF",
"id": 1,
"logistics_company": "顺丰速运"
},
{
"available": 1,
"code": "STO",
"id": 2,
"logistics_company": "申通快递"
}
]
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 3. erp打单信息同步--PddErpOrderSync
### 请求信息
```gotemplate
dll.PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
orderSn, orderState, waybillNo)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| logisticsId | string | 是 | 物流公司ID |
| orderSn | string | 是 | 拼多多订单号 |
| orderState | string | 是 | 订单状态 |
| waybillNo | string | 是 | 运单号 |
### 响应示例
```json
{
"erp_order_sync_response": {
"is_success": true,
"request_id": "17666480184871650"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 4. 拼多多订单同步--PddOrderSynchronization
### 请求信息
```gotemplate
dll.PddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsOnlineSendJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| logisticsCompany | string | 是 | 物流公司名称 |
| logisticsOnlineSendJson | string | 是 | 拼多多订单同步json字符串 |
### 响应示例
```json
{
"erp_order_sync_response": {
"is_success": true,
"request_id": "17666480184871651"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 5. 商品图片上传接口--PddGoodsImgUpload
### 请求信息
```gotemplate
dll.PddGoodsImgUpload(clientId, clientSecret, accessToken, filePath)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| filePath | string | 是 | 图片文件路径 |
### 响应示例
```json
{
"goods_img_upload_response": {
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"request_id": "17666480184871652"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 6. 商品新增接口--PddGoodsAdd
### 请求信息
```gotemplate
dll.PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| goodsAddJson | string | 是 | 商品信息JSON字符串 |
#### 商品信息JSON结构示例
```json
{
"goods_name": "测试商品",
"goods_desc": "商品描述",
"cat_id": 20111,
"goods_type": 1,
"market_price": 9900,
"is_folt": false,
"is_pre_sale": false,
"is_refundable": true,
"shipment_limit_second": 86400,
"cost_template_id": 10001,
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"carousel_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"detail_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"sku_list": [
{
"out_sku_sn": "SKU001",
"price": 8900,
"quantity": 100,
"spec_id_list": "1001:10001",
"sku_properties": [
{
"ref_pid": 1001,
"value": "红色",
"vid": 10001,
"punit": "个"
}
],
"is_onsale": 1,
"limit_quantity": 10,
"multi_price": 8500,
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"weight": 500
}
]
}
```
### 响应示例
```json
{
"goods_add_response": {
"goods_id": 123456789,
"goods_name": "测试商品",
"goods_sn": "G202501200001",
"request_id": "17666480184871653"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 7. 联合拼多多图片上传的商品新增--SelfPddGoodsAdd
### 请求信息
```gotemplate
dll.SelfPddGoodsAdd(clientId, clientSecret, accessToken, filePath, goodsAddJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| filePath | string | 是 | 图片文件路径 |
| goodsAddJson | string | 是 | 商品信息JSON字符串不需包含image_url|
#### 接口说明
此接口为组合接口,内部执行以下步骤:
1.上传商品主图文件到拼多多服务器
2.获取图片URL并自动填充到商品信息中
3.调用商品新增接口创建商品
#### 商品信息JSON结构示例
```json
{
"goods_name": "测试商品",
"goods_desc": "商品描述",
"cat_id": 20111,
"goods_type": 1,
"market_price": 9900,
"is_folt": false,
"is_pre_sale": false,
"is_refundable": true,
"shipment_limit_second": 86400,
"cost_template_id": 10001,
"image_url": "",
"carousel_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"detail_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"sku_list": [
{
"out_sku_sn": "SKU001",
"price": 8900,
"quantity": 100,
"spec_id_list": "1001:10001",
"sku_properties": [
{
"ref_pid": 1001,
"value": "红色",
"vid": 10001,
"punit": "个"
}
],
"is_onsale": 1,
"limit_quantity": 10,
"multi_price": 8500,
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"weight": 500
}
]
}
```
### 响应示例
```json
{
"goods_add_response": {
"goods_id": 123456790,
"goods_name": "测试商品",
"goods_sn": "G202501200002",
"request_id": "17666480184871654"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 8. 批量数据解密脱敏接口--PddOpenDecryptMaskBatch
### 请求信息
```gotemplate
dll.PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| reqJson | string | 是 | 信息JSON字符串 |
#### 信息JSON结构示例
```json
[
{
"data_tag": "251229-272441044622514",
"encrypted_data": "~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"
}
]
```
### 响应示例
```json
{
"open_decrypt_mask_batch_response": {
"data_decrypt_list": [
{
"data_tag": "str",
"data_type": 0,
"decrypted_data": "str",
"encrypted_data": "str",
"error_code": 0,
"error_msg": "str"
}
]
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 生成商家自定义的规格--PddGoodsSpecIdGet
### 请求信息
```gotemplate
dll.PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| parentSpecId | string | 是 | 拼多多标准规格ID |
| specName | string | 是 | 商家编辑的规格值,如颜色规格下设置白色属性 |
### 响应参数
```json
{
"goods_spec_id_get_response": {
"parent_spec_id": 0,
"spec_id": 0,
"spec_name": "str"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 修改商品SKU价格--PddGoodsSkuPriceUpdate
### 请求信息
```gotemplate
dll.PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, request)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| request | string | 是 | 价格更新请求JSON字符串 |
#### 请求JSON结构
```json
{
"goods_id": "必填商品id类型为LONG",
"ignore_edit_warn": "非必填是否获取商品发布警告信息默认为忽略类型为BOOLEAN",
"market_price": "非必填参考价单位分类型为LONG",
"market_price_in_yuan": "非必填参考价单位元类型为STRING",
"sku_price_list": [
{
"group_price": "非必填拼团购买价格单位分类型为LONG",
"is_onsale": "非必填sku上架状态0-已下架1-上架中类型为INTEGER",
"single_price": "非必填单独购买价格单位分类型为LONG",
"sku_id": "必填sku标识类型为LONG"
}
],
"sync_goods_operate": "非必填提交后上架状态0:上架,1:保持原样类型为INTEGER",
"two_pieces_discount": "非必填满2件折扣可选范围0-1000表示取消95表示95折设置需先查询规则接口获取实际可填范围类型为INTEGER"
}
```
### 响应参数
```json
{
"goods_update_sku_price_response": {
"goods_commit_id": 0,
"is_success": true
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 商品库存更新接口--PddGoodsQuantityUpdate
### 请求信息
```gotemplate
dll.PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, request)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| request | string | 是 | 库存更新请求JSON字符串 |
#### 请求JSON结构 request 字符串
```json
{
"force_update": "非必填是否强制更新仅update_type=1(全量更新)时有效默认值falseforce_update=false时quantity不能小于预扣库存force_update=true时代表强制更新当quantity<预扣库存时不报错直接将quantity清0类型为BOOLEAN",
"goods_id": "必填商品id类型为LONG",
"outer_id": "非必填sku商家编码类型为STRING",
"quantity": "必填库存修改值。当全量更新库存时quantity必须为大于等于0的正整数当增量更新库存时quantity为整数可小于等于0。若增量更新时传入的库存为负数则负数与实际库存之和不能小于0。比如当前实际库存为1传入增量更新quantity=-1库存改为0类型为LONG",
"sku_id": "非必填sku_id和outer_id必填一个类型为LONG",
"update_type": "非必填库存更新方式可选。1为全量更新2为增量更新。如果不填默认为全量更新类型为INTEGER"
}
```
### 响应参数
```json
{
"goods_quantity_update_response": {
"is_success": false
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 获取商品信息接口 -- OutPddAuthGetCommitDetailt
### 请求信息
```gotemplate
dll.OutPddAuthGetCommitDetailt(goodsCommitId, goodsId, accessToken)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| goodsCommitId | string | 是 | 商品提交ID |
| goodsId | string | 是 | 商品ID |
| accessToken | string | 是 | 授权令牌 |
### 响应参数
```json
```
## 获取商品详情信息接口 -- OutPddAuthGetGoodsDetail
### 请求信息
```gotemplate
dll.OutPddAuthGetGoodsDetail(goodsId, accessToken)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| goodsId | string | 是 | 商品ID |
| accessToken | string | 是 | 授权令牌 |
### 响应参数
```json
{
"bad_fruit_claim": 0,
"buy_limit": 999999,
"carousel_gallery_list": [
"https://img.pddpic.com/open-gw/2025-06-30/59c30d4c-193f-40a3-a639-7af59a381ec5.jpeg",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png",
"https://img.pddpic.com/open-gw/2025-06-30/4539f740-331b-4687-aa00-5c96855de6cd.jpeg",
"https://img.pddpic.com/open-gw/2025-06-30/b0e89e39-c97b-475d-9be2-f1909e30acb5.jpeg"
],
"cat_id": 15678,
"cost_template_id": 655688447565777,
"country_id": 0,
"customer_num": 2,
"customs": "",
"detail_gallery_list": [
"https://img.pddpic.com/open-gw/2025-06-30/b691c104-baf8-42b2-97e2-b7258113114b.jpeg",
"https://img.pddpic.com/open-gw/2023-09-07/53e6f7ff-d15e-4e8f-8625-e293717ca1e4.jpeg",
"https://img.pddpic.com/open-gw/2023-09-07/ecff591d-32a6-42c9-ba5a-6a42829092a8.jpeg",
"https://img.pddpic.com/open-gw/2023-10-16/7034f8a0-5d88-49f8-a96f-608abb8cac80.jpeg",
"https://img.pddpic.com/open-gw/2023-10-16/e10c2b6c-d4de-4fdd-8d48-f0a334735e9a.jpeg",
"https://img.pddpic.com/open-gw/2023-10-16/c19358fb-0a4d-49ad-bcc8-b2980e938064.jpeg",
"https://img.pddpic.com/open-gw/2025-06-30/1deeb9c0-7212-432b-a309-f774db6e1adb.jpeg"
],
"goods_desc": "书名:金属工艺学 下 第6版作者'邓文英,宋力宏主编'ISBN9787040456295出版社高等教育出版社",
"goods_id": 770621582375,
"goods_name": "金属工艺学 下 第6版 邓文英,宋力宏主编 高等教育出版社 978",
"goods_property_list": [
{
"punit": "",
"ref_pid": 425,
"template_pid": 401030,
"vid": 0,
"vvalue": "9787040456295"
},
{
"punit": "",
"ref_pid": 876,
"template_pid": 401029,
"vid": 0,
"vvalue": "金属工艺学 下 第6版"
},
{
"punit": "页",
"ref_pid": 692,
"template_pid": 401032,
"vid": 0,
"vvalue": "157"
},
{
"punit": "元",
"ref_pid": 879,
"template_pid": 401034,
"vid": 0,
"vvalue": "24.70"
},
{
"punit": "",
"ref_pid": 882,
"template_pid": 401037,
"vid": 0,
"vvalue": "邓文英,宋力宏主编"
},
{
"punit": "",
"ref_pid": 880,
"template_pid": 401035,
"vid": 483761,
"vvalue": "高等教育出版社"
},
{
"punit": "",
"ref_pid": 888,
"template_pid": 401043,
"vid": 0,
"vvalue": "平装"
}
],
"goods_type": 1,
"image_url": "",
"invoice_status": 0,
"is_customs": 0,
"is_folt": 0,
"is_group_pre_sale": 0,
"is_pre_sale": 0,
"is_refundable": 1,
"is_sku_pre_sale": 0,
"market_price": 5948,
"order_limit": 999999,
"outer_goods_id": "9787040456295",
"oversea_type": 0,
"pre_sale_time": 0,
"privacy_delivery": 0,
"quan_guo_lian_bao": 0,
"second_hand": 1,
"shipment_limit_second": 172800,
"sku_list": [
{
"is_onsale": 1,
"limit_quantity": 999999,
"multi_price": 1487,
"out_sku_sn": "9787040456295",
"price": 1587,
"quantity": 0,
"reserve_quantity": 0,
"sku_id": 1753931570290,
"sku_pre_sale_time": 0,
"spec": [
{
"parent_id": 1216,
"parent_name": "尺寸",
"spec_id": 27632894279,
"spec_name": "单本 无附赠 超七天不退换"
}
],
"thumb_url": "https://img.pddpic.com/open-gw/2025-06-30/59c30d4c-193f-40a3-a639-7af59a381ec5.jpeg",
"weight": 500
}
],
"status": 4,
"tiny_name": "金属工艺学 下 第6",
"two_pieces_discount": 96,
"video_gallery": [],
"warehouse": "",
"warm_tips": "",
"zhi_huan_bu_xiu": 0
}
```
## 生成自定义规格接口 -- OutPddAuthSetSpec
### 请求信息
```gotemplate
dll.OutPddAuthSetSpec(specTypeId, specName, accessToken)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| specTypeId | int | 是 | 规格类型ID |
| specName | string | 是 | 规格名称 |
| accessToken | string | 是 | 授权令牌 |
### 响应参数
```json
{
"parentSpecId": 3820,
"specName": "全新",
"specId": 1080396526
}
```
## 修改价格接口 -- OutPddAuthUpdatePrice
### 请求信息
```gotemplate
dll.OutPddAuthUpdatePrice(jsonData)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------|
| jsonData | int | 是 | 价格修改信息JSON字符串 |
### 响应参数
```json
[
{
"success": true,
"msg": "操作成功"
},
{
"success": false,
"msg": "操作失败"
}
]
```
## 修改库存接口 -- OutPddAuthUpdateStock
### 请求信息
```gotemplate
dll.OutPddAuthUpdateStock(jsonData)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------|
| jsonData | int | 是 | 价格修改信息JSON字符串 |
### 响应参数
```json
[
{
"success": true,
"msg": "操作成功"
},
{
"success": false,
"msg": "操作失败"
}
]
```
## 12.释放C字符串内存--FreeCString
### 请求信息
```gotemplate
dll.FreeCString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| str | string | 是 | 需要释放的字符串 |

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,25 @@
[app]
AppId = 1228288260261189
AppSecret = aq9gAwrwp6WGZkMRqKIXmnu2c2uCm82k
Domain = https://open.goofish.pro
[http]
Addr = 127.0.0.1:53368
[categoryListRequest]
Path = /api/open/product/category/list
ItemBizType: 2
SpBizType: 24
[batchCreatRequest]
Path = /api/open/product/batchCreate
[file]
TxtPath = modules/xianYu/productCategory.txt
ExcelPath = modules/xianYu/address.xlsx
SheetName = Result
[redis]
Password = Long6166@@
Addr = 127.0.0.1:6379
Db = 5
[tokenBucket]
BucketKeyPrefix = "token_bucket_"
TokenPerSecond = 10
BucketSize = 100
Delay = 100

View File

@ -0,0 +1,284 @@
cbf4e2ec8f2013d31921b9e373cead75电视剧
cbf4e2ec8f2013d3267e0a01017d9f44电影
cbf4e2ec8f2013d36f38848189966e7d生活
cbf4e2ec8f2013d3ac899d2620c5df2b成人教育音像
cbf4e2ec8f2013d3acde29f76907b07f动画
cbf4e2ec8f2013d3e10cfa39bf43dc0f儿童教育音像
d14d229692616168b108d382c4e6ea42废品回收
d816d18aa66dfb3d1921b9e373cead75励志成长
dbaba36adf47af96b108d382c4e6ea42不干胶标签
e59460ef9961e2bd28d88a08a19453dc古典吉他
e59460ef9961e2bda7f7e02f36b0b49a电箱吉他
86cddebb2de0815c1921b9e373cead75桌面文件柜
86cddebb2de0815c267e0a01017d9f44资料册
86cddebb2de0815c6f38848189966e7d镇纸
86cddebb2de0815ca7f7e02f36b0b49a文件袋
86cddebb2de0815cacde29f76907b07f文房墨汁
86cddebb2de0815ce10cfa39bf43dc0f文房四宝套装
879b743300e7a58137b3d33c282f2081古筝
8bd8d9724880b84d28d88a08a19453dc学习笔记
a457d6fc43c609bdac899d2620c5df2b单据收据
a457d6fc43c609bdacde29f76907b07f印台
a9ef3505c7fe4b661921b9e373cead75勾线笔
a9ef3505c7fe4b66a7f7e02f36b0b49a电子阅览器/电纸书
ab78823bfd3c7134b108d382c4e6ea42经济管理
ac69f9982deabde1acde29f76907b07f民谣吉他
ac69f9982deabde1e10cfa39bf43dc0f架子鼓
b12c1c13a8dc3b2b6f38848189966e7dPOP广告纸
b12c1c13a8dc3b2ba7f7e02f36b0b49a修正贴
b12c1c13a8dc3b2bac899d2620c5df2b学生用印
b12c1c13a8dc3b2bacde29f76907b07f名片
b2b61c32fc4c904428d88a08a19453dc背胶证件照
b3b713b29220947237b3d33c282f2081台历
4c49139fe1b6ae4aac899d2620c5df2b童书育儿
4fecb084c468ed626f38848189966e7d黑板
5042edcbd2cc4b94ac899d2620c5df2b生活百科
621bd460d751e0fc37b3d33c282f2081订书机
701ed8603d74ee60b108d382c4e6ea42报纸
722d38201b9c8cba267e0a01017d9f44社科心理
7912befd7e1215d11921b9e373cead75挂历
7dba397e41d08d4937b3d33c282f2081拆信刀
7eb776b01814cc6e1921b9e373cead75教材教辅
22e1d81dc4cf3a25a7f7e02f36b0b49a图书
2dfa3034d88aedcc1921b9e373cead75期刊/杂志
31329c43789fae0437b3d33c282f2081戏曲综艺
31329c43789fae04a7f7e02f36b0b49a音乐唱片/专辑
322a73805c38995f6f38848189966e7d宝珠笔
3cdbae6d47df9251a7f7e02f36b0b49a电子资料
22d3cfff678abab1e10cfa39bf43dc0f握笔器
b7fd03d456abe3011921b9e373cead75活页替芯
b7fd03d456abe301b108d382c4e6ea42索引纸
b7fd03d456abe301e10cfa39bf43dc0f拍纸本
c230ba4ca293f3b528d88a08a19453dc马克笔
c230ba4ca293f3b5a7f7e02f36b0b49a钢笔
c230ba4ca293f3b5ac899d2620c5df2b铅笔
c3c6e8d1d63c0618b108d382c4e6ea42文学/小说
c58d3dbcff05e404acde29f76907b07f笔筒
eac1d67ece5fa9b16f38848189966e7d钢琴
ee8603696d446e931921b9e373cead75电钢琴
06d80b131d7b0b616f38848189966e7d毛笔
0e28c0f1f1e57eb1ac899d2620c5df2b地图
0f75076039b85f74267e0a01017d9f44计算器
0f75076039b85f7428d88a08a19453dc
0f75076039b85f746f38848189966e7d板擦
0f75076039b85f74b108d382c4e6ea42算盘
11c38799bd389b3828d88a08a19453dc漫画书籍
ac69f9982deabde1a7f7e02f36b0b49a上弦器
83f9286d1ea41056ac899d2620c5df2b其他吉他配件
e59460ef9961e2bd1921b9e373cead75变调夹
83f9286d1ea4105637b3d33c282f2081古典吉他弦
e59460ef9961e2bdacde29f76907b07f吉他单块效果器
83f9286d1ea41056267e0a01017d9f44吉他效果器配件
83f9286d1ea41056b108d382c4e6ea42吉他电源
ac69f9982deabde1267e0a01017d9f44吉他综合效果器
e59460ef9961e2bdb108d382c4e6ea42吉他背包琴盒
83f9286d1ea4105628d88a08a19453dc吉他背带
83f9286d1ea410561921b9e373cead75吉他连接线
e59460ef9961e2bd6f38848189966e7d吊架
ac69f9982deabde1ac899d2620c5df2b弦枕
ac69f9982deabde11921b9e373cead75弦柱
e59460ef9961e2bd267e0a01017d9f44拨片
ac69f9982deabde128d88a08a19453dc拾音器
83f9286d1ea41056e10cfa39bf43dc0f曼陀铃弦
83f9286d1ea410566f38848189966e7d民谣吉他弦
ac69f9982deabde137b3d33c282f2081清洁保护品
83f9286d1ea41056a7f7e02f36b0b49a滑棒指套
e59460ef9961e2bde10cfa39bf43dc0f电吉他弦
e59460ef9961e2bdac899d2620c5df2b背带钮
83f9286d1ea41056acde29f76907b07f脚凳
ac69f9982deabde1b108d382c4e6ea42调音器
e59460ef9961e2bd37b3d33c282f2081电吉他
c6d5c9e68467b108ac899d2620c5df2b哑鼓垫
c6d5c9e68467b10837b3d33c282f2081镲片
c6d5c9e68467b10828d88a08a19453dc鼓凳
ac69f9982deabde16f38848189966e7d鼓刷
c6d5c9e68467b108b108d382c4e6ea42鼓架镲架
c6d5c9e68467b108a7f7e02f36b0b49a鼓棒鼓锤
1cac27c660d7b098b108d382c4e6ea42唢呐
1cac27c660d7b098267e0a01017d9f44
f22578f0c6a8eaa5267e0a01017d9f44尺八
f22578f0c6a8eaa51921b9e373cead75巴乌
1cac27c660d7b09828d88a08a19453dc
f22578f0c6a8eaa5acde29f76907b07f笛子
f22578f0c6a8eaa5e10cfa39bf43dc0f管子
1cac27c660d7b0981921b9e373cead75
1cac27c660d7b098a7f7e02f36b0b49a芦笙
1cac27c660d7b09837b3d33c282f2081葫芦丝
f22578f0c6a8eaa56f38848189966e7d葫芦笙
1cac27c660d7b098ac899d2620c5df2b陶笛
879b743300e7a581acde29f76907b07f三弦
1cac27c660d7b098e10cfa39bf43dc0f冬不拉
1cac27c660d7b0986f38848189966e7d古琴
1cac27c660d7b098acde29f76907b07f弹布尔
879b743300e7a581e10cfa39bf43dc0f扬琴
879b743300e7a5816f38848189966e7d月琴
879b743300e7a58128d88a08a19453dc柳琴
879b743300e7a5811921b9e373cead75热瓦普
879b743300e7a581b108d382c4e6ea42琵琶
879b743300e7a581ac899d2620c5df2b秦琴
879b743300e7a581a7f7e02f36b0b49a箜篌
879b743300e7a581267e0a01017d9f44
a2eba09f5b889a7c28d88a08a19453dc中胡
7d61e938542f6790b108d382c4e6ea42二胡
7d61e938542f6790267e0a01017d9f44京二胡
7d61e938542f6790acde29f76907b07f京胡
7d61e938542f679028d88a08a19453dc低音胡
a2eba09f5b889a7c37b3d33c282f2081四胡
a2eba09f5b889a7cb108d382c4e6ea42坠琴
7d61e938542f6790a7f7e02f36b0b49a板胡
a2eba09f5b889a7ca7f7e02f36b0b49a椰胡
7d61e938542f679037b3d33c282f2081艾捷克
7d61e938542f67901921b9e373cead75革胡
7d61e938542f67906f38848189966e7d马头琴
7d61e938542f6790e10cfa39bf43dc0f马骨胡
7d61e938542f6790ac899d2620c5df2b高胡
882b39ff0db2dd0037b3d33c282f2081军镲
00a32e7ff35aaf9e267e0a01017d9f44大钹
00a32e7ff35aaf9ee10cfa39bf43dc0f大铙
00a32e7ff35aaf9eacde29f76907b07f大顶钹
00a32e7ff35aaf9e1921b9e373cead75川钹
00a32e7ff35aaf9e6f38848189966e7d广钹
882b39ff0db2dd00a7f7e02f36b0b49a快板
882b39ff0db2dd0028d88a08a19453dc拍板
00a32e7ff35aaf9eb108d382c4e6ea42梆子
882b39ff0db2dd001921b9e373cead75水镲
882b39ff0db2dd00b108d382c4e6ea42碰钟
882b39ff0db2dd00acde29f76907b07f秧歌镲
882b39ff0db2dd00e10cfa39bf43dc0f腰鼓镲
882b39ff0db2dd00ac899d2620c5df2b萨巴依
882b39ff0db2dd00267e0a01017d9f44铜书板
00a32e7ff35aaf9eac899d2620c5df2b镲锅
0ea61a801ba323c1267e0a01017d9f44堂鼓
00a32e7ff35aaf9e28d88a08a19453dc战鼓
0ea61a801ba323c11921b9e373cead75排鼓
0ea61a801ba323c1b108d382c4e6ea42板鼓
00a32e7ff35aaf9e37b3d33c282f2081秧歌鼓
0ea61a801ba323c1e10cfa39bf43dc0f细腰鼓
00a32e7ff35aaf9ea7f7e02f36b0b49a腰鼓
0ea61a801ba323c1ac899d2620c5df2b花盆鼓
0ea61a801ba323c16f38848189966e7d象脚鼓
0ea61a801ba323c1acde29f76907b07f铜鼓
a7133eb411b587cf1921b9e373cead75空灵鼓/无忧鼓
0ea61a801ba323c1a7f7e02f36b0b49a云锣
a2eba09f5b889a7c267e0a01017d9f44京锣
a2eba09f5b889a7cac899d2620c5df2b低音锣
a2eba09f5b889a7cacde29f76907b07f开道锣
a2eba09f5b889a7ce10cfa39bf43dc0f手锣
0ea61a801ba323c137b3d33c282f2081武锣
0ea61a801ba323c128d88a08a19453dc舟山锣
a2eba09f5b889a7c6f38848189966e7d苏锣
a2eba09f5b889a7c1921b9e373cead75虎音锣
33a0daa5d89d68fa1921b9e373cead75宣纸
b12c1c13a8dc3b2b1921b9e373cead75吊牌
b12c1c13a8dc3b2be10cfa39bf43dc0f自封袋
b12c1c13a8dc3b2b267e0a01017d9f44贺卡明信片
22d3cfff678abab11921b9e373cead75书皮
b12c1c13a8dc3b2b37b3d33c282f2081修正带
b12c1c13a8dc3b2b28d88a08a19453dc修正液
22d3cfff678abab1a7f7e02f36b0b49a削笔器
22d3cfff678abab128d88a08a19453dc可爱印泥
b12c1c13a8dc3b2bb108d382c4e6ea42学生书包
22d3cfff678abab1acde29f76907b07f文具套装
22d3cfff678abab1267e0a01017d9f44文具盒
22d3cfff678abab16f38848189966e7d橡皮
22d3cfff678abab1b108d382c4e6ea42练字帖
22d3cfff678abab1ac899d2620c5df2b视力保护器
dbaba36adf47af9637b3d33c282f2081笔袋
54e552aa1c9b2cbcacde29f76907b07f彩泥橡皮泥
bf164bd2e8dd8cebb108d382c4e6ea42便条照片夹
bf164bd2e8dd8ceb28d88a08a19453dc便签盒座
bf164bd2e8dd8cebe10cfa39bf43dc0f卡套证件套
bf164bd2e8dd8ceb6f38848189966e7d名片册
86cddebb2de0815c37b3d33c282f2081名片盒
bf164bd2e8dd8cebacde29f76907b07f快劳夹
86cddebb2de0815c28d88a08a19453dc文件夹
86cddebb2de0815cb108d382c4e6ea42文件架
bf164bd2e8dd8ceb1921b9e373cead75档案盒
bf164bd2e8dd8cebac899d2620c5df2b档案袋
86cddebb2de0815cac899d2620c5df2b相册
1ad9ac4511bbb8646f38848189966e7d笔插
bf164bd2e8dd8ceba7f7e02f36b0b49a笔架
bf164bd2e8dd8ceb267e0a01017d9f44风琴包
d665d5e1347fa192a7f7e02f36b0b49a地球仪
bf164bd2e8dd8ceb37b3d33c282f2081展板
d665d5e1347fa192267e0a01017d9f44教学仪器器材
d665d5e1347fa1921921b9e373cead75教鞭
bb9bba251ee78e59267e0a01017d9f44旗帜
d665d5e1347fa19237b3d33c282f2081提示牌
d665d5e1347fa192b108d382c4e6ea42激光笔
0f75076039b85f74acde29f76907b07f白板
0f75076039b85f74e10cfa39bf43dc0f白板笔
d665d5e1347fa19228d88a08a19453dc粉笔
d665d5e1347fa192acde29f76907b07f绿板
d665d5e1347fa1926f38848189966e7d荧光板
d665d5e1347fa192ac899d2620c5df2b计划表
d665d5e1347fa192e10cfa39bf43dc0f软木板
a457d6fc43c609bda7f7e02f36b0b49a中性笔
c230ba4ca293f3b5e10cfa39bf43dc0f圆珠笔
c230ba4ca293f3b51921b9e373cead75铅芯
f9910185f1984f2937b3d33c282f2081正姿笔
c230ba4ca293f3b5acde29f76907b07f油漆笔
c230ba4ca293f3b5b108d382c4e6ea42泡泡笔
c230ba4ca293f3b537b3d33c282f2081墨水墨囊
c230ba4ca293f3b5267e0a01017d9f44荧光笔
f4a071d4dba28eccac899d2620c5df2b记号笔
c230ba4ca293f3b56f38848189966e7d针管笔
dfdbd3409fadcd3f6f38848189966e7d其他笔
58e84885c426409e267e0a01017d9f44书签
b7fd03d456abe30128d88a08a19453dc便签
e9fa1ad466b79d97b108d382c4e6ea42信封
af2cf5b1faa3537a1921b9e373cead75信纸
b7fd03d456abe30137b3d33c282f2081包装纸
e9fa1ad466b79d97a7f7e02f36b0b49a纪念册
b7fd03d456abe301ac899d2620c5df2b复写纸
b7fd03d456abe301267e0a01017d9f44奖状证书
e9fa1ad466b79d971921b9e373cead75手工纸
e9fa1ad466b79d9728d88a08a19453dc草稿纸
b7fd03d456abe3016f38848189966e7d日记本
e9fa1ad466b79d97ac899d2620c5df2b硬面抄
b7fd03d456abe301a7f7e02f36b0b49a记事本
b7fd03d456abe301acde29f76907b07f课业本
e9fa1ad466b79d9737b3d33c282f2081通讯录
dbaba36adf47af966f38848189966e7d磁性贴
6c0543ec11db7e61267e0a01017d9f44贴纸/标签
0f75076039b85f741921b9e373cead75圆规
0f75076039b85f74ac899d2620c5df2b显微镜
1c75d8021bacf61e267e0a01017d9f44放大镜
a9ef3505c7fe4b66b108d382c4e6ea42丙烯颜料
823f8d7bd96780d0ac899d2620c5df2b书法用纸
a9ef3505c7fe4b66ac899d2620c5df2b儿童填色本
a9ef3505c7fe4b66267e0a01017d9f44国画颜料
823f8d7bd96780d037b3d33c282f2081描图硫酸纸
5fd3299edc3ff44a37b3d33c282f2081毛边纸
823f8d7bd96780d01921b9e373cead75水彩笔
823f8d7bd96780d0267e0a01017d9f44水彩颜料
823f8d7bd96780d0acde29f76907b07f水粉水彩油画笔
823f8d7bd96780d0e10cfa39bf43dc0f水粉颜料
0f75076039b85f7437b3d33c282f2081油画棒
0f75076039b85f74a7f7e02f36b0b49a油画颜料
a9ef3505c7fe4b66acde29f76907b07f画板画架
823f8d7bd96780d0b108d382c4e6ea42石膏像
823f8d7bd96780d06f38848189966e7d素描本
a9ef3505c7fe4b66e10cfa39bf43dc0f绘图纸
823f8d7bd96780d028d88a08a19453dc色卡
a9ef3505c7fe4b666f38848189966e7d蜡笔
823f8d7bd96780d0a7f7e02f36b0b49a铅画纸
7dba397e41d08d49a7f7e02f36b0b49a裁剪刀片
7dba397e41d08d49b108d382c4e6ea42雕刻垫板
7dba397e41d08d49ac899d2620c5df2b切纸刀
7dba397e41d08d4928d88a08a19453dc美工刀
356e5d8126d3aefaa7f7e02f36b0b49a裁剪剪刀
e9fa1ad466b79d97267e0a01017d9f44回形针
621bd460d751e0fca7f7e02f36b0b49a回形针盒
621bd460d751e0fcb108d382c4e6ea42图钉工字钉
e9fa1ad466b79d97e10cfa39bf43dc0f大头针
e9fa1ad466b79d97acde29f76907b07f打孔机
621bd460d751e0fc28d88a08a19453dc票夹长尾夹
e9fa1ad466b79d976f38848189966e7d订书钉
a457d6fc43c609bd1921b9e373cead75凭证
a457d6fc43c609bde10cfa39bf43dc0f印油印泥
a457d6fc43c609bd28d88a08a19453dc报表
a457d6fc43c609bd267e0a01017d9f44湿手器
a457d6fc43c609bdb108d382c4e6ea42财务证明用品
a457d6fc43c609bd6f38848189966e7d账本账册
740736cf215b7509a7f7e02f36b0b49a电子壁纸

View File

@ -0,0 +1,195 @@
package xianYu
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
var (
gXianYuDll *XianYuDLL
)
// XianYuDLL 闲鱼工具DLL结构
type XianYuDLL struct {
Dll *syscall.DLL
freeCString *syscall.Proc // 释放C字符串
}
// InitXianYuDll 初始化 XianYuDLL
func InitXianYuDll(url string) (*XianYuDLL, error) {
if gXianYuDll != nil {
return gXianYuDll, nil
}
dllPath := filepath.Join(url, "xy.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("XianYu DLL 不存在: %s", dllPath)
}
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载XianYu DLL 失败: %s", err)
}
gXianYuDll = &XianYuDLL{
Dll: dll,
freeCString: dll.MustFindProc("FreeCString"),
}
return gXianYuDll, nil
}
// XianYuGoodsAdd 商品新增
func (m *XianYuDLL) XianYuGoodsAdd(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsCreat")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsCreat: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuGoodsAddCheckIsbn 商品新增检查ISBN
func (m *XianYuDLL) XianYuGoodsAddNew(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsCreatNew")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsCreatNew: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuLaunchGoods 商品上架
func (m *XianYuDLL) XianYuLaunchGoods(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsPublish")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsPublish: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuGetGoodsList 拉取商品列表
func (m *XianYuDLL) XianYuGetGoodsList(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteSelectGoodsListPrice")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteSelectGoodsListPrice: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuGetGoodsDetail 拉取商品详情
func (m *XianYuDLL) XianYuGetGoodsDetail(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGetGoodsDetail")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGetGoodsDetail: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuExecuteGoodsDownShelf 下架商品
func (m *XianYuDLL) XianYuExecuteGoodsDownShelf(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsDownShelf")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsDownShelf: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuExecuteGoodsUpdateStock 修改库存
func (m *XianYuDLL) XianYuExecuteGoodsUpdateStock(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsEditStock")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsEditStock: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// XianYuExecuteGoodsUpdatePrice 修改价格
func (m *XianYuDLL) XianYuExecuteGoodsUpdatePrice(bodyJson string, configFile string) (string, error) {
proc, err := m.Dll.FindProc("ExecuteGoodsEditPrice")
if err != nil {
return "", fmt.Errorf("找不到函数 ExecuteGoodsEditPrice: %v", err)
}
bodyJsonPtr, _ := syscall.BytePtrFromString(bodyJson)
configFile = configFile + "\\config.ini"
configFilePtr, _ := syscall.BytePtrFromString(configFile)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(bodyJsonPtr)),
uintptr(unsafe.Pointer(configFilePtr)),
)
result := cStr(resultPtr)
return result, nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}

BIN
planB/modules/xianYu/xy.dll Normal file

Binary file not shown.

View File

@ -0,0 +1,239 @@
##### FreeCString(str *C.char)
接收其他函数返回值之后,释放内存,参考示例
##### 内存释放示例
```go
func example () {
// ...其他逻辑
var res = StartServer (configFile *C.char)
FreeCString(res) //释放内存
}
```
##### StartServer (configFile *C.char)
启动http服务器参数配置文件路径不提供默认使用工程根目录config.ini
返回C字符串启动消息接收后使用FreeCString进行内存释放
##### StopServer
停止HTTP服务器
返回C字符串停止消息接收后使用FreeCString进行内存释放
##### GetServerStatus
获取服务器当前状态
返回C字符串指针消息running/stopped接收后使用FreeCString进行内存释放
##### GetServerAddress
获取服务器监听地址
返回C字符串指针服务器地址消息未运行返回空串接收后使用FreeCString进行内存释放
##### ReloadConfig(configFile *C.char)
重新加载配置文件参数配置文件路径不提供默认使用根目录config.ini
返回C字符串加载结果消息接收后使用FreeCString进行内存释放
### 以下都需要传递appid和appSecret ###
##### ExecuteGoodsCreat(bodyJson *C.char, configFile *C.char)
*管道通信直接调用此函数*
执行商品创建操作,参数商品信息,参考示例
返回C字符串指针创建商品结果信息接收后使用FreeCString进行内存释放
##### 商品信息参考示例
```json
{
"appId": 1228288260261189,
"appSecret": "aq9gAwrwp6WGZkMRqKIXmnu2c2uCm82k",
"token": "",
"apiShopId": 0,
"typePlatform": 4,
"shopId": 0,
"shopToken": "",
"shopName": "",
"province": 210000,
"city": 210100,
"district": 210101,
"typeClass": "",
"typeGoods": "",
"catIds": "d14d229692616168b108d382c4e6ea42",
"shop": [
{
"userName": "xy938400231518",
"province": 210000,
"city": 210100,
"district": 210101,
"title": "牧羊少年奇幻之旅",
"content": "牧羊少年奇幻之旅",
"mainImgs": ["https://img.cdn1.vip/i/68cf5cb4e5840_1758420148.webp"],
"contentImgs": []
}
],
"stuffStatus": 90,
"bookData": [
{
"ISBN": "9787530217054",
"Title": "牧羊少年奇幻之旅",
"Author": "保罗·柯艾略",
"Publisher": "北京十月文艺出版",
"itemBizType": 2,
"spBizType": 24,
"prices": [199999, 299999],
"stock": 100,
"catIds": "22e1d81dc4cf3a25a7f7e02f36b0b49a"
}
],
"itemKey": "itemAAAAA1111"
}
```
##### ExecuteGoodsPublish(bodyJson *C.char, configFile *C.char)
*管道通信直接调用此函数*
执行商品上架操作,参数上架信息,参考示例
返回C字符串指针行商品上架结果信息接收后使用FreeCString进行内存释放
##### 上架信息参考示例
```json
{
"product_id": 1250927879325125,
"user_name": ["xy938400231518"],
"specify_publish_time": "",
"notify_url": ""
}
```
#### 追加下架,改价,擦亮 ####
##### ExecuteGoodsDownShelf(bodyJson *C.char, configFile *C.char) ######
*管道通信直接调用此函数*
执行商品下架操作参数管家商品ID参考示例
返回C字符串指针行商品下架结果信息接收后使用FreeCString进行内存释放
##### 下架信息参考示例 #####
```json
{
"product_id": 1250927879325125
}
```
##### ExecuteGoodsFlash(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
执行商品擦亮操作参数管家商品ID参考示例
返回C字符串指针行商品擦亮结果信息接收后使用FreeCString进行内存释放
##### 擦亮信息参考示例 #####
```json
{
"product_id": 1250927879325125
}
```
##### ExecuteGoodsEditPrice(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
执行商品改价操作参数管家商品ID参考示例
返回C字符串指针行商品改价结果信息接收后使用FreeCString进行内存释放
##### 改价信息参考示例(单位:分) #####
```json
{
"product_id": 1250927879325125,
"price": 550000,
"originalPrice": 770000
}
```
##### ExecuteGoodsEditStock(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
执行商品改库存操作参数管家商品ID参考示例
返回C字符串指针行商品改价结果信息接收后使用FreeCString进行内存释放
##### 改库存信息参考示例(单位:分) #####
```json
{
"product_id": 1250927879325125,
"stock": 10
}
```
##### ExecuteSelectGoodsListPrice(bodyJson *C.char, configFile *C.char) #####
*管道通信直接调用此函数*
查询店铺列表操作参数管家商品ID参考示例
返回C字符串指针行商品改价结果信息接收后使用FreeCString进行内存释放
##### 查询参考示例(单位:分) #####
```json
{
//online_time 字段可传空
"online_time": [
1690300800,
1690366883
],
"product_status": 22
}
```

29
planB/planB.md Normal file
View File

@ -0,0 +1,29 @@
# Plan B
## 目录结构
```gotemplate
dispatcher 具体执行的平台操作(工厂模式,发布商品、上下架等)
|-kongfuzi 孔夫子
|-pinduoduo 拼多多
|-xianyu 闲鱼
initialization 初始化
|-config 初始化配置文件
|-golabl 初始化全局变量
|-platform 初始化任务平台(拼多多、闲鱼等)
|-pool 初始化协程池
|-redis 初始化redis
|-speed 初始化限速器
|-task 初始化任务获取header与footer
|-taskType 初始化任务类型(发布商品、上下架等)
|-init.go 初始化文件
interfaces 工厂模式接口
logic 逻辑执行
modules DLL模块
service 服务(针对数据库相关操作)
tool 工具
type 结构体
|-pinduoduo 拼多多结构体
|-xianyu 闲鱼结构体
validation 验证器
```

Some files were not shown because too many files have changed in this diff Show More