daShangDao_kfzgw-info/kongfz/kongfz.go
2026-02-27 11:46:40 +08:00

3789 lines
120 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

package main
/*
#include <stdlib.h>
*/
import "C"
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unsafe"
"github.com/PuerkitoBio/goquery"
_ "github.com/go-sql-driver/mysql"
"github.com/parnurzeal/gorequest"
)
// Config 结构体定义应用程序配置
type Config struct {
App struct {
MaxRetryTimes int `ini:"app.max_retry_times" json:"max_retry_times" default:"3"` // 最大重试次数
RateLimitDelay time.Duration `ini:"app.rate_limit_delay" json:"rate_limit_delay" default:"500ms"` // 请求延迟
Size int `ini:"app.size" json:"size" default:"5"` // 默认大小
DefaultUserAgent string `ini:"app.default_user_agent" json:"default_user_agent" default:"Mozilla/5.0"` // 默认 User-Agent
} `ini:"app" json:"app"`
API struct {
LoginURL string `ini:"api.login_url" json:"login_url" default:"https://login.kongfz.com/Pc/Login/account"` // 登录 URL
BookSearchURL string `ini:"api.book_search_url" json:"book_search_url" default:"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list"` // 图书搜索 URL
ProductSearchURL string `ini:"api.product_search_url" json:"product_search_url" default:"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list"` // 商品搜索 URL
} `ini:"api" json:"api"`
Proxy struct {
Servers string `ini:"proxy.servers" json:"servers" default:"http-dynamic.xiaoxiangdaili.com,http-dynamic-S02.xiaoxiangdaili.com,http-dynamic-S03.xiaoxiangdaili.com,http-dynamic-S04.xiaoxiangdaili.com"` // 代理服务器列表
Username string `ini:"proxy.username" json:"username" default:"1297757178467602432"` // 代理用户名
Password string `ini:"proxy.password" json:"password" default:"QgQBvP7f"` // 代理密码
TailMachineCode string `ini:"proxy.tail_machine_code" json:"tail_machine_code" default:"b7bf22a237ec692f13fcc2c43ee63252"` // 尾机编码
TailCardKey string `ini:"proxy.tail_card_key" json:"tail_card_key" default:"DL_20_YK_1920acb2129844c2aabade3896560a9b"` // 尾卡密钥
ProxyFilePath string `ini:"proxy.proxy_file_path" json:"proxy_file_path" default:"dll/proxyConfig.dll"` // 代理配置文件路径
} `ini:"proxy" json:"proxy"`
}
// BookInfo 图书详情结构体
type BookInfo struct {
BookName string `json:"book_name"` // 书名
Author string `json:"author"` // 作者
Publisher string `json:"publisher"` // 出版社
ISBN string `json:"isbn"` // ISBN
PublicationTime int64 `json:"publication_time"` // 出版时间
Edition string `json:"edition"` // 版次
PrintTime string `json:"print_time"` // 印刷时间
FixPrice string `json:"fix_price"` // 定价
BindingLayout string `json:"binding_layout"` // 装帧
Format string `json:"format"` // 开本
Paper string `json:"paper"` // 纸张
Pages string `json:"pages"` // 页数
Wordage string `json:"wordage"` // 字数
Languages string `json:"languages"` // 语种
Era string `json:"era"` // 年代
EngravingMethod string `json:"engraving_method"` // 刻印方式
Dimensions string `json:"dimensions"` // 尺寸
VolumeNumber string `json:"volume_number"` // 册数
BookPic string `json:"book_pic"` // 图书封面图(官图)
BookPicS string `json:"book_pic_s"` // 图书封面图(实拍图)
SellingPrice string `json:"selling_price"` // 售价
Condition string `json:"condition"` // 品相
ExpressDeliveryFee string `json:"express_delivery_fee"` // 快递费
Editor string `json:"editor"` // 编辑
Category string `json:"category"` // 分类
BuyCount string `json:"buy_count"` // 买过
SellCount string `json:"sell_count"` // 在卖
Content string `json:"content"` // 内容简介
AuthorIntroduction string `json:"author_introduction"` // 作者简介
Catalogue string `json:"catalogue"` // 目录
Mid int64 `json:"mid"` // 商家id
ItemId int64 `json:"item_id"` // 商品id
ShopId int64 `json:"shop_id"` // 店铺id
DetailUrl string `json:"detail_url"` // 商品详情url
}
// APIResponse 通用API响应结构体
type APIResponse struct {
Success bool `json:"success"` // 请求是否成功
Message string `json:"message,omitempty"` // 错误信息(成功时可选)
Data interface{} `json:"data,omitempty"` // 响应数据(失败时可选)
}
// UserInfo 孔网用户信息结构体
type UserInfo struct {
UserID int64 `json:"userId"` // 用户ID
Nickname string `json:"nickname"` // 用户昵称
Mobile string `json:"mobile"` // 手机号
}
// 全局配置变量
var (
cf Config // 存储应用程序配置
)
// ProductInfo 商品信息结构体
type ProductInfo struct {
ItemID string `json:"itemId"` // 商品ID
BookName string `json:"bookName"` // 书名
Price string `json:"price"` // 价格
ShippingFee string `json:"shippingFee"` // 运费
}
// BookDetailResponse 图书详情响应结构体
type BookDetailResponse struct {
Status bool `json:"status"` // 状态
Result BookList `json:"result"` // 结果数据
ErrMessage string `json:"errMessage"` // 错误信息
ErrCode int `json:"errCode"` // 错误码
}
// BookList 图书列表结构体
type BookList struct {
Current int `json:"current"` // 当前页码
Data []BookInformation `json:"data"` // 图书数据列表
Total int `json:"total"` // 总数
}
// BookInformation 图书信息结构体
type BookInformation struct {
Author string `json:"author"` // 作者
BookName string `json:"bookName"` // 书名
ContentIntroduction string `json:"contentIntroduction"` // 内容简介
ImgUrl string `json:"imgUrl"` // 图片URL
Isbn string `json:"isbn"` // ISBN
ItemUrls ItemUrls `json:"itemUrls"` // 商品URL
Mid int `json:"mid"` // 商家ID
NewMinPrice string `json:"newMinPrice"` // 新书最低价
OldMinPrice string `json:"oldMinPrice"` // 旧书最低价
Press string `json:"press"` // 出版社
Price string `json:"price"` // 价格
PubDate string `json:"pubDate"` // 出版日期
RiseTag string `json:"riseTag"` // 上涨标签
AuthorArr []AuthorInfo `json:"authorArr"` // 作者数组
PressUrl string `json:"pressUrl"` // 出版社URL
}
// AuthorInfo 作者信息结构体
type AuthorInfo struct {
Name string `json:"name"` // 作者姓名
OriName string `json:"oriName"` // 原始姓名
Nationality string `json:"nationality"` // 国籍
Role string `json:"role"` // 角色
Url string `json:"url"` // 作者页面URL
}
// ItemUrls 商品链接结构体
type ItemUrls struct {
AppUrl string `json:"appUrl"` // App链接
MUrl string `json:"mUrl"` // 移动端链接
MiniUrl string `json:"miniUrl"` // 小程序链接
PcUrl string `json:"pcUrl"` // PC端链接
}
// ParamsInfo 店铺快递费参数结构体
type ParamsInfo struct {
Params []Param `json:"params"` // 参数列表
Area string `json:"area"` // 地区编码
}
// Param 单个参数结构体
type Param struct {
UserId string `json:"userId"` // 用户ID
ItemId string `json:"itemId"` // 商品ID
}
// DataItem 快递费用响应数据项
type DataItem struct {
Fee []FeeInfo `json:"fee"` // 费用列表
IsSeller bool `json:"isSeller"` // 是否是卖家
ItemID string `json:"itemId"` // 商品ID
UserID string `json:"userId"` // 用户ID
}
// FeeInfo 费用信息结构体
type FeeInfo struct {
ShippingID string `json:"shippingId"` // 配送方式ID
ShippingName string `json:"shippingName"` // 配送方式名称
TotalFee string `json:"totalFee"` // 总费用
ShippingText string `json:"shippingText"` // 配送说明
FilterTotalFee string `json:"filterTotalFee"` // 过滤后的总费用
}
// ResponseStruct 快递费响应结构体
type ResponseStruct struct {
Status bool `json:"status"` // 状态
Data []DataItem `json:"data"` // 数据
Message string `json:"message"` // 消息
ErrType string `json:"errType"` // 错误类型
}
/*
* 获取店铺里商品的快递费(定位到河南)
* param params[ParamsInfo] 店铺快递费参数结构体
* param proxy[string] 代理服务器IP
* return 快递费用响应数据项数组,错误信息
* Error 没有商品信息
* Error 参数序列化失败
* Error 请求失败
* Error HTTP错误
* Error 解析JSON失败
*/
func getGoodsListShippingFee(params ParamsInfo, proxy string) ([]DataItem, error) {
// 检查参数是否为空
if params.Params == nil {
return nil, fmt.Errorf("没有商品信息!")
}
// 序列化参数为JSON
paramsJSON, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("参数序列化失败: %v", err)
}
// URL 编码参数
encodedParams := url.QueryEscape(string(paramsJSON))
// 构建请求URL
url := fmt.Sprintf("https://shop.kongfz.com/book/shopsearch/getShippingFee?params=%s", encodedParams)
// 创建请求对象
request := gorequest.New()
// 设置代理
if proxy != "" {
request.Proxy(proxy)
}
// 发送GET请求
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
End()
// 错误处理
if len(errs) > 0 {
return nil, fmt.Errorf("请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析JSON响应
responseStruct := ResponseStruct{}
err = json.Unmarshal([]byte(body), &responseStruct)
if err != nil {
return nil, fmt.Errorf("解析JSON失败: %v", err)
}
// 提取数据项
var dataItem []DataItem
for i := range responseStruct.Data {
dataItem = append(dataItem, responseStruct.Data[i])
}
return dataItem, nil
}
/*
* 孔网登录
* param username[string] 孔网用户名
* param password[string] 孔网密码
* return token,错误信息
* Error 登录请求失败
* Error 登录失败(HTTP状态码: %d)
* Error 登录成功但未获取到Cookie
* Error 登录失败: 未找到 PHPSESSID
* Error 账号或密码错误
* Error 登录失败
* Error 登录失败,未知错误!
*/
func outLogin(username, password string) (string, error) {
// 检查用户名和密码是否为空
if username == "" || password == "" {
return "", fmt.Errorf("请输入用户名和密码!")
}
// 准备POST请求的表单数据
formData := map[string]string{
"loginName": username,
"loginPass": password,
"returnUrl": "http://user.kongfz.com/",
}
// 孔网登录URL
loginUrl := "https://login.kongfz.com/Pc/Login/account"
// 发送登录请求
resp, body, errs := gorequest.New().
Post(loginUrl).
Set("Content-Type", "application/x-www-form-urlencoded").
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36").
Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8").
Send(formData).
Timeout(15 * time.Second).
End()
// 请求错误处理
if len(errs) > 0 {
return "", fmt.Errorf("登录请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("登录失败(HTTP状态码: %d)", resp.StatusCode)
}
// 提取Cookie
cookie := resp.Header.Get("Set-Cookie")
// 检查是否登录成功(通过响应内容判断)
if strings.Contains(body, "window.location.href='https://login.kongfz.cn/Pc/Session/rsync") {
if cookie == "" {
return "", fmt.Errorf("登录成功但未获取到Cookie")
}
// 登录成功
if strings.Contains(cookie, "PHPSESSID=") {
token := strings.Split(strings.Split(cookie, "PHPSESSID=")[1], ";")[0]
return token, nil
}
return "", fmt.Errorf("登录失败: 未找到PHPSESSID")
}
// 错误信息
var res struct {
Status bool `json:"status"`
ErrCode int `json:"errCode"`
ErrInfo string `json:"errInfo"`
}
// 解析json
if err := json.Unmarshal([]byte(body), &res); err == nil {
if res.ErrCode == 1001 || res.ErrCode == 1005 {
return "", fmt.Errorf("账号或密码错误!")
}
if res.ErrInfo != "" {
return "", fmt.Errorf("登录失败: %s", res.ErrInfo)
}
}
return "", fmt.Errorf("登录失败,未知错误!")
}
/*
* 获取孔网用户信息
* param token[string] 孔网token
* return 孔网用户信息结构体,错误信息
* Error 查询请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error 获取用户失败
*/
func outGetUserMsg(token string) (*UserInfo, error) {
// 用户信息URL
url := "https://user.kongfz.com/User/Index/getUserInfo/"
// 发送请求
resp, body, errs := gorequest.New().
Get(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(15 * time.Second).
End()
if len(errs) > 0 {
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
//检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 响应数据
var userInfo struct {
Status bool `json:"status"`
Data struct {
UserID int64 `json:"userId"`
Nickname string `json:"nickname"`
Mobile string `json:"mobile"`
}
}
// 解析json
if err := json.Unmarshal([]byte(body), &userInfo); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 创建用户信息对象
user := &UserInfo{}
if !userInfo.Status {
return nil, fmt.Errorf("获取用户失败!")
}
user.UserID = userInfo.Data.UserID
user.Nickname = userInfo.Data.Nickname
user.Mobile = userInfo.Data.Mobile
return user, nil
}
/*
* 获取商品模版-已登的店铺
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param itemId[string] 商品ID
* return 商品模板信息结构体,错误信息
* Error 请先登录获取Token
* Error 代理连接失败
* Error 查询请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error API返回错误
*/
func outGetGoodsTplMsg(token, proxy, itemId string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 商品模板URL
url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
itemId, time.Now().Unix())
// 创建请求
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
resp, body, errs := request.Get(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://seller.kongfz.com/").
Set("Origin", "https://seller.kongfz.com").
Timeout(15 * time.Second).
End()
// 错误处理
if errs != nil {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败")
}
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析json
var data map[string]interface{}
if err := json.Unmarshal([]byte(body), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 检查status状态
if val, ok := data["status"].(float64); ok && val == 1 {
return data, nil
}
return nil, fmt.Errorf("API返回错误: %+v", data)
}
/*
* 获取商品列表-已登的店铺
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param itemSn[string] 货号
* param priceMin[string] 价格区间-低
* param priceMax[string] 价格区间-高
* param startCreateTime[string] 开始时间
* param endCreateTime[string] 结束时间
* param requestType[string] 请求类型
* param isItemSnEqual[int] 商品ID
* param page[int] 页数
* param size[int] 大小
* return 商品列表响应信息结构体,错误信息
* Error 请先登录获取Token
* Error 代理连接失败
* Error 查询请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error API返回错误
*/
func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string,
startCreateTime string, endCreateTime string, requestType string,
isItemSnEqual int, page int, size int) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 已登店铺的商品信息URL
url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/unSold/list"
// 请求参数
formData := struct {
ItemSn string `json:"itemSn"`
PriceMin string `json:"priceMin"`
PriceMax string `json:"priceMax"`
StartCreateTime string `json:"startCreateTime"`
EndCreateTime string `json:"endCreateTime"`
RequestType string `json:"requestType,omitempty"`
IsItemSnEqual int `json:"isItemSnEqual,omitempty"`
Page int `json:"page,omitempty"`
Size int `json:"size,omitempty"`
}{
ItemSn: itemSn,
PriceMin: priceMin,
PriceMax: priceMax,
StartCreateTime: startCreateTime,
EndCreateTime: endCreateTime,
RequestType: requestType,
IsItemSnEqual: isItemSnEqual,
Page: page,
Size: size,
}
// 创建请求
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
// 发送POST请求
resp, body, errs := request.Post(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Send(formData).
Timeout(15 * time.Second).
End()
if errs != nil {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败")
}
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
//检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析json
var data map[string]interface{}
if err := json.Unmarshal([]byte(body), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
if _, ok := data["status"]; ok {
return data, nil
}
return nil, fmt.Errorf("API返回错误: %+v", data)
}
/*
* 新增商品-已登的店铺
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param formData[string] 商品信息结构体字符串
* return 新增商品响应信息结构体,错误信息
* Error 请先登录获取Token
* Error 代理连接失败
* Error 查询请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error API返回错误
*/
func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 新增商品的URL
url := "https://seller.kongfz.com/pc/itemInfo/add"
var repData map[string]interface{}
err := json.Unmarshal([]byte(formData), &repData)
if err != nil {
return nil, fmt.Errorf("JSON解析失败: %v", err)
}
//// 创建 multipart 请求体
//body := &bytes.Buffer{}
//writer := multipart.NewWriter(body)
//
//// 写入所有表单字段
//for key, value := range repData {
// if err := writer.WriteField(key, value); err != nil {
// return nil, fmt.Errorf("写入表单字段失败: %w", err)
// }
//}
//
//// 关闭writer以完成multipart写入
//if err := writer.Close(); err != nil {
// return nil, fmt.Errorf("关闭multipart写入器失败: %w", err)
//}
// 创建请求
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
resp, respBody, errs := request.Post(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
//Set("Content-Type", writer.FormDataContentType()).
Type("multipart").
Send(repData).
Timeout(15 * time.Second).
End()
if errs != nil {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败: %s", errs)
}
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
//检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析JSON响应
var data map[string]interface{}
if err := json.Unmarshal([]byte(respBody), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 检查响应状态
if val, ok := data["status"]; ok && fmt.Sprint(val) == "1" {
return data, nil
}
return nil, fmt.Errorf("API返回错误: %+v", data)
}
/*
* 获取图片key
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* return 获取图片key响应信息结构体,错误信息
*/
func outKfzimgKey(token, proxy string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 新增商品的URL
url := fmt.Sprintf("https://seller.kongfz.com/kfzimg-key?type=%s", "book")
// 创建请求
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
resp, body, errs := request.Get(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Timeout(15 * time.Second).
End()
if errs != nil {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败: %s", errs)
}
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
//检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 去除括号
jsonStr := strings.TrimPrefix(body, "(")
jsonStr = strings.TrimSuffix(jsonStr, ")")
// 解析JSON响应
var data map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 检查响应状态
if val, ok := data["status"]; ok && val == "success" {
return data, nil
}
return nil, fmt.Errorf("API返回错误: %+v", data)
}
/*
* 获取孔网图片
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* return 获取孔网图片响应信息结构体,错误信息
*/
func outKfzimgUpload(token, proxy, filePath string) (string, error) {
url := fmt.Sprint("https://seller.kongfz.com/kfzimg-upload")
fileName := filepath.Base(filePath)
// 创建multipart/form-data请求体
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 读取文件内容
fileData, err := os.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("读取文件失败: %v", err)
}
// 添加文件字段
part, err := writer.CreateFormFile("img", filepath.Base(filePath))
if err != nil {
return "", fmt.Errorf("创建表单文件失败: %v", err)
}
_, err = part.Write(fileData)
if err != nil {
return "", fmt.Errorf("写入文件数据失败: %v", err)
}
_ = writer.WriteField("autorotated", "1")
_ = writer.WriteField("name", fileName)
_ = writer.Close()
key, err := outKfzimgKey(token, proxy)
if err != nil {
return "", err
}
imageKey := key["imagekey"]
// 在发送请求前添加
log.Printf("发送请求到: %s", url)
log.Printf("Cookie: PHPSESSID=%s", token)
log.Printf("文件: %s", fileName)
log.Printf("Content-Type: %s", writer.FormDataContentType())
// 发送POST请求
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
resp, respBody, errs := request.Post(url).
//Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s; IMGKEY=%s", token, imageKey)).
Set("Content-Type", writer.FormDataContentType()). // 设置Content-Type
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "*/*").
Set("Host", "seller.kongfz.com").
Set("Connection", "keep-alive").
Send(body.String()). // 发送请求体
Timeout(30 * time.Second).
End()
if errs != nil {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return "", fmt.Errorf("代理连接失败: %s", errs)
}
return "", fmt.Errorf("查询请求失败: %v", errs)
}
// 在得到响应后添加
log.Printf("响应状态码: %d", resp.StatusCode)
log.Printf("响应头: %v", resp.Header)
//检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 正则表达式匹配路径
re := regexp.MustCompile(`(sw/.*?\.jpg)`)
matches := re.FindStringSubmatch(respBody)
if len(matches) > 0 {
return matches[1], nil
} else {
return "", fmt.Errorf("未找到匹配的路径")
}
}
/*
* 整合添加商品和获取孔网图片
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param filePath[string] 图片路径
* param formData[string] 商品JSON字符串
* return 添加商品响应信息结构体,错误信息
*/
func outAddGoodsAndFile(token, proxy, filePath, formData string) (string, error) {
upload, err := outKfzimgUpload(token, proxy, filePath)
if err != nil {
return "", err
}
var data map[string]interface{}
err = json.Unmarshal([]byte(formData), &data)
if err != nil {
return "", fmt.Errorf("JSON解析失败: %v", err)
}
data["images[0][imgUrl]"] = upload
dataStr, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
fmt.Println(string(dataStr))
goods, err := outAddGoods(token, proxy, string(dataStr))
if err != nil {
return "", err
}
goodsStr, err := json.Marshal(goods)
if err != nil {
return "", fmt.Errorf("goodsStr JSON序列化失败: %v", err)
}
return string(goodsStr), nil
}
/*
* 删除商品-已登的店铺
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param itemId[string] 商品ID
* return 删除商品响应信息结构体,错误信息
* Error 请先登录获取Token
* Error 代理连接失败
* Error 查询请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error API返回错误
*/
func outDelGoodsFromSelfShop(token, proxy, itemId string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 删除商品的URL
url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/quickUpdate"
formData := map[string]string{
"itemId": itemId,
"updateType": "delete",
"value": "1",
}
// 创建请求
request := gorequest.New()
if proxy != "" {
request.Proxy(proxy)
}
resp, body, errs := request.Post(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Send(formData).
Timeout(15 * time.Second).
End()
if errs != nil {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败")
}
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析json
var data map[string]interface{}
if err := json.Unmarshal([]byte(body), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 判断是否响应成功
if status, ok := data["status"].(bool); ok && status {
return data, nil
}
// 判断是否响应成功
if status, ok := data["status"].(float64); ok && status == 1 {
return data, nil
}
return nil, fmt.Errorf("API返回错误: %+v", data)
}
/*
* 获取孔网商品图片(官图和拍图)-带有店铺过滤
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param itemId[string] 商品ID
* return 删除商品响应信息结构体,错误信息
* Error 请先登录获取Token
* Error 代理连接失败
* Error 查询请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error API返回错误
*/
func outGetImageFilterShopId(token string, proxy string, isbn string, shopId int, isLiveImage int, isReturnMsg int) (map[string]string, error) {
// 判断是否为官图isLiveImage=0
if isLiveImage == 0 {
// 图书条目URL
gtUrl := fmt.Sprintf("%s?keyword=%s",
"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", isbn)
request := gorequest.New()
if proxy != "" {
request.Proxy(proxy)
}
resp, body, errs := request.Get(gtUrl).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://item.kongfz.com/").
Timeout(30 * time.Second).
End()
if len(errs) > 0 {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errs {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败")
}
return nil, fmt.Errorf("查询请求失败: %v", errs)
}
//检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析响应
var apiGtResp struct {
Status int `json:"status"`
ErrType string `json:"errType"`
Message string `json:"message"`
SystemTime int64 `json:"systemTime"`
Data struct {
ItemResponse struct {
Total int `json:"total"`
List []struct {
BookName string `json:"bookName"`
Mid int64 `json:"mid"`
ImgUrlEntity struct {
BigImgUrl string `json:"bigImgUrl"`
} `json:"imgUrlEntity"`
Isbn string `json:"isbn"`
BookShowInfo []string `json:"bookShowInfo"`
} `json:"list"`
} `json:"itemResponse"`
} `json:"data"`
}
if err := json.Unmarshal([]byte(body), &apiGtResp); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
if apiGtResp.ErrType == "102" {
return nil, fmt.Errorf("错误信息: %w状态码: %s", apiGtResp.Message, apiGtResp.ErrType)
}
var info map[string]string
// 如果找到条目返回图片URL
if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 {
list := apiGtResp.Data.ItemResponse.List[0]
info = map[string]string{
"book_name": list.BookName,
"book_pic": list.ImgUrlEntity.BigImgUrl,
"isbn": list.Isbn,
}
}
return info, nil
}
// 处理实拍图isLiveImage=1
if isLiveImage == 1 {
size := 10
// 实拍图
sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000",
"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, size)
//创建HTTP客户端
requestSpt := gorequest.New()
//设置代理(如果有提供代理URL)
if proxy != "" {
requestSpt.Proxy(proxy)
}
// 发送请求
respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl).
Proxy(proxy).
Set("Cookie", token).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://item.kongfz.com/").
Timeout(30 * time.Second).
End()
// 错误处理
if len(errsSpt) > 0 {
return nil, fmt.Errorf("请求失败: %v", errsSpt)
}
// 检查HTTP状态码
if respSpt.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status)
}
// 解析响应
var apiSptResp struct {
Status int `json:"status"`
ErrType string `json:"errType"`
Message string `json:"message"`
SystemTime int64 `json:"systemTime"`
Data struct {
ItemResponse struct {
Total int `json:"total"`
List []struct {
ItemId int64 `json:"itemId"`
Title string `json:"title"`
ImgUrl string `json:"imgUrl"`
ImgBigUrl string `json:"imgBigUrl"`
Author string `json:"author"`
PubDateText string `json:"pubDateText"`
Isbn string `json:"isbn"`
Press string `json:"press"`
ShopId int64 `json:"shopId"`
TplRecords []struct {
Key string `json:"key"`
Value string `json:"value"`
} `json:"tplRecords"`
} `json:"list"`
} `json:"itemResponse"`
} `json:"data"`
}
// 解析JSON
if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
if apiSptResp.ErrType == "102" {
return nil, fmt.Errorf("错误信息: %w状态码: %s", apiSptResp.Message, apiSptResp.ErrType)
}
var info map[string]string
if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 {
// 确定起始索引
var startIndex int
if size >= apiSptResp.Data.ItemResponse.Total {
startIndex = apiSptResp.Data.ItemResponse.Total - 1
} else {
startIndex = size - 1
}
// 尝试3次获取有效的图片
for attempt := 0; attempt < 3; attempt++ {
currentIndex := startIndex - attempt
// 检查索引是否有效
if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) {
log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex)
continue
}
randomNum := rand.Intn(currentIndex + 1)
item := apiSptResp.Data.ItemResponse.List[randomNum]
// 检查图片URL是否存在
if item.ImgBigUrl == "" {
log.Printf("[DEBUG] 索引 %d 的图片URL为空跳过", randomNum)
continue
}
// 过滤店铺
if item.ShopId == int64(shopId) {
log.Printf("[DEBUG] 索引 %d 的店铺ID需要过滤跳过", randomNum)
continue
}
// 返回图片信息
info = map[string]string{
"book_name": item.Title,
"book_pic_s": item.ImgUrl,
"isbn": item.Isbn,
}
return info, nil
}
}
}
return nil, nil
}
/*
* 获取孔网商品图片和信息(官图和拍图)
* param token[string] 孔网token
* param proxy[string] 代理服务器IP
* param isbn[string] isbn
* param isLiveImage[int] 是否官图 0官图 1实拍图
* param isReturnMsg[int] 是否有其他信息
* return 孔网商品图片和信息响应信息结构体,错误信息
* Error 请先登录获取Token
* Error 代理连接失败
* Error 查询请求失败
* Error 解析JSON失败
* Error 错误信息: %w状态码: %s
* Error 查询失败,没有数据!
*/
func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, isReturnMsg int) (*BookInfo, error) {
// isLiveImage 1实拍图 0官图 isReturnMsg 0商品信息
bookInfo := &BookInfo{}
// 处理官图isLiveImage=0
if isLiveImage == 0 {
// 孔网官图请求
gtUrl := fmt.Sprintf("%s?keyword=%s",
"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", isbn)
// 创建HTTP客户端
requestGt := gorequest.New()
// 设置代理(如果有提供代理URL)
if proxy != "" {
requestGt.Proxy(proxy)
}
// 发送GET请求
respGt, bodyGt, errsGt := requestGt.Get(gtUrl).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://item.kongfz.com/").
Timeout(30 * time.Second).
End()
if len(errsGt) > 0 {
// 检查是否是代理相关错误
var isProxyError bool
var errorDetails []string
for _, e := range errsGt {
errorStr := e.Error()
errorDetails = append(errorDetails, errorStr)
if strings.Contains(errorStr, "Proxy Authentication Required") ||
strings.Contains(errorStr, "connectex: A connection attempt failed") ||
strings.Contains(errorStr, "connectex: No connection could be made") ||
strings.Contains(errorStr, "proxyconnect tcp") ||
strings.Contains(errorStr, "timeout") ||
strings.Contains(errorStr, "connection refused") {
isProxyError = true
}
}
log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
if isProxyError {
// 处理代理失败
return nil, fmt.Errorf("代理连接失败")
}
return nil, fmt.Errorf("查询请求失败: %v", errsGt)
}
//检查HTTP状态码
if respGt.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", respGt.Status)
}
// 解析响应
var apiGtResp struct {
Status int `json:"status"`
ErrType string `json:"errType"`
Message string `json:"message"`
SystemTime int64 `json:"systemTime"`
Data struct {
ItemResponse struct {
Total int `json:"total"`
List []struct {
BookName string `json:"bookName"`
Mid int64 `json:"mid"`
ImgUrlEntity struct {
BigImgUrl string `json:"bigImgUrl"`
} `json:"imgUrlEntity"`
Isbn string `json:"isbn"`
BookShowInfo []string `json:"bookShowInfo"`
} `json:"list"`
} `json:"itemResponse"`
} `json:"data"`
}
// 解析JSON
if err := json.Unmarshal([]byte(bodyGt), &apiGtResp); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
if apiGtResp.ErrType == "102" {
return nil, fmt.Errorf("错误信息: %w状态码: %s", apiGtResp.Message, apiGtResp.ErrType)
}
// 如果找到条目返回图片URL
if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 {
list := apiGtResp.Data.ItemResponse.List[0]
bookShowInfo := list.BookShowInfo
bookInfo.BookName = list.BookName
bookInfo.BookPic = list.ImgUrlEntity.BigImgUrl
bookInfo.ISBN = list.Isbn
bookInfo.Mid = list.Mid
// 根据长度安全填充字段
if isReturnMsg == 0 {
if len(bookShowInfo) > 0 {
bookInfo.Author = bookShowInfo[0]
}
if len(bookShowInfo) > 1 {
bookInfo.Publisher = bookShowInfo[1]
}
if len(bookShowInfo) > 2 {
bookInfo.PublicationTime = validateDateFormat(bookShowInfo[2])
}
if len(bookShowInfo) > 3 {
bookInfo.BindingLayout = bookShowInfo[3]
}
if len(bookShowInfo) > 4 {
bookInfo.FixPrice = bookShowInfo[4]
} else {
log.Printf("[WARN] BookShowInfo 长度不足 (仅 %d 项): %v", len(bookShowInfo), bookShowInfo)
}
}
}
return bookInfo, nil
}
// 处理实拍图isLiveImage=1
if isLiveImage == 1 {
size := 10
// 实拍图
sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000",
"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, size)
//创建HTTP客户端
requestSpt := gorequest.New()
//设置代理(如果有提供代理URL)
if proxy != "" {
requestSpt.Proxy(proxy)
}
// 发送请求
respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://item.kongfz.com/").
Timeout(30 * time.Second).
End()
// 错误处理
if len(errsSpt) > 0 {
return nil, fmt.Errorf("请求失败: %v", errsSpt)
}
// 检查HTTP状态码
if respSpt.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status)
}
// 解析响应
var apiSptResp struct {
Status int `json:"status"`
ErrType string `json:"errType"`
Message string `json:"message"`
SystemTime int64 `json:"systemTime"`
Data struct {
ItemResponse struct {
Total int `json:"total"`
List []struct {
ItemId int64 `json:"itemId"`
Title string `json:"title"`
ImgUrl string `json:"imgUrl"`
ImgBigUrl string `json:"imgBigUrl"`
Author string `json:"author"`
PubDateText string `json:"pubDateText"`
Isbn string `json:"isbn"`
Press string `json:"press"`
ShopId int64 `json:"shopId"`
TplRecords []struct {
Key string `json:"key"`
Value string `json:"value"`
} `json:"tplRecords"`
} `json:"list"`
} `json:"itemResponse"`
} `json:"data"`
}
// 解析JSON
if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
if apiSptResp.ErrType == "102" {
return nil, fmt.Errorf("错误信息: %w状态码: %s", apiSptResp.Message, apiSptResp.ErrType)
}
if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 {
// 确定其实索引
var startIndex int
if size >= apiSptResp.Data.ItemResponse.Total {
startIndex = apiSptResp.Data.ItemResponse.Total - 1
} else {
startIndex = size - 1
}
// 尝试3次获取有效的图片
for attempt := 0; attempt < 3; attempt++ {
currentIndex := startIndex - attempt
// 检查索引是否有效
if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) {
log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex)
continue
}
// 随机选择商品
randomNum := rand.Intn(currentIndex + 1)
item := apiSptResp.Data.ItemResponse.List[randomNum]
// 检查图片URL是否存在
if item.ImgBigUrl == "" {
log.Printf("[DEBUG] 索引 %d 的图片URL为空跳过", randomNum)
continue
}
// 填充图书信息
bookInfo.BookPicS = item.ImgUrl
bookInfo.ISBN = item.Isbn
// 如果书名为空,使用商品标题
if bookInfo.BookName == "" {
bookInfo.BookName = item.Title
if isReturnMsg == 0 {
// 安全地获取TplRecords中的值
if len(item.TplRecords) > 0 {
bookInfo.Author = item.TplRecords[0].Value
}
if len(item.TplRecords) > 1 {
bookInfo.Publisher = item.TplRecords[1].Value
}
if len(item.TplRecords) > 2 {
bookInfo.PublicationTime = validateDateFormat(item.TplRecords[2].Value)
}
if len(item.TplRecords) > 3 {
bookInfo.BindingLayout = item.TplRecords[3].Value
}
}
}
return bookInfo, nil
}
}
}
return nil, fmt.Errorf("查询失败,没有数据!")
}
// 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息
func outGetMidDetail(token string, mid string) (*BookInfo, error) {
detailUrl := fmt.Sprintf("https://item.kongfz.com/book/%s.html", mid)
// 创建HTTP客户端设置超时时间
client := &http.Client{
Timeout: 120 * time.Second,
}
// 创建HTTP请求
req, err := http.NewRequest("GET", detailUrl, nil)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
// 设置请求头,模拟浏览器访问
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Cache-Control", "max-age=0")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token))
// 发送请求
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("请求失败: %w", err)
}
defer resp.Body.Close()
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %w", err)
}
var bookInfo BookInfo
// 创建goquery文档
doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
}
// 1. 提取书名
bookInfo.BookName = strings.TrimSpace(doc.Find("title").Text())
// 2. 提取右侧详细信息区域
rightDiv := doc.Find("div.detail-con-right")
if rightDiv.Length() == 0 {
return nil, fmt.Errorf("未找到详细信息区域")
}
// 左侧信息框
leftInfoBox := rightDiv.Find("div.info-con-box-left")
// 提取作者
leftInfoBox.Find(".zuozhe").Each(func(i int, s *goquery.Selection) {
authorText := s.Find(".text-value").Text()
// 清理作者信息
authorText = strings.ReplaceAll(authorText, "\n", "")
authorText = strings.ReplaceAll(authorText, " ", "")
authorText = strings.TrimSpace(authorText)
bookInfo.Author = authorText
})
// 提取出版社
leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "出版社") {
bookInfo.Publisher = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取出版时间(转换为时间戳)
leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "出版时间") {
timeStr := strings.TrimSpace(s.Find("span.text-value").Text())
// 尝试解析时间
t, err := time.Parse("2006-01", timeStr)
if err != nil {
// 如果月份解析失败,尝试只解析年份
t, err = time.Parse("2006", timeStr)
if err == nil {
bookInfo.PublicationTime = t.Unix()
}
} else {
bookInfo.PublicationTime = t.Unix()
}
}
})
// 提取版次
leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "版次") {
bookInfo.Edition = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取ISBN
leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "ISBN") {
bookInfo.ISBN = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取定价
leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "定价") {
bookInfo.FixPrice = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取装帧
leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "装帧") {
bookInfo.BindingLayout = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 右侧信息框
rightInfoBox := rightDiv.Find("div.info-con-box").Last()
// 提取开本
rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "开本") {
bookInfo.Format = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取纸张
rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "纸张") {
bookInfo.Paper = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取页数
rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "页数") {
bookInfo.Pages = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取语种
rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "正文语种") {
bookInfo.Languages = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取分类
rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) {
key := s.Find(".text-key").Text()
if strings.Contains(key, "分类") {
bookInfo.Category = strings.TrimSpace(s.Find("span.text-value").Text())
}
})
// 提取买过人数
rightInfoBox.Find(".sale-qty").Each(func(i int, s *goquery.Selection) {
text := s.Text()
// 使用正则提取数字
re := regexp.MustCompile(`(\d+)`)
matches := re.FindStringSubmatch(text)
if len(matches) > 1 {
bookInfo.BuyCount = matches[1]
}
})
// 3. 提取底部详细信息(内容简介、作者简介、目录)
bottomDiv := rightDiv.Find("div.detail-con-right-bottom")
// 提取内容简介
bottomDiv.Find(".jianjie").Each(func(i int, s *goquery.Selection) {
h5 := s.Find("h5").Text()
if strings.Contains(h5, "内容简介") {
content := s.Contents().FilterFunction(func(i int, s *goquery.Selection) bool {
return goquery.NodeName(s) == "#text"
}).Text()
// 清理文本
content = strings.ReplaceAll(content, "\n", " ")
content = strings.ReplaceAll(content, "&nbsp;", " ")
content = strings.TrimSpace(content)
bookInfo.Content = content
}
})
// 提取作者简介
bottomDiv.Find(".jianjie").Each(func(i int, s *goquery.Selection) {
h5 := s.Find("h5").Text()
if strings.Contains(h5, "作者简介") {
intro := s.Contents().FilterFunction(func(i int, s *goquery.Selection) bool {
return goquery.NodeName(s) == "#text"
}).Text()
intro = strings.ReplaceAll(intro, "\n", " ")
intro = strings.ReplaceAll(intro, "&nbsp;", " ")
intro = strings.TrimSpace(intro)
bookInfo.AuthorIntroduction = intro
}
})
// 提取目录
bottomDiv.Find(".jianjie").Each(func(i int, s *goquery.Selection) {
h5 := s.Find("h5").Text()
if strings.Contains(h5, "目录") {
catalog := s.Contents().FilterFunction(func(i int, s *goquery.Selection) bool {
return goquery.NodeName(s) == "#text"
}).Text()
catalog = strings.ReplaceAll(catalog, "\n", " ")
catalog = strings.ReplaceAll(catalog, "&nbsp;", " ")
catalog = strings.TrimSpace(catalog)
bookInfo.Catalogue = catalog
}
})
// 4. 提取图书封面图
doc.Find("div.detail-con-left").Find("img").Each(func(i int, s *goquery.Selection) {
if src, exists := s.Attr("src"); exists {
if strings.Contains(src, "kongfz") {
bookInfo.BookPic = src
}
}
})
// 5. 提取商品信息(售价、品相等)
doc.Find("div.store-list-item").First().Each(func(i int, s *goquery.Selection) {
// 提取售价
priceText := s.Find(".item-price").Text()
re := regexp.MustCompile(`¥(\d+\.?\d*)`)
matches := re.FindStringSubmatch(priceText)
if len(matches) > 1 {
bookInfo.SellingPrice = matches[1]
}
// 提取品相
qualityText := s.Find(".item-quality").Text()
if qualityText != "" {
bookInfo.Condition = strings.TrimSpace(qualityText)
}
})
// 6. 尝试提取店铺ID、商品ID等
// 从URL中提取mid
if midInt, err := strconv.ParseInt(mid, 10, 64); err == nil {
bookInfo.Mid = midInt
}
return &bookInfo, nil
}
/*
* 获取商品列表通过店铺ID
* param shopId[int] 店铺ID
* param proxy[string] 代理服务器IP
* param retPrice[int] 是否需要价格
* param sortType[string] 排序类型
* param sort[string] 排序
* param priceMin[float32] 价格区间-低
* param priceMax[float32] 价格区间-高
* param pageNum[int] 页数
* param returnNum[int] 返回数量
* return 商品列表响应信息结构体,商品总数,总页数,错误信息
* Error 无效的排序类型: %s可选值: sort, putDate, newItem, price
* Error 无效的排序类型: %s可选值: desc, asc
*/
func outGetGoodsListMsgByShopId(token string, shopId int, proxy string, retPrice int, isImage int, sortType string,
sort string, priceMin float32, priceMax float32,
pageNum, returnNum int) (books []BookInfo, goodsNum string, pNum string, err error) {
// 判断店铺ID
if shopId == 0 {
return nil, "", "", fmt.Errorf("店铺编码为空!")
}
// 判断是否有图片设置默认值0
var isImageStr string
if isImage == 0 {
isImageStr = "0"
} else {
isImageStr = "1"
}
// 判断一页图书数量设置默认值100
if returnNum == 0 {
returnNum = 100
}
// 判断页数设置默认值1
if pageNum == 0 {
pageNum = 1
}
// 判断排序类型设置默认值sort
if sortType == "" {
sortType = "sort"
} else {
validSortTypes := map[string]bool{
"sort": true,
"putDate": true,
"newItem": true,
"price": true,
}
if !validSortTypes[sortType] {
return nil, "", "", fmt.Errorf("无效的排序类型: %s可选值: sort, putDate, newItem, price", sortType)
}
}
// 判断排序设置默认值desc
if sort == "" {
sort = "desc"
} else {
validSorts := map[string]bool{
"desc": true,
"asc": true,
}
if !validSorts[sort] {
return nil, "", "", fmt.Errorf("无效的排序类型: %s可选值: desc, asc", sort)
}
}
var kfzUrl string
var pMin int
pMin = 0
var pMax int
pMax = 0
// 判断价格下限设置默认值0
if priceMin == 0 && priceMax == 0 {
// 调用的url
kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%d",
shopId, isImageStr, returnNum, pageNum, sortType, sort, pMin, pMax)
} else if priceMin == 0 {
kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%.2f",
shopId, isImageStr, returnNum, pageNum, sortType, sort, pMin, priceMax)
} else if priceMax == 0 {
kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%d",
shopId, isImageStr, returnNum, pageNum, sortType, sort, priceMin, pMax)
} else {
kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%.2f",
shopId, isImageStr, returnNum, pageNum, sortType, sort, priceMin, priceMax)
}
// 发送请求(移除了重试机制)
var response *http.Response
var errors []error
// 根据是否有代理发送请求
if proxy != "" {
response, _, errors = gorequest.New().
Get(kfzUrl).
Proxy(proxy).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36").
Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(120 * time.Second).
End()
} else {
fmt.Println("没使用代理")
response, _, errors = gorequest.New().
Get(kfzUrl).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36").
Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(120 * time.Second).
End()
}
// 检测请求是否错误
if len(errors) > 0 {
var proxyAuthFailed bool
var timeoutError bool
var connectionError bool
// 分析错误类型
for _, e := range errors {
errStr := e.Error()
if strings.Contains(errStr, "Proxy Authentication Required") {
proxyAuthFailed = true
}
if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "i/o timeout") {
timeoutError = true
}
if strings.Contains(errStr, "connection") || strings.Contains(errStr, "connect") {
connectionError = true
}
}
// 根据错误类型返回相应的错误信息
if proxyAuthFailed {
fmt.Println("代理认证失败")
return nil, "", "", fmt.Errorf("代理认证失败")
}
if timeoutError {
fmt.Println("请求超时,超时网址")
return nil, "", "", fmt.Errorf("请求超时,超时网址:%s", kfzUrl)
}
if connectionError {
fmt.Println("网络连接错误,错误网址:")
return nil, "", "", fmt.Errorf("网络连接错误,错误网址:%s", kfzUrl)
}
fmt.Println("查询请求失败:", errors)
return nil, "", "", fmt.Errorf("查询请求失败: %v失败网址:%s", errors, kfzUrl)
}
// 检查响应是否为空
if response == nil {
fmt.Println("响应为空")
return nil, "", "", fmt.Errorf("响应为空")
}
// 检查HTTP状态码
if response.StatusCode != http.StatusOK {
fmt.Println("HTTP错误")
return nil, "", "", fmt.Errorf("HTTP错误: %s", response.Status)
}
// 读取响应体
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, "", "", err
}
// 解析HTML文档
doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
if err != nil {
return nil, "", "", err
}
// 全部商品数量
num := doc.Find("div.crumbs-nav-main.clearfix").Find("span")
if match := regexp.MustCompile(`\d+`).FindString(num.Text()); match != "" {
goodsNum = match
}
// 商品页数
pg := doc.Find("li.pull-right.page_num").Find("span")
_, split, found := strings.Cut(strings.TrimSpace(pg.Text()), "/")
if found {
pNum = split
} else {
log.Printf("未找到页数!")
}
// 提取商品信息
infoDiv := doc.Find("div.list-content")
var params ParamsInfo
if infoDiv.Length() > 0 {
item := infoDiv.Find("div.item.clearfix")
for i := 0; i < item.Length(); i++ {
s := item.Eq(i)
book := BookInfo{}
// 书名
book.BookName = strings.TrimSpace(s.Find("div.title a.link").Text())
// 提取ISBN
book.ISBN = strings.TrimSpace(s.AttrOr("isbn", ""))
// 店铺ID
shopid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("shopid", "")))
book.ShopId = int64(shopid)
// 商品ID
itemid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("itemid", "")))
book.ItemId = int64(itemid)
// 详情URL
book.DetailUrl = s.Find("div.item-img a.img-box").AttrOr("href", "")
// 价格
if retPrice == 0 {
book.SellingPrice = s.Find("div.f_right.red.price").Find("span.bold").Text()
params.Params = append(params.Params, struct {
UserId string `json:"userId"`
ItemId string `json:"itemId"`
}{UserId: strings.TrimSpace(s.AttrOr("userid", "")), ItemId: strings.TrimSpace(s.AttrOr("itemid", ""))})
}
books = append(books, book)
}
// 如果需要查询快递费
if params.Params != nil {
params.Area = "13003000000"
dataItem, err := getGoodsListShippingFee(params, proxy)
if err != nil {
fmt.Println("查询快递费失败")
return nil, "", "", err
}
// 将快递费信息填充到图书信息中
for i := 0; i < len(books); i++ {
itemId := fmt.Sprintf("%d", books[i].ItemId)
for _, data := range dataItem {
if itemId == data.ItemID {
books[i].ExpressDeliveryFee = data.Fee[0].TotalFee
}
}
}
}
}
return books, goodsNum, pNum, nil
}
/*
* 获取商品信息通过商品详情链接
* param detailUrl[string] 详情页url
* param proxy[string] 代理服务器IP
* return 商品列表响应信息结构体,错误信息
* Error 未找到指定的contentSpan信息
* Error 无效的排序类型: %s可选值: desc, asc
*/
func outGetGoodsMsgByDetailUrl(detailUrl, proxy string) (*BookInfo, error) {
// 发送请求获取响应
response, err := fetchResponse(detailUrl, proxy)
if err != nil {
return nil, err
}
// 读取响应体
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
// 解析HTML文档
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
if err != nil {
return nil, err
}
// 获取商品信息的快递费
fee, err := getBookDetailShippingFee(detailUrl, proxy)
if err != nil {
return nil, err
}
book := BookInfo{}
// 书名
book.BookName = strings.TrimSpace(document.Find("h1.title").Text())
// 提取作者、出版社等信息
topDiv := document.Find("div.keywords-define.keywords-define-1000.clear-fix")
if topDiv.Length() > 0 {
topDiv.Find("li").Each(func(i int, li *goquery.Selection) {
titleSpan := li.Find("span.keywords-define-title")
contentSpan := li.Find("span.keywords-define-txt")
if contentSpan.Length() == 0 {
fmt.Printf("未找到指定的contentSpan信息")
}
titleText := strings.TrimSpace(titleSpan.Text())
contentText := strings.TrimSpace(contentSpan.Text())
titleText = strings.TrimSpace(titleText)
// 根据标题字段填充对应的图书信息字段
if strings.Contains(titleText, "作者") {
book.Author = cleanString(contentText)
}
if strings.Contains(titleText, "出版社") {
book.Publisher = contentText
}
if strings.Contains(titleText, "出版人") {
book.Publisher = contentText
}
if strings.Contains(titleText, "ISBN") {
book.ISBN = contentText
}
if strings.Contains(titleText, "出版时间") {
book.PublicationTime = validateDateFormat(contentText)
}
if strings.Contains(titleText, "版次") {
book.Edition = contentText
}
if strings.Contains(titleText, "装帧") {
book.BindingLayout = contentText
}
if strings.Contains(titleText, "开本") {
book.Format = contentText
}
if strings.Contains(titleText, "页数") {
book.Pages = contentText
}
if strings.Contains(titleText, "字数") {
book.Wordage = contentText
}
if strings.Contains(titleText, "纸张") {
book.Paper = contentText
}
if strings.Contains(titleText, "年代") {
book.Era = contentText
}
if strings.Contains(titleText, "刻印方式") {
book.EngravingMethod = contentText
}
if strings.Contains(titleText, "尺寸") {
book.Dimensions = contentText
}
if strings.Contains(titleText, "册数") {
book.VolumeNumber = contentText
}
})
} else {
// 备选提取方式
botDiv := document.Find("div.detail-lists.clear-fix")
botDiv.Find("li").Each(func(i int, li *goquery.Selection) {
spanText := strings.TrimSpace(li.Find("span").Text())
spanText = strings.TrimSpace(spanText)
if strings.Contains(li.Text(), "作者") {
book.Author = cleanString(li.Text())
book.Author = strings.ReplaceAll(book.Author, "作者:", "")
book.Author = strings.ReplaceAll(book.Author, "著", "")
}
if strings.Contains(li.Text(), "出版社") {
book.Publisher = spanText
}
if strings.Contains(li.Text(), "出版时间") {
book.PublicationTime = validateDateFormat(spanText)
}
if strings.Contains(li.Text(), "ISBN") {
book.ISBN = spanText
}
if strings.Contains(li.Text(), "装帧") {
book.BindingLayout = spanText
}
if strings.Contains(li.Text(), "开本") {
book.Format = spanText
}
if strings.Contains(li.Text(), "纸张") {
book.Paper = spanText
}
if strings.Contains(li.Text(), "版次") {
book.Edition = spanText
}
if strings.Contains(li.Text(), "页数") {
book.Pages = spanText
}
if strings.Contains(li.Text(), "字数") {
book.Wordage = spanText
}
})
}
// 提取商品图片
var imgUrls []string
tpUl := document.Find("ul.lg-list")
tpUl.Find("img").Each(func(i int, s *goquery.Selection) {
dataImgUrl, exists := s.Attr("data-imgurl")
if exists && dataImgUrl != "" {
imgUrls = append(imgUrls, dataImgUrl)
}
})
book.BookPicS = strings.Join(imgUrls, ",")
// 提取售价
price := document.Find("i.now-price-text").Text()
priceN := regexp.MustCompile(`(\d+\.?\d*)`)
if match := priceN.FindStringSubmatch(price); len(match) > 0 {
book.SellingPrice = match[1]
}
// 提取定价
fixPrice := document.Find("span.origin-price-text.clearfix").Text()
fixPriceN := regexp.MustCompile(`(\d+\.?\d*)`)
if match := fixPriceN.FindStringSubmatch(fixPrice); len(match) > 0 {
book.FixPrice = match[1]
}
// 提取品相
text := document.Find("span.quality-text-cot.clearfix i").Text()
book.Condition = strings.TrimSpace(text)
// 设置快递费
book.ExpressDeliveryFee = fee
return &book, nil
}
/*
* 获取商品信息的快递费(定位到河南)
* param url[string] 获取快递费url
* param proxy[string] 代理服务器IP
* return 商品列表响应信息结构体,错误信息
* Error 无效的店铺编码: %s
* Error 无效的图书编码: %s
* Error 请求失败
* Error HTTP错误
* Error 解析JSON失败
*/
func getBookDetailShippingFee(url, proxy string) (string, error) {
// 从URL中提取店铺ID和商品ID
compile := regexp.MustCompile(`kongfz\.com/(\d+)/(\d+)`)
match := compile.FindStringSubmatch(url)
var shippingFee string
var shopId int
var itemId int
if len(match) == 3 {
// 提取店铺ID
firstNum, err := strconv.Atoi(match[1])
if err != nil {
return "", fmt.Errorf("无效的店铺编码: %s", match[1])
}
shopId = firstNum
// 提取商品ID
secondNum, err := strconv.Atoi(match[2])
if err != nil {
return "", fmt.Errorf("无效的图书编码: %s", match[2])
}
itemId = secondNum
}
// 构建快递费查询URL
shippingFeeUrl := fmt.Sprintf("https://book.kongfz.com/store-web/pc/v1/mould/calculateFee?area=13003000000&itemId=%d&shopId=%d", itemId, shopId)
// 创建HTTP客户端
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
// 设置超时和其他配置
request.Timeout(30 * time.Second)
// 发送请求
resp, body, errs := request.Get(shippingFeeUrl).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
End()
// 错误处理
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP错误: %s", resp.Status)
}
calculateFee := struct {
ErrCode int `json:"errCode"`
ErrMessage string `json:"errMessage"`
Result struct {
FeeList []struct {
FreeCondition string `json:"freeCondition"`
ShippingID string `json:"shippingId"`
ShippingName string `json:"shippingName"`
ShippingValue string `json:"shippingValue"`
} `json:"feeList"`
FeeText string `json:"feeText"`
} `json:"result"`
Status bool `json:"status"`
}{}
// 解析JSON
err := json.Unmarshal([]byte(body), &calculateFee)
if err != nil {
return "", fmt.Errorf("解析JSON失败: %v", err)
}
// 提取快递费
for _, fee := range calculateFee.Result.FeeList {
shippingFee = fee.ShippingValue
}
return shippingFee, nil
}
/*
* 公用发送请求方法
* param url[string] url
* param proxy[string] 代理服务器IP
* return 商品列表响应信息结构体,错误信息
* Error 请求失败
* Error 响应为空 (尝试 %d/%d)
* Error HTTP状态码: %d,HTTP请求失败: %s (尝试 %d/%d)
* Error 代理认证失败
* Error 请求超时,经过 %d 次尝试,超时网址:%s
* Error 网络连接错误,经过 %d 次尝试,错误网址:%s
* Error 查询请求失败,经过 %d 次尝试: %v失败网址:%s
* Error HTTP错误
*/
func fetchResponse(url, proxy string) (*http.Response, error) {
log.Printf("调用的URL: %s", url)
maxRetries := 3
var detailsResp *http.Response
var errors []error
// 重试机制
for attempt := 0; attempt < maxRetries; attempt++ {
if attempt > 0 {
log.Printf("第 %d 次重试请求...", attempt)
// 重试前等待,使用指数退避策略
waitTime := time.Duration(attempt*attempt) * 1000 // 平方退避
log.Printf("等待 %v 后重试", waitTime)
time.Sleep(waitTime)
}
// 根据是否有代理发送请求
if proxy != "" {
detailsResp, _, errors = gorequest.New().
Get(url).
Proxy(proxy).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36").
Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(120 * time.Second).
End()
}
if proxy == "" {
detailsResp, _, errors = gorequest.New().
Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36").
Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(120 * time.Second).
End()
}
// 检查是否需要重试
shouldRetry := false
if len(errors) > 0 {
shouldRetry = true
log.Printf("请求失败 (尝试 %d/%d): %v", attempt+1, maxRetries+1, errors)
} else if detailsResp == nil {
shouldRetry = true
log.Printf("响应为空 (尝试 %d/%d)", attempt+1, maxRetries+1)
} else if detailsResp.StatusCode != http.StatusOK {
// 只对服务器错误进行重试,不对客户端错误重试
if detailsResp.StatusCode >= 500 {
shouldRetry = true
}
log.Printf("HTTP状态码: %d,HTTP请求失败: %s (尝试 %d/%d)", detailsResp.StatusCode, detailsResp.Body, attempt+1, maxRetries+1)
}
// 如果不需要重试,跳出循环
if !shouldRetry {
break
}
// 如果是最后一次尝试,不继续重试
if attempt == maxRetries {
break
}
// 关闭响应体(如果存在)
if detailsResp != nil && detailsResp.Body != nil {
detailsResp.Body.Close()
}
}
// 检测请求是否错误
if len(errors) > 0 {
var proxyAuthFailed bool
var timeoutError bool
var connectionError bool
// 分析错误类型
for _, e := range errors {
errStr := e.Error()
if strings.Contains(errStr, "Proxy Authentication Required") {
proxyAuthFailed = true
}
if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "i/o timeout") {
timeoutError = true
}
if strings.Contains(errStr, "connection") || strings.Contains(errStr, "connect") {
connectionError = true
}
}
// 根据错误类型返回相应的错误信息
if proxyAuthFailed {
return nil, fmt.Errorf("代理认证失败")
}
if timeoutError {
return nil, fmt.Errorf("请求超时,经过 %d 次尝试,超时网址:%s", maxRetries+1, url)
}
if connectionError {
return nil, fmt.Errorf("网络连接错误,经过 %d 次尝试,错误网址:%s", maxRetries+1, url)
}
return nil, fmt.Errorf("查询请求失败,经过 %d 次尝试: %v失败网址:%s", maxRetries+1, errors, url)
}
// 检查HTTP状态码
if detailsResp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", detailsResp.Status)
}
return detailsResp, nil
}
/*
* 获取销量榜商品列表
* param catId[int] 分类ID
* param proxy[string] 代理服务器IP
* return isbn数组,错误信息
* Error 请求失败
* Error HTTP错误
* Error 解析JSON失败
* Error API返回错误: %s (代码: %d)
*/
func outGetTopGoodsListMsg(catId int, proxy string) ([]string, error) {
// 构建请求URL
url := fmt.Sprintf("https://item.kongfz.com/api/pc/getSellWellListDetail?page=1&pageSize=100&timeRank=2&catId=%d", catId)
// 创建HTTP客户端
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
}
// 设置超时和其他配置
request.Timeout(30 * time.Second)
// 发送GET请求
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://item.kongfz.com/").
End()
// 错误处理
if len(errs) > 0 {
return nil, fmt.Errorf("请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
}
// 解析响应
var bookDetailResponse BookDetailResponse
err := json.Unmarshal([]byte(body), &bookDetailResponse)
if err != nil {
return nil, fmt.Errorf("解析JSON失败: %v", err)
}
// 检查响应状态
if !bookDetailResponse.Status {
return nil, fmt.Errorf("API返回错误: %s (代码: %d)", bookDetailResponse.ErrMessage, bookDetailResponse.ErrCode)
}
// 提取ISBN列表
var isbnList []string
for _, item := range bookDetailResponse.Result.Data {
if item.Isbn != "" {
isbnList = append(isbnList, item.Isbn)
}
}
// 去除重复的ISBN
isbnList = removeDuplicateISBNs(isbnList)
return isbnList, nil
}
/*
* 去除重复的ISBN
* param isbns[[]string] isbn数组
* return isbn数组
*/
func removeDuplicateISBNs(isbns []string) []string {
seen := make(map[string]bool)
var result []string
for _, isbn := range isbns {
if !seen[isbn] {
seen[isbn] = true
result = append(result, isbn)
}
}
return result
}
/*
* 生成签名
* param params[map[string]interface{}] 生成签名的字段map
* param appSecret[string] app密钥
* return 签名
*/
func generateSign(params map[string]interface{}, appSecret string) string {
// 获取所有键并排序
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
// 拼接签名字符串
var signStr strings.Builder
for _, k := range keys {
// 跳过 sign 参数
if strings.ToLower(k) == "sign" {
continue
}
// 获取参数值
value := ""
if params[k] != nil {
value = fmt.Sprintf("%v", params[k])
}
// 按照文档格式:参数名+参数值
signStr.WriteString(k + value)
}
// 根据签名方法生成签名
signMethod := "md5"
if method, ok := params["signMethod"].(string); ok {
signMethod = strings.ToLower(method)
}
signString := signStr.String()
// 使用MD5算法生成签名
if signMethod == "md5" {
// MD5算法md5(appSecret + signString + appSecret)
data := appSecret + signString + appSecret
hash := md5.Sum([]byte(data))
result := strings.ToUpper(fmt.Sprintf("%x", hash))
//fmt.Printf("Debug: MD5签名结果: %s\n", result)
return result
}
return ""
}
// KwAPIResponse 孔网API响应结构体
type KwAPIResponse struct {
ErrorResponse *ErrorResponse `json:"errorResponse"` // 错误响应
SuccessResponse []ShippingMethod `json:"successResponse"` // 成功响应
RequestId string `json:"requestId"` // 请求ID
RequestMethod string `json:"requestMethod"` // 请求方法
}
// ErrorResponse 孔网API错误响应结构体
type ErrorResponse struct {
Code int `json:"code"` // 错误码
Msg string `json:"msg"` // 错误信息
SubCode *int `json:"subCode"` // 使用指针类型因为可能是null
SubMsg *string `json:"subMsg"` // 使用指针类型因为可能是null
}
type ShippingMethod struct {
ShippingId string `json:"shippingId"` // 配送方式ID
ShippingName string `json:"shippingName"` // 配送方式名称
IsDefault bool `json:"isDefault"` // 是否默认配送方式
Companies []Company `json:"companies"` // 快递公司列表
}
type Company struct {
ShippingCom string `json:"shippingCom"` // 快递公司代号
ShippingComName string `json:"shippingComName"` // 快递公司名称
IsDefault bool `json:"isDefault"` // 是否默认
}
/*
* 获取配送方式列表
* param appId[int] appkey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* return 配送方式列表结构体字符串,错误信息
*/
func kongfzDeliveryMethodList(appId int, appSecret, accessToken string) (string, error) {
kUrl := fmt.Sprint("https://open.kongfz.com/router/rest")
dateTime := getCurrentTimeGMT8()
// 生成签名参数
params := map[string]interface{}{
"method": "kongfz.delivery.method.list",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"simplify": 0,
}
// 生成签名
sign := generateSign(params, appSecret)
// 构建请求体
formData := map[string]interface{}{
"method": "kongfz.delivery.method.list",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"sign": sign,
"simplify": 0,
}
// 发送POST请求
request := gorequest.New()
resp, body, errs := request.Post(kUrl).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "*/*").
Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
Type("form"). // 关键:明确指定为表单格式
Timeout(30 * time.Second).
Send(formData).
End()
// 错误处理
if len(errs) > 0 {
return "", fmt.Errorf("登录请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理
if response["ErrorResponse"] != nil {
var kwAPIResponse KwAPIResponse
if err := json.Unmarshal([]byte(body), &kwAPIResponse); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", kwAPIResponse.ErrorResponse.Msg, kwAPIResponse.ErrorResponse.Code)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
/*
* 订单发货
* param appId[int] appkey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* param orderId[int] 订单编号
* param shippingId[string] 配送方式
* param shippingCom[string] 快递公司。当shippingId!=noLogistics时此参数为必填。
* param shipmentNum[string] 快递单号。当shippingId!=noLogistics时此参数为必填。
* param userDefined[string] 用户自定义物流公司。当shippingCom=other时此参数为必填。
* param moreShipmentNum[string] 填写更多的快递单号,以逗号分隔。
* return 订单发货结构体字符串,错误信息
*/
func kongfzOrderDeliver(appId int, appSecret, accessToken string,
orderId int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum string) (string, error) {
kUrl := fmt.Sprint("https://open.kongfz.com/router/rest")
dateTime := getCurrentTimeGMT8()
// 参数验证
if shippingId != "noLogistics" {
if shippingCom == "" {
return "", fmt.Errorf("当 shippingId 不等于 noLogistics 时shippingCom 参数为必填。shippingId是: %v", shippingId)
}
if shipmentNum == "" {
return "", fmt.Errorf("当 shippingId 不等于 noLogistics 时, shipmentNum 参数为必填。shippingId是: %v", shippingId)
}
}
if shipmentNum == "other" {
if userDefined == "" {
return "", fmt.Errorf("当 shippingCom 等于 other 时, userDefined 参数为必填。shipmentNum是: %v", shipmentNum)
}
}
// 生成签名参数
params := map[string]interface{}{
"method": "kongfz.order.deliver",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"simplify": 0,
"orderId": orderId,
"shippingId": shippingId,
"shippingCom": shippingCom,
"shipmentNum": shipmentNum,
"userDefined": userDefined,
"moreShipmentNum": moreShipmentNum,
}
// 生成签名
sign := generateSign(params, appSecret)
// 构建请求体
formData := map[string]interface{}{
"method": "kongfz.order.deliver",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"sign": sign,
"simplify": 0,
"orderId": orderId,
"shippingId": shippingId,
"shippingCom": shippingCom,
"shipmentNum": shipmentNum,
"userDefined": userDefined,
"moreShipmentNum": moreShipmentNum,
}
// 发送POST请求
request := gorequest.New()
resp, body, errs := request.Post(kUrl).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "*/*").
Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
Type("form"). // 关键:明确指定为表单格式
Timeout(30 * time.Second).
Send(formData).
End()
// 错误处理
if len(errs) > 0 {
return "", fmt.Errorf("登录请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理
if response["ErrorResponse"] != nil {
var kwAPIResponse KwAPIResponse
if err := json.Unmarshal([]byte(body), &kwAPIResponse); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", kwAPIResponse.ErrorResponse.Msg, kwAPIResponse.ErrorResponse.Code)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
/*
* 孔网订单同步
* param appId[int] appkey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* param shippingComName[string] 快递公司名称
* param orderId[int] 订单编号
* param shippingId[string] 配送方式
* param shippingCom[string] 快递公司。当shippingId!=noLogistics时此参数为必填。
* param shipmentNum[string] 快递单号。当shippingId!=noLogistics时此参数为必填。
* param userDefined[string] 用户自定义物流公司。当shippingCom=other时此参数为必填。
* param moreShipmentNum[string] 填写更多的快递单号,以逗号分隔。
* return 订单同步结构体字符串,错误信息
*/
func kongfzOrderSynchronization(appId int, appSecret, accessToken string, shippingComName string,
orderId int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum string) (string, error) {
// 获取配送方式列表
deliveryMethodList, err := kongfzDeliveryMethodList(appId, appSecret, accessToken)
if err != nil {
return "", err
}
// 解析配送方式
var kw KwAPIResponse
if err := json.Unmarshal([]byte(deliveryMethodList), &kw); err != nil {
return "", fmt.Errorf("解析JSON失败: %v", err)
}
// 根据快递公司名称查找对应的配送方式和快递公司代号
for _, shippingMethod := range kw.SuccessResponse {
for _, companies := range shippingMethod.Companies {
if shippingComName == companies.ShippingComName {
shippingId = shippingMethod.ShippingId
shippingCom = companies.ShippingCom
}
}
}
// 如果未找到,使用默认值
if shippingId == "" {
shippingId = "express"
shippingCom = "other"
userDefined = shippingComName
}
// 执行订单发货
orderDeliver, err := kongfzOrderDeliver(appId, appSecret, accessToken, orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum)
if err != nil {
return "", err
}
return orderDeliver, err
}
/*
* 上传图片接口
* param appId[int] appKey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* param filePath[string] 图片url/本地图片路径
* param savePath[string] 图片下载路径如果是图片url需要下载到本地
* return 上传图片响应结构体,错误信息
* Error 解析图片URL失败
* Error 读取文件失败
* Error 创建表单文件失败
* Error 写入文件数据失败
*/
func kongfzImageUpload(appId int, appSecret, accessToken string, filePath, savePath string) (string, error) {
var image string
var needCleanup bool = false // 标记是否需要清理
defer func() {
// 函数结束时删除临时图片
if needCleanup && image != "" {
if err := os.Remove(image); err != nil {
log.Printf("警告:删除临时图片失败 %s: %v", image, err)
} else {
log.Printf("已清理临时图片: %s", image)
}
}
}()
if strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://") {
// 解析URL
parse, err := url.Parse(filePath)
if err != nil {
return "", fmt.Errorf("解析图片URL失败: %v", err)
}
// 获取路径部分
fPath := parse.Path
// 获取文件名
fileName := path.Base(fPath)
savePath = filepath.Join(savePath, fileName)
image, err = downloadImage(filePath, savePath)
if err != nil {
return "", err
}
needCleanup = true // 标记需要清理
} else {
// 本地文件
image = filePath
// 本地文件不需要清理,由调用者管理
}
kUrl := fmt.Sprint("https://open.kongfz.com/router/image/upload")
dateTime := getCurrentTimeGMT8()
// 生成签名参数
params := map[string]interface{}{
"method": "kongfz.image.upload",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"simplify": 0,
}
params["bucket"] = "book"
// 生成签名
sign := generateSign(params, appSecret)
// 创建multipart/form-data请求体
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 读取文件内容
fileData, err := os.ReadFile(image)
if err != nil {
return "", fmt.Errorf("读取文件失败: %v", err)
}
// 添加文件字段
part, err := writer.CreateFormFile("image", filepath.Base(image))
if err != nil {
return "", fmt.Errorf("创建表单文件失败: %v", err)
}
_, err = part.Write(fileData)
if err != nil {
return "", fmt.Errorf("写入文件数据失败: %v", err)
}
// 添加其他参数到multipart表单
for key, value := range params {
writer.WriteField(key, fmt.Sprintf("%v", value))
}
writer.WriteField("sign", sign) // 添加签名
writer.Close() // 关闭writer完成表单构建
// 发送POST请求
request := gorequest.New()
resp, respBody, errs := request.Post(kUrl).
Timeout(30*time.Second).
Set("Content-Type", writer.FormDataContentType()). // 设置Content-Type
Send(body.String()). // 发送请求体
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(respBody), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
/*
* 添加店铺商品
* param appId[int] appKey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* param shopItemAddJson[string] 店铺商品请求结构体字符串
* return 添加店铺商品响应结构体,错误信息
*/
func kongfzShopItemAdd(appId int, appSecret, accessToken string, shopItemAddJson string) (string, error) {
kUrl := fmt.Sprint("https://open.kongfz.com/router/rest")
dateTime := getCurrentTimeGMT8()
// 生成签名参数
params := map[string]interface{}{
"method": "kongfz.shop.item.add",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"simplify": 0,
}
if shopItemAddJson == "" {
return "", fmt.Errorf("shopItemAddJson 参数为空!")
}
toParams, err := addStructToParams(shopItemAddJson, params)
if err != nil {
return "", err
}
// 生成签名
sign := generateSign(toParams, appSecret)
if sign == "" {
return "", fmt.Errorf("生成 sign 签名错误!")
}
params["sign"] = sign
request := gorequest.New()
resp, body, errs := request.Get(kUrl).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(30 * time.Second).
Send(params).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
/*
* 查询订单列表
* param appId[int] appKey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* param orderListJson[string] 查询订单请求结构体字符串
* return 添加店铺商品响应结构体,错误信息
*/
func kongfzOrderList(appId int, appSecret, accessToken string, orderListJson string) (string, error) {
kUrl := fmt.Sprint("https://open.kongfz.com/router/rest")
dateTime := getCurrentTimeGMT8()
// 生成签名参数
params := map[string]interface{}{
"method": "kongfz.order.list",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"simplify": 0,
}
if orderListJson == "" {
return "", fmt.Errorf("orderListJson 参数为空!")
}
toParams, err := addStructToParams(orderListJson, params)
if err != nil {
return "", err
}
// 生成签名
sign := generateSign(toParams, appSecret)
if sign == "" {
return "", fmt.Errorf("生成 sign 签名错误!")
}
params["sign"] = sign
request := gorequest.New()
resp, body, errs := request.Post(kUrl).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Content-Type", "application/x-www-form-urlencoded").
Timeout(30 * time.Second).
Send(params).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
/*
* 查询单个订单
* param appId[int] appKey
* param appSecret[string] app密钥
* param accessToken[string] 访问token
* param userType[string] 查询订单请求结构体字符串
* param orderId[string] 查询订单请求结构体字符串
* return 添加店铺商品响应结构体,错误信息
*/
func kongfzOrderGet(appId int, appSecret, accessToken string, userType string, orderId int) (string, error) {
kUrl := fmt.Sprint("https://open.kongfz.com/router/rest")
dateTime := getCurrentTimeGMT8()
// 生成签名参数
params := map[string]interface{}{
"method": "kongfz.order.get",
"appId": appId,
"accessToken": accessToken,
"datetime": dateTime,
"format": "json",
"v": "1.0",
"signMethod": "md5",
"simplify": 0,
}
params["userType"] = userType
params["orderId"] = strconv.Itoa(orderId)
// 生成签名
sign := generateSign(params, appSecret)
if sign == "" {
return "", fmt.Errorf("生成 sign 签名错误!")
}
params["sign"] = sign
request := gorequest.New()
resp, body, errs := request.Post(kUrl).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Content-Type", "application/x-www-form-urlencoded").
Timeout(30 * time.Second).
Send(params).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// =============== 辅助函数 ==============
/*
* 将结构体的字段添加到 params 映射中
* param req[string] 需要合并的json字符串
* param params[map[string]interface{}] 合并的map
* return map响应结构体,错误信息
*/
func addStructToParams(req string, params map[string]interface{}) (map[string]interface{}, error) {
// 将JSON字符串解析为map
var tempMap map[string]interface{}
if err := json.Unmarshal([]byte(req), &tempMap); err != nil {
return nil, fmt.Errorf("解析 req json失败%v ", err)
}
// 合并到params
for k, v := range tempMap {
// 只添加非nil的值
if v != nil {
params[k] = v
}
}
return params, nil
}
/*
* 下载图片到本地
* param filePath[string] 文件路径
* param savePath[string] 需要保存的路径
* return 本地图片路径,错误信息
*/
func downloadImage(filePath, savePath string) (string, error) {
// 创建HTTP请求
request := gorequest.New()
resp, body, err := request.Get(filePath).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36").
EndBytes()
if err != nil {
return "", fmt.Errorf("请求失败: %v", err)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP 状态码错误: %d", resp.StatusCode)
}
errs := os.WriteFile(savePath, body, 0644)
if errs != nil {
return "", fmt.Errorf("保存文件失败: %v", errs)
}
return savePath, nil
}
/*
* 获取GMT+8当前时间的字符串格式
* return 返回时间字符串格式2006-01-02 15:04:05
*/
func getCurrentTimeGMT8() string {
// 创建北京时间GMT+8
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
loc = time.FixedZone("GMT+8", 8*60*60)
}
now := time.Now().In(loc)
return now.Format("2006-01-02 15:04:05")
}
/*
* 替换所有空白字符为空格
* return 返回字符串
*/
func cleanString(s string) string {
s = strings.ReplaceAll(s, "\n", "")
s = strings.ReplaceAll(s, "\r", "")
s = strings.ReplaceAll(s, "\t", "")
s = strings.ReplaceAll(s, " ", "")
return removeDuplicates(s)
}
/*
* 字符串去重
* return 返回字符串
*/
func removeDuplicates(s string) string {
seen := make(map[rune]bool)
var result strings.Builder
for _, r := range s {
if !seen[r] {
seen[r] = true
result.WriteRune(r)
}
}
return result.String()
}
/*
* 验证日期格式并转换为时间戳
* param dateStr[string] 时间字符串
* return 时间戳
*/
func validateDateFormat(dateStr string) int64 {
// 去除前后空格
dateStr = strings.TrimSpace(dateStr)
// 替换各种分隔符为统一的分隔符"-"
dateStr = regexp.MustCompile(`[/_\\.,\s]+`).ReplaceAllString(dateStr, "-")
// 处理纯年份格式 (4位数字)
if regexp.MustCompile(`^\d{4}$`).MatchString(dateStr) {
dateStr += "-01-01"
}
// 处理年月格式 (4位数字-1或2位数字)
if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})$`).FindStringSubmatch(dateStr); len(matches) == 3 {
year := matches[1]
month := matches[2]
if len(month) == 1 {
month = "0" + month
}
if monthNum, _ := strconv.Atoi(month); monthNum >= 1 && monthNum <= 12 {
dateStr = year + "-" + month + "-01"
} else {
return 0
}
}
// 处理年月日格式 (4位数字-1或2位数字-1或2位数字)
if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})-(\d{1,2})`).FindStringSubmatch(dateStr); len(matches) >= 4 {
year := matches[1]
month := matches[2]
day := matches[3]
// 标准化月份和日期为两位数
if len(month) == 1 {
month = "0" + month
}
if len(day) == 1 {
day = "0" + day
}
dateStr = year + "-" + month + "-" + day
}
// 加载上海时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
// 如果加载时区失败使用UTC+8作为后备方案
loc = time.FixedZone("CST", 8*60*60)
}
// 尝试解析为标准日期格式,并指定上海时区
parsedTime, err := time.ParseInLocation("2006-01-02", dateStr, loc)
if err != nil {
return 0
}
// 返回秒级时间戳UTC时间但解析时已经考虑了时区偏移
return parsedTime.Unix()
}
// 初始化配置--暂时没用
func initializeConfig(config Config) {
// 设置全局配置
cf = config
}
// =================== C 导出函数 =======================
// OutLogin 孔网登录
//
//export OutLogin
func OutLogin(username, password *C.char) *C.char {
goUsername := C.GoString(username)
goPassword := C.GoString(password)
respToken, err := outLogin(goUsername, goPassword)
// 构建响应数据
resp := struct {
Token string `json:"token"`
}{
Token: respToken,
}
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: resp,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetUserMsg 获取孔网用户信息
//
//export OutGetUserMsg
func OutGetUserMsg(token *C.char) *C.char {
goToken := C.GoString(token)
userInfo, err := outGetUserMsg(goToken)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: userInfo,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetGoodsTplMsg 获取商品模版--已登的店铺
//
//export OutGetGoodsTplMsg
func OutGetGoodsTplMsg(token, proxy, itemId *C.char) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goItemId := C.GoString(itemId)
info, err := outGetGoodsTplMsg(goToken, goProxy, goItemId)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: info,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺
//
//export OutGetGoodsListMsgFromSelfShop
func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char,
endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goItemSn := C.GoString(itemSn)
goPriceMin := C.GoString(priceMin)
goPriceMax := C.GoString(priceMax)
goStartCreateTime := C.GoString(startCreateTime)
goEndCreateTime := C.GoString(endCreateTime)
goRequestType := C.GoString(requestType)
goIsItemSnEqual := int(isItemSnEqual)
goPage := int(page)
goSize := int(size)
info, err := outGetGoodsListMsgFromSelfShop(goToken, goProxy, goItemSn, goPriceMin, goPriceMax, goStartCreateTime, goEndCreateTime, goRequestType, goIsItemSnEqual, goPage, goSize)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: info,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutAddGoods 新增商品-已登的店铺
//
//export OutAddGoods
func OutAddGoods(token, proxy, formData *C.char) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goFormData := C.GoString(formData)
info, err := outAddGoods(goToken, goProxy, goFormData)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: info,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutAddGoodsAndFile 整合添加商品和获取孔网图片
//
//export OutAddGoodsAndFile
func OutAddGoodsAndFile(token, proxy, filePath, formData *C.char) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goFilePath := C.GoString(filePath)
goFormData := C.GoString(formData)
addGoodsAndFile, err := outAddGoodsAndFile(goToken, goProxy, goFilePath, goFormData)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(addGoodsAndFile)
}
// OutDelGoodsFromSelfShop 删除商品-已登的店铺
//
//export OutDelGoodsFromSelfShop
func OutDelGoodsFromSelfShop(token, proxy, itemId *C.char) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goItemId := C.GoString(itemId)
info, err := outDelGoodsFromSelfShop(goToken, goProxy, goItemId)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: info,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetImageFilterShopId 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
//
//export OutGetImageFilterShopId
func OutGetImageFilterShopId(token, proxy, isbn *C.char, shopId C.int, isLiveImage C.int, isReturnMsg C.int) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goIsbn := C.GoString(isbn)
goShopId := int(shopId)
goIsLiveImage := int(isLiveImage)
goIsReturnMsg := int(isReturnMsg)
info, err := outGetImageFilterShopId(goToken, goProxy, goIsbn, goShopId, goIsLiveImage, goIsReturnMsg)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: info,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图)
//
//export OutGetImageByIsbn
func OutGetImageByIsbn(token, proxy, isbn *C.char, isLiveImage C.int, isReturnMsg C.int) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goIsbn := C.GoString(isbn)
goIsLiveImage := int(isLiveImage)
goIsReturnMsg := int(isReturnMsg)
bookInfo, err := outGetImageByIsbn(goToken, goProxy, goIsbn, goIsLiveImage, goIsReturnMsg)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: bookInfo,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetMidDetail 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息
//
//export OutGetMidDetail
func OutGetMidDetail(token, mid *C.char) *C.char {
goToken := C.GoString(token)
gomid := C.GoString(mid)
bookInfo, err := outGetMidDetail(goToken, gomid)
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: bookInfo,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID
//
//export OutGetGoodsListMsgByShopId
func OutGetGoodsListMsgByShopId(token *C.char, shopId C.int, proxy *C.char, retPrice C.int, isImage C.int, sortType *C.char, sort *C.char, priceMin C.float, priceMax C.float, pageNum, returnNum C.int) *C.char {
goToken := C.GoString(token)
goShopId := int(shopId)
goProxy := C.GoString(proxy)
goRetPrice := int(retPrice)
goIsImage := int(isImage)
goSortType := C.GoString(sortType)
goSort := C.GoString(sort)
goPriceMin := float32(priceMin)
goPriceMax := float32(priceMax)
goPageNum := int(pageNum)
goReturnNum := int(returnNum)
books, num, pNum, err := outGetGoodsListMsgByShopId(goToken, goShopId, goProxy, goRetPrice, goIsImage,
goSortType, goSort, goPriceMin, goPriceMax, goPageNum, goReturnNum)
// 构建统一格式的响应
bookInfo := struct {
GoodsNum string `json:"goods_num,omitempty"`
PNum string `json:"pnum,omitempty"`
BookInfo interface{} `json:"book_info,omitempty"`
}{
GoodsNum: num,
PNum: pNum,
BookInfo: books,
}
// 构建API响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: bookInfo,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接
//
//export OutGetGoodsMsgByDetailUrl
func OutGetGoodsMsgByDetailUrl(detailUrl, proxy *C.char) *C.char {
goDetailUrl := C.GoString(detailUrl)
goProxy := C.GoString(proxy)
response, err := outGetGoodsMsgByDetailUrl(goDetailUrl, goProxy)
// 构建统一格式的响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: response,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// OutGetTopGoodsListMsg 获取销量榜商品列表
//
//export OutGetTopGoodsListMsg
func OutGetTopGoodsListMsg(catId C.int, proxy *C.char) *C.char {
goCatId := int(catId)
goProxy := C.GoString(proxy)
response, err := outGetTopGoodsListMsg(goCatId, goProxy)
// 构建统一格式的响应
var apiResponse APIResponse
if err != nil {
apiResponse = APIResponse{
Success: false,
Message: err.Error(),
}
} else {
apiResponse = APIResponse{
Success: true,
Data: response,
}
}
// 转换为JSON字符串
jsonData, marshalErr := json.Marshal(apiResponse)
if marshalErr != nil {
// 如果JSON序列化失败返回错误信息
apiResponse = APIResponse{
Success: false,
Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr),
}
errorJson, _ := json.Marshal(apiResponse)
return C.CString(string(errorJson))
}
return C.CString(string(jsonData))
}
// KongfzDeliveryMethodList 获取配送方式列表
//
//export KongfzDeliveryMethodList
func KongfzDeliveryMethodList(appId C.int, appSecret, accessToken *C.char) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
list, err := kongfzDeliveryMethodList(goAppId, goAppSecret, goAccessToken)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(list)
}
// KongfzOrderDeliver 订单发货
//
//export KongfzOrderDeliver
func KongfzOrderDeliver(appId C.int, appSecret, accessToken *C.char,
orderId C.int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum *C.char) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
goOrderId := int(orderId)
goShippingId := C.GoString(shippingId)
goShippingCom := C.GoString(shippingCom)
goShipmentNum := C.GoString(shipmentNum)
goUserDefined := C.GoString(userDefined)
goMoreShipmentNum := C.GoString(moreShipmentNum)
deliver, err := kongfzOrderDeliver(goAppId, goAppSecret, goAccessToken,
goOrderId, goShippingId, goShippingCom, goShipmentNum, goUserDefined, goMoreShipmentNum)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(deliver)
}
// KongfzOrderSynchronization 孔网订单同步
//
//export KongfzOrderSynchronization
func KongfzOrderSynchronization(appId C.int, appSecret, accessToken, shippingComName *C.char,
orderId C.int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum *C.char) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
goShippingComName := C.GoString(shippingComName)
goOrderId := int(orderId)
goShippingId := C.GoString(shippingId)
goShippingCom := C.GoString(shippingCom)
goShipmentNum := C.GoString(shipmentNum)
goUserDefined := C.GoString(userDefined)
goMoreShipmentNum := C.GoString(moreShipmentNum)
synchronization, err := kongfzOrderSynchronization(goAppId, goAppSecret, goAccessToken, goShippingComName,
goOrderId, goShippingId, goShippingCom, goShipmentNum, goUserDefined, goMoreShipmentNum)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(synchronization)
}
// KongfzImageUpload 上传图片接口
//
//export KongfzImageUpload
func KongfzImageUpload(appId C.int, appSecret, accessToken, filePath, savePath *C.char) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
goFilePath := C.GoString(filePath)
goSavePath := C.GoString(savePath)
upload, err := kongfzImageUpload(goAppId, goAppSecret, goAccessToken, goFilePath, goSavePath)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(upload)
}
// KongfzShopItemAdd 添加店铺商品
//
//export KongfzShopItemAdd
func KongfzShopItemAdd(appId C.int, appSecret, accessToken, shopItemAddJson *C.char) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
goShopItemAddJson := C.GoString(shopItemAddJson)
upload, err := kongfzShopItemAdd(goAppId, goAppSecret, goAccessToken, goShopItemAddJson)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(upload)
}
// KongfzOrderList 查询订单列表
//
//export KongfzOrderList
func KongfzOrderList(appId C.int, appSecret, accessToken *C.char, orderListJson *C.char) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
goOrderListJson := C.GoString(orderListJson)
list, err := kongfzOrderList(goAppId, goAppSecret, goAccessToken, goOrderListJson)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(list)
}
// KongfzOrderGet 查询单个订单
//
//export KongfzOrderGet
func KongfzOrderGet(appId C.int, appSecret, accessToken *C.char, userType *C.char, orderId C.int) *C.char {
goAppId := int(appId)
goAppSecret := C.GoString(appSecret)
goAccessToken := C.GoString(accessToken)
goUserType := C.GoString(userType)
goOrderId := int(orderId)
orderGet, err := kongfzOrderGet(goAppId, goAppSecret, goAccessToken, goUserType, goOrderId)
if err != nil {
return C.CString(fmt.Sprint(err))
}
return C.CString(orderGet)
}
// Initialize 初始化配置
//
//export Initialize
func Initialize(configJSON *C.char) *C.char {
configStr := C.GoString(configJSON)
log.Printf("[DEBUG] 接收到的配置JSON: %s", configStr)
var config Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf(`{"success":false,"message":"配置解析失败: %v"}`, err))
}
initializeConfig(config)
return C.CString(`{"success":true,"message":"初始化成功"}`)
}
// FreeCString 释放C字符串内存
//
//export FreeCString
func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str))
}
// CSV_VERSION 版本号
const (
CSV_VERSION = "v3"
)
// 获取版本信息
//
//export GetVersion
func GetVersion() *C.char {
return C.CString(CSV_VERSION)
}
// 空main函数编译DLL时需要
func main() {
}