修改获取店铺商品接口,生成so文件,调用so文件接口

This commit is contained in:
Cai1Cai1 2025-11-28 14:03:15 +08:00
parent aed65dfb31
commit 8a111054a6
14 changed files with 5474 additions and 3679 deletions

Binary file not shown.

View File

@ -23,11 +23,11 @@ extern const char *_GoStringPtr(_GoString_ s);
#line 3 "main.go" #line 3 "main.go"
#include <stdlib.h> #include <stdlib.h>
// proxyConfig.dll 函数声明 // proxyConfig.dll 函数声明
extern char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode); extern char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
extern void FreeCString(char* str); extern void FreeCString(char* str);
#line 1 "cgo-generated-wrapper" #line 1 "cgo-generated-wrapper"
@ -116,13 +116,17 @@ extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* pr
// //
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData); extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
// 获取图片URL(官图和拍图)带有店铺过滤
//
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* isbn, int shopId, char* proxy, int isLiveImage, int isReturnMsg);
// 获取商品图片(带有Out的都非官方标准接口) // 获取商品图片(带有Out的都非官方标准接口)
// //
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg); extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg);
// 获取商品列表通过店铺ID(带有Out的都非官方标准接口) // 获取商品列表通过店铺ID(带有Out的都非官方标准接口)
// //
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum); extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
// 获取商品信息通过商品详情链接(带有0ut的都非官方标准接口) // 获取商品信息通过商品详情链接(带有0ut的都非官方标准接口)
// //

Binary file not shown.

View File

@ -21,13 +21,8 @@ extern const char *_GoStringPtr(_GoString_ s);
/* Start of preamble from import "C" comments. */ /* Start of preamble from import "C" comments. */
#line 3 "kfztp.go" #line 3 "proxyConfig.go"
#include <stdlib.h>
#include <stdlib.h>
// proxyConfig.dll 函数声明
extern char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
extern void FreeCString(char* str);
#line 1 "cgo-generated-wrapper" #line 1 "cgo-generated-wrapper"
@ -92,24 +87,13 @@ extern "C" {
#endif #endif
// 获取商品图片(带有Out的都非官方标准接口) // 导出函数:获取代理健康状态(用于调试)
// //
extern __declspec(dllexport) char* OutGetImageByIsbn(char* isbn, char* proxy, _Bool isLiveImage, _Bool isReturnMsg); extern __declspec(dllexport) char* GetProxyHealth(void);
// 获取商品列表通过店铺ID(带有Out的都非官方标准接口) // 导出函数:代理类型管理器
// //
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, _Bool isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum); extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
// 获取商品信息通过商品详情链接(带有0ut的都非官方标准接口)
//
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
// 获取销量榜商品列表(带有Out的都非官放标准接口)
//
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
extern __declspec(dllexport) char* GetKFZSPTImageURL(char* proxyType, char* username, char* password, char* machineCode, char* isbn);
extern __declspec(dllexport) char* GetKFZGTImageURL(char* proxyType, char* username, char* password, char* machineCode, char* isbn);
extern __declspec(dllexport) char* Initialize(char* configJSON);
// 导出函数释放C字符串内存 // 导出函数释放C字符串内存
// //

496
dyso.go Normal file
View File

