修改获取店铺商品接口,生成so文件,调用so文件接口
This commit is contained in:
parent
aed65dfb31
commit
8a111054a6
BIN
dll/kongfz.dll
BIN
dll/kongfz.dll
Binary file not shown.
@ -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.
@ -21,14 +21,9 @@ 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
496
dyso.go
Normal 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)
|
||||||
|
}
|
||||||
378
iniConfigUtil.go
378
iniConfigUtil.go
@ -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
BIN
kfzgw_linux
Normal file
Binary file not shown.
Binary file not shown.
446
main.go
446
main.go
@ -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)
|
||||||
@ -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
3331
main_so.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -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的都非官方标准接口)
|
||||||
//
|
//
|
||||||
2222
zjdydll.go
2222
zjdydll.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user