@ -0,0 +1,496 @@
package main
/*
#cgo linux LDFLAGS: -ldl
#cgo windows LDFLAGS: -lkernel32
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
static void* dlopen(const char* filename, int flags) {
return (void*)LoadLibraryA(filename);
}
static void dlclose(void* handle) {
FreeLibrary((HMODULE)handle);
}
static void* dlsym(void* handle, const char* name) {
return (void*)GetProcAddress((HMODULE)handle, name);
}
static const char* dlerror() {
static char buf[256];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), 0, buf, sizeof(buf), NULL);
return buf;
}
#define RTLD_LAZY 0
#else
#include <dlfcn.h>
#endif
*/
import "C"
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"unsafe"
)
// 配置结构
type Configs struct {
App struct {
MaxRetryTimes int `json:"max_retry_times"`
RateLimitDelay int `json:"rate_limit_delay"`
Size int `json:"size"`
DefaultUserAgent string `json:"default_user_agent"`
} `json:"app"`
API struct {
LoginURL string `json:"login_url"`
BookSearchURL string `json:"book_search_url"`
ProductSearchURL string `json:"product_search_url"`
} `json:"api"`
Proxy struct {
Servers string `json:"servers"`
Username string `json:"username"`
Password string `json:"password"`
TailMachineCode string `json:"tail_machine_code"`
TailCardKey string `json:"tail_card_key"`
ProxyFilePath string `json:"proxy_file_path"`
} `json:"proxy"`
}
// API响应结构
type APIResponseSO struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
GoodsNum string `json:"goods_num,omitempty"`
PNum string `json:"pnum,omitempty"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// APIResp 简化的响应结构体
type APIRespSO struct {
Success bool `json:"success"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
type SOManager struct {
handle unsafe.Pointer
}
func NewSOManager(soPath string) (*SOManager, error) {
// 根据平台调整库文件扩展名
if runtime.GOOS == "windows" {
soPath = changeExtensionToDLL(soPath)
}
// 使用 dlopen 加载 .so 文件
cSoPath := C.CString(soPath)
defer C.free(unsafe.Pointer(cSoPath))
handle := C.dlopen(cSoPath, C.RTLD_LAZY)
if handle == nil {
return nil, fmt.Errorf("加载SO文件失败: %s", C.GoString(C.dlerror()))
}
return &SOManager{handle: handle}, nil
}
func (m *SOManager) Close() {
if m.handle != nil {
C.dlclose(m.handle)
}
}
func changeExtensionToDLL(path string) string {
ext := filepath.Ext(path)
if ext == ".so" {
return path[:len(path)-len(ext)] + ".dll"
}
return path
}
// 获取函数指针
func (m *SOManager) getFunction(funcName string) (unsafe.Pointer, error) {
cFuncName := C.CString(funcName)
defer C.free(unsafe.Pointer(cFuncName))
symbol := C.dlsym(m.handle, cFuncName)
if symbol == nil {
return nil, fmt.Errorf("找不到函数 %s: %s", funcName, C.GoString(C.dlerror()))
}
return symbol, nil
}
// 初始化
func (m *SOManager) Initialize(configJSON string) (string, error) {
function, err := m.getFunction("Initialize")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char) *C.char)(unsafe.Pointer(&function))
configJSONC := C.CString(configJSON)
defer C.free(unsafe.Pointer(configJSONC))
result := (*funcPtr)(configJSONC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 登录
func (m *SOManager) OutLogin(username, password string) (string, error) {
function, err := m.getFunction("OutLogin")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char) *C.char)(unsafe.Pointer(&function))
usernameC := C.CString(username)
passwordC := C.CString(password)
defer C.free(unsafe.Pointer(usernameC))
defer C.free(unsafe.Pointer(passwordC))
result := (*funcPtr)(usernameC, passwordC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取用户信息
func (m *SOManager) OutGetUserMsg(token string) (string, error) {
function, err := m.getFunction("OutGetUserMsg")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
defer C.free(unsafe.Pointer(tokenC))
result := (*funcPtr)(tokenC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取商品模版
func (m *SOManager) OutGetGoodsTplMsg(token, itemId, proxy string) (string, error) {
function, err := m.getFunction("OutGetGoodsTplMsg")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
itemIdC := C.CString(itemId)
proxyC := C.CString(proxy)
defer C.free(unsafe.Pointer(tokenC))
defer C.free(unsafe.Pointer(itemIdC))
defer C.free(unsafe.Pointer(proxyC))
result := (*funcPtr)(tokenC, itemIdC, proxyC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取商品列表-已登的店铺
func (m *SOManager) OutGetGoodsListMsgFromSelfShop(token string, proxy string,
itemSn string, priceMin string, priceMax string, startCreateTime int,
endCreateTime int, requestType string, isItemSnEqual int, page int, size int) (string, error) {
function, err := m.getFunction("OutGetGoodsListMsgFromSelfShop")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char, *C.char, *C.char, *C.char, C.int, C.int, *C.char, C.int, C.int, C.int) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
proxyC := C.CString(proxy)
itemSnC := C.CString(itemSn)
priceMinC := C.CString(priceMin)
priceMaxC := C.CString(priceMax)
startCreateTimeC := C.int(startCreateTime)
endCreateTimeC := C.int(endCreateTime)
requestTypeC := C.CString(requestType)
isItemSnEqualC := C.int(isItemSnEqual)
pageC := C.int(page)
sizeC := C.int(size)
defer C.free(unsafe.Pointer(tokenC))
defer C.free(unsafe.Pointer(proxyC))
defer C.free(unsafe.Pointer(itemSnC))
defer C.free(unsafe.Pointer(priceMinC))
defer C.free(unsafe.Pointer(priceMaxC))
defer C.free(unsafe.Pointer(requestTypeC))
result := (*funcPtr)(tokenC, proxyC, itemSnC, priceMinC, priceMaxC, startCreateTimeC,
endCreateTimeC, requestTypeC, isItemSnEqualC, pageC, sizeC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 删除商品-已登的店铺
func (m *SOManager) OutDelGoodsFromSelfShop(token, itemId, proxy string) (string, error) {
function, err := m.getFunction("OutDelGoodsFromSelfShop")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
itemIdC := C.CString(itemId)
proxyC := C.CString(proxy)
defer C.free(unsafe.Pointer(tokenC))
defer C.free(unsafe.Pointer(itemIdC))
defer C.free(unsafe.Pointer(proxyC))
result := (*funcPtr)(tokenC, itemIdC, proxyC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 新增商品
func (m *SOManager) OutAddGoods(token, proxy, formData string) (string, error) {
function, err := m.getFunction("OutAddGoods")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
proxyC := C.CString(proxy)
formDataC := C.CString(formData)
defer C.free(unsafe.Pointer(tokenC))
defer C.free(unsafe.Pointer(proxyC))
defer C.free(unsafe.Pointer(formDataC))
result := (*funcPtr)(tokenC, proxyC, formDataC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取图片URL(官图和拍图)带有店铺过滤
func (m *SOManager) OutGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (string, error) {
function, err := m.getFunction("OutGetImageFilterShopId")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char, C.int, *C.char, C.int, C.int) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
isbnC := C.CString(isbn)
shopIdC := C.int(shopId)
proxyC := C.CString(proxy)
isLiveImageC := C.int(isLiveImage)
isReturnMsgC := C.int(isReturnMsg)
defer C.free(unsafe.Pointer(tokenC))
defer C.free(unsafe.Pointer(isbnC))
defer C.free(unsafe.Pointer(proxyC))
result := (*funcPtr)(tokenC, isbnC, shopIdC, proxyC, isLiveImageC, isReturnMsgC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取商品图片
func (m *SOManager) OutGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (string, error) {
function, err := m.getFunction("OutGetImageByIsbn")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char, *C.char, C.int, C.int) *C.char)(unsafe.Pointer(&function))
tokenC := C.CString(token)
isbnC := C.CString(isbn)
proxyC := C.CString(proxy)
isLiveImageC := C.int(isLiveImage)
isReturnMsgC := C.int(isReturnMsg)
defer C.free(unsafe.Pointer(tokenC))
defer C.free(unsafe.Pointer(isbnC))
defer C.free(unsafe.Pointer(proxyC))
result := (*funcPtr)(tokenC, isbnC, proxyC, isLiveImageC, isReturnMsgC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取商品列表通过店铺ID
func (m *SOManager) OutGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage int, sortType string,
sort string, priceMin float32, priceMax float32, pageNum, returnNum int) (string, error) {
function, err := m.getFunction("OutGetGoodsListMsgByShopId")
if err != nil {
return "", err
}
funcPtr := (*func(C.int, *C.char, C.int, C.int, *C.char, *C.char, C.double, C.double, C.int, C.int) *C.char)(unsafe.Pointer(&function))
shopIdC := C.int(shopId)
proxyC := C.CString(proxy)
retPriceC := C.int(retPrice)
isImageC := C.int(isImage)
sortTypeC := C.CString(sortType)
sortC := C.CString(sort)
priceMinC := C.double(priceMin)
priceMaxC := C.double(priceMax)
pageNumC := C.int(pageNum)
returnNumC := C.int(returnNum)
defer C.free(unsafe.Pointer(proxyC))
defer C.free(unsafe.Pointer(sortTypeC))
defer C.free(unsafe.Pointer(sortC))
result := (*funcPtr)(shopIdC, proxyC, retPriceC, isImageC, sortTypeC, sortC, priceMinC, priceMaxC, pageNumC, returnNumC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取商品信息通过商品详情链接
func (m *SOManager) OutGetGoodsMsgByDetailUrl(detailUrl, proxy string) (string, error) {
function, err := m.getFunction("OutGetGoodsMsgByDetailUrl")
if err != nil {
return "", err
}
funcPtr := (*func(*C.char, *C.char) *C.char)(unsafe.Pointer(&function))
detailUrlC := C.CString(detailUrl)
proxyC := C.CString(proxy)
defer C.free(unsafe.Pointer(detailUrlC))
defer C.free(unsafe.Pointer(proxyC))
result := (*funcPtr)(detailUrlC, proxyC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 获取销量榜商品列表
func (m *SOManager) OutGetTopGoodsListMsg(catId int, proxy string) (string, error) {
function, err := m.getFunction("OutGetTopGoodsListMsg")
if err != nil {
return "", err
}
funcPtr := (*func(C.int, *C.char) *C.char)(unsafe.Pointer(&function))
catIdC := C.int(catId)
proxyC := C.CString(proxy)
defer C.free(unsafe.Pointer(proxyC))
result := (*funcPtr)(catIdC, proxyC)
defer C.free(unsafe.Pointer(result))
return C.GoString(result), nil
}
// 创建默认配置
func createDefaultConfig() Configs {
var configs Configs
// App配置
configs.App.MaxRetryTimes = 3
configs.App.RateLimitDelay = 1000
configs.App.Size = 10
configs.App.DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
// API配置
configs.API.LoginURL = "https://login.kongfz.com/Pc/Login/account"
configs.API.BookSearchURL = "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list"
configs.API.ProductSearchURL = "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list"
// 代理配置
configs.Proxy.Servers = "http-dynamic.xiaoxiangdaili.com,http-dynamic-S02.xiaoxiangdaili.com,http-dynamic-S03.xiaoxiangdaili.com,http-dynamic-S04.xiaoxiangdaili.com"
configs.Proxy.Username = "1297757178467602432"
configs.Proxy.Password = "QgQBvP7f"
configs.Proxy.TailMachineCode = "b7bf22a237ec692f13fcc2c43ee63252"
configs.Proxy.TailCardKey = "DL_20_YK_1920acb2129844c2aabade3896560a9b"
configs.Proxy.ProxyFilePath = "so/proxyConfig.so"
return configs
}
// 获取当前可执行文件所在目录
func getExecutableDir() string {
exePath, err := os.Executable()
if err != nil {
return "."
}
return filepath.Dir(exePath)
}
func main() {
http.HandleFunc("/api/kfzShopBookInfo", handleGetKFZShopBookInfo)
port := "8080"
log.Printf("🚀 服务器启动在端口 %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("服务器启动失败: %v", err)
}
}
func handleGetKFZShopBookInfo(w http.ResponseWriter, r *http.Request) {
// 设置响应头
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 只支持GET请求
if r.Method != http.MethodGet {
sendErrorResponse(w, http.StatusMethodNotAllowed, "只支持GET方法")
return
}
// 加载SO
manager, err := NewSOManager("so/kongfz.so")
if err != nil {
log.Printf("初始化SO管理器失败: %v", err)
sendErrorResponse(w, http.StatusInternalServerError, "初始化SO管理器失败")
return
}
defer manager.Close()
log.Println("✅ SO加载成功")
// 使用默认配置初始化
config := createDefaultConfig()
configJSON, err := json.Marshal(config)
if err != nil {
log.Printf("序列化配置失败: %v", err)
sendErrorResponse(w, http.StatusInternalServerError, "序列化配置失败")
return
}
result, err := manager.Initialize(string(configJSON))
if err != nil {
log.Printf("初始化失败: %v", err)
sendErrorResponse(w, http.StatusInternalServerError, "初始化失败")
return
}
var initResp APIResponseSO
if err := json.Unmarshal([]byte(result), &initResp); err != nil {
log.Printf("解析初始化响应失败: %v", err)
sendErrorResponse(w, http.StatusInternalServerError, "解析初始化响应失败")
return
}
if !initResp.Success {
log.Printf("初始化失败: %s", initResp.Message)
sendErrorResponse(w, http.StatusInternalServerError, "初始化失败: "+initResp.Message)
return
}
log.Println("✅ SO初始化成功")
// 登录示例
user, err := manager.OutLogin("18904056801", "Long6166@@")
if err != nil {
log.Printf("登录失败: %v", err)
} else {
log.Printf("登录结果: %s", user)
}
// 解析token
var data APIResponseSO
if err := json.Unmarshal([]byte(user), &data); err != nil {
log.Printf("解析登录响应失败: %v", err)
} else {
var token string
if dataMap, ok := data.Data.(map[string]interface{}); ok {
if tk, exists := dataMap["token"]; exists {
token = tk.(string)
log.Printf("获取到Token: %s", token)
// 使用token获取用户信息
msg, err := manager.OutGetUserMsg(token)
if err != nil {
log.Printf("获取用户信息失败: %v", err)
} else {
log.Printf("用户信息: %s", msg)
}
} else {
log.Println("Token 不存在")
}
} else {
log.Println("Data 格式不正确")
}
}
// 返回成功响应
response := APIRespSO{
Success: true,
Message: "SO调用成功",
}
json.NewEncoder(w).Encode(response)
}
// 发送错误响应
func sendErrorResponse(w http.ResponseWriter, statusCode int, message string) {
response := APIRespSO{
Success: false,
Message: message,
}
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(response)
}

View File

@ -1,378 +0,0 @@
// Package utils 提供配置文件加载工具支持从INI文件加载配置到结构体
// 支持类型: int, string, bool, float, time.Duration 及其切片类型
// 特性:
// - 支持默认值标签 `default`
// - 支持INI路径标签 `ini:"section.key"`
// - 支持时间单位转换标签 `multiplier`
// - 自动处理切片类型(逗号分隔)
package main
import (
"log"
"os"
"reflect"
"regexp"
"strconv"
"time"
"github.com/go-ini/ini"
)
// LoadConfig 从INI文件加载配置到结构体
// 参数:
//
// configPtr: 指向配置结构体的指针(必须是结构体指针)
// filename: INI配置文件的路径
//
// 返回:
//
// error: 加载成功返回nil失败返回ConfigError详细错误信息
//
// 用法说明:
// 1. 定义配置结构体使用标签声明INI映射关系和默认值
// 2. 调用LoadConfig(&config, "config.ini")
// 3. 检查错误并应用配置
//
// 示例结构体:
//
// type AppConfig struct {
// Port int `ini:"server.port" default:"8080"`
// Timeout time.Duration `ini:"server.timeout" multiplier:"1s"`
// Features []string `ini:"server.features"`
// }
func LoadConfig(configPtr interface{}, filename string) error {
// 验证输入必须是指针
if reflect.TypeOf(configPtr).Kind() != reflect.Ptr {
return &ConfigError{Message: "configPtr必须是指向结构体的指针"} // 返回错误
}
// 验证输入必须指向结构体
configValue := reflect.ValueOf(configPtr).Elem()
// 确保输入是结构体
if configValue.Kind() != reflect.Struct {
return &ConfigError{Message: "configPtr必须指向结构体"} // 返回错误
}
// 设置默认值(如果结构体有默认值)
setDefaultValues(configValue)
// 检查配置文件是否存在
if _, err := os.Stat(filename); os.IsNotExist(err) {
log.Printf("配置文件不存在: %s, 使用默认值", filename) // 打印信息
return nil // 返回错误
}
// 加载INI文件
iniCfg, err := ini.Load(filename)
// 处理错误
if err != nil {
return &ConfigError{Message: "加载配置文件失败", Cause: err} // 返回错误
}
// 映射配置到结构体
if err := mapConfig(iniCfg, configValue); err != nil {
return err // 返回错误
}
// 处理特殊类型如time.Duration
processSpecialTypes(configValue)
// 返回成功
return nil
}
// setDefaultValues 设置结构体字段的默认值
// 遍历结构体字段,检测`default`标签并设置初始值
// setDefaultValues 设置结构体字段的默认值
func setDefaultValues(configValue reflect.Value) {
// 获取结构体类型
configType := configValue.Type()
// 遍历字段
for i := 0; i < configType.NumField(); i++ {
// 获取字段信息
field := configType.Field(i)
// 获取字段值
fieldValue := configValue.Field(i)
// 如果字段是结构体,递归处理
if fieldValue.Kind() == reflect.Struct {
setDefaultValues(fieldValue)
continue
}
// 如果字段已经设置了值,跳过
if !fieldValue.IsZero() {
continue // 跳过
}
// 检查是否有默认值标签
if defaultValue, ok := field.Tag.Lookup("default"); ok {
setValueFromString(fieldValue, defaultValue) // 设置字段值
}
}
}
// mapConfig 将INI配置映射到结构体字段
// 解析`ini`标签获取section和key读取对应配置值
func mapConfig(iniCfg *ini.File, configValue reflect.Value) error {
// 获取结构体类型
configType := configValue.Type()
// 遍历字段
for i := 0; i < configType.NumField(); i++ {
field := configType.Field(i) // 获取字段信息
fieldValue := configValue.Field(i) // 获取字段值
// 如果字段是结构体,递归处理
if fieldValue.Kind() == reflect.Struct {
if err := mapConfig(iniCfg, fieldValue); err != nil {
return err
}
continue
}
// 获取INI标签
iniTag, ok := field.Tag.Lookup("ini")
// 如果没有INI标签跳过这个字段
if !ok || iniTag == "" {
continue // 跳过
}
// 解析INI键名支持section.key格式
sectionName, keyName := parseIniTag(iniTag)
// 获取INI值
section, err := iniCfg.GetSection(sectionName)
// 如果section不存在
if err != nil {
continue // 跳过这个字段
}
// 获取INI键值
key, err := section.GetKey(keyName)
// 如果key不存在
if err != nil {
continue //跳过这个字段
}
// 设置结构体字段值
if err := setFieldValue(fieldValue, key); err != nil {
// 返回错误
return &ConfigError{
Message: "设置字段值失败", // 返回错误信息
Cause: err, // 返回错误原因
Field: field.Name, // 返回字段名称
Tag: iniTag, // 返回标签
}
}
}
return nil
}
// parseIniTag 解析INI标签格式
// 输入: "section.key" 格式的字符串
// 返回: (section名称, key名称)
func parseIniTag(tag string) (section, key string) {
// 默认section
section = "DEFAULT"
// 默认key
key = tag
// 检查是否有section前缀
if parts := regexp.MustCompile(`^(\w+)\.(\w+)$`).FindStringSubmatch(tag); len(parts) == 3 {
section = parts[1] // 设置section
key = parts[2] // 设置key
}
// 返回section和key
return section, key
}
// setFieldValue 根据INI键值设置结构体字段值
// 自动处理基础类型和time.Duration类型转换
func setFieldValue(fieldValue reflect.Value, key *ini.Key) error {
// 检查字段类型
switch fieldValue.Kind() {
case reflect.String: // 字符串类型
fieldValue.SetString(key.String()) // 设置字段值
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 整数类型
// 特殊处理time.Duration类型
if fieldValue.Type() == reflect.TypeOf(time.Duration(0)) {
duration, err := time.ParseDuration(key.String()) // 解析字符串为time.Duration
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetInt(int64(duration)) // 设置字段值
} else {
intValue, err := key.Int() // 获取整数值
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetInt(int64(intValue)) // 设置字段值
}
case reflect.Bool: // 布尔类型
boolValue, err := key.Bool() // 获取布尔值
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetBool(boolValue) // 设置字段值
case reflect.Float32, reflect.Float64: // 浮点类型
floatValue, err := key.Float64() // 获取浮点值
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetFloat(floatValue) // 设置字段值
case reflect.Slice: // 切片类型
return setSliceValue(fieldValue, key) // 处理切片类型字段
default: // 其他类型
return &ConfigError{Message: "不支持的字段类型", FieldType: fieldValue.Type().String()} // 返回错误
}
// 返回成功
return nil
}
// setSliceValue 设置切片类型字段值
// 将逗号分隔的字符串解析为指定类型的切片
func setSliceValue(fieldValue reflect.Value, key *ini.Key) error {
// 获取切片元素类型
sliceType := fieldValue.Type().Elem()
// 获取逗号分隔的值
values := key.Strings(",")
// 创建新切片
slice := reflect.MakeSlice(fieldValue.Type(), len(values), len(values))
// 遍历值
for i, val := range values {
elemValue := reflect.New(sliceType).Elem() // 创建新元素
// 根据切片元素类型设置值
switch sliceType.Kind() {
case reflect.String: // 字符串类型
elemValue.SetString(val) // 设置字段值
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 整数类型
intVal, err := strconv.ParseInt(val, 10, 64) // 解析字符串为整数
// 处理错误
if err != nil {
return err // 返回错误
}
elemValue.SetInt(intVal) // 设置字段值
case reflect.Float32, reflect.Float64: // 浮点类型
floatVal, err := strconv.ParseFloat(val, 64) // 解析字符串为浮点数
// 处理错误
if err != nil {
return err // 返回错误
}
elemValue.SetFloat(floatVal) // 设置字段值
case reflect.Bool: // 布尔类型
boolVal, err := strconv.ParseBool(val) // 解析字符串为布尔值
// 处理错误
if err != nil {
return err // 返回错误
}
elemValue.SetBool(boolVal) // 设置字段值
default:
return &ConfigError{Message: "不支持的切片元素类型", FieldType: sliceType.String()} // 返回错误
}
slice.Index(i).Set(elemValue) // 设置切片元素
}
// 设置切片字段值
fieldValue.Set(slice)
// 返回成功
return nil
}
// setValueFromString 从字符串解析值到结构体字段(用于默认值)
// 支持基础类型转换,不处理复杂类型
func setValueFromString(fieldValue reflect.Value, valueStr string) {
switch fieldValue.Kind() {
case reflect.String:
fieldValue.SetString(valueStr)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if intValue, err := strconv.ParseInt(valueStr, 10, 64); err == nil {
fieldValue.SetInt(intValue)
}
case reflect.Bool:
if boolValue, err := strconv.ParseBool(valueStr); err == nil {
fieldValue.SetBool(boolValue)
}
case reflect.Float32, reflect.Float64:
if floatValue, err := strconv.ParseFloat(valueStr, 64); err == nil {
fieldValue.SetFloat(floatValue)
}
case reflect.Slice:
// 切片类型需要特殊处理,这里简化处理
default:
// 保留原panic调用但修改提示信息
panic("未处理的默认值类型")
}
}
// processSpecialTypes 处理特殊类型转换
// 当前支持time.Duration的倍数转换使用multiplier标签
// processSpecialTypes 处理特殊类型转换
func processSpecialTypes(configValue reflect.Value) {
// 获取结构体类型
configType := configValue.Type()
// 遍历结构体字段
for i := 0; i < configType.NumField(); i++ {
field := configType.Field(i) // 获取字段
fieldValue := configValue.Field(i) // 获取字段值
// 如果字段是结构体,递归处理
if fieldValue.Kind() == reflect.Struct {
processSpecialTypes(fieldValue)
continue
}
// 处理time.Duration类型的倍数转换
if fieldValue.Type() == reflect.TypeOf(time.Duration(0)) {
// 检查是否存在multiplier标签
if multiplier, ok := field.Tag.Lookup("multiplier"); ok {
// 解析multiplier标签
if mult, err := time.ParseDuration(multiplier); err == nil {
duration := time.Duration(fieldValue.Int()) // 将字段值转换为time.Duration
fieldValue.SetInt(int64(duration * mult)) // 将字段值设置为转换后的时间
}
}
}
}
}
// ConfigError 自定义配置错误类型
// 包含错误原因、字段信息和原始错误
type ConfigError struct {
Message string
Cause error
Field string
FieldType string
Tag string
}
// Error 实现error接口提供详细错误信息
func (e *ConfigError) Error() string {
msg := "配置错误: " + e.Message
if e.Field != "" {
msg += " [字段: " + e.Field + "]"
}
if e.FieldType != "" {
msg += " [类型: " + e.FieldType + "]"
}
if e.Tag != "" {
msg += " [标签: " + e.Tag + "]"
}
if e.Cause != nil {
msg += " - " + e.Cause.Error()
}
return msg
}

BIN
kfzgw_linux Normal file

Binary file not shown.

Binary file not shown.

2134
kfztp.go

File diff suppressed because it is too large Load Diff

460
main.go
View File

@ -1,11 +1,11 @@
package main package main
/* /*
#include <stdlib.h> #include <stdlib.h>
// proxyConfig.dll 函数声明 // proxyConfig.dll 函数声明
extern char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode); extern char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
extern void FreeCString(char* str); extern void FreeCString(char* str);
*/ */
import "C" import "C"
import ( import (
@ -17,6 +17,7 @@ import (
"log" "log"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -238,6 +239,17 @@ type UserInfo struct {
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
} }
// 店铺快递费参数
type ParamsInfo struct {
Params []Param `json:"params"`
Area string `json:"area"`
}
type Param struct {
UserId string `json:"userId"`
ItemId string `json:"itemId"`
}
// ProxyConfigManager 代理配置DLL管理器 // ProxyConfigManager 代理配置DLL管理器
type ProxyConfigManager struct { type ProxyConfigManager struct {
dll *syscall.DLL dll *syscall.DLL
@ -641,6 +653,190 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error)
return nil, fmt.Errorf("API返回错误: %+v", data) return nil, fmt.Errorf("API返回错误: %+v", data)
} }
// 获取图片URL(官图和拍图)带有店铺过滤
func outGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (map[string]string, error) {
fmt.Println("[DEBUG] 使用的ISBN: ", isbn)
if isLiveImage == 0 {
gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, 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
}
if isLiveImage == 1 {
// 实拍图
sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000", cf.API.ProductSearchURL, isbn, cf.App.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 cf.App.Size >= apiSptResp.Data.ItemResponse.Total {
startIndex = apiSptResp.Data.ItemResponse.Total - 1
} else {
startIndex = cf.App.Size - 1
}
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": item.ImgUrl,
"isbn": item.Isbn,
}
return info, nil
}
}
}
return nil, nil
}
// 获取图片URL(官图和拍图) // 获取图片URL(官图和拍图)
func outGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (*BookInfo, error) { func outGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (*BookInfo, error) {
fmt.Println("[DEBUG] 使用的ISBN: ", isbn) fmt.Println("[DEBUG] 使用的ISBN: ", isbn)
@ -657,7 +853,7 @@ func outGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int,
} }
// 发送请求 // 发送请求
respGt, bodyGt, errsGt := requestGt.Get(gtUrl). respGt, bodyGt, errsGt := requestGt.Get(gtUrl).
Set("Cookie", token). 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("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", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
@ -859,121 +1055,8 @@ func outGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int,
return nil, fmt.Errorf("查询失败,没有数据!") return nil, fmt.Errorf("查询失败,没有数据!")
} }
// 登录获取账号cookie
func loginCookie() (cookie string, err error) {
account := &AccountCredential{
ID: 0,
Username: "",
Password: "",
Token: "",
}
var change = false
if account.Username == "" || change {
account, err = getRandomAccount()
if err != nil {
log.Printf("获取账号失败: %v", err)
}
change = false
}
fmt.Println("获取的账号: ", account.Username)
// 将拿出的账号token赋值给cookie
cookie = account.Token
// 若cookie为""说明该账号没有token还未登录过进行登录
if cookie == "" {
// 登录获取cookie
cookie, err = loginAndGetCookie(account)
if err != nil {
// 若登录失败,则标记为需要更换账号,然后重试
change = true
log.Printf("登录获取cookie失败: %v ,更换账号重试", err)
}
// 成功拿到cookie,则将新拿到的cookie赋值给account的token
account.Token = cookie
// 将新拿到的cookie同步更新token到数据库
err = updateAccountToken(account.ID, cookie)
if err != nil {
log.Printf("更新账号token失败: %v", err)
}
}
return cookie, nil
}
// 登录并获取cookie
func loginAndGetCookie(account *AccountCredential) (string, error) {
formData := map[string]string{
"loginName": account.Username,
"loginPass": account.Password,
}
// 没有代理
resp, body, errs := gorequest.New().
Post(cf.API.LoginURL).
Set("Content-Type", "application/x-www-form-urlencoded").
Set("User-Agent", cf.App.DefaultUserAgent).
Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8").
Send(formData).
Timeout(30 * time.Second).
End()
// 处理请求错误
if len(errs) > 0 {
// 检查是否是代理认证失败
var proxyAuthFailed bool
for _, e := range errs {
if strings.Contains(e.Error(), "Proxy Authentication Required") {
proxyAuthFailed = true
break
}
}
if proxyAuthFailed {
return "", fmt.Errorf("代理认证失败")
}
return "", fmt.Errorf("登录请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("登录失败(HTTP状态码: %d)", resp.StatusCode)
}
if !strings.Contains(body, "window.location.href='https://login.kongfz.cn/Pc/Session/rsync") {
// 尝试解析JSON响应获取错误信息
var response struct {
Status bool `json:"status"`
ErrCode *int `json:"errCode"`
ErrInfo string `json:"errInfo"`
}
if err := json.Unmarshal([]byte(body), &response); err != nil {
// 账号密码错误
if response.ErrCode != nil && *response.ErrCode == 1001 {
// 标记账号为不可用
if err := updateAccountInvalid(account.ID); err != nil {
log.Printf("标记账号不可用失败: %v", err)
}
return "", fmt.Errorf("账号或密码错误")
}
if response.ErrInfo != "" {
if err := updateAccountInvalid(account.ID); err != nil {
log.Printf("标记账号不可用失败: %v", err)
}
return "", fmt.Errorf(response.ErrInfo)
} else {
if err := updateAccountNeedVerify(account.ID); err != nil {
log.Printf("标记账号需要验证失败: %v", err)
}
return "", fmt.Errorf("该账号需要手机号登录验证")
}
} else {
return "", fmt.Errorf("登录响应不包含成功跳转信息")
}
}
// 提取Cookie
cookies := resp.Header.Get("Set-Cookie")
if cookies == "" {
return "", fmt.Errorf("登录成功但未获取到Cookie")
}
log.Printf("登录成功,用户: %s", account.Username)
return cookies, nil
}
// 获取商品列表通过店铺ID // 获取商品列表通过店铺ID
func outGetGoodsListMsgByShopId(shopId int, proxy string, isImage int, sortType string, sort string, priceMin float32, priceMax float32, pageNum, returnNum int) (books []BookInfo, goodsNum string, pNum string, err error) { func outGetGoodsListMsgByShopId(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 // 判断店铺ID
if shopId == 0 { if shopId == 0 {
return nil, "", "", fmt.Errorf("店铺编码为空!") return nil, "", "", fmt.Errorf("店铺编码为空!")
@ -1063,6 +1146,7 @@ func outGetGoodsListMsgByShopId(shopId int, proxy string, isImage int, sortType
log.Printf("未找到页数!") log.Printf("未找到页数!")
} }
infoDiv := doc.Find("div.list-content") infoDiv := doc.Find("div.list-content")
var params ParamsInfo
if infoDiv.Length() > 0 { if infoDiv.Length() > 0 {
item := infoDiv.Find("div.item.clearfix") item := infoDiv.Find("div.item.clearfix")
for i := 0; i < item.Length(); i++ { for i := 0; i < item.Length(); i++ {
@ -1078,13 +1162,102 @@ func outGetGoodsListMsgByShopId(shopId int, proxy string, isImage int, sortType
// 商品ID // 商品ID
itemid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("itemid", ""))) itemid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("itemid", "")))
book.ItemId = int64(itemid) book.ItemId = int64(itemid)
// 详情URL
book.DetailUrl = s.Find("div.item-img a.img-box").AttrOr("href", "") 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) books = append(books, book)
} }
if params.Params != nil {
params.Area = "13003000000"
dataItem, err := getGoodsListShippingFee(params, proxy)
if err != nil {
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 return books, goodsNum, pNum, nil
} }
// 定义对应的结构体
type FeeInfo struct {
ShippingID string `json:"shippingId"`
ShippingName string `json:"shippingName"`
TotalFee string `json:"totalFee"`
ShippingText string `json:"shippingText"`
FilterTotalFee string `json:"filterTotalFee"`
}
type DataItem struct {
Fee []FeeInfo `json:"fee"`
IsSeller bool `json:"isSeller"`
ItemID string `json:"itemId"`
UserID string `json:"userId"`
}
type ResponseStruct struct {
Status bool `json:"status"`
Data []DataItem `json:"data"`
Message string `json:"message"`
ErrType string `json:"errType"`
}
// 获取店铺里商品的快递费(定位到河南)
func getGoodsListShippingFee(params ParamsInfo, proxy string) ([]DataItem, error) {
if params.Params == nil {
return nil, fmt.Errorf("没有商品信息!")
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("参数序列化失败: %v", err)
}
// URL 编码参数
encodedParams := url.QueryEscape(string(paramsJSON))
url := fmt.Sprintf("https://shop.kongfz.com/book/shopsearch/getShippingFee?params=%s", encodedParams)
request := gorequest.New()
if proxy != "" {
request.Proxy(proxy)
}
// 发送请求
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)
}
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
}
// 获取商品信息通过商品详情链接 // 获取商品信息通过商品详情链接
func outGetGoodsMsgByDetailUrl(detailUrl, proxy string) (*BookInfo, error) { func outGetGoodsMsgByDetailUrl(detailUrl, proxy string) (*BookInfo, error) {
response, err := fetchResponse(detailUrl, proxy) response, err := fetchResponse(detailUrl, proxy)
@ -2109,7 +2282,7 @@ func FetchBookDetailsShippingFee(url string) (string, error) {
// 查找包含快递费用的元素 // 查找包含快递费用的元素
const elements = document.querySelectorAll('*'); const elements = document.querySelectorAll('*');
let shippingFee = null; let shippingFee = null;
elements.forEach(element => { elements.forEach(element => {
if (element.textContent.includes('快递')) { if (element.textContent.includes('快递')) {
const match = element.textContent.match(/快递(\d+\.\d{2})/); const match = element.textContent.match(/快递(\d+\.\d{2})/);
@ -2129,7 +2302,7 @@ func FetchBookDetailsShippingFee(url string) (string, error) {
console.log('找到快递费用: 包邮'); console.log('找到快递费用: 包邮');
} }
}); });
if (!shippingFee) { if (!shippingFee) {
// 如果没找到,尝试搜索整个页面 // 如果没找到,尝试搜索整个页面
const pageText = document.body.innerText; const pageText = document.body.innerText;
@ -2137,7 +2310,7 @@ func FetchBookDetailsShippingFee(url string) (string, error) {
if (match) { if (match) {
shippingFee = match[1]; shippingFee = match[1];
console.log('找到快递费用: ' + shippingFee); console.log('找到快递费用: ' + shippingFee);
} }
// 尝试匹配包邮 // 尝试匹配包邮
else if (pageText.match(/快递[:\s]包邮/)) { else if (pageText.match(/快递[:\s]包邮/)) {
shippingFee = '包邮'; shippingFee = '包邮';
@ -2915,6 +3088,43 @@ func OutAddGoods(token, proxy, formData *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 获取图片URL(官图和拍图)带有店铺过滤
//
//export OutGetImageFilterShopId
func OutGetImageFilterShopId(token, isbn *C.char, shopId C.int, proxy *C.char, isLiveImage C.int, isReturnMsg C.int) *C.char {
goToken := C.GoString(token)
goIsbn := C.GoString(isbn)
goShopId := int(shopId)
goProxy := C.GoString(proxy)
goIsLiveImage := int(isLiveImage)
goIsReturnMsg := int(isReturnMsg)
info, err := outGetImageFilterShopId(goToken, goIsbn, goShopId, goProxy, goIsLiveImage, goIsReturnMsg)
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))
}
// 获取商品图片(带有Out的都非官方标准接口) // 获取商品图片(带有Out的都非官方标准接口)
// //
//export OutGetImageByIsbn //export OutGetImageByIsbn
@ -2954,9 +3164,10 @@ func OutGetImageByIsbn(token, isbn, proxy *C.char, isLiveImage C.int, isReturnMs
// 获取商品列表通过店铺ID(带有Out的都非官方标准接口) // 获取商品列表通过店铺ID(带有Out的都非官方标准接口)
// //
//export OutGetGoodsListMsgByShopId //export OutGetGoodsListMsgByShopId
func OutGetGoodsListMsgByShopId(shopId C.int, proxy *C.char, isImage C.int, sortType *C.char, sort *C.char, priceMin C.float, priceMax C.float, pageNum, returnNum C.int) *C.char { func OutGetGoodsListMsgByShopId(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 {
goShopId := int(shopId) goShopId := int(shopId)
goProxy := C.GoString(proxy) goProxy := C.GoString(proxy)
goRetPrice := int(retPrice)
goIsImage := int(isImage) goIsImage := int(isImage)
goSortType := C.GoString(sortType) goSortType := C.GoString(sortType)
goSort := C.GoString(sort) goSort := C.GoString(sort)
@ -2964,7 +3175,7 @@ func OutGetGoodsListMsgByShopId(shopId C.int, proxy *C.char, isImage C.int, sort
goPriceMax := float32(priceMax) goPriceMax := float32(priceMax)
goPageNum := int(pageNum) goPageNum := int(pageNum)
goReturnNum := int(returnNum) goReturnNum := int(returnNum)
books, num, pNum, err := outGetGoodsListMsgByShopId(goShopId, goProxy, goIsImage, goSortType, goSort, goPriceMin, goPriceMax, goPageNum, goReturnNum) books, num, pNum, err := outGetGoodsListMsgByShopId(goShopId, goProxy, goRetPrice, goIsImage, goSortType, goSort, goPriceMin, goPriceMax, goPageNum, goReturnNum)
// 构建统一格式的响应 // 构建统一格式的响应
bookInfo := struct { bookInfo := struct {
GoodsNum string `json:"goods_num,omitempty"` GoodsNum string `json:"goods_num,omitempty"`
@ -3092,5 +3303,4 @@ func FreeCString(str *C.char) {
//// 空main函数编译DLL时需要 //// 空main函数编译DLL时需要
//func main() { //func main() {
//
//} //}

3331
main_so.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -96,13 +96,37 @@ extern "C" {
// //
extern __declspec(dllexport) char* OutLogin(char* username, char* password); extern __declspec(dllexport) char* OutLogin(char* username, char* password);
// 获取用户信息(带有Out的都非官方标准接口)
//
extern __declspec(dllexport) char* OutGetUserMsg(char* token);
// 获取商品模版(带有Out的都非官方标准接口)
//
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* itemId, char* proxy);
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
//
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, int startCreateTime, int endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
// 删除商品-已登的店铺(带有Out的都非官方标准接口)
//
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
// 新增商品(带有Out的都非官方标准接口)
//
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
// 获取图片URL(官图和拍图)带有店铺过滤
//
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* isbn, int shopId, char* proxy, int isLiveImage, int isReturnMsg);
// 获取商品图片(带有Out的都非官方标准接口) // 获取商品图片(带有Out的都非官方标准接口)
// //
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg); extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg);
// 获取商品列表通过店铺ID(带有Out的都非官方标准接口) // 获取商品列表通过店铺ID(带有Out的都非官方标准接口)
// //
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum); extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
// 获取商品信息通过商品详情链接(带有0ut的都非官方标准接口) // 获取商品信息通过商品详情链接(带有0ut的都非官方标准接口)
// //

2286
zjdydll.go

File diff suppressed because it is too large Load Diff