修改所有

This commit is contained in:
Cai1Cai1 2025-12-22 19:09:56 +08:00
parent 8a111054a6
commit 905da936db
61 changed files with 172002 additions and 3048 deletions

2
.gitignore vendored
View File

@ -5,7 +5,7 @@
*.exe *.exe
*.exe~ *.exe~
#*.dll #*.dll
*.so #*.so
*.dylib *.dylib
# Test binary, built with `go test -c` # Test binary, built with `go test -c`

View File

@ -1,202 +0,0 @@
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
// 全局变量
var (
// 数据库连接配置
DBUsername = "root" // 用户名
DBPassword = "Long6166@@" // 密码
DBHost = "nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com" // 主机
DBPort = 26247 // 端口
DBDataBase = "book_center" // 数据库
DBCharset = "utf8mb4" // 字符集
//连接池配置
MaxOpenConns = 20
MaxIdleConns = 10
)
// 连接数据库
func connectDB() (*sql.DB, error) {
// 数据源名称格式
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local",
DBUsername,
DBPassword,
DBHost,
DBPort,
DBDataBase,
DBCharset)
// 连接数据库
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("打开数据库连接失败: %v", err)
}
// 设置连接池参数
db.SetMaxOpenConns(MaxOpenConns) // 最大打开连接数
db.SetMaxIdleConns(MaxIdleConns) // 最大空闲连接数
// 测试链接
err = db.Ping()
if err != nil {
return nil, fmt.Errorf("数据库连接测试失败: %v", err)
}
return db, nil
}
func selectBookIsbn(db *sql.DB, bookId int) error {
//// 执行语句
//query := `SELECT isbn, MAX(create_time) as max_create_time
//FROM shop_goods_rejection
//WHERE isbn IS NOT NULL
//GROUP BY isbn`
//
//exec, err := db.Exec(query)
//fmt.Println(exec, err)
return nil
}
//func main() {
//db, err := connectDB()
//if err != nil {
// log.Fatal("数据库连接失败:", err)
//}
//
//books, err := GetAllCategoryBooks()
//if err != nil {
// fmt.Printf("", err)
//}
//if len(books) == 0 {
// fmt.Printf("数组是空!")
//}
//// 查询数据库中不存在的ISBN
//missingISBNs, err := findMissingISBNs(db, books)
//if err != nil {
// log.Fatal("查询失败:", err)
//}
//log.Printf("数据库中不存在的ISBN数量: %d", len(missingISBNs))
//fmt.Println("数据库中不存在的ISBN数组:")
//for i, isbn := range missingISBNs {
// fmt.Printf("%d. %s\n", i+1, isbn)
//}
//
//missingISBNsString := strings.Join(missingISBNs, "\n")
//
//// 这里您可以将 missingISBNs 数组用于后续处理
//// 比如保存到文件、插入到另一个表等
//fmt.Printf("\n新的missingISBNs字符串: %v\n", missingISBNsString)
//}
// 查询数据库中不存在的ISBN
func findMissingISBNs(db *sql.DB, books []string) ([]string, error) {
if len(books) == 0 {
return []string{}, nil
}
// 查询数据库中存在的ISBN
existingISBNs, err := queryExistingISBNs(db, books)
if err != nil {
return nil, err
}
// 创建现有ISBN的映射用于快速查找
existingMap := make(map[string]bool)
for _, isbn := range existingISBNs {
existingMap[isbn] = true
}
// 创建新数组存储数据库中不存在的ISBN
missingISBNs := make([]string, 0)
for _, isbn := range books {
if !existingMap[isbn] {
missingISBNs = append(missingISBNs, isbn)
}
}
return missingISBNs, nil
}
// 查询数据库中存在的ISBN
func queryExistingISBNs(db *sql.DB, isbns []string) ([]string, error) {
if len(isbns) == 0 {
return []string{}, nil
}
// 分批处理避免SQL语句过长
batchSize := 1000
allExistingISBNs := make([]string, 0)
for i := 0; i < len(isbns); i += batchSize {
end := i + batchSize
if end > len(isbns) {
end = len(isbns)
}
batchISBNs := isbns[i:end]
batchExisting, err := queryBatchISBNs(db, batchISBNs)
if err != nil {
return nil, err
}
allExistingISBNs = append(allExistingISBNs, batchExisting...)
}
return allExistingISBNs, nil
}
// 批量查询ISBN
func queryBatchISBNs(db *sql.DB, isbns []string) ([]string, error) {
if len(isbns) == 0 {
return []string{}, nil
}
// 构建IN查询的占位符
placeholders := make([]string, len(isbns))
args := make([]interface{}, len(isbns))
for i, isbn := range isbns {
placeholders[i] = "?"
args[i] = isbn
}
query := fmt.Sprintf(`
SELECT isbn
FROM book_center
WHERE isbn IN (%s)
AND del_flag = 0
`, joinPlaceholders(placeholders, ","))
rows, err := db.Query(query, args...)
if err != nil {
return nil, fmt.Errorf("查询数据库失败: %v", err)
}
defer rows.Close()
var existingISBNs []string
for rows.Next() {
var isbn string
if err := rows.Scan(&isbn); err != nil {
return nil, fmt.Errorf("扫描结果失败: %v", err)
}
existingISBNs = append(existingISBNs, isbn)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历结果失败: %v", err)
}
return existingISBNs, nil
}
// joinPlaceholders 连接占位符
func joinPlaceholders(placeholders []string, sep string) string {
if len(placeholders) == 0 {
return ""
}
result := placeholders[0]
for i := 1; i < len(placeholders); i++ {
result += sep + placeholders[i]
}
return result
}

View File

@ -1,299 +0,0 @@
package main
import (
"database/sql"
"fmt"
"regexp"
"strings"
_ "github.com/go-sql-driver/mysql"
)
// func main() {
// // 构建数据库连接字符串
// dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
// "root", "Long6166@@", "nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com", "26247", "book_center")
// // 连接数据库
// db, err := sql.Open("mysql", dsn)
// if err != nil {
// log.Fatal("数据库连接失败:", err)
// }
// defer db.Close()
//
// // 测试数据库连接
// err = db.Ping()
// if err != nil {
// log.Fatal("数据库连接测试失败:", err)
// }
// fmt.Println("数据库连接成功")
//
// // 查询包含"影印版"或"(影印版)"的书名
// books, err := queryBooksWithCopyVersion(db)
// if err != nil {
// log.Fatal("查询失败:", err)
// }
//
// fmt.Printf("找到 %d 本包含'影印版'或'(影印版)'的书\n", len(books))
//
// if len(books) == 0 {
// fmt.Println("没有找到需要更新的图书")
// return
// }
//
// // 更新书名,删除"影印版"和"(影印版)"
// updatedCount, err := updateBookNames(db, books)
// if err != nil {
// log.Fatal("更新失败:", err)
// }
//
// fmt.Printf("成功更新 %d 本书的书名\n", updatedCount)
// }
//
// Book 结构体对应 book_center 表
type Book struct {
ID int64
BookName string
ISBN string
}
// queryBooksWithCopyVersion 查询包含"影印版"或"(影印版)"的书名
func queryBooksWithCopyVersion(db *sql.DB) ([]Book, error) {
query := `
SELECT id, book_name, isbn
FROM book_center
WHERE book_name LIKE '%影印版%'
AND del_flag = 0
`
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var books []Book
for rows.Next() {
var book Book
err := rows.Scan(&book.ID, &book.BookName, &book.ISBN)
if err != nil {
return nil, err
}
books = append(books, book)
}
return books, nil
}
// cleanBookName 清理书名,删除所有影印版相关文字
func cleanBookName(bookName string) string {
// 定义需要删除的模式
patterns := []string{
"(影印版)", // 全角括号
"(影印版)", // 半角括号
"影印版", // 无括号
"【影印版】", // 方头括号
"[影印版]", // 方括号
}
newName := bookName
// 逐个删除模式
for _, pattern := range patterns {
newName = strings.ReplaceAll(newName, pattern, "")
}
// 删除多余的空格
newName = strings.TrimSpace(newName)
newName = regexp.MustCompile(`\s+`).ReplaceAllString(newName, " ")
return newName
}
// updateBookNames 更新书名,删除所有影印版相关文字
func updateBookNames(db *sql.DB, books []Book) (int, error) {
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
updateStmt, err := tx.Prepare(`
UPDATE book_center
SET book_name = ?, update_time = UNIX_TIMESTAMP()
WHERE id = ? AND isbn = ?
`)
if err != nil {
return 0, err
}
defer updateStmt.Close()
updatedCount := 0
for _, book := range books {
// 清理书名
newBookName := cleanBookName(book.BookName)
// 如果书名没有变化,跳过更新
if newBookName == book.BookName {
fmt.Printf("跳过: 书名无变化 (ID: %d, ISBN: %s)\n", book.ID, book.ISBN)
continue
}
// 如果书名超过字段长度限制,截断
if len(newBookName) > 400 {
newBookName = newBookName[:400]
fmt.Printf("警告: 书名超长,已截断 (ID: %d)\n", book.ID)
}
result, err := updateStmt.Exec(newBookName, book.ID, book.ISBN)
if err != nil {
fmt.Printf("更新失败 (ID: %d): %v\n", book.ID, err)
continue
}
rowsAffected, err := result.RowsAffected()
if err != nil {
fmt.Printf("获取影响行数失败 (ID: %d): %v\n", book.ID, err)
continue
}
if rowsAffected > 0 {
updatedCount++
fmt.Printf("更新成功: ID=%d, ISBN=%s\n", book.ID, book.ISBN)
fmt.Printf(" 原书名: %s\n", book.BookName)
fmt.Printf(" 新书名: %s\n", newBookName)
fmt.Println(" ---")
}
}
err = tx.Commit()
if err != nil {
return 0, err
}
return updatedCount, nil
}
// 没用了
//// 获取销量榜所有分类中图书的isbn
//func GetAllCategoryBooks() ([]string, error) {
// // 1. 获取分类列表
// categories, err := GetSalesBookInfo()
// if err != nil {
// return nil, fmt.Errorf("获取分类列表失败: %v", err)
// }
// // 初始化返回的ISBN数组
// var allISBNs []string
//
// for i, category := range categories {
// fmt.Printf("\n=== 正在处理分类 %d/%d: %s (ID: %d) ===\n", i+1, len(categories), category.Key, category.Value)
// // 根据分类ID获取图书详情
// bookDetails, err := GetBookDetailsByCategory(category.Value)
// if err != nil {
// fmt.Printf("获取分类 %s 的图书信息失败: %v\n", category.Key, err)
// continue
// }
// // 将当前分类的图书数据添加到结果map中
// if bookDetails != nil && bookDetails.Result.Data != nil {
// for _, book := range bookDetails.Result.Data {
// if book.Isbn != "" {
// allISBNs = append(allISBNs, book.Isbn)
// fmt.Printf("添加到ISBN列表: %s\n", book.Isbn)
// }
// }
// fmt.Printf("分类 %s 获取到 %d 本图书\n", category.Key, len(bookDetails.Result.Data))
// } else {
// fmt.Printf("分类 %s 没有获取到图书数据\n", category.Key)
// }
// time.Sleep(500 * time.Microsecond)
// }
// // 去重处理(可选)
// allISBNs = removeDuplicateISBNs(allISBNs)
// // 打印统计信息
// fmt.Printf("\n=== 总计: %d 个分类, %d 个唯一ISBN ===\n", len(categories), len(allISBNs))
// return allISBNs, nil
//}
//
//// 获取销量榜的图书信息
//func GetSalesBookInfo() ([]SalesCategory, error) {
// // 构建请求URL
// url := "https://item.kongfz.com/api/Pc/getSellWellCatList"
// // 创建HTTP客户端
// client := &http.Client{
// Timeout: 30 * time.Second,
// }
// // 创建请求
// req, err := http.NewRequest("GET", url, nil)
// if err != nil {
// return nil, fmt.Errorf("创建请求失败: %v", err)
// }
// // 设置请求头,模拟浏览器请求
// req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
// req.Header.Set("Accept", "application/json, text/plain, */*")
// req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
// req.Header.Set("Referer", "https://item.kongfz.com/")
// // 发送请求
// resp, err := client.Do(req)
// if err != nil {
// return nil, fmt.Errorf("请求失败: %v", err)
// }
// defer resp.Body.Close()
// // 检查HTTP状态码
// if resp.StatusCode != http.StatusOK {
// return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
// }
// // 读取响应体
// body, err := io.ReadAll(resp.Body)
// if err != nil {
// return nil, fmt.Errorf("读取响应失败: %v", err)
// }
// // 解析JSON响应
// var salesCategoryResponse SalesCategoryResponse
// err = json.Unmarshal(body, &salesCategoryResponse)
// if err != nil {
// return nil, fmt.Errorf("解析JSON失败: %v", err)
// }
// return salesCategoryResponse.Result, nil
//}
//
//// 根据分类ID获取图书详情
//func GetBookDetailsByCategory(catId int) (*BookDetailResponse, error) {
// // 构建请求URL
// url := fmt.Sprintf("https://item.kongfz.com/api/pc/getSellWellListDetail?page=1&pageSize=100&timeRank=2&catId=%d", catId)
// // 创建HTTP客户端
// client := &http.Client{
// Timeout: 30 * time.Second,
// }
// // 创建请求
// req, err := http.NewRequest("GET", url, nil)
// if err != nil {
// return nil, fmt.Errorf("创建请求失败: %v", err)
// }
// // 设置请求头,模拟浏览器请求
// req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
// req.Header.Set("Accept", "application/json, text/plain, */*")
// req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
// req.Header.Set("Referer", "https://item.kongfz.com/")
// // 发送请求
// resp, err := client.Do(req)
// if err != nil {
// return nil, fmt.Errorf("请求失败: %v", err)
// }
// defer resp.Body.Close()
// // 检查HTTP状态码
// if resp.StatusCode != http.StatusOK {
// return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
// }
// // 读取响应体
// body, err := io.ReadAll(resp.Body)
// if err != nil {
// return nil, fmt.Errorf("读取响应失败: %v", err)
// }
// var bookDetailResponse BookDetailResponse
// err = json.Unmarshal(body, &bookDetailResponse)
// if err != nil {
// return nil, fmt.Errorf("解析JSON失败: %v", err)
// }
// if !bookDetailResponse.Status {
// return nil, fmt.Errorf("API返回错误: %s (代码: %d)", bookDetailResponse.ErrMessage, bookDetailResponse.ErrCode)
// }
// return &bookDetailResponse, nil
//}

BIN
csv/csv.dll Normal file

Binary file not shown.

1882
csv/csv.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,8 +21,9 @@ extern const char *_GoStringPtr(_GoString_ s);
/* Start of preamble from import "C" comments. */ /* Start of preamble from import "C" comments. */
#line 3 "proxyConfig.go" #line 3 "csv.go"
#include <stdlib.h>
#include <stdlib.h>
#line 1 "cgo-generated-wrapper" #line 1 "cgo-generated-wrapper"
@ -87,17 +88,14 @@ extern "C" {
#endif #endif
// 导出函数:获取代理健康状态(用于调试) // ===================== C 函数导入 ==================
// OpenCSVFile 打开/创建CSV文件
// //
extern __declspec(dllexport) char* GetProxyHealth(void); extern __declspec(dllexport) char* OpenCSVFile(char* filename, char delimiter, int hasHeader);
// 导出函数:代理类型管理器 // UpdateCSVRowSafe 修改csv文件行数据
// //
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode); extern __declspec(dllexport) char* UpdateCSVRowSafe(long long int handleID, int rowNum, char* newRow);
// 导出函数释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus #ifdef __cplusplus
} }

413
csv/csvDllTest.go Normal file
View File

@ -0,0 +1,413 @@
package main
import "C"
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// CsvDLL 代理DLL结构
type csvDLL struct {
dll *syscall.DLL
openCSVFile *syscall.Proc // 打开/创建CSV文件
readRows *syscall.Proc // 读取多行数据
writeRows *syscall.Proc // 写入/覆盖行数据
appendRows *syscall.Proc // 追加行数据
getRowCount *syscall.Proc // 获取总行数
findRows *syscall.Proc // 搜索行
closeCSVFile *syscall.Proc // 关闭CSV文件
mergeCSVFiles *syscall.Proc // 合并多个CSV文件
getError *syscall.Proc // 获取错误信息
updateCSVRowSafe *syscall.Proc
createOpenCSVFile *syscall.Proc
freeCString *syscall.Proc // 释放C字符串
}
// 初始化csvDLL
func InitCsvDLL() (*csvDLL, error) {
dllPath := filepath.Join("csv/dll", "csv.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("csv DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载csv DLL 失败: %s", err)
} else {
return &csvDLL{
dll: dll,
openCSVFile: dll.MustFindProc("OpenCSVFile"),
updateCSVRowSafe: dll.MustFindProc("UpdateCSVRowSafe"),
readRows: dll.MustFindProc("ReadRows"),
writeRows: dll.MustFindProc("WriteRows"),
appendRows: dll.MustFindProc("AppendRows"),
getRowCount: dll.MustFindProc("GetRowCount"),
findRows: dll.MustFindProc("FindRows"),
closeCSVFile: dll.MustFindProc("CloseCSVFile"),
mergeCSVFiles: dll.MustFindProc("MergeCSVFiles"),
createOpenCSVFile: dll.MustFindProc("CreateOpenCSVFile"),
getError: dll.MustFindProc("GetError"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
// cStr 获取C字符串
func (m *csvDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
// 打开/创建CSV文件
func (m *csvDLL) OpenCSVFile(filePath string, delimiter byte, hasHeader bool) (int64, error) {
proc, err := m.dll.FindProc("OpenCSVFile")
if err != nil {
return -1, fmt.Errorf("找不到函数 OpenCSVFile: %v", err)
}
filePathPtr, _ := syscall.BytePtrFromString(filePath)
hasHeaderInt := 0
if hasHeader {
hasHeaderInt = 1
}
info, _, _ := proc.Call(
uintptr(unsafe.Pointer(filePathPtr)),
uintptr(delimiter),
uintptr(hasHeaderInt))
return int64(info), nil
}
// CSV响应结构体
type CSVResponses struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data CSVData `json:"data,omitempty"`
}
type CSVData struct {
HandleID int64 `json:"handleID"`
}
func (m *csvDLL) CreateOpenCSVFile(filePath string, delimiter byte, hasHeader bool) (*CSVResponses, error) {
proc, err := m.dll.FindProc("CreateOpenCSVFile")
if err != nil {
return nil, fmt.Errorf("找不到函数 CreateOpenCSVFile: %v", err)
}
filePathPtr, _ := syscall.BytePtrFromString(filePath)
hasHeaderInt := 0
if hasHeader {
hasHeaderInt = 1
}
info, _, _ := proc.Call(
uintptr(unsafe.Pointer(filePathPtr)),
uintptr(delimiter),
uintptr(hasHeaderInt))
var csvResponse CSVResponses
str := m.cStr(info)
if err := json.Unmarshal([]byte(str), &csvResponse); err != nil {
return nil, err
}
return &csvResponse, nil
}
func (m *csvDLL) UpdateCSVRowSafe(handleID int64, rowNum int, newRow []string) (*CSVResponses, error) {
proc, err := m.dll.FindProc("UpdateCSVRowSafe")
if err != nil {
return nil, fmt.Errorf("找不到函数 UpdateCSVRowSafe: %v", err)
}
jsonString, _ := json.Marshal(newRow)
newRowPtr, _ := syscall.BytePtrFromString(string(jsonString))
info, _, _ := proc.Call(
uintptr(handleID),
uintptr(rowNum),
uintptr(unsafe.Pointer(newRowPtr)))
var csvResponse CSVResponses
str := m.cStr(info)
if err := json.Unmarshal([]byte(str), &csvResponse); err != nil {
return nil, err
}
return &csvResponse, nil
}
//// 读取多行数据
//func (m *csvDLL) ReadRows(handle int64) ([]string, error) {
// proc, err := m.dll.FindProc("ReadRows")
// if err != nil {
// return nil, fmt.Errorf("找不到函数 ReadRows: %v", err)
// }
//
//}
// 写入/覆盖行数据
func (m *csvDLL) WriteRows(handle int64, rowsData [][]string, header int) (int, error) {
proc, err := m.dll.FindProc("WriteRows")
if err != nil {
return -1, fmt.Errorf("找不到函数 WriteRows: %v", err)
}
var buffer bytes.Buffer
writer := csv.NewWriter(&buffer)
// 写入所有行
if err := writer.WriteAll(rowsData); err != nil {
return -1, fmt.Errorf("序列化 CSV 数据失败: %v", err)
}
writer.Flush()
// 转换为 C 字符串
cStr := C.CString(buffer.String())
ret, _, _ := proc.Call(
uintptr(handle),
uintptr(unsafe.Pointer(cStr)),
uintptr(header))
freeProc, _ := m.dll.FindProc("FreeCString")
if freeProc != nil {
defer freeProc.Call(uintptr(unsafe.Pointer(cStr)))
}
return int(ret), nil
}
// 定义请求结构体
type OpenCSVRequest struct {
FilePath string `json:"filePath"`
Delimiter string `json:"delimiter"` // 可以是逗号、分号等字符
HasHeader bool `json:"hasHeader"`
}
// 定义响应结构体
type OpenCSVResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Handle int64 `json:"handle,omitempty"`
Error string `json:"error,omitempty"`
}
func handleOpenCSVFile(w http.ResponseWriter, r *http.Request) {
// 设置响应头
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 只允许 POST 请求
if r.Method != http.MethodPost {
response := OpenCSVResponse{
Success: false,
Message: "只支持POST请求",
}
w.WriteHeader(http.StatusMethodNotAllowed)
json.NewEncoder(w).Encode(response)
return
}
// 1.初始化DLL管理器
dll, err := InitCsvDLL()
if err != nil {
response := OpenCSVResponse{
Success: false,
Message: "初始化DLL失败",
Error: err.Error(),
}
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(response)
return
}
// 2.读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
response := OpenCSVResponse{
Success: false,
Message: "读取请求体失败",
Error: err.Error(),
}
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(response)
return
}
defer r.Body.Close()
// 3.解析JSON请求
var req OpenCSVRequest
if err := json.Unmarshal(body, &req); err != nil {
response := OpenCSVResponse{
Success: false,
Message: "JSON解析失败",
Error: err.Error(),
}
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(response)
return
}
// 4.验证必填参数
if req.FilePath == "" {
response := OpenCSVResponse{
Success: false,
Message: "filePath参数不能为空",
}
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(response)
return
}
// 5. 处理分隔符参数
delimiter := ','
if req.Delimiter != "" {
// 确保分隔符是单个字符
if len(req.Delimiter) == 1 {
delimiter = rune(req.Delimiter[0])
} else {
// 尝试解析常见分隔符的字符串表示
switch req.Delimiter {
case "comma", ",":
delimiter = ','
case "semicolon", ";":
delimiter = ';'
case "tab", "\t":
delimiter = '\t'
case "pipe", "|":
delimiter = '|'
default:
response := OpenCSVResponse{
Success: false,
Message: "无效的分隔符,请使用单个字符或预定义的分隔符名称",
}
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(response)
return
}
}
}
// 6. 调用DLL函数
handle, err := dll.OpenCSVFile(req.FilePath, byte(delimiter), req.HasHeader)
if err != nil {
response := OpenCSVResponse{
Success: false,
Message: "打开CSV文件失败",
Error: err.Error(),
}
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(response)
return
}
// 7. 返回成功响应
response := OpenCSVResponse{
Success: true,
Message: "CSV文件打开成功",
Handle: handle,
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
func main() {
dll, err := InitCsvDLL()
if err != nil {
}
file, err := dll.CreateOpenCSVFile("csv/taskLog.csv", ',', true)
if err != nil {
}
newRow := []string{"9787115524539", "100.00", "1", "上传成功", ""}
safe, err := dll.UpdateCSVRowSafe(file.Data.HandleID, 9, newRow)
if err != nil {
}
marshal, _ := json.Marshal(safe)
fmt.Println(string(marshal))
//http.HandleFunc("/csv/openCSVFile", handleOpenCSVFile)
//port := "8080"
//server := &http.Server{
// Addr: ":" + port,
// Handler: nil,
//}
//
//// 4. 优雅关闭设置
//done := make(chan bool, 1)
//quit := make(chan os.Signal, 1)
//signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
//
//// 5. 优雅关闭协程
//go func() {
// <-quit
// fmt.Println("\n服务器正在关闭...")
//
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// defer cancel()
//
// if err := server.Shutdown(ctx); err != nil {
// fmt.Printf("强制关闭服务器: %v\n", err)
// }
// close(done)
//}()
//
//// 启动服务器
//if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
// fmt.Printf("服务器启动失败: %s\n", err)
//}
//// 7. 等待关闭完成
//<-done
//fmt.Println("服务器已关闭")
}
// 解码从DLL读取的数据
func decodeRowData(buffer []byte, maxBytes int) [][]string {
var rows [][]string
var currentRow []string
offset := 0
for offset < maxBytes {
if offset+4 > maxBytes {
break
}
// 读取单元格长度
cellLen := int(uint32(buffer[offset]) |
uint32(buffer[offset+1])<<8 |
uint32(buffer[offset+2])<<16 |
uint32(buffer[offset+3])<<24)
offset += 4
if cellLen == 0 {
// 行结束
if len(currentRow) > 0 {
rows = append(rows, currentRow)
currentRow = nil
}
continue
}
if offset+cellLen > maxBytes {
break
}
// 读取单元格数据
cell := string(buffer[offset : offset+cellLen])
offset += cellLen
currentRow = append(currentRow, cell)
}
return rows
}

1050
csv/csvTool.go Normal file

File diff suppressed because it is too large Load Diff

BIN
csv/dll/csv.dll Normal file

Binary file not shown.

139
csv/dll/csv.h Normal file
View File

@ -0,0 +1,139 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "newcsv.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern __declspec(dllexport) long long int InitCSVManager(void);
// OpenCSVFile 打开/创建CSV文件
//
extern __declspec(dllexport) long long int OpenCSVFile(char* filename, char delimiter, int hasHeader);
// UpdateCSVRowSafe 修改csv文件行数据
//
extern __declspec(dllexport) char* UpdateCSVRowSafe(long long int handleID, int rowNum, char* newRow);
extern __declspec(dllexport) char* CreateOpenCSVFile(char* filename, char delimiter, int hasHeader);
// ReadRows 读取多行数据
//
extern __declspec(dllexport) long long int ReadRows(long long int handle, long long int startRow, long long int count, char* buffer, int bufferSize);
// WriteRows 写入/覆盖行数据
//
extern __declspec(dllexport) int WriteRows(long long int handle, char* rowsData, int header);
// AppendRows 追加行数据
//
extern __declspec(dllexport) int AppendRows(long long int handle, char* rowsData, int dataSize, long long int rowCount);
// GetRowCount 获取总行数
//
extern __declspec(dllexport) long long int GetRowCount(long long int handle);
// FindRows 搜索行
//
extern __declspec(dllexport) long long int FindRows(long long int handle, char* searchText, long long int columnIndex, char* resultBuffer, int bufferSize, long long int maxResults);
// MergeCSVFiles 合并多个CSV文件(线程安全)
//
extern __declspec(dllexport) long long int MergeCSVFiles(long long int* handlesPtr, int handlesCount, char* outputFilename, char delimiter, int hasHeader);
// CloseCSVFile 关闭文件
//
extern __declspec(dllexport) int CloseCSVFile(long long int handle);
// GetError 获取错误信息
//
extern __declspec(dllexport) int GetError(char* buffer, int bufferSize);
// 导出函数释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

1225
csv/newcsv.go Normal file

File diff suppressed because it is too large Load Diff

8
csv/taskLog1.csv Normal file
View File

@ -0,0 +1,8 @@
"isbn","价格","库存","日志","三方平台id"
"9787107267505","30.80","1","上传成功","877133619369"
"9787200066883","23.17","1","上传成功","877132920079"
"9787115524539","8.87","","调用过于频繁,请调整调用频率",""
"9787810791373","32.02","","调用过于频繁,请调整调用频率",""
"9787548745600","151.30","","商品价格不在设置的价格区间",""
"9787111546955","8.47","","商品信息中包含违规内容",""
"9787303284382","62.76","1","上传成功","877133371509"
1 isbn 价格 库存 日志 三方平台id
2 9787107267505 30.80 1 上传成功 877133619369
3 9787200066883 23.17 1 上传成功 877132920079
4 9787115524539 8.87 调用过于频繁,请调整调用频率
5 9787810791373 32.02 调用过于频繁,请调整调用频率
6 9787548745600 151.30 商品价格不在设置的价格区间
7 9787111546955 8.47 商品信息中包含违规内容
8 9787303284382 62.76 1 上传成功 877133371509

11
csv/test.csv Normal file
View File

@ -0,0 +1,11 @@
测试行tou1,测试值tou1,测试数据tou1,覆盖测试tou
测行2,测值2,测数据2,覆盖测试2
测行3,测值3,测数据3,覆盖测试3
测行4,测值4,测数据4,覆盖测试4
测行5,测值5,测数据5,覆盖测试5
测行6,测值6,测数据6,覆盖测试6
测行7,测值7,测数据7,覆盖测试7
测行8,测值8,测数据8,覆盖测试8
测行9,测值9,测数据9
测行10,测值10,测数据10,覆盖测试10,覆盖测试10
1 测试行tou1,测试值tou1,测试数据tou1,覆盖测试tou
2 测行2,测值2,测数据2,覆盖测试2
3 测行3,测值3,测数据3,覆盖测试3
4 测行4,测值4,测数据4,覆盖测试4
5 测行5,测值5,测数据5,覆盖测试5
6 测行6,测值6,测数据6,覆盖测试6
7 测行7,测值7,测数据7,覆盖测试7
8 测行8,测值8,测数据8,覆盖测试8
9 测行9,测值9,测数据9
10 测行10,测值10,测数据10,覆盖测试10,覆盖测试10

54
csv/test.go Normal file
View File

@ -0,0 +1,54 @@
package main
//func main() {
//filename := filepath.Join("csv", "test1.csv")
//handle := openCSVFile(filename, ',', true)
//fmt.Println("handle:", handle)
//
//// 测试1基础写入
//fmt.Println("\n=== 测试1基础写入 ===")
//// 清空并写入新数据
//newRow := make([][]string, 0)
//newRow = append(newRow, []string{"基础测试行5", "基础值5", "基础数据5", "基础测试5"})
//
//for i := 1; i <= 10; i++ {
// row := []string{
// "基础行" + strconv.Itoa(i),
// "基础值" + strconv.Itoa(i),
// "基础数据" + strconv.Itoa(i),
// fmt.Sprintf("基础测试%d", i),
// }
// newRow = append(newRow, row)
//}
//
//start := time.Now()
//rows := writeRows(handle, newRow, 0)
//
//elapsed := time.Since(start)
//fmt.Printf("基础写入完成,影响行数: %d耗时: %v\n", rows, elapsed)
//
//buffer := make([]byte, 4096)
//rowss := readRows(handle, 0, rows, buffer)
//fmt.Printf("读取到 %d 行数据\n", rowss)
//
//decodedRows := decodeRowData(buffer, 4096)
//fmt.Println("合并文件前几行数据:")
//for i, row := range decodedRows {
// if i >= int(rowss) {
// break
// }
// fmt.Printf(" 行 %d: %v\n", i, row)
//}
//// 测试3读写混合
//fmt.Println("\n=== 测试3读写混合 ===")
//testMixedOperations(handle)
//file, err := closeCSVFile(handle)
//if err != nil {
// fmt.Println(err)
//}
//if file == 0 {
// fmt.Println("\n文件保存成功")
//}
//}

BIN
dll/csv.dll Normal file

Binary file not shown.

134
dll/csv.h Normal file
View File

@ -0,0 +1,134 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "csv.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern __declspec(dllexport) long long int InitCSVManager(void);
// OpenCSVFile 打开/创建CSV文件
//
extern __declspec(dllexport) long long int OpenCSVFile(char* filename, char delimiter, int hasHeader);
// ReadRows 读取多行数据
//
extern __declspec(dllexport) long long int ReadRows(long long int handle, long long int startRow, long long int count, char* buffer, int bufferSize);
// WriteRows 写入/覆盖行数据
//
extern __declspec(dllexport) int WriteRows(long long int handle, char* rowsData, int dataSize, long long int rowCount);
// AppendRows 追加行数据
//
extern __declspec(dllexport) int AppendRows(long long int handle, char* rowsData, int dataSize, long long int rowCount);
// GetRowCount 获取总行数
//
extern __declspec(dllexport) long long int GetRowCount(long long int handle);
// FindRows 搜索行
//
extern __declspec(dllexport) long long int FindRows(long long int handle, char* searchText, long long int columnIndex, char* resultBuffer, int bufferSize, long long int maxResults);
// MergeCSVFiles 合并多个CSV文件线程安全
//
extern __declspec(dllexport) long long int MergeCSVFiles(long long int* handlesPtr, int handlesCount, char* outputFilename, char delimiter, int hasHeader);
// CloseCSVFile 关闭文件
//
extern __declspec(dllexport) int CloseCSVFile(long long int handle);
// GetError 获取错误信息
//
extern __declspec(dllexport) int GetError(char* buffer, int bufferSize);
// 导出函数释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

BIN
dll/excel.dll Normal file

Binary file not shown.

141
dll/excel.h Normal file
View File

@ -0,0 +1,141 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "main.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
// 创建新的Excel管理器并返回指针
//
extern __declspec(dllexport) uintptr_t NewExcelManagerInstance(void);
// 释放Excel管理器
//
extern __declspec(dllexport) void FreeExcelManager(uintptr_t handle);
// 读取Excel数据
//
extern __declspec(dllexport) int ReadExcelData(uintptr_t handle, char* filename, char* sheet, char** result);
// 批量写入数据到Excel文件
//
extern __declspec(dllexport) int WriteBatchData(uintptr_t handle, char* filename, char* sheet, char* cells, char* values, int count);
// 追加数据到Excel文件末尾
//
extern __declspec(dllexport) int AppendDataToExcel(uintptr_t handle, char* filename, char* sheet, char* values, int count);
// 搜索包含关键字的单元格
//
extern __declspec(dllexport) int SearchByKeyword(uintptr_t handle, char* filename, char* sheet, char* keyword, char** result);
// 搜索整行包含关键字的行
//
extern __declspec(dllexport) int SearchRowsByKeyword(uintptr_t handle, char* filename, char* sheet, char* keyword, char** result);
// 创建新文件并写入数据
//
extern __declspec(dllexport) int CreateAndWriteExcel(uintptr_t handle, char* filename, char* sheet, char* rowsData);
// 增强版合并Excel文件支持指定文件列表
//
extern __declspec(dllexport) int MergeExcelFilesEx(uintptr_t handle, char* sourceDir, char* specificFiles, char* outputFile, char* sheetName, int mergeByColumn, int includeHeaders, int skipEmptyRows, char* filePattern, char* sourceSheet, int addSourceColumn, int addIndexColumn);
// 并行合并Excel文件增强版
//
extern __declspec(dllexport) int MergeExcelFilesParallelEx(uintptr_t handle, char* sourceDir, char** specificFiles, int fileCount, char* outputFile, char* sheetName, int includeHeaders, int skipEmptyRows, char* filePattern, char* sourceSheet, int addSourceColumn, int addIndexColumn, int workers);
// 合并同一文件中的多个sheet
//
extern __declspec(dllexport) int MergeSheetsInFile(uintptr_t handle, char* filename, char* outputFile, char* targetSheetName);
// 释放C字符串
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

Binary file not shown.

View File

@ -106,7 +106,7 @@ extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* itemId,
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口) // 获取商品列表-已登的店铺(带有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); extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
// 删除商品-已登的店铺(带有Out的都非官方标准接口) // 删除商品-已登的店铺(带有Out的都非官方标准接口)
// //

Binary file not shown.

View File

@ -21,7 +21,7 @@ extern const char *_GoStringPtr(_GoString_ s);
/* Start of preamble from import "C" comments. */ /* Start of preamble from import "C" comments. */
#line 3 "proxyConfig.go" #line 3 "proxy.go"
#include <stdlib.h> #include <stdlib.h>
#line 1 "cgo-generated-wrapper" #line 1 "cgo-generated-wrapper"
@ -95,6 +95,26 @@ extern __declspec(dllexport) char* GetProxyHealth(void);
// //
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode); extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
// 导出函数:查询机器码
//
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
// 导出函数:充值卡密
//
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
// 导出函数:获取代理服务器列表
//
extern __declspec(dllexport) char* GetProxies(char* machineCode);
// 导出函数:检查卡密是否过期
//
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
// 导出函数:初始化代理管理器
//
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
// 导出函数释放C字符串内存 // 导出函数释放C字符串内存
// //
extern __declspec(dllexport) void FreeCString(char* str); extern __declspec(dllexport) void FreeCString(char* str);

995
dyso.go
View File

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

2088
es/main.go Normal file

File diff suppressed because it is too large Load Diff

150418
es/sale_isbns_empty_pic.txt Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

BIN
excel/dll/excel.dll Normal file

Binary file not shown.

141
excel/dll/excel.h Normal file
View File

@ -0,0 +1,141 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "main.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
// 创建新的Excel管理器并返回指针
//
extern __declspec(dllexport) long long int NewExcelManagerInstance(void);
// 释放Excel管理器
//
extern __declspec(dllexport) void FreeExcelManager(long long int handle);
// 读取Excel数据
//
extern __declspec(dllexport) int ReadExcelData(long long int handle, char* filename, char* sheet, char** result);
// 批量写入数据到Excel文件
//
extern __declspec(dllexport) int WriteBatchData(long long int handle, char* filename, char* sheet, char* cells, char* values, int count);
// 追加数据到Excel文件末尾
//
extern __declspec(dllexport) int AppendDataToExcel(long long int handle, char* filename, char* sheet, char* values, int count);
// 搜索包含关键字的单元格
//
extern __declspec(dllexport) int SearchByKeyword(long long int handle, char* filename, char* sheet, char* keyword, char** result);
// 搜索整行包含关键字的行
//
extern __declspec(dllexport) int SearchRowsByKeyword(long long int handle, char* filename, char* sheet, char* keyword, char** result);
// 创建新文件并写入数据
//
extern __declspec(dllexport) int CreateAndWriteExcel(long long int handle, char* filename, char* sheet, char* rowsData);
// 增强版合并Excel文件支持指定文件列表
//
extern __declspec(dllexport) int MergeExcelFilesEx(long long int handle, char* sourceDir, char* specificFiles, char* outputFile, char* sheetName, int mergeByColumn, int includeHeaders, int skipEmptyRows, char* filePattern, char* sourceSheet, int addSourceColumn, int addIndexColumn);
// 并行合并Excel文件增强版
//
extern __declspec(dllexport) int MergeExcelFilesParallelEx(long long int handle, char* sourceDir, char** specificFiles, int fileCount, char* outputFile, char* sheetName, int includeHeaders, int skipEmptyRows, char* filePattern, char* sourceSheet, int addSourceColumn, int addIndexColumn, int workers);
// 合并同一文件中的多个sheet
//
extern __declspec(dllexport) int MergeSheetsInFile(long long int handle, char* filename, char* outputFile, char* targetSheetName);
// 释放C字符串
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

516
excel/excelDllTest.go Normal file
View File

@ -0,0 +1,516 @@
package main
import "C"
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
// ExcelManagerDLL 封装DLL操作
type ExcelManagerDLL struct {
dll *windows.LazyDLL
newExcelManager *windows.LazyProc
freeExcelManager *windows.LazyProc
readExcelData *windows.LazyProc
writeBatchData *windows.LazyProc
appendDataToExcel *windows.LazyProc
searchByKeyword *windows.LazyProc
searchRowsByKeyword *windows.LazyProc
createAndWriteExcel *windows.LazyProc
mergeExcelFilesEx *windows.LazyProc
mergeExcelFilesParallelEx *windows.LazyProc
mergeSheetsInFile *windows.LazyProc
freeCString *windows.LazyProc
}
// LoadExcelManagerDLL 加载DLL并获取函数指针
func LoadExcelManagerDLL(dllPath string) (*ExcelManagerDLL, error) {
// 使用windows包的LazyDLL加载DLL
dll := windows.NewLazyDLL(dllPath)
// 验证DLL是否成功加载
if err := dll.Load(); err != nil {
return nil, fmt.Errorf("加载DLL失败: %v", err)
}
return &ExcelManagerDLL{
dll: dll,
newExcelManager: dll.NewProc("NewExcelManagerInstance"),
freeExcelManager: dll.NewProc("FreeExcelManager"),
readExcelData: dll.NewProc("ReadExcelData"),
writeBatchData: dll.NewProc("WriteBatchData"),
appendDataToExcel: dll.NewProc("AppendDataToExcel"),
searchByKeyword: dll.NewProc("SearchByKeyword"),
searchRowsByKeyword: dll.NewProc("SearchRowsByKeyword"),
createAndWriteExcel: dll.NewProc("CreateAndWriteExcel"),
mergeExcelFilesEx: dll.NewProc("MergeExcelFilesEx"),
mergeExcelFilesParallelEx: dll.NewProc("MergeExcelFilesParallelEx"),
mergeSheetsInFile: dll.NewProc("MergeSheetsInFile"),
freeCString: dll.NewProc("FreeCString"),
}, nil
}
// NewExcelManagerInstance 安全地创建实例(使用句柄)
func (dll *ExcelManagerDLL) NewExcelManagerInstance() (int64, error) {
ret, _, err := dll.newExcelManager.Call()
if err != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("调用NewExcelManagerInstance失败: %v", err)
}
return int64(ret), nil
}
func (dll *ExcelManagerDLL) FreeExcelManager(handle int64) error {
_, _, err := dll.freeExcelManager.Call(uintptr(handle))
if err != windows.ERROR_SUCCESS {
return fmt.Errorf("调用FreeExcelManager失败: %v", err)
}
return nil
}
func (dll *ExcelManagerDLL) ReadExcelData(handle int64, filename, sheet string) (string, error) {
var resultPtr uintptr
// 转换字符串
filenamePtr, _ := syscall.BytePtrFromString(filename)
sheetPtr, _ := syscall.BytePtrFromString(sheet)
// 调用DLL函数
ret, _, _ := dll.readExcelData.Call(
uintptr(handle),
uintptr(unsafe.Pointer(filenamePtr)),
uintptr(unsafe.Pointer(sheetPtr)),
uintptr(unsafe.Pointer(&resultPtr)),
)
if int32(ret) != 0 {
return "", fmt.Errorf("读取Excel数据失败错误码: %d", ret)
}
// 转换结果字符串
if resultPtr != 0 {
resultStr := C.GoString((*C.char)(unsafe.Pointer(resultPtr)))
// 释放C字符串
dll.FreeCString((*C.char)(unsafe.Pointer(resultPtr)))
return resultStr, nil
}
return "", nil
}
func (dll *ExcelManagerDLL) WriteBatchData(handle int64, filename, sheet string, cells, values []string) error {
if len(cells) != len(values) {
return fmt.Errorf("单元格和值数量不匹配")
}
// 转换字符串
filenamePtr, _ := syscall.BytePtrFromString(filename)
sheetPtr, _ := syscall.BytePtrFromString(sheet)
cellMarshal, err := json.Marshal(cells)
if err != nil {
return fmt.Errorf("信息cells转换失败")
}
cellsString := string(cellMarshal)
cellsPtr, _ := syscall.BytePtrFromString(cellsString)
valuesMarshal, err := json.Marshal(values)
if err != nil {
return fmt.Errorf("信息values转换失败")
}
valuesString := string(valuesMarshal)
valuesPtr, _ := syscall.BytePtrFromString(valuesString)
// 调用DLL函数
ret, _, _ := dll.writeBatchData.Call(
uintptr(handle),
uintptr(unsafe.Pointer(filenamePtr)),
uintptr(unsafe.Pointer(sheetPtr)),
uintptr(unsafe.Pointer(cellsPtr)),
uintptr(unsafe.Pointer(valuesPtr)),
uintptr(len(cells)),
)
if int32(ret) != 0 {
return fmt.Errorf("批量写入Excel数据失败错误码: %d", ret)
}
return nil
}
func (dll *ExcelManagerDLL) AppendDataToExcel(handle int64, filename, sheet string, values []string) error {
// 转换字符串
filenamePtr, _ := syscall.BytePtrFromString(filename)
sheetPtr, _ := syscall.BytePtrFromString(sheet)
valuesMarshal, err := json.Marshal(values)
if err != nil {
return fmt.Errorf("信息values转换失败")
}
valuesString := string(valuesMarshal)
valuesPtr, _ := syscall.BytePtrFromString(valuesString)
// 调用DLL函数
ret, _, err := dll.appendDataToExcel.Call(
uintptr(handle),
uintptr(unsafe.Pointer(filenamePtr)),
uintptr(unsafe.Pointer(sheetPtr)),
uintptr(unsafe.Pointer(valuesPtr)),
uintptr(len(values)),
)
if int32(ret) != 0 {
return fmt.Errorf("追加Excel数据失败错误码: %d", ret)
}
return nil
}
func (dll *ExcelManagerDLL) SearchByKeyword(handle int64, filename, sheet, keyword string) (string, error) {
// 转换字符串
filenamePtr, _ := syscall.BytePtrFromString(filename)
sheetPtr, _ := syscall.BytePtrFromString(sheet)
keywordPtr, _ := syscall.BytePtrFromString(keyword)
// 分配内存存储结果指针
var resultPtr uintptr
// 调用DLL函数
ret, _, _ := dll.searchByKeyword.Call(
uintptr(handle),
uintptr(unsafe.Pointer(filenamePtr)),
uintptr(unsafe.Pointer(sheetPtr)),
uintptr(unsafe.Pointer(keywordPtr)),
uintptr(unsafe.Pointer(&resultPtr)),
)
if int32(ret) != 0 {
return "", fmt.Errorf("搜索Excel数据失败错误码: %d", ret)
}
// 转换结果字符串
if resultPtr != 0 {
resultStr := C.GoString((*C.char)(unsafe.Pointer(resultPtr)))
// 释放C字符串
dll.FreeCString((*C.char)(unsafe.Pointer(resultPtr)))
return resultStr, nil
}
return "", nil
}
// CreateAndWriteExcel 创建新文件并写入数据
func (dll *ExcelManagerDLL) CreateAndWriteExcel(handle int64, filename, sheet string, data [][]string) error {
// 确保目录存在
dir := filepath.Dir(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建目录失败: %v", err)
}
fmt.Printf("调试信息:\n")
fmt.Printf(" - 文件名: %s\n", filename)
fmt.Printf(" - Sheet: %s\n", sheet)
marshal, err2 := json.Marshal(data)
if err2 != nil {
return err2
}
dataString := string(marshal)
filenamePtr, _ := syscall.BytePtrFromString(filename)
sheetPtr, _ := syscall.BytePtrFromString(sheet)
dataStringPtr, _ := syscall.BytePtrFromString(dataString)
// 调用DLL函数
ret, _, _ := dll.createAndWriteExcel.Call(
uintptr(handle),
uintptr(unsafe.Pointer(filenamePtr)),
uintptr(unsafe.Pointer(sheetPtr)),
uintptr(unsafe.Pointer(dataStringPtr)),
)
fmt.Printf(" - DLL调用返回: %d\n", int32(ret))
if int32(ret) != 0 {
return fmt.Errorf("创建并写入Excel文件失败错误码: %d", ret)
}
return nil
}
func (dll *ExcelManagerDLL) MergeExcelFilesEx(handle int64, config MergeConfig) error {
// 转换字符串
sourceDirPtr, _ := syscall.BytePtrFromString(config.SourceDir)
outputFilePtr, _ := syscall.BytePtrFromString(config.OutputFile)
sheetNamePtr, _ := syscall.BytePtrFromString(config.SheetName)
filePatternPtr, _ := syscall.BytePtrFromString(config.FilePattern)
sourceSheetPtr, _ := syscall.BytePtrFromString(config.SourceSheet)
marshal, err := json.Marshal(config.SpecificFiles)
if err != nil {
return fmt.Errorf("序列化失败: %s", err)
}
specificFilesString := string(marshal)
specificFilesPtr, _ := syscall.BytePtrFromString(specificFilesString)
// 调用DLL函数
ret, _, err := dll.mergeExcelFilesEx.Call(
uintptr(handle),
uintptr(unsafe.Pointer(sourceDirPtr)),
uintptr(unsafe.Pointer(specificFilesPtr)),
uintptr(unsafe.Pointer(outputFilePtr)),
uintptr(unsafe.Pointer(sheetNamePtr)),
uintptr(boolToInt(config.MergeByColumn)),
uintptr(boolToInt(config.IncludeHeaders)),
uintptr(boolToInt(config.SkipEmptyRows)),
uintptr(unsafe.Pointer(filePatternPtr)),
uintptr(unsafe.Pointer(sourceSheetPtr)),
uintptr(boolToInt(config.AddSourceColumn)),
uintptr(boolToInt(config.AddIndexColumn)),
)
if int32(ret) != 0 {
return fmt.Errorf("合并Excel文件失败错误码: %d", ret)
}
return nil
}
func (dll *ExcelManagerDLL) FreeCString(str *C.char) {
if str != nil {
dll.freeCString.Call(uintptr(unsafe.Pointer(str)))
}
}
//// MergeConfig 合并配置
//type MergeConfig struct {
// SourceDir string // 源目录
// SpecificFiles []string // 指定要合并的文件列表
// OutputFile string // 输出文件
// SheetName string // 目标sheet名称
// MergeByColumn bool // 是否按列合并(默认按行)
// IncludeHeaders bool // 是否包含表头(仅第一个文件)
// SkipEmptyRows bool // 是否跳过空行
// FilePattern string // 文件匹配模式,如 "*.xlsx"
// SourceSheet string // 源sheet名称为空则使用第一个sheet
// AddSourceColumn bool // 是否添加源文件列
// AddIndexColumn bool // 是否添加序号列
//}
// 辅助函数
func boolToInt(b bool) int32 {
if b {
return 1
}
return 0
}
// 创建测试Excel文件
func createTestExcelFile(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
// 写入一些测试数据简单的CSV格式
content := `姓名,年龄,城市
张三,25,北京
李四,30,上海
王五,28,广州
测试数据,35,深圳`
_, err = file.WriteString(content)
return err
}
func main() {
fmt.Println("=== Excel Manager DLL 动态加载测试程序 ===")
// 1. 加载DLL
dllPath := "excel/dll/excel.dll"
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
log.Fatalf("找不到DLL文件: %s", dllPath)
}
fmt.Printf("1. 加载DLL: %s\n", dllPath)
excelDLL, err := LoadExcelManagerDLL(dllPath)
if err != nil {
log.Fatalf("加载DLL失败: %v", err)
}
fmt.Println(" DLL加载成功")
// 2. 创建ExcelManager实例
fmt.Println("\n2. 创建ExcelManager实例...")
handle, err := excelDLL.NewExcelManagerInstance()
if err != nil || handle == 0 {
log.Fatalf("创建ExcelManager实例失败: %v", err)
}
fmt.Printf(" ExcelManager实例创建成功句柄: 0x%x\n", handle)
defer excelDLL.FreeExcelManager(handle)
//// 创建测试目录
//testDir := "./test_data"
//if err := os.MkdirAll(testDir, 0755); err != nil {
// log.Fatalf("创建测试目录失败: %v", err)
//}
//3. 测试CreateAndWriteExcel函数
fmt.Println("\n3. 测试CreateAndWriteExcel函数...")
newFile := filepath.Join("excel", "3128518632404838051.xlsx")
//// 创建测试数据
//testData := [][]string{
// {"序号", "姓名", "年龄", "城市"},
// {"1", "张三", "25", "北京"},
// {"2", "李四", "30", "上海"},
// {"3", "王五", "28", "广州"},
// {"4", "赵六", "35", "深圳"},
// {"5", "测试人员", "40", "杭州"},
//}
//
//fmt.Printf(" 创建新文件: %s\n", newFile)
//fmt.Printf(" 数据维度: %d行 × %d列\n", len(testData), len(testData[0]))
//
//if err := excelDLL.CreateAndWriteExcel(handle, newFile, "员工信息", testData); err != nil {
// fmt.Printf("CreateAndWriteExcel失败: %v\n", err)
//} else {
// fmt.Println("CreateAndWriteExcel成功")
// // 验证文件是否创建成功
// if _, err := os.Stat(newFile); err == nil {
// fmt.Println(" 文件已成功创建")
// // 尝试读取刚刚创建的文件
// fmt.Println(" 验证文件内容...")
result, err := excelDLL.ReadExcelData(handle, newFile, "订单操作记录")
if err != nil {
fmt.Printf(" 读取文件失败: %v\n", err)
} else {
fmt.Printf(" 原始数据:\n%s\n", result)
}
// 1. 去除首尾可能存在的空格
result = strings.TrimSpace(result)
//
// }
//}
//
//// 4. 测试批量写入
//fmt.Println("\n5. 测试批量写入...")
//cells := []string{"A6", "B6", "C6", "D6", "E6"}
//values := []string{"5", "批量2", "批量3", "批量4", "批量5"}
//if err := excelDLL.WriteBatchData(handle, newFile, "员工信息", cells, values); err != nil {
// fmt.Printf(" 批量写入失败: %v\n", err)
//} else {
// fmt.Println(" 批量写入成功")
//}
//
//// 5. 测试追加数据
//fmt.Println("\n6. 测试追加数据...")
//appendValues := []string{"6", "孙七", "32", "南京", "追加数据"}
//if err := excelDLL.AppendDataToExcel(handle, newFile, "员工信息", appendValues); err != nil {
// fmt.Printf(" 追加数据失败: %v\n", err)
//} else {
// fmt.Println(" 追加数据成功")
//}
//
//// 7. 测试搜索功能
//fmt.Println("\n7. 测试搜索功能...")
//keyword := "nihao"
//searchResult, err := excelDLL.SearchByKeyword(handle, newFile, "员工信息", keyword)
//if err != nil {
// fmt.Printf("搜索失败: %v\n", err)
//} else {
// if searchResult == "" {
// fmt.Printf("没有搜索到该信息: %s\n", keyword)
// } else {
// fmt.Printf("搜索成功,结果:\n%s\n", searchResult)
// }
//}
//
//// 8. 测试合并文件(需要至少两个文件)
//fmt.Println("\n8. 测试合并文件...")
//// 合并两个文件
//mergedFile := filepath.Join("excel", "merged_output.xlsx")
//fmt.Println(mergedFile)
//mergeConfig := MergeConfig{
// SourceDir: "excel",
// SpecificFiles: []string{newFile, "excel/new_file1.xlsx"},
// OutputFile: mergedFile,
// SheetName: "合并结果",
// MergeByColumn: false,
// IncludeHeaders: true,
// SkipEmptyRows: false,
// FilePattern: "",
// SourceSheet: "", // 使用默认sheet
// AddSourceColumn: true,
// AddIndexColumn: true,
//}
//
//if err := excelDLL.MergeExcelFilesEx(handle, mergeConfig); err != nil {
// fmt.Printf(" 合并文件失败: %v\n", err)
//} else {
// fmt.Println(" 合并文件成功")
// fmt.Printf(" 输出文件: %s\n", mergedFile)
//}
//// 9. 清理测试文件(可选)
//fmt.Println("\n9. 清理测试文件...")
//cleanup := true
//if cleanup {
// filesToRemove := []string{
// newFile,
// secondFile,
// filepath.Join(testDir, "merged_output.xlsx"),
// }
//
// for _, file := range filesToRemove {
// if _, err := os.Stat(file); err == nil {
// if err := os.Remove(file); err == nil {
// fmt.Printf(" 删除: %s\n", file)
// }
// }
// }
}
//fmt.Println("\n=== 所有测试完成 ===")
//
////显 示测试总结
//fmt.Println("\n测试总结:")
//fmt.Println("1. CreateAndWriteExcel - 创建新文件并写入数据 ✓")
//fmt.Println("2. WriteDataToExcel - 写入单个单元格 ✓")
//fmt.Println("3. WriteBatchData - 批量写入数据 ✓")
//fmt.Println("4. AppendDataToExcel - 追加数据 ✓")
//fmt.Println("5. SearchByKeyword - 搜索关键词 ✓")
//fmt.Println("6. MergeExcelFilesEx - 合并文件 ✓")
//fmt.Println("7. ReadExcelData - 读取数据 ✓")
//}
//func ParseExcelDataToJSON(excelData string) ([]map[string]string, error) {
// if excelData == "" {
// return nil, fmt.Errorf("Excel数据为空")
// }
//
// // 使用csv解析器解析数据
// reader := csv.NewReader(strings.NewReader(excelData))
// records, err := reader.ReadAll()
// if err != nil {
// return nil, fmt.Errorf("解析CSV数据失败: %v", err)
// }
//
// if len(records) < 2 {
// return nil, fmt.Errorf("数据行数不足")
// }
//
// // 提取表头(第一行)
// headers := records[0]
//
// // 将数据转换为JSON格式
// var jsonData []map[string]string
//
// // 从第二行开始(跳过表头)
// for i := 1; i < len(records); i++ {
// row := records[i]
// rowData := make(map[string]string)
//
// // 确保每行的列数与表头一致
// for j := 0; j < len(headers) && j < len(row); j++ {
// rowData[headers[j]] = row[j]
// }
// jsonData = append(jsonData, rowData)
// }
//
// return jsonData, nil
//}

1425
excel/excel_so.go Normal file

File diff suppressed because it is too large Load Diff

1451
excel/main.go Normal file

File diff suppressed because it is too large Load Diff

BIN
excel/new_file.xlsx Normal file

Binary file not shown.

621
excel/test.go Normal file
View File

@ -0,0 +1,621 @@
package main
//import (
// "fmt"
// "github.com/xuri/excelize/v2"
// "os"
// "time"
//)
//
//// ============ main函数 ============
//
//func main() {
// // 创建Excel管理器
// excelMgr := NewExcelManager()
// defer excelMgr.CloseAll()
//
// fmt.Println("=== Excel文件操作工具 ===")
// fmt.Println()
//
// // 1. 创建测试文件
// fmt.Println("=== 步骤1: 创建测试文件 ===")
// createTestFiles()
// fmt.Println()
//
// // 2. 测试基本功能
// fmt.Println("=== 步骤2: 测试基本功能 ===")
// testBasicFunctions(excelMgr)
// fmt.Println()
//
// // 3. 测试WriteData函数
// fmt.Println("=== 步骤3: 测试WriteData函数 ===")
// testWriteDataFunction(excelMgr)
// fmt.Println()
//
// // 4. 测试SearchRowData函数
// fmt.Println("=== 步骤4: 测试SearchRowData函数 ===")
// testSearchRowDataFunction(excelMgr)
// fmt.Println()
// //
// //// 5. 测试CreateAndWrite函数
// fmt.Println("=== 步骤5: 测试CreateAndWrite函数 ===")
// testCreateAndWriteFunction(excelMgr)
// fmt.Println()
//
// //// 3. 测试合并功能(包含源文件和序号)
// //fmt.Println("=== 步骤3: 测试合并功能(包含源文件和序号)===")
// //start := time.Now()
// //mergeConfig := MergeConfig{
// // SourceDir: ".",
// // OutputFile: "excel/merged_with_info.xlsx",
// // SheetName: "合并数据",
// // SourceSheet: "员工数据",
// // IncludeHeaders: true,
// // SkipEmptyRows: false,
// // FilePattern: "excel/data*.xlsx",
// // AddSourceColumn: false,
// // AddIndexColumn: false,
// //}
// //
// //err := excelMgr.MergeExcelFiles(mergeConfig)
// //if err != nil {
// // fmt.Printf("合并失败: %v\n", err)
// //} else {
// // elapsed := time.Since(start)
// // fmt.Printf("合并完成,耗时: %v\n", elapsed)
// //}
// //fmt.Println()
//
// //// 4. 测试并行合并
// //fmt.Println("=== 步骤4: 测试并行合并 ===")
// //start = time.Now()
// //parallelConfig := MergeConfig{
// // SourceDir: ".",
// // OutputFile: "excel/merged_parallel.xlsx",
// // SheetName: "并行合并数据",
// // SourceSheet: "员工数据",
// // IncludeHeaders: true,
// // SkipEmptyRows: false,
// // FilePattern: "excel/data*.xlsx",
// // AddSourceColumn: false,
// // AddIndexColumn: false,
// //}
// //
// //err = excelMgr.MergeExcelFilesParallel(parallelConfig, 2)
// //if err != nil {
// // fmt.Printf("并行合并失败: %v\n", err)
// //} else {
// // elapsed := time.Since(start)
// // fmt.Printf("并行合并完成,耗时: %v\n", elapsed)
// //}
//
// //// 5. 测试按列合并
// //fmt.Println("=== 步骤5: 测试按列合并 ===")
// //start = time.Now()
// //columnConfig := MergeConfig{
// // SourceDir: ".",
// // OutputFile: "merged_by_column.xlsx",
// // SheetName: "按列合并",
// // SourceSheet: "员工数据",
// // IncludeHeaders: false,
// // MergeByColumn: true,
// // FilePattern: "data*.xlsx",
// //}
// //
// //err = excelMgr.MergeByColumn(columnConfig)
// //if err != nil {
// // fmt.Printf("按列合并失败: %v\n", err)
// //} else {
// // elapsed := time.Since(start)
// // fmt.Printf("按列合并完成,耗时: %v\n", elapsed)
// //}
// //fmt.Println()
//
// // 6. 测试合并指定文件
// fmt.Println("=== 步骤6: 测试合并指定文件 ===")
// start := time.Now()
// specificConfig := MergeConfig{
// SpecificFiles: []string{"excel/data1.xlsx", "excel/data3.xlsx"},
// OutputFile: "excel/merged_specific.xlsx",
// SheetName: "指定文件合并",
// SourceSheet: "员工数据",
// IncludeHeaders: true,
// SkipEmptyRows: false,
// AddSourceColumn: false,
// AddIndexColumn: false,
// }
//
// err := excelMgr.MergeExcelFiles(specificConfig)
// if err != nil {
// fmt.Printf("指定文件合并失败: %v\n", err)
// } else {
// elapsed := time.Since(start)
// fmt.Printf("指定文件合并完成,耗时: %v\n", elapsed)
// }
// fmt.Println()
//
// //// 7. 测试合并多sheet文件
// //fmt.Println("=== 步骤7: 测试合并多sheet文件 ===")
// //err = createMultiSheetFile()
// //if err != nil {
// // fmt.Printf("创建多sheet文件失败: %v\n", err)
// // return
// //}
// //
// //start = time.Now()
// //err = excelMgr.MergeSheetsInSameFile("multi_sheet.xlsx", "merged_sheets.xlsx", "所有Sheet数据")
// //if err != nil {
// // fmt.Printf("合并sheet失败: %v\n", err)
// //} else {
// // elapsed := time.Since(start)
// // fmt.Printf("合并sheet完成耗时: %v\n", elapsed)
// //}
// //fmt.Println()
//
// // 8. 清理测试文件
// //fmt.Println("=== 步骤8: 清理测试文件 ===")
// //cleanupTestFiles()
//}
//
//// 测试基本功能
//func testBasicFunctions(em *ExcelManager) {
// // 测试读取数据
// rows, err := em.ReadData("excel/data1.xlsx", "员工数据")
// if err != nil {
// fmt.Printf("读取数据失败: %v\n", err)
// return
// }
// fmt.Printf("data1.xlsx 有 %d 行数据\n", len(rows))
//
// // 测试搜索功能
// results, err := em.SearchByKeyword("excel/data1.xlsx", "员工数据", "员工")
// if err != nil {
// fmt.Printf("搜索失败: %v\n", err)
// } else {
// fmt.Printf("找到包含'员工'的 %d 个结果\n", len(results))
// }
//
// // 测试追加数据
// newRow := []interface{}{999, "测试员工", 30, "测试部", "2023-12-01", 20000}
// err = em.AppendData("excel/data1.xlsx", "员工数据", newRow)
// if err != nil {
// fmt.Printf("追加数据失败: %v\n", err)
// } else {
// fmt.Println("追加数据成功")
// }
//}
//
////// 测试WriteData函数 - 从最大行后面加入新数据
////func testWriteDataFunction(em *ExcelManager) {
//// // 1. 首先读取现有文件获取最大行数
//// fmt.Println("1. 读取现有文件获取最大行数:")
//// rows, err := em.ReadData("excel/data1.xlsx", "员工数据")
//// if err != nil {
//// fmt.Printf("读取数据失败: %v\n", err)
//// return
//// }
////
//// maxRow := len(rows)
//// fmt.Printf(" 文件当前有 %d 行数据\n", maxRow)
////
//// // 显示最后几行数据
//// if maxRow > 0 {
//// showRows := 3
//// if maxRow < showRows {
//// showRows = maxRow
//// }
//// fmt.Printf(" 最后%d行数据预览:\n", showRows)
//// for i := maxRow - showRows; i < maxRow; i++ {
//// fmt.Printf(" 第%d行: %v\n", i+1, rows[i])
//// }
//// }
////
//// // 2. 在最大行后面添加新数据
//// fmt.Println("\n2. 在最大行后面添加新数据:")
////
//// // 计算新数据开始的行号
//// // 注意Excel行号从1开始但rows长度已经包含了所有行
//// startRow := maxRow + 1
////
//// // 准备要添加的新数据从startRow开始
//// newData := make(map[string]interface{})
////
//// // 添加第1条新记录在startRow行
//// newData[fmt.Sprintf("A%d", startRow)] = maxRow + 1
//// newData[fmt.Sprintf("B%d", startRow)] = "新增员工1"
//// newData[fmt.Sprintf("C%d", startRow)] = 35
//// newData[fmt.Sprintf("D%d", startRow)] = "研发部"
//// newData[fmt.Sprintf("E%d", startRow)] = time.Now().Format("2006-01-02")
//// newData[fmt.Sprintf("F%d", startRow)] = 25000
////
//// // 添加第2条新记录在startRow+1行
//// newData[fmt.Sprintf("A%d", startRow+1)] = maxRow + 2
//// newData[fmt.Sprintf("B%d", startRow+1)] = "新增员工2"
//// newData[fmt.Sprintf("C%d", startRow+1)] = 28
//// newData[fmt.Sprintf("D%d", startRow+1)] = "测试部"
//// newData[fmt.Sprintf("E%d", startRow+1)] = time.Now().Format("2006-01-02")
//// newData[fmt.Sprintf("F%d", startRow+1)] = 18000
////
//// // 添加第3条新记录在startRow+2行
//// newData[fmt.Sprintf("A%d", startRow+2)] = maxRow + 3
//// newData[fmt.Sprintf("B%d", startRow+2)] = "新增员工3"
//// newData[fmt.Sprintf("C%d", startRow+2)] = 32
//// newData[fmt.Sprintf("D%d", startRow+2)] = "运维部"
//// newData[fmt.Sprintf("E%d", startRow+2)] = time.Now().Format("2006-01-02")
//// newData[fmt.Sprintf("F%d", startRow+2)] = 22000
////
//// fmt.Printf(" 将在第%d行开始添加3条新记录\n", startRow)
////
//// // 使用WriteData写入新数据
//// err = em.WriteData("excel/data1.xlsx", "员工数据", newData)
//// if err != nil {
//// fmt.Printf("写入新数据失败: %v\n", err)
//// return
//// }
////
//// fmt.Println(" 写入新数据成功")
////
//// // 3. 验证添加结果
//// fmt.Println("\n3. 验证添加结果:")
////
//// // 重新读取文件
//// rows, err = em.ReadData("excel/data1.xlsx", "员工数据")
//// if err != nil {
//// fmt.Printf("重新读取数据失败: %v\n", err)
//// return
//// }
////
//// newMaxRow := len(rows)
//// fmt.Printf(" 添加后文件有 %d 行数据,增加了 %d 行\n", newMaxRow, newMaxRow-maxRow)
////
//// // 显示新增的几行数据
//// if newMaxRow > maxRow {
//// addedRows := newMaxRow - maxRow
//// fmt.Printf(" 新增的%d行数据:\n", addedRows)
//// for i := maxRow; i < newMaxRow; i++ {
//// fmt.Printf(" 第%d行: %v\n", i+1, rows[i])
//// }
//// }
////
//// // 4. 测试在空文件的最大行后添加数据
//// fmt.Println("\n4. 测试在空文件的最大行后添加数据:")
////
//// // 创建一个新的空Excel文件
//// emptyData := [][]interface{}{
//// {"ID", "Name", "Value"}, // 只有表头
//// }
////
//// // 先创建只有表头的文件
//// emptyErr := em.CreateAndWrite("excel/empty_test.xlsx", "测试数据", emptyData)
//// if emptyErr != nil {
//// fmt.Printf("创建空文件失败: %v\n", emptyErr)
//// } else {
//// fmt.Println(" 创建空文件成功")
////
//// // 读取空文件
//// emptyRows, readErr := em.ReadData("excel/empty_test.xlsx", "测试数据")
//// if readErr != nil {
//// fmt.Printf("读取空文件失败: %v\n", readErr)
//// } else {
//// emptyMaxRow := len(emptyRows)
//// fmt.Printf(" 空文件有 %d 行数据\n", emptyMaxRow)
////
//// // 在空文件的最大行后添加数据
//// emptyNewData := make(map[string]interface{})
////
//// // 由于只有表头所以从第2行开始添加
//// emptyStartRow := emptyMaxRow + 1
////
//// // 添加测试数据
//// emptyNewData[fmt.Sprintf("A%d", emptyStartRow)] = 1
//// emptyNewData[fmt.Sprintf("B%d", emptyStartRow)] = "测试项目1"
//// emptyNewData[fmt.Sprintf("C%d", emptyStartRow)] = 100.5
////
//// emptyNewData[fmt.Sprintf("A%d", emptyStartRow+1)] = 2
//// emptyNewData[fmt.Sprintf("B%d", emptyStartRow+1)] = "测试项目2"
//// emptyNewData[fmt.Sprintf("C%d", emptyStartRow+1)] = 200.75
////
//// fmt.Printf(" 将在空文件的第%d行开始添加2条记录\n", emptyStartRow)
////
//// // 写入数据
//// writeErr := em.WriteData("excel/empty_test.xlsx", "测试数据", emptyNewData)
//// if writeErr != nil {
//// fmt.Printf(" 向空文件写入数据失败: %v\n", writeErr)
//// } else {
//// fmt.Println(" 向空文件写入数据成功")
////
//// // 验证结果
//// finalRows, finalErr := em.ReadData("excel/empty_test.xlsx", "测试数据")
//// if finalErr != nil {
//// fmt.Printf(" 验证数据失败: %v\n", finalErr)
//// } else {
//// finalRowCount := len(finalRows)
//// fmt.Printf(" 最终文件有 %d 行数据:\n", finalRowCount)
////
//// for i, row := range finalRows {
//// fmt.Printf(" 第%d行: %v\n", i+1, row)
//// }
//// }
//// }
//// }
//// }
////
//// // 5. 测试替换现有行的数据
//// fmt.Println("\n5. 测试替换现有行的数据:")
////
//// // 替换第2行的部分数据
//// replaceData := map[string]interface{}{
//// "B2": "修改后的姓名",
//// "D2": "修改后的部门",
//// "F2": 30000, // 修改薪资
//// }
////
//// fmt.Println(" 将修改第2行的数据:")
//// fmt.Println(" B2: '修改后的姓名'")
//// fmt.Println(" D2: '修改后的部门'")
//// fmt.Println(" F2: 30000")
////
//// replaceErr := em.WriteData("excel/data1.xlsx", "员工数据", replaceData)
//// if replaceErr != nil {
//// fmt.Printf(" 修改数据失败: %v\n", replaceErr)
//// } else {
//// fmt.Println(" 修改数据成功")
////
//// // 验证修改结果
//// verifyRows, verifyErr := em.ReadData("excel/data1.xlsx", "员工数据")
//// if verifyErr != nil {
//// fmt.Printf(" 验证修改失败: %v\n", verifyErr)
//// } else if len(verifyRows) >= 2 {
//// fmt.Printf(" 修改后的第2行数据: %v\n", verifyRows[1])
//// }
//// }
////}
//
//// 测试SearchRowData函数
//func testSearchRowDataFunction(em *ExcelManager) {
// fmt.Println("1. 测试搜索包含'员工'的行:")
// rows, err := em.SearchRowData("excel/data1.xlsx", "员工数据", "员工")
// if err != nil {
// fmt.Printf("搜索行数据失败: %v\n", err)
// } else {
// fmt.Printf("找到 %d 行包含'员工':\n", len(rows))
// for i, row := range rows {
// fmt.Printf(" 第%d行: %v\n", i+1, row)
// }
// }
//
// fmt.Println("\n2. 测试搜索包含'技术部'的行:")
// rows, err = em.SearchRowData("excel/data1.xlsx", "员工数据", "技术部")
// if err != nil {
// fmt.Printf("搜索行数据失败: %v\n", err)
// } else {
// fmt.Printf("找到 %d 行包含'技术部':\n", len(rows))
// for i, row := range rows {
// fmt.Printf(" 第%d行: %v\n", i+1, row)
// }
// }
//
// fmt.Println("\n3. 测试搜索包含'30000'的行:")
// rows, err = em.SearchRowData("excel/data1.xlsx", "员工数据", "30000")
// if err != nil {
// fmt.Printf("搜索行数据失败: %v\n", err)
// } else {
// fmt.Printf("找到 %d 行包含'30000':\n", len(rows))
// for i, row := range rows {
// fmt.Printf(" 第%d行: %v\n", i+1, row)
// }
// }
//
// fmt.Println("\n4. 测试搜索不存在的关键词:")
// rows, err = em.SearchRowData("excel/data1.xlsx", "员工数据", "不存在的关键词")
// if err != nil {
// fmt.Printf("搜索行数据失败: %v\n", err)
// } else {
// fmt.Printf("找到 %d 行包含'不存在的关键词'\n", len(rows))
// if len(rows) == 0 {
// fmt.Println(" (正确: 没有找到匹配的行)")
// }
// }
//}
//
//// 测试CreateAndWrite函数
//func testCreateAndWriteFunction(em *ExcelManager) {
// fmt.Println("1. 测试创建新文件并写入数据:")
//
// //// 准备测试数据
// //testData := [][]string{}{
// // {"序号", "产品名称", "价格", "库存", "类别"},
// // {1, "笔记本电脑", 6999.99, 50, "电子产品"},
// // {2, "智能手机", 3999.99, 100, "电子产品"},
// // {3, "办公椅", 899.99, 30, "办公家具"},
// // {4, "台灯", 199.99, 80, "家居用品"},
// // {5, "书籍", 59.99, 200, "文化用品"},
// // {6, "水杯", 39.99, 150, "日用品"},
// // {7, "背包", 299.99, 60, "箱包"},
// // {8, "鼠标", 99.99, 120, "电子产品"},
// // {9, "键盘", 199.99, 70, "电子产品"},
// // {10, "显示器", 1299.99, 40, "电子产品"},
// //}
//
// err := em.CreateAndWrite("excel/test_create.xlsx", "产品数据", testData)
// if err != nil {
// fmt.Printf("创建并写入文件失败: %v\n", err)
// } else {
// fmt.Println("创建并写入文件成功")
//
// // 验证文件
// if _, err := os.Stat("excel/test_create.xlsx"); err == nil {
// fmt.Println("文件创建成功: excel/test_create.xlsx")
//
// rows, err := em.ReadData("excel/test_create.xlsx", "产品数据")
// if err != nil {
// fmt.Printf("读取文件失败: %v\n", err)
// } else {
// fmt.Printf("文件有 %d 行数据:\n", len(rows))
//
// // 显示前5行
// limit := 5
// if len(rows) < limit {
// limit = len(rows)
// }
//
// fmt.Println(" 前5行数据:")
// for i := 0; i < limit; i++ {
// fmt.Printf(" 第%d行: %v\n", i+1, rows[i])
// }
//
// if len(rows) > limit {
// fmt.Printf(" ... 还有 %d 行数据\n", len(rows)-limit)
// }
// }
//
// // 测试搜索功能
// fmt.Println("\n2. 在新建文件中测试搜索:")
// searchResults, err := em.SearchByKeyword("excel/test_create.xlsx", "产品数据", "电子")
// if err != nil {
// fmt.Printf("搜索失败: %v\n", err)
// } else {
// fmt.Printf("找到 %d 个包含'电子'的单元格:\n", len(searchResults))
// for i, result := range searchResults {
// fmt.Printf(" %d. %s\n", i+1, result)
// }
// }
//
// // 测试行搜索
// fmt.Println("\n3. 在新建文件中测试行搜索:")
// rowResults, err := em.SearchRowData("excel/test_create.xlsx", "产品数据", "电子")
// if err != nil {
// fmt.Printf("行搜索失败: %v\n", err)
// } else {
// fmt.Printf("找到 %d 行包含'电子':\n", len(rowResults))
// for i, row := range rowResults {
// fmt.Printf(" 第%d行: %v\n", i+1, row)
// }
// }
// }
// }
//}
//
//// 创建测试文件
//func createTestFiles() {
// testFiles := []string{
// "excel/data1.xlsx",
// "excel/data2.xlsx",
// "excel/data3.xlsx",
// }
//
// for i, filename := range testFiles {
// data := [][]interface{}{
// {"ID", "姓名", "年龄", "部门", "入职日期", "薪资"},
// {i*100 + 1, fmt.Sprintf("员工%d", i*3+1), 25 + i, "技术部", "2023-01-15", 15000 + i*1000},
// {i*100 + 2, fmt.Sprintf("员工%d", i*3+2), 28 + i, "市场部", "2023-02-20", 12000 + i*1000},
// {i*100 + 3, fmt.Sprintf("员工%d", i*3+3), 32 + i, "销售部", "2023-03-10", 18000 + i*1000},
// {i*100 + 4, fmt.Sprintf("员工%d", i*3+4), 26 + i, "人事部", "2023-04-05", 10000 + i*1000},
// }
// err := createExcelFile(filename, "员工数据", data)
// if err != nil {
// fmt.Printf("创建文件 %s 失败: %v\n", filename, err)
// return
// }
// fmt.Printf("创建文件: %s (共%d行数据)\n", filename, len(data))
// }
//}
//
//// 创建多sheet测试文件
//func createMultiSheetFile() error {
// file := excelize.NewFile()
// defer file.Close()
//
// // Sheet1
// file.NewSheet("部门数据")
// data1 := [][]interface{}{
// {"部门", "人数", "预算"},
// {"技术部", 50, 1000000},
// {"市场部", 30, 800000},
// {"销售部", 40, 900000},
// }
// writeDataToSheet(file, "部门数据", data1)
//
// // Sheet2
// file.NewSheet("项目数据")
// data2 := [][]interface{}{
// {"项目", "负责人", "进度", "预算"},
// {"项目A", "张三", "80%", 500000},
// {"项目B", "李四", "60%", 300000},
// {"项目C", "王五", "90%", 400000},
// }
// writeDataToSheet(file, "项目数据", data2)
//
// // Sheet3
// file.NewSheet("财务数据")
// data3 := [][]interface{}{
// {"月份", "收入", "支出", "利润"},
// {"1月", 500000, 300000, 200000},
// {"2月", 550000, 320000, 230000},
// {"3月", 600000, 350000, 250000},
// }
// writeDataToSheet(file, "财务数据", data3)
//
// // 删除默认的Sheet1
// file.DeleteSheet("Sheet1")
//
// return file.SaveAs("multi_sheet.xlsx")
//}
//
//// 写入数据到sheet
//func writeDataToSheet(file *excelize.File, sheet string, data [][]interface{}) {
// for rowIndex, row := range data {
// for colIndex, value := range row {
// cell, _ := excelize.CoordinatesToCellName(colIndex+1, rowIndex+1)
// file.SetCellValue(sheet, cell, value)
// }
// }
//}
//
//// 创建Excel文件
//func createExcelFile(filename, sheet string, data [][]interface{}) error {
// file := excelize.NewFile()
// defer file.Close()
//
// // 删除默认的Sheet1
// file.DeleteSheet("Sheet1")
//
// // 创建新sheet
// sheetIndex, err := file.NewSheet(sheet)
// if err != nil {
// return err
// }
// file.SetActiveSheet(sheetIndex)
//
// // 写入数据
// for rowIndex, row := range data {
// for colIndex, value := range row {
// cell, _ := excelize.CoordinatesToCellName(colIndex+1, rowIndex+1)
// file.SetCellValue(sheet, cell, value)
// }
// }
//
// return file.SaveAs(filename)
//}
//
//// 清理测试文件
//func cleanupTestFiles() {
// filesToClean := []string{
// "data1.xlsx",
// "data2.xlsx",
// "data3.xlsx",
// "merged_with_info.xlsx",
// "merged_parallel.xlsx",
// "merged_by_column.xlsx",
// "merged_specific.xlsx",
// "multi_sheet.xlsx",
// "merged_sheets.xlsx",
// }
//
// for _, file := range filesToClean {
// if _, err := os.Stat(file); err == nil {
// os.Remove(file)
// fmt.Printf("删除: %s\n", file)
// }
// }
//}

24
go.mod
View File

@ -5,9 +5,14 @@ go 1.25
require ( require (
github.com/PuerkitoBio/goquery v1.10.3 github.com/PuerkitoBio/goquery v1.10.3
github.com/chromedp/chromedp v0.14.2 github.com/chromedp/chromedp v0.14.2
github.com/go-ini/ini v1.67.0 github.com/disintegration/imaging v1.6.2
github.com/elastic/go-elasticsearch/v8 v8.19.0
github.com/go-sql-driver/mysql v1.9.3 github.com/go-sql-driver/mysql v1.9.3
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/parnurzeal/gorequest v0.3.0 github.com/parnurzeal/gorequest v0.3.0
github.com/xuri/excelize/v2 v2.10.0
golang.org/x/image v0.25.0
golang.org/x/sys v0.37.0
) )
require ( require (
@ -15,15 +20,26 @@ require (
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
github.com/elazarl/goproxy v1.7.2 // indirect github.com/elazarl/goproxy v1.7.2 // indirect
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect github.com/gobwas/ws v1.4.0 // indirect
github.com/moul/http2curl v1.0.0 // indirect github.com/moul/http2curl v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/smartystreets/goconvey v1.8.1 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect github.com/tiendc/go-deepcopy v1.7.1 // indirect
golang.org/x/net v0.39.0 // indirect github.com/xuri/efp v0.0.1 // indirect
golang.org/x/sys v0.34.0 // indirect github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/text v0.30.0 // indirect
) )

58
go.sum
View File

@ -12,12 +12,21 @@ github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipw
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/elastic/elastic-transport-go/v8 v8.7.0 h1:OgTneVuXP2uip4BA658Xi6Hfw+PeIOod2rY3GVMGoVE=
github.com/elastic/elastic-transport-go/v8 v8.7.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
github.com/elastic/go-elasticsearch/v8 v8.19.0 h1:VmfBLNRORY7RZL+9hTxBD97ehl9H8Nxf2QigDh6HuMU=
github.com/elastic/go-elasticsearch/v8 v8.19.0/go.mod h1:F3j9e+BubmKvzvLjNui/1++nJuJxbkhHefbaT0kFKGY=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs= github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@ -26,6 +35,7 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
@ -35,6 +45,8 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI= github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
@ -43,19 +55,45 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -70,8 +108,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -91,8 +129,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -111,8 +149,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

BIN
image/dll/image.dll Normal file

Binary file not shown.

113
image/dll/image.h Normal file
View File

@ -0,0 +1,113 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "image.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
// =================== C 导出函数 =======================
// 处理图片成功
//
extern __declspec(dllexport) char* ProcessImage(char* jsonConfig);
// 根据原始图片生成新的白底图片
//
extern __declspec(dllexport) char* CreateWhiteBottomCenteredImage(char* jsonConfig, int width, int height);
// 根据高度生成等比例图片
//
extern __declspec(dllexport) char* ResizeToHeightQuality(char* jsonConfig, int targetHeight);
// 去掉白边并转PNG图片工具
//
extern __declspec(dllexport) char* RemoveWhiteBorderAndPNG(char* jsonConfig);
// 导出函数释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

590
image/image.go Normal file
View File

@ -0,0 +1,590 @@
package main
// #include <stdlib.h>
import "C"
import (
"encoding/json"
"fmt"
"github.com/disintegration/imaging"
"github.com/nfnt/resize"
"golang.org/x/image/draw"
"image"
"image/color"
"image/jpeg"
_ "image/jpeg"
"image/png"
_ "image/png"
"os"
"path/filepath"
"strings"
"unsafe"
)
// Config 配置结构体
type Config struct {
OutputDir string // 输出目录路径
FileName string // 文件名
MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名
MinWhitePct float64 // 纯白占比下限0-1
MaxWhitePct float64 // 纯白占比上限0-1
Extensions []string // 支持的图片扩展名
}
// 检查图片
func validateConfig(config *Config) error {
// 检查百分比范围
if config.MinWhitePct < 0 || config.MinWhitePct > 1 {
return fmt.Errorf("纯白占比下限必须在0-1之间")
}
if config.MaxWhitePct < 0 || config.MaxWhitePct > 1 {
return fmt.Errorf("纯白占比上限必须在0-1之间")
}
if config.MinWhitePct > config.MaxWhitePct {
return fmt.Errorf("下限不能大于上限")
}
return nil
}
func createDirs(config *Config) error {
// 创建输出根目录
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
return err
}
// 创建匹配目录
matchPath := filepath.Join(config.OutputDir, config.MatchDir)
if err := os.MkdirAll(matchPath, 0755); err != nil {
return err
}
// 创建不匹配目录
unmatchPath := filepath.Join(config.OutputDir, config.UnmatchDir)
if err := os.MkdirAll(unmatchPath, 0755); err != nil {
return err
}
equalHeightPath := filepath.Join(config.OutputDir, config.EqualHeightDir)
if err := os.MkdirAll(equalHeightPath, 0755); err != nil {
return err
}
whitePath := filepath.Join(config.OutputDir, config.WhiteDir)
if err := os.MkdirAll(whitePath, 0755); err != nil {
return err
}
whiteBorderPngPath := filepath.Join(config.OutputDir, config.WhiteBorderPngDir)
if err := os.MkdirAll(whiteBorderPngPath, 0755); err != nil {
return err
}
return nil
}
// 计算纯白占比
func calculateWhitePercentage(imagePath string) (float64, error) {
// 打开图片文件
file, err := os.Open(imagePath)
if err != nil {
return 0, err
}
defer file.Close()
// 解码图片
img, _, err := image.Decode(file)
if err != nil {
return 0, err
}
bounds := img.Bounds()
totalPixels := bounds.Dx() * bounds.Dy()
whitePixels := 0
// 遍历每个像素,判断是否为纯白色
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
pixel := color.RGBAModel.Convert(img.At(x, y)).(color.RGBA)
// 判断是否为纯白色 (R=255, G=255, B=255)
if pixel.R == 255 && pixel.G == 255 && pixel.B == 255 {
whitePixels++
}
}
}
return float64(whitePixels) / float64(totalPixels), nil
}
// 复制文件到相应目录
func copyToDestination(srcPath string, config *Config, isMatch bool) error {
filename := filepath.Base(srcPath)
// 确定目标目录
var destDir string
if isMatch {
destDir = filepath.Join(config.OutputDir, config.MatchDir)
} else {
destDir = filepath.Join(config.OutputDir, config.UnmatchDir)
}
destPath := filepath.Join(destDir, filename)
// 读取源文件
data, err := os.ReadFile(srcPath)
if err != nil {
return err
}
// 写入目标文件
return os.WriteFile(destPath, data, 0644)
}
// 保存文件
func saveImage(outputPath string, img image.Image, format string) error {
// 创建输出文件
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
// 根据原始格式保存图片
switch format {
case "jpeg", "jpg":
// JPEG 格式可以设置质量参数
return jpeg.Encode(outFile, img, &jpeg.Options{Quality: 95})
case "png":
// PNG 格式通常不需要质量参数
return png.Encode(outFile, img)
default:
// 默认使用 JPEG 格式
return jpeg.Encode(outFile, img, &jpeg.Options{Quality: 95})
}
}
// 检测图片纯白占比
func processImage(config *Config) error {
// 创建输出目录
if err := createDirs(config); err != nil {
return fmt.Errorf("创建目录失败: %v\n", err)
}
if err := validateConfig(config); err != nil {
return err
}
// 计算纯白占比
whitePct, err := calculateWhitePercentage(config.FileName)
if err != nil {
return fmt.Errorf("错误: %v\n", err)
}
// 判断是否在范围内
isMatch := whitePct >= config.MinWhitePct && whitePct <= config.MaxWhitePct
status := "❌ 不满足"
if isMatch {
status = "✅ 满足"
}
fmt.Printf("纯白占比: %.2f%% %s\n", whitePct*100, status)
// 复制文件到相应目录
if err = copyToDestination(config.FileName, config, isMatch); err != nil {
return fmt.Errorf("复制失败: %v\n", err)
}
return nil
}
// 根据原始图片生成新的白底图片
func createWhiteBottomCenteredImage(config *Config, width, height int) (string, error) {
// 创建输出目录
if err := createDirs(config); err != nil {
fmt.Printf("创建目录失败: %v\n", err)
os.Exit(1)
}
// 打开图片
file, err := os.Open(config.FileName)
if err != nil {
return "", fmt.Errorf("打开图片失败: %v", err)
}
defer file.Close()
// 解码原始图片
img, format, err := image.Decode(file)
if err != nil {
return "", fmt.Errorf("图片解码失败: %v", err)
}
// 创建透明背景
dst := image.NewRGBA(image.Rect(0, 0, width, height))
// 设置背景颜色
var bgColor color.Color
bgColor = color.RGBA{255, 255, 255, 255} // 白色
// 填充透明背景
draw.Draw(dst, dst.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src)
// 计算居中位置
srcBounds := img.Bounds()
srcWidth := srcBounds.Dx()
srcHeight := srcBounds.Dy()
x := (width - srcWidth) / 2
y := (height - srcHeight) / 2
// 将原图绘制到中央
draw.Draw(dst, image.Rect(x, y, x+srcWidth, y+srcHeight), img, image.Point{}, draw.Over)
filename := filepath.Base(config.FileName)
destPath := filepath.Join(config.OutputDir, config.WhiteDir, filename)
saveImage(destPath, dst, format)
return destPath, nil
}
// 根据高度生成等比例图片
func resizeToHeightQuality(config *Config, targetHeight int) (string, error) {
// 创建输出目录
if err := createDirs(config); err != nil {
fmt.Printf("创建目录失败: %v\n", err)
os.Exit(1)
}
// 打开图片
file, err := os.Open(config.FileName)
if err != nil {
return "", fmt.Errorf("打开图片失败: %v", err)
}
defer file.Close()
// 解码原始图片
img, format, err := image.Decode(file)
if err != nil {
return "", fmt.Errorf("图片解码失败: %v", err)
}
bounds := img.Bounds()
srcWidth := bounds.Dx()
srcHeight := bounds.Dy()
// 计算等比例缩放后的宽度
targetWidth := uint(float64(srcWidth) * float64(targetHeight) / float64(srcHeight))
// 使用 Lanczos3 插值算法进行高质量缩放
imageNew := resize.Resize(targetWidth, uint(targetHeight), img, resize.Lanczos3)
// 保存图片到指定目录下
filename := filepath.Base(config.FileName)
destPath := filepath.Join(config.OutputDir, config.EqualHeightDir, filename)
saveImage(destPath, imageNew, format)
return destPath, nil
}
// ImageToPNGConverter 图片去白边并转为PNG
type ImageToPNGConverter struct {
Threshold int
Margin int
BgColor color.RGBA
DetectColor *color.RGBA
KeepTransparent bool
PNGCompressLevel png.CompressionLevel
Quality int
}
// 去掉白边并转PNG图片工具
func removeWhiteBorderAndPNG(config *Config) (string, error) {
// 创建输出目录
if err := createDirs(config); err != nil {
fmt.Printf("创建目录失败: %v\n", err)
os.Exit(1)
}
// 打开图片
file, err := os.Open(config.FileName)
if err != nil {
return "", fmt.Errorf("打开图片失败: %v", err)
}
defer file.Close()
// 解码原始图片
img, _, err := image.Decode(file)
if err != nil {
return "", fmt.Errorf("图片解码失败: %v", err)
}
compressLevel := 6
// 创建转换器
compressionLevel := png.DefaultCompression
switch {
case compressLevel <= 0:
compressionLevel = png.NoCompression
case compressLevel >= 9:
compressionLevel = png.BestCompression
default:
// 使用默认压缩级别
}
converter := newImageToPNGConverter(
240,
0,
&color.RGBA{R: 255, G: 255, B: 255, A: 255},
nil,
false,
compressionLevel,
95,
)
toPNG := converter.convertToPNG(img, true)
// 保存图片到指定目录下
filename := filepath.Base(config.FileName)
// 去除扩展名
nameWithoutExt := strings.TrimSuffix(filename, filepath.Ext(filename))
destPath := filepath.Join(config.OutputDir, config.WhiteBorderPngDir, nameWithoutExt+".png")
saveImage(destPath, toPNG, "png")
return destPath, nil
}
// newImageToPNGConverter 创建新的转换器
func newImageToPNGConverter(threshold, margin int, bgColor, detectColor *color.RGBA,
keepTransparent bool, compressLevel png.CompressionLevel, quality int) *ImageToPNGConverter {
// 默认背景色为白色
bg := color.RGBA{R: 255, G: 255, B: 255, A: 255}
if bgColor != nil {
bg = *bgColor
}
return &ImageToPNGConverter{
Threshold: threshold,
Margin: margin,
BgColor: bg,
DetectColor: detectColor,
KeepTransparent: keepTransparent,
PNGCompressLevel: compressLevel,
Quality: quality,
}
}
// ConvertToPNG 转换图片为PNG格式
func (c *ImageToPNGConverter) convertToPNG(img image.Image, addBackground bool) image.Image {
// 先裁剪白边
trimmed := c.trimImage(img)
// 检查是否有alpha通道
_, hasAlpha := trimmed.(*image.NRGBA)
if !hasAlpha {
_, hasAlpha = trimmed.(*image.RGBA)
}
if hasAlpha {
if c.KeepTransparent {
// 保持透明
return trimmed
} else if addBackground {
// 添加背景色
bg := image.NewRGBA(trimmed.Bounds())
draw.Draw(bg, bg.Bounds(), &image.Uniform{C: c.BgColor}, image.Point{}, draw.Src)
draw.Draw(bg, bg.Bounds(), trimmed, trimmed.Bounds().Min, draw.Over)
return bg
}
} else {
// 非透明图像
if c.KeepTransparent {
// 转换为RGBA
rgba := image.NewRGBA(trimmed.Bounds())
draw.Draw(rgba, rgba.Bounds(), trimmed, trimmed.Bounds().Min, draw.Src)
return rgba
}
return trimmed
}
return trimmed
}
// TrimImage 裁剪图片白边
func (c *ImageToPNGConverter) trimImage(img image.Image) image.Image {
borders := c.findBorders(img)
// 创建一个新的图像并裁剪
trimmed := imaging.Crop(img, borders)
return trimmed
}
// FindBorders 查找图片的有效边界
func (c *ImageToPNGConverter) findBorders(img image.Image) image.Rectangle {
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
// 检查图像是否有alpha通道
_, hasAlpha := img.(*image.NRGBA)
if !hasAlpha {
_, hasAlpha = img.(*image.RGBA)
}
// 初始化边界
left := width
top := height
right := 0
bottom := 0
// 查找非背景区域
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
pixel := img.At(x, y)
if !c.isBackgroundColor(pixel, hasAlpha) {
if x < left {
left = x
}
if x > right {
right = x
}
if y < top {
top = y
}
if y > bottom {
bottom = y
}
}
}
}
// 如果没有找到非背景区域,返回整个图像
if left > right || top > bottom {
return bounds
}
// 添加边距
left = max(bounds.Min.X, left-c.Margin)
top = max(bounds.Min.Y, top-c.Margin)
right = min(bounds.Max.X, right+c.Margin+1)
bottom = min(bounds.Max.Y, bottom+c.Margin+1)
return image.Rect(left, top, right, bottom)
}
// IsBackgroundColor 判断像素是否为背景色
func (c *ImageToPNGConverter) isBackgroundColor(pixel color.Color, hasAlpha bool) bool {
r, g, b, a := pixel.RGBA()
// 转换为8位值
r8 := uint8(r >> 8)
g8 := uint8(g >> 8)
b8 := uint8(b >> 8)
a8 := uint8(a >> 8)
// 检查透明度
if hasAlpha && a8 < 25 { // 透明度 > 90%
return true
}
// 如果指定了检测颜色
if c.DetectColor != nil {
dr, dg, db, _ := c.DetectColor.RGBA()
dr8 := uint8(dr >> 8)
dg8 := uint8(dg >> 8)
db8 := uint8(db >> 8)
// threshold 是 int 类型,需要转换为 uint8 比较
threshold8 := uint8(255 - c.Threshold)
return absDiff(r8, dr8) <= threshold8 &&
absDiff(g8, dg8) <= threshold8 &&
absDiff(b8, db8) <= threshold8
}
// 自动检测白色/浅色背景
// 注意:这里的 c.Threshold 是 int需要转换为 uint8
threshold8 := uint8(c.Threshold)
return r8 >= threshold8 &&
g8 >= threshold8 &&
b8 >= threshold8
}
// 辅助函数
func absDiff(a, b uint8) uint8 {
if a > b {
return a - b
}
return b - a
}
// =================== C 导出函数 =======================
// 检测图片纯白占比
//
//export ProcessImage
func ProcessImage(jsonConfig *C.char) *C.char {
configStr := C.GoString(jsonConfig)
var config *Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
}
if err := processImage(config); err != nil {
return C.CString(fmt.Sprintf("%v", err))
}
return C.CString("成功")
}
// 根据原始图片生成新的白底图片
//
//export CreateWhiteBottomCenteredImage
func CreateWhiteBottomCenteredImage(jsonConfig *C.char, width, height C.int) *C.char {
configStr := C.GoString(jsonConfig)
widthInt := int(width)
heightInt := int(height)
var config *Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
}
fileName, err := createWhiteBottomCenteredImage(config, widthInt, heightInt)
if err != nil {
return C.CString(fmt.Sprintf("%v", err))
}
return C.CString(fileName)
}
// 根据高度生成等比例图片
//
//export ResizeToHeightQuality
func ResizeToHeightQuality(jsonConfig *C.char, targetHeight C.int) *C.char {
configStr := C.GoString(jsonConfig)
targetHeightInt := int(targetHeight)
var config *Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
}
fileName, err := resizeToHeightQuality(config, targetHeightInt)
if err != nil {
return C.CString(fmt.Sprintf("%v", err))
}
return C.CString(fileName)
}
// 去掉白边并转PNG图片工具
//
//export RemoveWhiteBorderAndPNG
func RemoveWhiteBorderAndPNG(jsonConfig *C.char) *C.char {
configStr := C.GoString(jsonConfig)
var config *Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
}
fileName, err := removeWhiteBorderAndPNG(config)
if err != nil {
return C.CString(fmt.Sprintf("%v", err))
}
return C.CString(fileName)
}
// 导出函数释放C字符串内存
//
//export FreeCString
func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str))
}
//func main() {
//
//}

87
image/imageDllTest.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// ImageDLL 图片处理DLL结构
type imageDLL struct {
dll *syscall.DLL
processImage *syscall.Proc
freeCString *syscall.Proc
}
// 初始化imageDLL
func InitImageDll() (*imageDLL, error) {
dllPath := filepath.Join("image", "dll", "image.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("image DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载image DLL 失败: %s", err)
} else {
return &imageDLL{
dll: dll,
processImage: dll.MustFindProc("ProcessImage"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
func (m *imageDLL) ProcessImage(config *Config) error {
marshal, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("json转换失败: %s", err)
}
fromString, _ := syscall.BytePtrFromString(string(marshal))
info, _, _ := m.processImage.Call(uintptr(unsafe.Pointer(fromString)))
cStr(info)
return nil
}
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
//
//func main() {
//
// config := &Config{
// InputDir: "D:\\www\\wwwroot\\imageTool\\img\\image", // 输入图片目录
// OutputDir: "D:\\isbn_images\\result", // 输出根目录
// FileName: "D:\\isbn_images\\result\\9771671688095.jpg",
// MatchDir: "matched", // 满足条件的图片目录
// UnmatchDir: "unmatched", // 不满足条件的图片目录
// EqualHeightDir: "equalHeight",
// MinWhitePct: 0.1, // 纯白占比下限 10%
// MaxWhitePct: 0.65, // 纯白占比上限 90%
// Extensions: []string{"jpg", "jpeg", "png", "gif", "bmp", "webp"},
// }
//
// dll, err := InitImageDll()
// if err != nil {
// fmt.Println(err)
// }
// err = dll.ProcessImage(config)
// if err != nil {
// fmt.Println(err)
// }
//}

43
image/imageTest.go Normal file
View File

@ -0,0 +1,43 @@
package main
import "fmt"
func main() {
// ==================== 在这里设置你的参数 ====================
config := &Config{
OutputDir: "D:\\isbn_images\\result", // 输出根目录
FileName: "D:\\isbn_images\\result\\9771671688095.jpg",
MatchDir: "matched", // 满足条件的图片目录
UnmatchDir: "unmatched", // 不满足条件的图片目录
EqualHeightDir: "equalHeight",
WhiteDir: "white",
WhiteBorderPngDir: "whiteBorderPng",
MinWhitePct: 0.1, // 纯白占比下限 10%
MaxWhitePct: 0.65, // 纯白占比上限 90%
Extensions: []string{"jpg", "jpeg", "png", "gif", "bmp", "webp"},
}
//err := processImage(config)
//if err != nil {
// fmt.Println(err)
//}
//
//file, err := resizeToHeightQuality(config, 700)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(file)
//config.FileName = file
//fmt.Println(config)
//file1, err := createWhiteBottomCenteredImage(config, 800, 800)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(file1)
png, err := removeWhiteBorderAndPNG(config)
if err != nil {
fmt.Println(err)
}
fmt.Println(png)
}

644
image/imageTool.go Normal file
View File

@ -0,0 +1,644 @@
package main
//import (
// "fmt"
// "image"
// "image/color"
// "image/draw"
// "image/png"
// "io/ioutil"
// "os"
// "path/filepath"
// "strings"
// "sync"
// "time"
//
// "github.com/disintegration/imaging"
//)
//
////// ImageToPNGConverter 图片去白边并转为PNG
////type ImageToPNGConverter struct {
//// Threshold int
//// Margin int
//// BgColor color.RGBA
//// DetectColor *color.RGBA
//// KeepTransparent bool
//// PNGCompressLevel png.CompressionLevel
//// Quality int
////}
//
//// NewImageToPNGConverter 创建新的转换器
//func NewImageToPNGConverter(threshold, margin int, bgColor, detectColor *color.RGBA,
// keepTransparent bool, compressLevel png.CompressionLevel, quality int) *ImageToPNGConverter {
//
// // 默认背景色为白色
// bg := color.RGBA{R: 255, G: 255, B: 255, A: 255}
// if bgColor != nil {
// bg = *bgColor
// }
//
// return &ImageToPNGConverter{
// Threshold: threshold,
// Margin: margin,
// BgColor: bg,
// DetectColor: detectColor,
// KeepTransparent: keepTransparent,
// PNGCompressLevel: compressLevel,
// Quality: quality,
// }
//}
//
//// IsBackgroundColor 判断像素是否为背景色
//func (c *ImageToPNGConverter) IsBackgroundColor(pixel color.Color, hasAlpha bool) bool {
// r, g, b, a := pixel.RGBA()
//
// // 转换为8位值
// r8 := uint8(r >> 8)
// g8 := uint8(g >> 8)
// b8 := uint8(b >> 8)
// a8 := uint8(a >> 8)
//
// // 检查透明度
// if hasAlpha && a8 < 25 { // 透明度 > 90%
// return true
// }
//
// // 如果指定了检测颜色
// if c.DetectColor != nil {
// dr, dg, db, _ := c.DetectColor.RGBA()
// dr8 := uint8(dr >> 8)
// dg8 := uint8(dg >> 8)
// db8 := uint8(db >> 8)
//
// // threshold 是 int 类型,需要转换为 uint8 比较
// threshold8 := uint8(255 - c.Threshold)
// return absDiff(r8, dr8) <= threshold8 &&
// absDiff(g8, dg8) <= threshold8 &&
// absDiff(b8, db8) <= threshold8
// }
//
// // 自动检测白色/浅色背景
// // 注意:这里的 c.Threshold 是 int需要转换为 uint8
// threshold8 := uint8(c.Threshold)
// return r8 >= threshold8 &&
// g8 >= threshold8 &&
// b8 >= threshold8
//}
//
//// FindBorders 查找图片的有效边界
//func (c *ImageToPNGConverter) FindBorders(img image.Image) image.Rectangle {
// bounds := img.Bounds()
// width := bounds.Dx()
// height := bounds.Dy()
//
// // 检查图像是否有alpha通道
// _, hasAlpha := img.(*image.NRGBA)
// if !hasAlpha {
// _, hasAlpha = img.(*image.RGBA)
// }
//
// // 初始化边界
// left := width
// top := height
// right := 0
// bottom := 0
//
// // 查找非背景区域
// for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
// for x := bounds.Min.X; x < bounds.Max.X; x++ {
// pixel := img.At(x, y)
// if !c.IsBackgroundColor(pixel, hasAlpha) {
// if x < left {
// left = x
// }
// if x > right {
// right = x
// }
// if y < top {
// top = y
// }
// if y > bottom {
// bottom = y
// }
// }
// }
// }
//
// // 如果没有找到非背景区域,返回整个图像
// if left > right || top > bottom {
// return bounds
// }
//
// // 添加边距
// left = max(bounds.Min.X, left-c.Margin)
// top = max(bounds.Min.Y, top-c.Margin)
// right = min(bounds.Max.X, right+c.Margin+1)
// bottom = min(bounds.Max.Y, bottom+c.Margin+1)
//
// return image.Rect(left, top, right, bottom)
//}
//
//// TrimImage 裁剪图片白边
//func (c *ImageToPNGConverter) TrimImage(img image.Image) image.Image {
// borders := c.FindBorders(img)
//
// // 创建一个新的图像并裁剪
// trimmed := imaging.Crop(img, borders)
// return trimmed
//}
//
//// ConvertToPNG 转换图片为PNG格式
//func (c *ImageToPNGConverter) ConvertToPNG(img image.Image, addBackground bool) image.Image {
// // 先裁剪白边
// trimmed := c.TrimImage(img)
//
// // 检查是否有alpha通道
// _, hasAlpha := trimmed.(*image.NRGBA)
// if !hasAlpha {
// _, hasAlpha = trimmed.(*image.RGBA)
// }
//
// if hasAlpha {
// if c.KeepTransparent {
// // 保持透明
// return trimmed
// } else if addBackground {
// // 添加背景色
// bg := image.NewRGBA(trimmed.Bounds())
// draw.Draw(bg, bg.Bounds(), &image.Uniform{C: c.BgColor}, image.Point{}, draw.Src)
// draw.Draw(bg, bg.Bounds(), trimmed, trimmed.Bounds().Min, draw.Over)
// return bg
// }
// } else {
// // 非透明图像
// if c.KeepTransparent {
// // 转换为RGBA
// rgba := image.NewRGBA(trimmed.Bounds())
// draw.Draw(rgba, rgba.Bounds(), trimmed, trimmed.Bounds().Min, draw.Src)
// return rgba
// }
// return trimmed
// }
//
// return trimmed
//}
//
//// ProcessImageFile 处理单个图片文件
//func (c *ImageToPNGConverter) ProcessImageFile(inputPath, outputPath string) map[string]interface{} {
// result := map[string]interface{}{
// "success": false,
// "input_path": inputPath,
// "output_path": outputPath,
// }
//
// // 打开图片文件
// file, err := os.Open(inputPath)
// if err != nil {
// result["error"] = err.Error()
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
// return result
// }
// defer file.Close()
//
// // 解码图像
// img, format, err := image.Decode(file)
// if err != nil {
// result["error"] = err.Error()
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
// return result
// }
//
// // 获取原始信息
// origBounds := img.Bounds()
// origSize := origBounds.Size()
// origArea := origSize.X * origSize.Y
//
// // 转换为PNG
// resultImg := c.ConvertToPNG(img, true)
//
// // 获取处理后的信息
// newBounds := resultImg.Bounds()
// newSize := newBounds.Size()
// newArea := newSize.X * newSize.Y
//
// // 计算尺寸减少比例
// sizeReduction := 0.0
// if origArea > 0 {
// sizeReduction = 1 - float64(newArea)/float64(origArea)
// }
//
// // 获取原始文件大小
// fileInfo, _ := os.Stat(inputPath)
// origFileSize := fileInfo.Size()
//
// // 保存为PNG
// outputFile, err := os.Create(outputPath)
// if err != nil {
// result["error"] = err.Error()
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
// return result
// }
// defer outputFile.Close()
//
// encoder := png.Encoder{CompressionLevel: c.PNGCompressLevel}
// err = encoder.Encode(outputFile, resultImg)
// if err != nil {
// result["error"] = err.Error()
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
// return result
// }
//
// // 获取新文件大小
// newFileInfo, _ := os.Stat(outputPath)
// newFileSize := newFileInfo.Size()
//
// // 计算文件大小变化
// fileSizeChange := 0.0
// if origFileSize > 0 {
// fileSizeChange = float64(newFileSize) / float64(origFileSize)
// }
//
// result["success"] = true
// result["orig_format"] = format
// result["orig_size"] = origSize
// result["new_size"] = newSize
// result["size_reduction"] = sizeReduction
// result["orig_file_size"] = origFileSize
// result["new_file_size"] = newFileSize
// result["file_size_change"] = fileSizeChange
// result["message"] = fmt.Sprintf("成功: %s (%s→PNG, %dx%d→%dx%d)",
// filepath.Base(inputPath), format, origSize.X, origSize.Y, newSize.X, newSize.Y)
//
// return result
//}
//
//// BatchPNGConverter 批量PNG转换器
//type BatchPNGConverter struct {
// converter *ImageToPNGConverter
// outputDir string
// statistics map[string]interface{}
// mu sync.Mutex
//}
//
//// NewBatchPNGConverter 创建批量转换器
//func NewBatchPNGConverter(converter *ImageToPNGConverter, outputDir string) *BatchPNGConverter {
// os.MkdirAll(outputDir, 0755)
// return &BatchPNGConverter{
// converter: converter,
// outputDir: outputDir,
// statistics: make(map[string]interface{}),
// }
//}
//
//// GetOutputPath 生成输出路径
//func (b *BatchPNGConverter) GetOutputPath(inputPath, suffix string) string {
// baseName := filepath.Base(inputPath)
// ext := filepath.Ext(baseName)
// nameWithoutExt := strings.TrimSuffix(baseName, ext)
//
// outputFilename := nameWithoutExt + suffix + ".png"
// return filepath.Join(b.outputDir, outputFilename)
//}
//
//// ProcessSingle 处理单张图片
//func (b *BatchPNGConverter) ProcessSingle(inputPath, outputPath, suffix string) map[string]interface{} {
// if outputPath == "" {
// outputPath = b.GetOutputPath(inputPath, suffix)
// }
//
// // 确保输出目录存在
// os.MkdirAll(filepath.Dir(outputPath), 0755)
//
// return b.converter.ProcessImageFile(inputPath, outputPath)
//}
//
//// ProcessBatch 批量处理图片
//func (b *BatchPNGConverter) ProcessBatch(inputPaths []string, suffix string, maxWorkers int) map[string]interface{} {
// startTime := time.Now()
//
// stats := map[string]interface{}{
// "total": len(inputPaths),
// "success": 0,
// "failed": 0,
// "total_size_reduction": 0.0,
// "total_file_size_orig": int64(0),
// "total_file_size_new": int64(0),
// "results": []map[string]interface{}{},
// }
//
// // 使用工作池
// var wg sync.WaitGroup
// semaphore := make(chan struct{}, maxWorkers)
// resultsChan := make(chan map[string]interface{}, len(inputPaths))
//
// for _, inputPath := range inputPaths {
// wg.Add(1)
// go func(path string) {
// defer wg.Done()
// semaphore <- struct{}{}
// defer func() { <-semaphore }()
//
// outputPath := b.GetOutputPath(path, suffix)
// result := b.ProcessSingle(path, outputPath, suffix)
// resultsChan <- result
// }(inputPath)
// }
//
// // 收集结果
// go func() {
// wg.Wait()
// close(resultsChan)
// }()
//
// completed := 0
// for result := range resultsChan {
// completed++
// b.mu.Lock()
// stats["results"] = append(stats["results"].([]map[string]interface{}), result)
//
// if result["success"].(bool) {
// stats["success"] = stats["success"].(int) + 1
// stats["total_size_reduction"] = stats["total_size_reduction"].(float64) + result["size_reduction"].(float64)
// stats["total_file_size_orig"] = stats["total_file_size_orig"].(int64) + result["orig_file_size"].(int64)
// stats["total_file_size_new"] = stats["total_file_size_new"].(int64) + result["new_file_size"].(int64)
// } else {
// stats["failed"] = stats["failed"].(int) + 1
// }
// b.mu.Unlock()
//
// fmt.Printf("[%d/%d] %s\n", completed, len(inputPaths), result["message"])
// }
//
// // 计算统计信息
// stats["elapsed_time"] = time.Since(startTime).Seconds()
// if stats["success"].(int) > 0 {
// stats["avg_size_reduction"] = stats["total_size_reduction"].(float64) / float64(stats["success"].(int))
// if stats["total_file_size_orig"].(int64) > 0 {
// stats["total_file_size_change"] = float64(stats["total_file_size_new"].(int64)) / float64(stats["total_file_size_orig"].(int64))
// } else {
// stats["total_file_size_change"] = 1.0
// }
// }
//
// return stats
//}
//
//// FindImageFiles 查找目录中的图片文件
//func FindImageFiles(directory string, recursive bool) []string {
// supportedExtensions := map[string]bool{
// ".jpg": true,
// ".jpeg": true,
// ".png": true,
// ".gif": true,
// ".bmp": true,
// ".tif": true,
// ".tiff": true,
// ".webp": true,
// ".jfif": true,
// ".ico": true,
// ".ppm": true,
// ".pgm": true,
// ".pbm": true,
// ".pnm": true,
// }
//
// var imagePaths []string
//
// if recursive {
// filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
// if err != nil {
// return err
// }
// if !info.IsDir() {
// ext := strings.ToLower(filepath.Ext(path))
// if supportedExtensions[ext] {
// imagePaths = append(imagePaths, path)
// }
// }
// return nil
// })
// } else {
// files, err := ioutil.ReadDir(directory)
// if err != nil {
// return imagePaths
// }
//
// for _, file := range files {
// if !file.IsDir() {
// ext := strings.ToLower(filepath.Ext(file.Name()))
// if supportedExtensions[ext] {
// imagePaths = append(imagePaths, filepath.Join(directory, file.Name()))
// }
// }
// }
// }
//
// return imagePaths
//}
//
//// PrintBanner 打印程序标题
//func PrintBanner() {
// banner := `
//╔══════════════════════════════════════════════════╗
//║ 图片去白边转PNG工具 v1.0 ║
//║ Image White Border Removal & PNG Converter ║
//╚══════════════════════════════════════════════════╝
//`
// fmt.Println(banner)
//}
//
//// PrintSummary 打印处理总结
//func PrintSummary(stats map[string]interface{}) {
// fmt.Println("\n" + strings.Repeat("=", 60))
// fmt.Println("📊 处理总结")
// fmt.Println(strings.Repeat("=", 60))
// fmt.Printf("📁 总共处理: %d 张图片\n", stats["total"])
// fmt.Printf("✅ 成功: %d 张\n", stats["success"])
// fmt.Printf("❌ 失败: %d 张\n", stats["failed"])
//
// if stats["success"].(int) > 0 {
// fmt.Printf("⏱️ 耗时: %.2f 秒\n", stats["elapsed_time"].(float64))
//
// if avgReduction, ok := stats["avg_size_reduction"]; ok {
// fmt.Printf("📐 平均尺寸减少: %.1f%%\n", avgReduction.(float64)*100)
// }
//
// if change, ok := stats["total_file_size_change"]; ok {
// changeVal := change.(float64)
// if changeVal < 1 {
// fmt.Printf("💾 总文件大小减少: %.1f%%\n", (1-changeVal)*100)
// } else if changeVal > 1 {
// fmt.Printf("💾 总文件大小增加: %.1f%%\n", (changeVal-1)*100)
// } else {
// fmt.Println("💾 总文件大小基本不变")
// }
// }
// }
// fmt.Println(strings.Repeat("=", 60))
//}
//
//// 辅助函数
//func absDiff(a, b uint8) uint8 {
// if a > b {
// return a - b
// }
// return b - a
//}
//
//func max(a, b int) int {
// if a > b {
// return a
// }
// return b
//}
//
//func min(a, b int) int {
// if a < b {
// return a
// }
// return b
//}
//
//func main() {
// // 直接设置参数值,不需要命令行输入
//
// // ============ 参数配置区 ============
// // 基础参数
// inputPath := "D:\\isbn_images\\result\\matched\\9771671688095.jpg" // 输入文件或目录路径
// outputPath := "D:\\isbn_images\\result\\matched\\output.png" // 输出文件路径(单文件模式)
// outputDir := "D:\\isbn_images\\result\\matched\\" // 输出目录路径(批量模式)
// suffix := "_trimmed" // 输出文件名后缀
//
// // 处理参数
// threshold := 240 // 背景检测阈值 (0-255)
// margin := 0 // 保留边距像素
// transparent := false // 保持透明背景
// compressLevel := 6 // PNG压缩级别 (0-9)
//
// // 批量处理参数
// recursive := false // 递归处理子目录
// jobs := 4 // 并行处理数
// force := true // 覆盖已存在的输出文件设置为true不询问
// verbose := true // 显示详细处理信息
// showBanner := true // 显示标题横幅
// // ============ 参数配置结束 ============
//
// if showBanner {
// PrintBanner()
// }
//
// // 创建转换器
// compressionLevel := png.DefaultCompression
// switch {
// case compressLevel <= 0:
// compressionLevel = png.NoCompression
// case compressLevel >= 9:
// compressionLevel = png.BestCompression
// default:
// // 使用默认压缩级别
// }
//
// converter := NewImageToPNGConverter(
// threshold,
// margin,
// &color.RGBA{R: 255, G: 255, B: 255, A: 255},
// nil,
// transparent,
// compressionLevel,
// 95,
// )
//
// batchConverter := NewBatchPNGConverter(converter, outputDir)
//
// // 检查输入路径
// info, err := os.Stat(inputPath)
// if err != nil {
// fmt.Printf("❌ 错误: 路径不存在 - %s\n", inputPath)
// return
// }
//
// if !info.IsDir() {
// // 单文件模式
// fmt.Printf("📄 处理单文件: %s\n", inputPath)
//
// // 如果outputPath为空则生成默认输出路径
// if outputPath == "" {
// outputPath = batchConverter.GetOutputPath(inputPath, suffix)
// }
//
// // 检查输出文件是否存在如果force为false则询问
// if _, err := os.Stat(outputPath); err == nil && !force {
// fmt.Printf("⚠️ 警告: 输出文件已存在 - %s\n", outputPath)
// fmt.Println("已设置force=true直接覆盖")
// }
//
// result := batchConverter.ProcessSingle(inputPath, outputPath, suffix)
//
// if result["success"].(bool) {
// fmt.Printf("\n✅ %s\n", result["message"])
// fmt.Printf("💾 输出文件: %s\n", result["output_path"])
//
// if reduction, ok := result["size_reduction"]; ok {
// reductionVal := reduction.(float64)
// if reductionVal > 0 {
// fmt.Printf("📐 尺寸减少: %.1f%%\n", reductionVal*100)
// } else if reductionVal < 0 {
// fmt.Printf("📐 尺寸增加: %.1f%%\n", -reductionVal*100)
// }
// }
//
// if change, ok := result["file_size_change"]; ok {
// changeVal := change.(float64)
// if changeVal < 1 {
// fmt.Printf("💿 文件大小减少: %.1f%%\n", (1-changeVal)*100)
// } else if changeVal > 1 {
// fmt.Printf("💿 文件大小增加: %.1f%%\n", (changeVal-1)*100)
// }
// }
// } else {
// fmt.Printf("\n❌ %s\n", result["message"])
// }
// } else {
// // 批量模式
// fmt.Printf("📁 扫描目录: %s\n", inputPath)
// imageFiles := FindImageFiles(inputPath, recursive)
//
// if len(imageFiles) == 0 {
// fmt.Println("未找到支持的图片文件")
// return
// }
//
// fmt.Printf("找到 %d 张图片\n", len(imageFiles))
//
// // 检查输出目录如果force为false则询问
// if _, err := os.Stat(outputDir); err == nil && !force {
// files, _ := ioutil.ReadDir(outputDir)
// if len(files) > 0 {
// fmt.Printf("⚠️ 警告: 输出目录不为空 - %s\n", outputDir)
// fmt.Println("已设置force=true直接继续处理")
// }
// }
//
// fmt.Printf("📂 输出目录: %s\n", outputDir)
// fmt.Printf("⚡ 并行处理: %d 个线程\n", jobs)
// fmt.Println(strings.Repeat("-", 60))
//
// // 批量处理
// stats := batchConverter.ProcessBatch(imageFiles, suffix, jobs)
//
// // 打印总结
// PrintSummary(stats)
//
// // 显示失败详情
// if stats["failed"].(int) > 0 && verbose {
// fmt.Println("\n❌ 失败详情:")
// for _, result := range stats["results"].([]map[string]interface{}) {
// if !result["success"].(bool) {
// fmt.Printf(" %s: %s\n",
// filepath.Base(result["input_path"].(string)),
// result["error"])
// }
// }
// }
// }
//}

Binary file not shown.

969
kfztp.go
View File

@ -1,71 +1,80 @@
package main package main
import ( //import (
"encoding/json" // "crypto/md5"
"fmt" // "encoding/json"
"log" // "fmt"
"net/http" // "log"
"time" // "math/rand"
// "net/http"
"github.com/parnurzeal/gorequest" // "net/url"
) // "regexp"
// "strconv"
// 获取商品模版 // "strings"
func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) { // "sync"
if token == "" { // "time"
return nil, fmt.Errorf("请先登录获取Token") //
} // "github.com/parnurzeal/gorequest"
//)
url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d", //
itemId, time.Now().Unix()) //// 获取商品模版
//func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) {
log.Printf("请求URL: %s", url) // if token == "" {
// return nil, fmt.Errorf("请先登录获取Token")
// 创建HTTP客户端 // }
request := gorequest.New() //
// url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
// 设置代理如果有提供代理URL // itemId, time.Now().Unix())
if proxy != "" { //
request.Proxy(proxy) // log.Printf("请求URL: %s", url)
log.Printf("使用代理: %s", proxy) //
} // // 创建HTTP客户端
// request := gorequest.New()
resp, body, errs := request.Get(url). //
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). // // 设置代理如果有提供代理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"). // if proxy != "" {
Set("Accept", "application/json, text/plain, */*"). // request.Proxy(proxy)
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). // log.Printf("使用代理: %s", proxy)
Set("Referer", "https://seller.kongfz.com/"). // }
Set("Origin", "https://seller.kongfz.com"). //
Timeout(30 * time.Second). // resp, body, errs := request.Get(url).
End() // 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").
if len(errs) > 0 { // Set("Accept", "application/json, text/plain, */*").
return nil, fmt.Errorf("请求失败: %v", errs) // Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
} // Set("Referer", "https://seller.kongfz.com/").
// Set("Origin", "https://seller.kongfz.com").
// 检查HTTP状态码 // Timeout(30 * time.Second).
if resp.StatusCode != http.StatusOK { // End()
return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status) //
} // if len(errs) > 0 {
// return nil, fmt.Errorf("请求失败: %v", errs)
var data map[string]interface{} // }
if err := json.Unmarshal([]byte(body), &data); err != nil { //
return nil, fmt.Errorf("解析JSON失败: %w", err) // // 检查HTTP状态码
} // if resp.StatusCode != http.StatusOK {
// return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status)
// 检查API响应状态 // }
if val, ok := data["status"].(float64); ok && val == 1 { //
return data, nil // var data map[string]interface{}
} // if err := json.Unmarshal([]byte(body), &data); err != nil {
// return nil, fmt.Errorf("解析JSON失败: %w", err)
return nil, fmt.Errorf("API返回错误: %+v", data) // }
} //
// // 检查API响应状态
// if val, ok := data["status"].(float64); ok && val == 1 {
// return data, nil
// }
//
// return nil, fmt.Errorf("API返回错误: %+v", data)
//}
//
//func main() { //func main() {
// fmt.Println("=== 孔夫子商品模板API服务 ===") // fmt.Println("=== 孔夫子商品模板API服务 ===")
// //
// http.HandleFunc("/api/goodsTemplate", getGoodsTemplateHandler) // http.HandleFunc("/api/goodsTemplate", getGoodsTemplateHandler)
// http.HandleFunc("/api/outGetImageByIsbn", getOutGetImageByIsbnHandler)
//
// port := "8989" // port := "8989"
// server := &http.Server{ // server := &http.Server{
// Addr: ":" + port, // Addr: ":" + port,
@ -76,60 +85,782 @@ func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}
// fmt.Printf("服务器启动失败: %v\n", err) // fmt.Printf("服务器启动失败: %v\n", err)
// } // }
//} //}
//
// 获取商品模板接口 //// 获取商品模板接口
func getGoodsTemplateHandler(w http.ResponseWriter, r *http.Request) { //func getGoodsTemplateHandler(w http.ResponseWriter, r *http.Request) {
// 设置响应头 // // 设置响应头
w.Header().Set("Content-Type", "application/json") // w.Header().Set("Content-Type", "application/json")
//
// 只处理GET请求 // // 只处理GET请求
if r.Method != http.MethodGet { // if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed) // w.WriteHeader(http.StatusMethodNotAllowed)
json.NewEncoder(w).Encode(map[string]interface{}{ // json.NewEncoder(w).Encode(map[string]interface{}{
"success": false, // "success": false,
"error": "只支持GET请求", // "error": "只支持GET请求",
}) // })
return // return
} // }
//
// 获取查询参数 // // 获取查询参数
token := r.URL.Query().Get("token") // token := r.URL.Query().Get("token")
itemId := r.URL.Query().Get("itemId") // itemId := r.URL.Query().Get("itemId")
proxy := r.URL.Query().Get("proxy") // proxy := r.URL.Query().Get("proxy")
//
// 验证必需参数 // // 验证必需参数
if token == "" || itemId == "" { // if token == "" || itemId == "" {
w.WriteHeader(http.StatusBadRequest) // w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]interface{}{ // json.NewEncoder(w).Encode(map[string]interface{}{
"success": false, // "success": false,
"error": "缺少必需参数: token 和 itemid", // "error": "缺少必需参数: token 和 itemid",
"usage": map[string]string{ // "usage": map[string]string{
"token": "登录Token (PHPSESSID)", // "token": "登录Token (PHPSESSID)",
"itemid": "商品ID", // "itemid": "商品ID",
"proxy": "代理地址 (可选)", // "proxy": "代理地址 (可选)",
}, // },
"example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080", // "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
}) // })
return // return
} // }
//
log.Printf("收到请求 - 商品ID: %s", itemId) // log.Printf("收到请求 - 商品ID: %s", itemId)
//
// 调用函数获取商品模板 // // 调用函数获取商品模板
result, err := outGetGoodsTplMsgLinux(token, itemId, proxy) // result, err := outGetGoodsTplMsgLinux(token, itemId, proxy)
if err != nil { // if err != nil {
log.Printf("获取商品模板失败: %v", err) // log.Printf("获取商品模板失败: %v", err)
w.WriteHeader(http.StatusInternalServerError) // w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]interface{}{ // json.NewEncoder(w).Encode(map[string]interface{}{
"success": false, // "success": false,
"error": err.Error(), // "error": err.Error(),
}) // })
return // return
} // }
//
// 返回成功响应 // // 返回成功响应
json.NewEncoder(w).Encode(map[string]interface{}{ // json.NewEncoder(w).Encode(map[string]interface{}{
"success": true, // "success": true,
"data": result, // "data": result,
}) // })
} //}
//
//func getOutGetImageByIsbnHandler(w http.ResponseWriter, r *http.Request) {
// // 设置响应头
// w.Header().Set("Content-Type", "application/json")
//
// // 只处理GET请求
// if r.Method != http.MethodGet {
// w.WriteHeader(http.StatusMethodNotAllowed)
// json.NewEncoder(w).Encode(map[string]interface{}{
// "success": false,
// "error": "只支持GET请求",
// })
// return
// }
//
// // 获取查询参数
// token := r.URL.Query().Get("token")
// isbn := r.URL.Query().Get("isbn")
// proxyType := r.URL.Query().Get("proxyType")
// username := r.URL.Query().Get("username")
// password := r.URL.Query().Get("password")
// machineCode := r.URL.Query().Get("machineCode")
// //proxy := r.URL.Query().Get("proxy")
//
// // 验证必需参数
// if token == "" || isbn == "" {
// w.WriteHeader(http.StatusBadRequest)
// json.NewEncoder(w).Encode(map[string]interface{}{
// "success": false,
// "error": "缺少必需参数: token 和 isbn",
// "usage": map[string]string{
// "token": "登录Token (PHPSESSID)",
// "isbn": "isbn",
// "proxy": "代理地址 (可选)",
// },
// "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
// })
// return
// }
//
// manager, err2 := proxyTypeManager(proxyType, username, password, machineCode)
// if err2 != nil {
// return
// }
// log.Printf("收到请求 - 商品ID: %s", isbn)
//
// // 调用函数获取商品模板
// result, err := OutGetImageByIsbns(token, isbn, manager, 0, 0)
// if err != nil {
// log.Printf("获取商品模板失败: %v", err)
// w.WriteHeader(http.StatusInternalServerError)
// json.NewEncoder(w).Encode(map[string]interface{}{
// "success": false,
// "error": err.Error(),
// })
// return
// }
// // 返回成功响应
// json.NewEncoder(w).Encode(map[string]interface{}{
// "success": true,
// "data": result,
// })
//}
//
//// 条目详情结构体
//type BookInfo struct {
// BookName string `json:"book_name"` // 书名
// Author string `json:"author"` // 作者
// Publisher string `json:"publisher"` // 出版社
// ISBN string `json:"isbn"` // ISBN
// PublicationTime int64 `json:"publication_time"` // 出版时间
// Edition string `json:"edition"` // 版次
// PrintTime string `json:"print_time"` // 印刷时间
// FixPrice string `json:"fix_price"` // 定价
// BindingLayout string `json:"binding_layout"` // 装帧
// Format string `json:"format"` // 开本
// Paper string `json:"paper"` // 纸张
// Pages string `json:"pages"` // 页数
// Wordage string `json:"wordage"` // 字数
// Languages string `json:"languages"` // 语种
// Era string `json:"era"` // 年代
// EngravingMethod string `json:"engraving_method"` // 刻印方式
// Dimensions string `json:"dimensions"` // 尺寸
// VolumeNumber string `json:"volume_number"` // 册数
// BookPic string `json:"book_pic"` // 图书封面图(官图)
// BookPicS string `json:"book_pic_s"` // 图书封面图(实拍图)
// SellingPrice string `json:"selling_price"` // 售价
// Condition string `json:"condition"` // 品相
// ExpressDeliveryFee string `json:"express_delivery_fee"` // 快递费
// Editor string `json:"editor"` // 编辑
// Category string `json:"category"` // 分类
// BuyCount string `json:"buy_count"` // 买过
// SellCount string `json:"sell_count"` // 在卖
// Content string `json:"content"` // 内容
// Mid int64 `json:"mid"` // 商家id
// ItemId int64 `json:"item_id"` // 商品id
// ShopId int64 `json:"shop_id"` // 店铺id
// DetailUrl string `json:"detail_url"` // 商品详情url
//}
//
//// 获取图片URL(官图和拍图)
//func OutGetImageByIsbns(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (*BookInfo, error) {
// fmt.Println("[DEBUG] 使用的ISBN: ", isbn)
// // isLiveImage 1实拍图 0官图 isReturnMsg 0商品信息
// bookInfo := &BookInfo{}
// //if isLiveImage == 0 {
// // 孔网官图请求
// gtUrl := fmt.Sprintf("%s?keyword=%s", "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", isbn)
// // 创建HTTP客户端
// requestGt := gorequest.New()
// // 设置代理(如果有提供代理URL)
// if proxy != "" {
// requestGt.Proxy(proxy)
// }
// // 发送请求
// respGt, bodyGt, errsGt := requestGt.Get(gtUrl).
// Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
// Set("Accept", "application/json, text/plain, */*").
// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
// Set("Referer", "https://item.kongfz.com/").
// Timeout(30 * time.Second).
// End()
// if len(errsGt) > 0 {
// // 检查是否是代理相关错误
// var isProxyError bool
// var errorDetails []string
// for _, e := range errsGt {
// errorStr := e.Error()
// errorDetails = append(errorDetails, errorStr)
// if strings.Contains(errorStr, "Proxy Authentication Required") ||
// strings.Contains(errorStr, "connectex: A connection attempt failed") ||
// strings.Contains(errorStr, "connectex: No connection could be made") ||
// strings.Contains(errorStr, "proxyconnect tcp") ||
// strings.Contains(errorStr, "timeout") ||
// strings.Contains(errorStr, "connection refused") {
// isProxyError = true
// }
// }
// log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
// if isProxyError {
// // 处理代理失败
// return nil, fmt.Errorf("代理连接失败")
// }
// return nil, fmt.Errorf("查询请求失败: %v", errsGt)
// }
// //检查HTTP状态码
// if respGt.StatusCode != http.StatusOK {
// return nil, fmt.Errorf("HTTP错误: %s", respGt.Status)
// }
// // 解析响应
// var apiGtResp struct {
// Status int `json:"status"`
// ErrType string `json:"errType"`
// Message string `json:"message"`
// SystemTime int64 `json:"systemTime"`
// Data struct {
// ItemResponse struct {
// Total int `json:"total"`
// List []struct {
// BookName string `json:"bookName"`
// Mid int64 `json:"mid"`
// ImgUrlEntity struct {
// BigImgUrl string `json:"bigImgUrl"`
// } `json:"imgUrlEntity"`
// Isbn string `json:"isbn"`
// BookShowInfo []string `json:"bookShowInfo"`
// } `json:"list"`
// } `json:"itemResponse"`
// } `json:"data"`
// }
// if err := json.Unmarshal([]byte(bodyGt), &apiGtResp); err != nil {
// return nil, fmt.Errorf("解析JSON失败: %w", err)
// }
// if apiGtResp.ErrType == "102" {
// return nil, fmt.Errorf("错误信息: %w状态码: %s", apiGtResp.Message, apiGtResp.ErrType)
// }
// // 如果找到条目返回图片URL
// if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 {
// list := apiGtResp.Data.ItemResponse.List[0]
// bookShowInfo := list.BookShowInfo
// bookInfo.BookName = list.BookName
// bookInfo.BookPic = list.ImgUrlEntity.BigImgUrl
// bookInfo.ISBN = list.Isbn
// // 根据长度安全填充字段
// if isReturnMsg == 0 {
// if len(bookShowInfo) > 0 {
// bookInfo.Author = bookShowInfo[0]
// }
// if len(bookShowInfo) > 1 {
// bookInfo.Publisher = bookShowInfo[1]
// }
// if len(bookShowInfo) > 2 {
// bookInfo.PublicationTime = validateDateFormat(bookShowInfo[2])
// }
// if len(bookShowInfo) > 3 {
// bookInfo.BindingLayout = bookShowInfo[3]
// }
// if len(bookShowInfo) > 4 {
// bookInfo.FixPrice = bookShowInfo[4]
// } else {
// log.Printf("[WARN] BookShowInfo 长度不足 (仅 %d 项): %v", len(bookShowInfo), bookShowInfo)
// }
// }
// }
// //return bookInfo, nil
//
// //}
// //if isLiveImage == 1 {
//
// size := 10
// // 实拍图
// sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000",
// "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, size)
// //创建HTTP客户端
// requestSpt := gorequest.New()
// //设置代理(如果有提供代理URL)
// if proxy != "" {
// requestSpt.Proxy(proxy)
// }
// // 发送请求
// respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl).
// Proxy(proxy).
// Set("Cookie", token).
// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
// Set("Accept", "application/json, text/plain, */*").
// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
// Set("Referer", "https://item.kongfz.com/").
// Timeout(30 * time.Second).
// End()
// // 错误处理
// if len(errsSpt) > 0 {
// return nil, fmt.Errorf("请求失败: %v", errsSpt)
// }
// // 检查HTTP状态码
// if respSpt.StatusCode != http.StatusOK {
// return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status)
// }
// // 解析响应
// var apiSptResp struct {
// Status int `json:"status"`
// ErrType string `json:"errType"`
// Message string `json:"message"`
// SystemTime int64 `json:"systemTime"`
// Data struct {
// ItemResponse struct {
// Total int `json:"total"`
// List []struct {
// ItemId int64 `json:"itemId"`
// Title string `json:"title"`
// ImgUrl string `json:"imgUrl"`
// ImgBigUrl string `json:"imgBigUrl"`
// Author string `json:"author"`
// PubDateText string `json:"pubDateText"`
// Isbn string `json:"isbn"`
// Press string `json:"press"`
// ShopId int64 `json:"shopId"`
// TplRecords []struct {
// Key string `json:"key"`
// Value string `json:"value"`
// } `json:"tplRecords"`
// } `json:"list"`
// } `json:"itemResponse"`
// } `json:"data"`
// }
// // 解析JSON
// if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil {
// return nil, fmt.Errorf("解析JSON失败: %w", err)
// }
// if apiSptResp.ErrType == "102" {
// return nil, fmt.Errorf("错误信息: %w状态码: %s", apiSptResp.Message, apiSptResp.ErrType)
// }
// if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 {
// // 确定其实索引
// var startIndex int
// if size >= apiSptResp.Data.ItemResponse.Total {
// startIndex = apiSptResp.Data.ItemResponse.Total - 1
// } else {
// startIndex = size - 1
// }
// for attempt := 0; attempt < 3; attempt++ {
// currentIndex := startIndex - attempt
// // 检查索引是否有效
// if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) {
// log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex)
// continue
// }
// randomNum := rand.Intn(currentIndex + 1)
// item := apiSptResp.Data.ItemResponse.List[randomNum]
// // 检查图片URL是否存在
// if item.ImgBigUrl == "" {
// log.Printf("[DEBUG] 索引 %d 的图片URL为空跳过", randomNum)
// continue
// }
// // 响应信息
// bookInfo.BookPicS = item.ImgUrl
// bookInfo.ISBN = item.Isbn
// if bookInfo.BookName == "" {
// bookInfo.BookName = item.Title
// if isReturnMsg == 0 {
// // 安全地获取TplRecords中的值
// if len(item.TplRecords) > 0 {
// bookInfo.Author = item.TplRecords[0].Value
// }
// if len(item.TplRecords) > 1 {
// bookInfo.Publisher = item.TplRecords[1].Value
// }
// if len(item.TplRecords) > 2 {
// bookInfo.PublicationTime = validateDateFormat(item.TplRecords[2].Value)
// }
// if len(item.TplRecords) > 3 {
// bookInfo.BindingLayout = item.TplRecords[3].Value
// }
// }
// }
// return bookInfo, nil
// }
// //}
// }
// return nil, fmt.Errorf("查询失败,没有数据!")
//}
//
//// 验证日期格式
//func validateDateFormat(dateStr string) int64 {
// // 去除前后空格
// dateStr = strings.TrimSpace(dateStr)
//
// // 替换各种分隔符为统一的分隔符"-"
// dateStr = regexp.MustCompile(`[/_\\.,\s]+`).ReplaceAllString(dateStr, "-")
//
// // 处理纯年份格式 (4位数字)
// if regexp.MustCompile(`^\d{4}$`).MatchString(dateStr) {
// dateStr += "-01-01"
// }
//
// // 处理年月格式 (4位数字-1或2位数字)
// if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})$`).FindStringSubmatch(dateStr); len(matches) == 3 {
// year := matches[1]
// month := matches[2]
// if len(month) == 1 {
// month = "0" + month
// }
// if monthNum, _ := strconv.Atoi(month); monthNum >= 1 && monthNum <= 12 {
// dateStr = year + "-" + month + "-01"
// } else {
// return 0
// }
// }
//
// // 处理年月日格式 (4位数字-1或2位数字-1或2位数字)
// if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})-(\d{1,2})`).FindStringSubmatch(dateStr); len(matches) >= 4 {
// year := matches[1]
// month := matches[2]
// day := matches[3]
//
// // 标准化月份和日期为两位数
// if len(month) == 1 {
// month = "0" + month
// }
// if len(day) == 1 {
// day = "0" + day
// }
//
// dateStr = year + "-" + month + "-" + day
// }
//
// // 加载上海时区
// loc, err := time.LoadLocation("Asia/Shanghai")
// if err != nil {
// // 如果加载时区失败使用UTC+8作为后备方案
// loc = time.FixedZone("CST", 8*60*60)
// }
//
// // 尝试解析为标准日期格式,并指定上海时区
// parsedTime, err := time.ParseInLocation("2006-01-02", dateStr, loc)
// if err != nil {
// return 0
// }
//
// // 返回秒级时间戳UTC时间但解析时已经考虑了时区偏移
// return parsedTime.Unix()
//}
//
//// 代理类型常量
//const (
// CalfElephantProxyType = "CALF_ELEPHANT_PROXY"
// TailProxyType = "TAIL_PROXY"
//)
//
//// 代理服务器列表
//var (
// servers = []string{
// "http-dynamic.xiaoxiangdaili.com",
// "http-dynamic-S02.xiaoxiangdaili.com",
// "http-dynamic-S03.xiaoxiangdaili.com",
// "http-dynamic-S04.xiaoxiangdaili.com",
// }
// randMutex sync.Mutex
// globalRand *rand.Rand
//
// // 代理健康状态管理
// proxyHealthMaps = make(map[string]*ProxyHealth)
// proxyHealthMutex sync.RWMutex
//)
//
//// ProxyManager 代理管理器结构体
//type ProxyManager struct {
// servers []string `json:"servers"` // 代理服务器列表
// username string `json:"username"` // 代理账号
// password string `json:"password"` // 代理密码
// tailCardSecret string `json:"tail_card_secret"` // 尾巴代理卡密
// proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
//}
//
//// 代理健康状态
//type ProxyHealth struct {
// SuccessCount int // 成功次数
// FailCount int // 失败次数
// LastCheck time.Time // 最后检查时间
// ResponseTime time.Duration // 响应时间
// IsHealthy bool // 是否健康
//}
//
//func init() {
// // 创建全局的随机数生成器
// globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
//}
//
//// 获取代理URL
//func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
// switch proxyType {
// case CalfElephantProxyType:
// return buildCalfElephantProxyURL(username, password)
// case TailProxyType:
// return buildTailProxyURL(machineCode)
// default:
// return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
// }
//}
//
//// 构建小象代理URL
//func buildCalfElephantProxyURL(username, password string) (string, error) {
// server := randomServer()
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
// url.QueryEscape(username),
// url.QueryEscape(password),
// server,
// 10030)
//
// // 检测代理可用性
// if err := checkProxyHealth(proxyURL); err != nil {
// log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
// // 尝试下一个代理服务器
// return tryNextCalfElephantProxy(username, password, server)
// }
//
// log.Printf("[INFO] 使用小象代理: %s", server)
// return proxyURL, nil
//}
//
//// 尝试下一个小象代理服务器
//func tryNextCalfElephantProxy(username, password, failedServer string) (string, error) {
// // 创建服务器副本并排除失败的服务器
// availableServers := make([]string, 0)
// for _, server := range servers {
// if server != failedServer {
// availableServers = append(availableServers, server)
// }
// }
//
// if len(availableServers) == 0 {
// return "", fmt.Errorf("所有小象代理服务器都不可用")
// }
//
// // 随机尝试可用服务器
// for _, server := range shuffleServers(availableServers) {
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
// url.QueryEscape(username),
// url.QueryEscape(password),
// server,
// 10030)
//
// if err := checkProxyHealth(proxyURL); err == nil {
// log.Printf("[INFO] 切换到可用代理: %s", server)
// return proxyURL, nil
// }
// log.Printf("[WARN] 代理 %s 检测失败", server)
// }
//
// return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
//}
//
//// 构建内置代理URL
//func buildTailProxyURL(machineCode string) (string, error) {
// proxies, err := getProxies(machineCode)
// if err != nil {
// return "", err
// }
// if len(proxies) == 0 {
// return "", fmt.Errorf("未获取到有效代理")
// }
//
// // 过滤并选择健康的代理
// healthyProxies := filterHealthyProxies(proxies)
// if len(healthyProxies) > 0 {
// proxyURL := "http://" + randomElement(healthyProxies)
// log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
// return proxyURL, nil
// }
//
// // 如果没有健康代理,检测所有代理
// log.Printf("[INFO] 未找到健康代理,开始检测代理可用性...")
// return findWorkingTailProxy(proxies)
//}
//
//// 获取代理服务器列表
//func getProxies(machineCode string) ([]string, error) {
// log.Printf("[INFO] 开始获取代理列表: %s", machineCode)
// // 生成签名
// sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
//
// GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
// machineCode, sign)
//
// req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
// _, body, errs := req.End()
// if len(errs) > 0 {
// return nil, fmt.Errorf("获取代理失败: %v", errs)
// }
//
// // 检查是否为JSON错误响应
// if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
// // 尝试解析为JSON错误响应
// var errorResp struct {
// Code int `json:"code"`
// Message string `json:"message"`
// }
// if err := json.Unmarshal([]byte(body), &errorResp); err == nil {
// return nil, fmt.Errorf("获取代理失败: %s (错误码: %d)", errorResp.Message, errorResp.Code)
// }
// }
//
// // 解析响应
// lines := strings.Split(strings.TrimSpace(body), "\n")
// var proxies []string
// for _, line := range lines {
// line = strings.TrimSpace(line)
// if line != "" && !strings.HasPrefix(line, "{") {
// proxies = append(proxies, line)
// }
// }
// if len(proxies) == 0 {
// return nil, fmt.Errorf("未获取到有效代理")
// }
//
// log.Printf("[INFO] 获取到 %d 个代理", len(proxies))
// return proxies, nil
//}
//
//// 过滤健康代理
//func filterHealthyProxies(proxies []string) []string {
// proxyHealthMutex.RLock()
// defer proxyHealthMutex.RUnlock()
//
// var healthy []string
// for _, proxy := range proxies {
// if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
// // 检查是否在最近检查过5分钟内
// if time.Since(health.LastCheck) < 5*time.Minute {
// healthy = append(healthy, proxy)
// }
// }
// }
// return healthy
//}
//
//// 查找可用的尾巴代理
//func findWorkingTailProxy(proxies []string) (string, error) {
// // 打乱代理顺序
// shuffledProxies := shuffleServers(proxies)
//
// // 并发检测代理
// type proxyResult struct {
// proxy string
// err error
// }
//
// ch := make(chan proxyResult, len(shuffledProxies))
// var wg sync.WaitGroup
//
// // 限制并发数
// sem := make(chan struct{}, 5)
//
// for _, proxy := range shuffledProxies {
// wg.Add(1)
// go func(p string) {
// defer wg.Done()
// sem <- struct{}{}
// defer func() { <-sem }()
//
// proxyURL := "http://" + p
// err := checkProxyHealth(proxyURL)
// ch <- proxyResult{proxy: p, err: err}
// }(proxy)
// }
//
// wg.Wait()
// close(ch)
//
// // 收集结果
// var workingProxies []string
// for result := range ch {
// if result.err == nil {
// workingProxies = append(workingProxies, result.proxy)
// // 更新健康状态
// updateProxyHealth(result.proxy, true, 0)
// } else {
// updateProxyHealth(result.proxy, false, 0)
// }
// }
//
// if len(workingProxies) > 0 {
// selected := randomElement(workingProxies)
// log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
// return "http://" + selected, nil
// }
//
// return "", fmt.Errorf("所有尾巴代理都不可用")
//}
//
//// 检测代理健康状态
//func checkProxyHealth(proxyURL string) error {
// start := time.Now()
//
// req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
// resp, _, errs := req.Get("https://shop.kongfz.com/").End()
//
// responseTime := time.Since(start)
//
// if len(errs) > 0 {
// updateProxyHealth(proxyURL, false, responseTime)
// return fmt.Errorf("代理连接失败: %v", errs)
// }
//
// if resp.StatusCode != 200 {
// updateProxyHealth(proxyURL, false, responseTime)
// return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
// }
//
// updateProxyHealth(proxyURL, true, responseTime)
// log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
// return nil
//}
//
//// 更新代理健康状态
//func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
// proxyHealthMutex.Lock()
// defer proxyHealthMutex.Unlock()
//
// host := getProxyHost(proxyURL)
// if _, exists := proxyHealthMaps[host]; !exists {
// proxyHealthMaps[host] = &ProxyHealth{}
// }
//
// health := proxyHealthMaps[host]
// health.LastCheck = time.Now()
//
// if success {
// health.SuccessCount++
// health.FailCount = 0
// health.ResponseTime = responseTime
// health.IsHealthy = true
// } else {
// health.FailCount++
// health.SuccessCount = 0
// // 连续失败3次标记为不健康
// if health.FailCount >= 3 {
// health.IsHealthy = false
// }
// }
//}
//
//// 获取代理主机名
//func getProxyHost(proxyURL string) string {
// if strings.HasPrefix(proxyURL, "http://") {
// proxyURL = proxyURL[7:]
// }
// // 去除认证信息
// if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
// proxyURL = proxyURL[atIndex+1:]
// }
// // 去除端口
// if colonIndex := strings.Index(proxyURL, ":"); colonIndex != -1 {
// proxyURL = proxyURL[:colonIndex]
// }
// return proxyURL
//}
//
//// 随机代理服务器
//func randomServer() string {
// return randomElement(servers)
//}
//
//// 线程安全的随机元素选择
//func randomElement(slice []string) string {
// randMutex.Lock()
// defer randMutex.Unlock()
// return slice[globalRand.Intn(len(slice))]
//}
//
//// 打乱服务器顺序
//func shuffleServers(servers []string) []string {
// randMutex.Lock()
// defer randMutex.Unlock()
//
// shuffled := make([]string, len(servers))
// copy(shuffled, servers)
// globalRand.Shuffle(len(shuffled), func(i, j int) {
// shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
// })
// return shuffled
//}

BIN
kongfz/dll/kongfz.dll Normal file

Binary file not shown.

145
kongfz/dll/kongfz.h Normal file
View File

@ -0,0 +1,145 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "kongfz.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
// 孔网登录
//
extern __declspec(dllexport) char* OutLogin(char* username, char* password);
// 获取孔网用户信息
//
extern __declspec(dllexport) char* OutGetUserMsg(char* token);
// 获取商品模版--已登的店铺
//
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
// 获取商品列表-已登的店铺
//
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
// 新增商品-已登的店铺(带有Out的都非官方标准接口)
//
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
// 删除商品-已登的店铺
//
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
// 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
//
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
// 获取孔网商品图片和信息(官图和拍图)
//
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg);
// 获取商品列表通过店铺ID
//
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);
// 获取商品信息通过商品详情链接
//
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
// 获取销量榜商品列表
//
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
// 初始化配置
//
extern __declspec(dllexport) char* Initialize(char* configJSON);
// 释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

2089
kongfz/kongfz.go Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

24
main.go
View File

@ -461,9 +461,9 @@ func outGetGoodsTplMsg(token, itemId, proxy string) (map[string]interface{}, err
return nil, fmt.Errorf("API返回错误: %+v", data) return nil, fmt.Errorf("API返回错误: %+v", data)
} }
// 获取商品列表 // 获取商品列表-已登的店铺
func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime int, func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime string,
endCreateTime int, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) { endCreateTime string, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) {
if token == "" { if token == "" {
return nil, fmt.Errorf("请先登录获取Token") return nil, fmt.Errorf("请先登录获取Token")
} }
@ -472,8 +472,8 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p
ItemSn string `json:"itemSn"` ItemSn string `json:"itemSn"`
PriceMin string `json:"priceMin"` PriceMin string `json:"priceMin"`
PriceMax string `json:"priceMax"` PriceMax string `json:"priceMax"`
StartCreateTime int `json:"startCreateTime"` StartCreateTime string `json:"startCreateTime"`
EndCreateTime int `json:"endCreateTime"` EndCreateTime string `json:"endCreateTime"`
RequestType string `json:"requestType,omitempty"` RequestType string `json:"requestType,omitempty"`
IsItemSnEqual int `json:"isItemSnEqual,omitempty"` IsItemSnEqual int `json:"isItemSnEqual,omitempty"`
Page int `json:"page,omitempty"` Page int `json:"page,omitempty"`
@ -655,7 +655,6 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error)
// 获取图片URL(官图和拍图)带有店铺过滤 // 获取图片URL(官图和拍图)带有店铺过滤
func outGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (map[string]string, error) { 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 { if isLiveImage == 0 {
gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn) gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn)
request := gorequest.New() request := gorequest.New()
@ -2873,6 +2872,7 @@ func updateAccountToken(id int64, token string) error {
return err return err
} }
// =================== C 导出函数 =======================
// 登录(带有Out的都非官方标准接口) // 登录(带有Out的都非官方标准接口)
// //
//export OutLogin //export OutLogin
@ -2980,15 +2980,15 @@ func OutGetGoodsTplMsg(token, itemId, proxy *C.char) *C.char {
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口) // 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
// //
//export OutGetGoodsListMsgFromSelfShop //export OutGetGoodsListMsgFromSelfShop
func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime C.int, func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char,
endCreateTime C.int, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char { endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char {
goToken := C.GoString(token) goToken := C.GoString(token)
goProxy := C.GoString(proxy) goProxy := C.GoString(proxy)
goItemSn := C.GoString(itemSn) goItemSn := C.GoString(itemSn)
goPriceMin := C.GoString(priceMin) goPriceMin := C.GoString(priceMin)
goPriceMax := C.GoString(priceMax) goPriceMax := C.GoString(priceMax)
goStartCreateTime := int(startCreateTime) goStartCreateTime := C.GoString(startCreateTime)
goEndCreateTime := int(endCreateTime) goEndCreateTime := C.GoString(endCreateTime)
goRequestType := C.GoString(requestType) goRequestType := C.GoString(requestType)
goIsItemSnEqual := int(isItemSnEqual) goIsItemSnEqual := int(isItemSnEqual)
goPage := int(page) goPage := int(page)
@ -3054,7 +3054,7 @@ func OutDelGoodsFromSelfShop(token, proxy, itemId *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 新增商品(带有Out的都非官方标准接口) // 新增商品-已登的店铺(带有Out的都非官方标准接口)
// //
//export OutAddGoods //export OutAddGoods
func OutAddGoods(token, proxy, formData *C.char) *C.char { func OutAddGoods(token, proxy, formData *C.char) *C.char {
@ -3280,6 +3280,8 @@ func OutGetTopGoodsListMsg(catId C.int, proxy *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 初始化配置
//
//export Initialize //export Initialize
func Initialize(configJSON *C.char) *C.char { func Initialize(configJSON *C.char) *C.char {
configStr := C.GoString(configJSON) configStr := C.GoString(configJSON)

249
md/csv.md Normal file
View File

@ -0,0 +1,249 @@
# csv.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
```gotemplate
// CsvDLL CSV文件工具DLL结构
type csvDLL struct {
dll *syscall.DLL
openCSVFile *syscall.Proc // 打开/创建CSV文件
readRows *syscall.Proc // 读取多行数据
writeRows *syscall.Proc // 写入/覆盖行数据
appendRows *syscall.Proc // 追加行数据
getRowCount *syscall.Proc // 获取总行数
findRows *syscall.Proc // 搜索行
closeCSVFile *syscall.Proc // 关闭CSV文件
mergeCSVFiles *syscall.Proc // 合并多个CSV文件
getError *syscall.Proc // 获取错误信息
freeCString *syscall.Proc // 释放C字符串
}
// 初始化csvDLL
func InitCsvDLL() (*csvDLL, error) {
dllPath := filepath.Join("dll", "csv.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("csv DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载csv DLL 失败: %s", err)
} else {
return &csvDLL{
dll: dll,
openCSVFile: dll.MustFindProc("OpenCSVFile"),
readRows: dll.MustFindProc("ReadRows"),
writeRows: dll.MustFindProc("WriteRows"),
appendRows: dll.MustFindProc("AppendRows"),
getRowCount: dll.MustFindProc("GetRowCount"),
findRows: dll.MustFindProc("FindRows"),
closeCSVFile: dll.MustFindProc("CloseCSVFile"),
mergeCSVFiles: dll.MustFindProc("MergeCSVFiles"),
getError: dll.MustFindProc("GetError"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
dll,err := InitCsvDLL()
```
### 获取C字符串
```gotemplate
// cStr 获取C字符串
func (m *csvDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
```
## 2.使用dll函数示例
```gotemplate
// 打开/创建CSV文件
func (m *csvDLL) OpenCSVFile(filePath string, delimiter byte, hasHeader bool) (int64, error) {
proc, err := m.dll.FindProc("OpenCSVFile")
if err != nil {
return -1, fmt.Errorf("找不到函数 OpenCSVFile: %v", err)
}
filePathPtr, _ := syscall.BytePtrFromString(filePath)
hasHeaderInt := 0
if hasHeader {
hasHeaderInt = 1
}
info, _, _ := proc.Call(
uintptr(unsafe.Pointer(filePathPtr)),
uintptr(delimiter),
uintptr(hasHeaderInt))
return int64(info), nil
}
```
# 接口详情
## 1.打开/创建CSV文件(句柄)--OpenCSVFile
### 请求信息
```gotemplate
dll.OpenCSVFile(filename,delimiter,hasHeader)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| filename | string | 是 | 文件名称(带路径) |
| delimiter | string | 是 | 分隔符(如 ',') |
| hasHeader | string | 是 | 是否有标题行 |
### 响应示例
```
句柄int64
```
## 2.读取多行数据--ReadRows
### 请求信息
```gotemplate
dll.ReadRows(handle,startRow,count,buffer)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|---------------|
| handle | int64 | 是 | 文件句柄 |
| startRow | int64 | 是 | 起始行 |
| count | int64 | 是 | 总数 |
| buffer | []byte | 是 | 返回的文件数据信息byte |
### 响应示例
```
句柄int64
```
## 3.写入/覆盖行数据--WriteRows
### 请求信息
```gotemplate
dll.WriteRows(handle,rowsData,header)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|-----------------------|
| handle | int64 | 是 | 文件句柄 |
| rowsData | string | 是 | 写入的数据,[][]string转换成字符串 |
| header | int | 是 | 是否有文件头0:有 |
### 响应示例
```
文件数据行数int64
```
## 4.追加行数据--AppendRows
### 请求信息
```gotemplate
dll.AppendRows(handle,rowsData,rowCount)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|----------------------------|
| handle | int64 | 是 | 文件句柄 |
| rowsData | []byte | 是 | 写入的数据,将[][]string转换成[]byte |
| rowCount | int | 是 | 数据行数 |
### 响应示例
```
文件数据行数int64
```
## 5.获取总行数--GetRowCount
### 请求信息
```gotemplate
dll.GetRowCount(handle)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|----------------------------|
| handle | int64 | 是 | 文件句柄 |
### 响应示例
```
文件总行数int64
```
## 6.搜索行--FindRows
### 请求信息
```gotemplate
dll.FindRows(handle,searchText,columnIndex,resultBuffer,maxResults)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|----------------------|
| handle | int64 | 是 | 文件句柄 |
| searchText | string | 是 | 要搜索的文本内容 |
| columnIndex | int64 | 是 | 指定搜索的列索引,索引从0开始 -1:表示搜索所有列 |
| resultBuffer | []byte | 是 | 结果缓冲区,用于存储找到的行索引 |
| maxResults | int64 | 是 | 限制最大返回结果数量 |
### 响应示例
```
实际找到的匹配行数int64
```
## 7.合并多个CSV文件(线程安全)--MergeCSVFiles
### 请求信息
```gotemplate
dll.MergeCSVFiles(handlesPtr,handlesCount,outputFilename,delimiter,hasHeader)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|---------|--|----------------------------|
| handlesPtr | []int64 | 是 | 文件句柄数组 |
| handlesCount | string | 是 | 文件句柄数组长度 |
| columnIndex | int64 | 是 | 指定搜索的列索引,索引从0开始 -1:表示搜索所有列 |
| resultBuffer | []byte | 是 | 结果缓冲区,用于存储找到的行索引 |
| maxResults | int64 | 是 | 限制最大返回结果数量 |
### 响应示例
```
文件句柄int64
```
## 8.关闭文件--CloseCSVFile
### 请求信息
```gotemplate
dll.MergeCSVFiles(handle)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|---------|--|-------------------------|
| handle | []int64 | 是 | 文件句柄 |
### 响应示例
```
结果int64 0:表示成功
```
## 8.获取错误信息--GetError
### 请求信息
```gotemplate
dll.GetError(buffer)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|---------|--|-------------------------|
| buffer | []byte | 是 | 结果缓冲区,用于存储找到的行索引 |
### 响应示例
```
结果:int
```
## 9.释放C字符串内存--FreeCString
### 请求信息
```gotemplate
dll.FreeCString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| str | string | 是 | 需要释放的字符串 |

199
md/image.md Normal file
View File

@ -0,0 +1,199 @@
# image.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
```gotemplate
// ImageDLL 图片处理DLL结构
type imageDLL struct {
dll *syscall.DLL
processImage *syscall.Proc
freeCString *syscall.Proc
}
// 初始化imageDLL
func InitImageDll() (*imageDLL, error) {
dllPath := filepath.Join("image", "dll", "image.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("image DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载image DLL 失败: %s", err)
} else {
return &imageDLL{
dll: dll,
processImage: dll.MustFindProc("ProcessImage"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
dll,err := InitImageDll()
```
### 获取C字符串
```gotemplate
// cStr 将 C 字符串指针转换为 Go 字符串
func cStr(ptr uintptr) string {
if ptr == 0 {
return ""
}
var b []byte
for {
c := *(*byte)(unsafe.Pointer(ptr))
if c == 0 {
break
}
b = append(b, c)
ptr++
}
return string(b)
}
```
## 2.使用dll函数示例
```gotemplate
func (m *imageDLL) ProcessImage(config *Config) error {
marshal, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("json转换失败: %s", err)
}
fromString, _ := syscall.BytePtrFromString(string(marshal))
info, _, _ := m.processImage.Call(uintptr(unsafe.Pointer(fromString)))
cStr(info)
return nil
}
```
# 接口详情
## 1.检测图片纯白占比--ProcessImage
### 请求信息
```gotemplate
dll.ProcessImage(jsonConfig)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|------------|
| jsonConfig | string | 是 | json字符串 |
### json字符串结构体
```gotemplate
// Config 配置结构体
type Config struct {
OutputDir string // 输出目录路径
FileName string // 文件名
MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名
MinWhitePct float64 // 纯白占比下限0-1
MaxWhitePct float64 // 纯白占比上限0-1
Extensions []string // 支持的图片扩展名
}
```
### 响应示例
```
string
```
## 2.根据原始图片生成新的白底图片--CreateWhiteBottomCenteredImage
### 请求信息
```gotemplate
dll.CreateWhiteBottomCenteredImage(jsonConfig,width,height)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|---------|
| jsonConfig | string | 是 | json字符串 |
| width | int | 是 | 宽度 |
| height | int | 是 | 高度 |
### json字符串结构体
```gotemplate
// Config 配置结构体
type Config struct {
OutputDir string // 输出目录路径
FileName string // 文件名
MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名
MinWhitePct float64 // 纯白占比下限0-1
MaxWhitePct float64 // 纯白占比上限0-1
Extensions []string // 支持的图片扩展名
}
```
### 响应示例
```
文件名称:string
```
## 3.根据高度生成等比例图片--ResizeToHeightQuality
### 请求信息
```gotemplate
dll.ResizeToHeightQuality(jsonConfig,targetHeight)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|---------|
| jsonConfig | string | 是 | json字符串 |
| targetHeight | int | 是 | 等比例高度 |
### json字符串结构体
```gotemplate
// Config 配置结构体
type Config struct {
OutputDir string // 输出目录路径
FileName string // 文件名
MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名
MinWhitePct float64 // 纯白占比下限0-1
MaxWhitePct float64 // 纯白占比上限0-1
Extensions []string // 支持的图片扩展名
}
```
### 响应示例
```
文件名称:string
```
## 4.去掉白边并转PNG图片工具--RemoveWhiteBorderAndPNG
### 请求信息
```gotemplate
dll.RemoveWhiteBorderAndPNG(jsonConfig)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|--|---------|
| jsonConfig | string | 是 | json字符串 |
### json字符串结构体
```gotemplate
// Config 配置结构体
type Config struct {
OutputDir string // 输出目录路径
FileName string // 文件名
MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名
MinWhitePct float64 // 纯白占比下限0-1
MaxWhitePct float64 // 纯白占比上限0-1
Extensions []string // 支持的图片扩展名
}
```
### 响应示例
```
文件名称:string
```
## 9.释放C字符串内存--FreeCString
### 请求信息
```gotemplate
dll.FreeCString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| str | string | 是 | 需要释放的字符串 |

1597
md/kongfz.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,736 +0,0 @@
# 针对kongfz.dll文件使用教程
## 1.需要先创建dll工具实例
### 1. 加载DLL用于调用dll文件代码示例
```go
func main(){
// 加载DLL
manager, err := NewDLLManager("main.dll")
if err != nil {
log.Fatalf("初始化DLL管理器失败: %v", err)
}
// 确保字段被释放但是需要注意如果加这个可能把你的dll文件释放掉了导致程序崩溃
defer manager.Close()
}
type DLLManager struct {
dll *syscall.DLL
}
func NewDLLManager(dllPath string) (*DLLManager, error) {
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载DLL失败: %v", err)
}
return &DLLManager{dll: dll}, nil
}
func (m *DLLManager) Close() {
if m.dll != nil {
m.dll.Release()
}
}
func (m *DLLManager) Initialize(configJSON string) (string, error) {
return m.callFunction("Initialize", configJSON)
}
// 修改后的 GetKFZGTImageURL - 需要5个参数
func (m *DLLManager) GetKFZGTImageURL(proxyType, username, password, machineCode, isbn string) (string, error) {
return m.callFunctionFiveArgs("GetKFZGTImageURL",
proxyType, username, password, machineCode, isbn)
}
// 修改后的 GetKFZSPTImageURL - 需要5个参数
func (m *DLLManager) GetKFZSPTImageURL(proxyType, username, password, machineCode, isbn string) (string, error) {
return m.callFunctionFiveArgs("GetKFZSPTImageURL",
proxyType, username, password, machineCode, isbn)
}
// 修改后的 GetKFZShopBookInfo - 需要12个参数
func (m *DLLManager) GetKFZShopBookInfo(proxyType, username, password, machineCode, shopId, isImage, bookNum, pageNum, sortType, sort, priceDown, priceUp string) (string, error) {
return m.callFunctionTwelveArgs("GetKFZShopBookInfo",
proxyType, username, password, machineCode, shopId, isImage, bookNum, pageNum, sortType, sort, priceDown, priceUp)
}
```
### 2.初始化配置信息,代码示例:
````go
func main(){
// 使用默认配置初始化
config := createDefaultConfig()
configJSON, err := json.Marshal(config)
if err != nil {
log.Fatalf("序列化配置失败: %v", err)
}
result, err := manager.Initialize(string(configJSON))
if err != nil {
log.Fatalf("初始化失败: %v", err)
}
var initResp APIResponses
if err := json.Unmarshal([]byte(result), &initResp); err != nil {
log.Fatalf("解析初始化响应失败: %v", err)
}
if !initResp.Success {
log.Fatalf("初始化失败: %s", initResp.Message)
}
}
// 创建默认配置示例
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 = "dll/proxyConfig.dll"
return configs
}
// 配置结构
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"`
}
````
### 直接调用接口,代码示例:入参、返回参数都在下面
```go
func main() {
// 调用方法示例
shopResult, err := manager.GetKFZShopBookInfo(
proxyType, username, password, machineCode,
shopId, isImage, bookNum, pageNum, sortType, sort, priceDown, priceUp,
)
}
```
---
## 接口详情
### 1. 获取孔网官图
**请求信息**
gtResult, err := manager.GetKFZGTImageURL(testCase.isbn)
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|------|--------|----|-----------|
| proxyType | string | 是 | 代理类型(小象代理CALF_ELEPHANT_PROXY,内置代理TAIL_PROXY) |
| username | string | 是 | 小象代理账号 |
| password | string | 是 | 小象代理密码 |
| machineCode | string | 是 | 内置代理机械码 |
| isbn | string | 是 | 图书的 ISBN 号 |
**响应数据**
| 字段名 | 类型 | 说明 | 示例值 |
|--------|------|------|---------|
| `success` | boolean | 请求是否成功 | `true` |
| `message` | string | 响应消息 | `"查询成功"` |
| `data` | array | 图书信息列表 | - |
**图书信息字段说明(data数据)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| `author` | string | 作者 |
| `binding_layout` | string | 装帧布局 |
| `book_name` | string | 图书名称 |
| `book_pic` | string | 图书图片(大图) |
| `book_pic_s` | string | 图书图片(小图) |
| `buyCount` | string | 购买数量 |
| `category` | string | 分类 |
| `condition` | string | 图书状况 |
| `content` | string | 内容简介 |
| `edition` | string | 版次 |
| `editor` | string | 编者 |
| `express_delivery_fee` | string | 快递费用 |
| `fix_price` | string | 定价 |
| `format` | string | 开本 |
| `isbn` | string | ISBN号 |
| `item_id` | number | 商品ID |
| `languages` | string | 语言 |
| `mid` | number | 商家ID |
| `pages` | string | 页数 |
| `paper` | string | 纸张 |
| `print_time` | string | 印刷时间 |
| `publication_time` | number | 出版时间(时间戳) |
| `publisher` | string | 出版社 |
| `sellCount` | string | 销售数量 |
| `shop_id` | number | 店铺ID |
| `wordage` | string | 字数 |
**响应示例**
```json
{
"success": true,
"message": "查询成功",
"data": {
"author": "",
"binding_layout": "",
"book_name": "",
"book_pic": "",
"book_pic_s": "",
"buyCount": "",
"category": "",
"condition": "",
"content": "",
"edition": "",
"editor": "",
"express_delivery_fee": "",
"fix_price": "",
"format": "",
"isbn": "",
"item_id": 0,
"languages": "",
"mid": 0,
"pages": "",
"paper": "",
"print_time": "",
"publication_time": 0,
"publisher": "",
"sellCount": "",
"shop_id": 0,
"wordage": ""
}
}
```
### 2. 获取孔网实拍图
**请求信息**
sptResult, err := manager.GetKFZSPTImageURL(testCase.isbn)
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|------|--------|----|------------|
| proxyType | string | 是 | 代理类型(小象代理CALF_ELEPHANT_PROXY,内置代理TAIL_PROXY) |
| username | string | 是 | 小象代理账号 |
| password | string | 是 | 小象代理密码 |
| machineCode | string | 是 | 内置代理机械码 |
| isbn | string | 是 | 图书的 ISBN 号 |
**响应数据**
| 字段名 | 类型 | 说明 | 示例值 |
|--------|------|------|---------|
| `success` | boolean | 请求是否成功 | `true` |
| `message` | string | 响应消息 | `"查询成功"` |
| `data` | array | 图书信息列表 | - |
**图书信息字段说明(data数据)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| `author` | string | 作者 |
| `binding_layout` | string | 装帧布局 |
| `book_name` | string | 图书名称 |
| `book_pic` | string | 图书图片(大图) |
| `book_pic_s` | string | 图书图片(小图) |
| `buyCount` | string | 购买数量 |
| `category` | string | 分类 |
| `condition` | string | 图书状况 |
| `content` | string | 内容简介 |
| `edition` | string | 版次 |
| `editor` | string | 编者 |
| `express_delivery_fee` | string | 快递费用 |
| `fix_price` | string | 定价 |
| `format` | string | 开本 |
| `isbn` | string | ISBN号 |
| `item_id` | number | 商品ID |
| `languages` | string | 语言 |
| `mid` | number | 商家ID |
| `pages` | string | 页数 |
| `paper` | string | 纸张 |
| `print_time` | string | 印刷时间 |
| `publication_time` | number | 出版时间(时间戳) |
| `publisher` | string | 出版社 |
| `sellCount` | string | 销售数量 |
| `shop_id` | number | 店铺ID |
| `wordage` | string | 字数 |
**响应示例**
```json
{
"success": true,
"message": "查询成功",
"data": {
"author": "",
"binding_layout": "",
"book_name": "",
"book_pic": "",
"book_pic_s": "https://www0.kfzimg.com/sw/kfz-cos/kfzimg/adaaecbf/a1e5df0a21cfdbba_s.jpg",
"buyCount": "",
"category": "",
"condition": "",
"content": "",
"edition": "",
"editor": "",
"express_delivery_fee": "",
"fix_price": "",
"format": "",
"isbn": "",
"item_id": 0,
"languages": "",
"mid": 0,
"pages": "",
"paper": "",
"print_time": "",
"publication_time": 0,
"publisher": "",
"sellCount": "",
"shop_id": 0,
"wordage": ""
}
}
```
### 3. 获取店铺图书信息
**请求信息**
shopResult, err := manager.GetKFZShopBookInfo(
proxyType, //代理类型
username, //小象代理账号
password, //小象代理密码
machineCode, //内置代理机械码
shopId,
isImage,
bookNum,
pageNum,
sortType,
sort,
priceDown,
priceUp,
)
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|--------|----|------------------------------------------------|
| fetchMode | string | 是 | 是否使用代理 (使用代理proxy 不使用代理direct) |
| proxyType | string | 是 | 代理类型(小象代理CALF_ELEPHANT_PROXY,内置代理TAIL_PROXY) |
| username | string | 是 | 小象代理账号 |
| password | string | 是 | 小象代理密码 |
| machineCode | string | 是 | 内置代理机械码 |
| shopId | string | 是 | 店铺编号 |
| isImage | string | 否 | 是否有图片 |
| bookNum | string | 否 | 图书数量 |
| pageNum | string | 否 | 页数 |
| sortType | string | 否 | 排序类型(sort,putDate,newItem,price只有这四种) |
| sort | string | 否 | 排序方式(asc,desc) |
| priceUp | string | 否 | 最低价格 |
| priceDown | string | 否 | 最高价格 |
**响应数据**
| 字段名 | 类型 | 说明 | 示例值 |
|--------|------|--------|---------|
| `success` | boolean | 请求是否成功 | `true` |
| `message` | string | 响应消息 | `"查询成功"` |
| `goods_num` | string | 商品数量 | `"查询成功"` |
| `pnum` | string | 店铺商品页数 | `"查询成功"` |
| `data` | array | 图书信息列表 | - |
**图书信息字段说明(data数据)**
| 字段名 | 类型 | 说明 |
|--------|------|--------|
| `book_name` | string | 书名 |
| `author` | string | 作者 |
| `publisher` | string | 出版社 |
| `isbn` | string | ISBN号 |
| `publication_time` | number | 出版时间(时间戳) |
| `edition` | string | 版次 |
| `print_time` | string | 印刷时间 |
| `fix_price` | string | 定价 |
| `binding_layout` | string | 装帧 |
| `format` | string | 开本 |
| `paper` | string | 纸张 |
| `pages` | string | 页数 |
| `wordage` | string | 字数 |
| `languages` | string | 语种 |
| `era` | string | 年代 |
| `engraving_method` | string | 刻印方式 |
| `dimensions` | string | 尺寸 |
| `volume_number` | string | 册数 |
| `book_pic` | string | 图书图片(官图) |
| `book_pic_s` | string | 图书图片(实拍图) |
| `selling_price` | string | 售价 |
| `condition` | string | 品相 |
| `express_delivery_fee` | string | 快递费 |
| `editor` | string | 编辑 |
| `category` | string | 分类 |
| `buy_count` | string | 买过 |
| `sell_count` | string | 买过 |
| `content` | string | 内容 |
| `mid` | number | 商家ID |
| `item_id` | number | 商品ID |
| `shop_id` | number | 店铺ID |
**响应示例**
```json
{
"success": true,
"message": "查询成功",
"goods_num": "",
"pnum": "",
"data": [
{
"author": "",
"binding_layout": "",
"book_name": "",
"book_pic": "",
"book_pic_s": "https://www0.kfzimg.com/sw/kfz-cos/kfzimg/cddbcbda/6833c1d27437d121_s.jpg",
"buyCount": "",
"category": "",
"condition": "",
"content": "",
"edition": "",
"editor": "",
"express_delivery_fee": "",
"fix_price": "",
"format": "",
"isbn": "",
"item_id": 0,
"languages": "",
"mid": 0,
"pages": "",
"paper": "",
"print_time": "",
"publication_time": 0,
"publisher": "",
"sellCount": "",
"shop_id": 0,
"wordage": ""
}
]
}
```
### 4. 根据url获取单个图书详情信息
**请求信息**
shopResult, err := manager.GetUrlBookDetails(
fetchMode,
proxyType,
username,
password,
machineCode,
url
)
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|--------|----|------------------------------------------------|
| fetchMode | string | 是 | 是否使用代理 (使用代理proxy 不使用代理direct) |
| proxyType | string | 是 | 代理类型(小象代理CALF_ELEPHANT_PROXY,内置代理TAIL_PROXY) |
| username | string | 是 | 小象代理账号 |
| password | string | 是 | 小象代理密码 |
| machineCode | string | 是 | 内置代理机械码 |
| url | string | 是 | 传入详情页的url |
**响应数据**
| 字段名 | 类型 | 说明 | 示例值 |
|--------|------|--------|---------|
| `success` | boolean | 请求是否成功 | `true` |
| `message` | string | 响应消息 | `"查询成功"` |
| `goods_num` | string | 商品数量 | `"查询成功"` |
| `pnum` | string | 店铺商品页数 | `"查询成功"` |
| `data` | array | 图书信息列表 | - |
**图书信息字段说明(data数据)**
| 字段名 | 类型 | 说明 |
|--------|------|--------|
| `book_name` | string | 书名 |
| `author` | string | 作者 |
| `publisher` | string | 出版社 |
| `isbn` | string | ISBN号 |
| `publication_time` | number | 出版时间(时间戳) |
| `edition` | string | 版次 |
| `print_time` | string | 印刷时间 |
| `fix_price` | string | 定价 |
| `binding_layout` | string | 装帧 |
| `format` | string | 开本 |
| `paper` | string | 纸张 |
| `pages` | string | 页数 |
| `wordage` | string | 字数 |
| `languages` | string | 语种 |
| `era` | string | 年代 |
| `engraving_method` | string | 刻印方式 |
| `dimensions` | string | 尺寸 |
| `volume_number` | string | 册数 |
| `book_pic` | string | 图书图片(官图) |
| `book_pic_s` | string | 图书图片(实拍图) |
| `selling_price` | string | 售价 |
| `condition` | string | 品相 |
| `express_delivery_fee` | string | 快递费 |
| `editor` | string | 编辑 |
| `category` | string | 分类 |
| `buy_count` | string | 买过 |
| `sell_count` | string | 买过 |
| `content` | string | 内容 |
| `mid` | number | 商家ID |
| `item_id` | number | 商品ID |
| `shop_id` | number | 店铺ID |
**响应示例**
```json
{
"success": true,
"message": "查询成功",
"goods_num": "",
"pnum": "",
"data": [
{
"author": "",
"binding_layout": "",
"book_name": "",
"book_pic": "",
"book_pic_s": "https://www0.kfzimg.com/sw/kfz-cos/kfzimg/cddbcbda/6833c1d27437d121_s.jpg",
"buyCount": "",
"category": "",
"condition": "",
"content": "",
"edition": "",
"editor": "",
"express_delivery_fee": "",
"fix_price": "",
"format": "",
"isbn": "",
"item_id": 0,
"languages": "",
"mid": 0,
"pages": "",
"paper": "",
"print_time": "",
"publication_time": 0,
"publisher": "",
"sellCount": "",
"shop_id": 0,
"wordage": ""
}
]
}
```
### 5. 初始化
**请求信息**
manager.Initialize(
configJSON,
)
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|--------|--------|----|------|
| configJSON | string | 是 | 配置信息 |
**响应数据**
| 字段名 | 类型 | 说明 | 示例值 |
|--------|------|--------|---------|
| `success` | boolean | 请求是否成功 | `true` |
| `message` | string | 响应消息 | `"查询成功"` |
**图书信息字段说明(data数据)**
| 字段名 | 类型 | 说明 |
|--------|------|--------|
| `book_name` | string | 书名 |
| `author` | string | 作者 |
| `publisher` | string | 出版社 |
| `isbn` | string | ISBN号 |
| `publication_time` | number | 出版时间(时间戳) |
| `edition` | string | 版次 |
| `print_time` | string | 印刷时间 |
| `fix_price` | string | 定价 |
| `binding_layout` | string | 装帧 |
| `format` | string | 开本 |
| `paper` | string | 纸张 |
| `pages` | string | 页数 |
| `wordage` | string | 字数 |
| `languages` | string | 语种 |
| `era` | string | 年代 |
| `engraving_method` | string | 刻印方式 |
| `dimensions` | string | 尺寸 |
| `volume_number` | string | 册数 |
| `book_pic` | string | 图书图片(官图) |
| `book_pic_s` | string | 图书图片(实拍图) |
| `selling_price` | string | 售价 |
| `condition` | string | 品相 |
| `express_delivery_fee` | string | 快递费 |
| `editor` | string | 编辑 |
| `category` | string | 分类 |
| `buy_count` | string | 买过 |
| `sell_count` | string | 买过 |
| `content` | string | 内容 |
| `mid` | number | 商家ID |
| `item_id` | number | 商品ID |
| `shop_id` | number | 店铺ID |
# proxyConfig.dll文件使用讲解
由于是kongfz.dll文件去调用proxyConfig.dll文件里面的函数无需再去调用。
除非直接使用proxyConfig.dll文件。
## 1.直接使用proxyConfig.dll文件应先加载DLL
```go
func main(){
// 加载DLL
manager, err := NewDLLManager("文件路径")
if err != nil {
log.Fatalf("初始化DLL管理器失败: %v", err)
}
// 确保字段被释放但是需要注意如果加这个可能把你的dll文件释放掉了导致程序崩溃
defer manager.Close()
}
type DLLManager struct {
dll *syscall.DLL
}
func NewDLLManager(dllPath string) (*DLLManager, error) {
dll, err := syscall.LoadDLL(dllPath)
if err != nil {
return nil, fmt.Errorf("加载DLL失败: %v", err)
}
return &DLLManager{dll: dll}, nil
}
func (m *DLLManager) Close() {
if m.dll != nil {
m.dll.Release()
}
}
// ProxyTypeManager 调用代理类型管理器
func (m *ProxyConfigManager) ProxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
proc, err := m.dll.FindProc("ProxyTypeManager")
if err != nil {
return "", fmt.Errorf("找不到函数 ProxyTypeManager: %v", err)
}
// 准备参数
proxyTypePtr, _ := syscall.BytePtrFromString(proxyType)
usernamePtr, _ := syscall.BytePtrFromString(username)
passwordPtr, _ := syscall.BytePtrFromString(password)
machineCodePtr, _ := syscall.BytePtrFromString(machineCode)
// 调用函数
r1, _, err := proc.Call(
uintptr(unsafe.Pointer(proxyTypePtr)),
uintptr(unsafe.Pointer(usernamePtr)),
uintptr(unsafe.Pointer(passwordPtr)),
uintptr(unsafe.Pointer(machineCodePtr)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return "", fmt.Errorf("调用 ProxyTypeManager 失败: %v", err)
}
// 转换结果
result := (*byte)(unsafe.Pointer(r1))
var resultBytes []byte
for i := 0; ; i++ {
bytePtr := (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(result)) + uintptr(i)))
if *bytePtr == 0 {
break
}
resultBytes = append(resultBytes, *bytePtr)
}
// 释放内存
freeProc, _ := m.dll.FindProc("FreeCString")
if freeProc != nil {
freeProc.Call(r1)
}
return string(resultBytes), nil
}
```
## 直接调用ProxyTypeManager函数方法
```go
typeManager, err := globalProxyManager.ProxyTypeManager(
proxyType,
username,
password,
machineCode,
)
```
**请求信息**
typeManager, err := globalProxyManager.ProxyTypeManager(
proxyType,
username,
password,
machineCode,
)
**请求参数**
| 参数名 | 类型 | 必填 | 说明 |
|------|--------|----|------------|
| proxyType | string | 是 | 代理类型(小象代理CALF_ELEPHANT_PROXY,内置代理TAIL_PROXY) |
| username | string | 是 | 小象代理账号 |
| password | string | 是 | 小象代理密码 |
| machineCode | string | 是 | 内置代理机械码 |
**响应信息**
string字符串 http://18434270290:JWt15lWW@111.179.77.244:44751

197
md/proxy.md Normal file
View File

@ -0,0 +1,197 @@
# proxy.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
```gotemplate
// ProxyDLL 代理DLL结构
type proxyDLL struct {
dll *syscall.DLL
proxyTypeManager *syscall.Proc // 获取代理服务器
getMachineCode *syscall.Proc // 获取机器码
rechargeCard *syscall.Proc // 充值卡密
getProxies *syscall.Proc // 获取代理服务器
checkTailCardSecretExpired *syscall.Proc // 检查卡密是否过期
freeCString *syscall.Proc // 释放C字符串内存
}
// 初始化代理DLL
func InitProxyDLL() (*proxyDLL, error) {
dllPath := filepath.Join("dll", "proxy1.dll")
log.Printf("[DEBUG] 调用proxy.dll文件文件路径: %s", dllPath)
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("proxy DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载proxy DLL 失败: %s", err)
} else {
return &proxyDLL{
dll: dll,
proxyTypeManager: dll.MustFindProc("ProxyTypeManager"),
getMachineCode: dll.MustFindProc("GetMachineCode"),
rechargeCard: dll.MustFindProc("RechargeCard"),
getProxies: dll.MustFindProc("GetProxies"),
checkTailCardSecretExpired: dll.MustFindProc("CheckTailCardSecretExpired"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
// 函数调用
dll, err := InitProxyDLL()
if err != nil {
return "", err
}
```
### 获取C字符串
```gotemplate
// cStr 获取C字符串
func (m *kongfzDLL) cStr(p uintptr) string {
if p == 0 {
return ""
}
b := []byte{}
for i := uintptr(0); ; i++ {
c := *(*byte)(unsafe.Pointer(p + i))
if c == 0 {
break
}
b = append(b, c)
}
s := string(b)
if m.freeCString != nil {
m.freeCString.Call(p)
}
return s
}
```
## 2.使用dll函数示例
```gotemplate
// ProxyTypeManager 获取代理服务器
func (m *proxyDLL) ProxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
proc, err := m.dll.FindProc("ProxyTypeManager")
if err != nil {
return "", fmt.Errorf("找不到函数 ProxyTypeManager: %v", err)
}
proxyTypePtr, _ := syscall.BytePtrFromString(proxyType)
usernamePtr, _ := syscall.BytePtrFromString(username)
passwordPtr, _ := syscall.BytePtrFromString(password)
machineCodePtr, _ := syscall.BytePtrFromString(machineCode)
info, _, err := proc.Call(
uintptr(unsafe.Pointer(proxyTypePtr)),
uintptr(unsafe.Pointer(usernamePtr)),
uintptr(unsafe.Pointer(passwordPtr)),
uintptr(unsafe.Pointer(machineCodePtr)))
if err != nil && err.Error() != "The operation completed successfully." {
return "", fmt.Errorf("调用函数 ProxyTypeManager 失败: %v", err)
}
return m.cStr(info), nil
}
```
# 接口详情
## 1.获取代理服务器--ProxyTypeManager
### 请求信息
```gotemplate
dll.ProxyTypeManager(proxyType, username, password, machineCode)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----------------------------------------------|
| proxyType | string | 是 | 代理类型 小象代理CALF_ELEPHANT_PROXY 内置代理TAIL_PROXY |
| username | string | 是 | 小象账号 |
| password | string | 是 | 小象密码 |
| machineCode | string | 是 | 内置代理机器码 |
### 响应示例
```
代理服务器字符串示例
http://18434270290:JWt15lWW@183.7.131.66:36806
```
## 2.查询机器码--GetMachineCode
### 请求信息
```gotemplate
dll.GetMachineCode(tailCardSecret)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----|
| tailCardSecret | string | 是 | 卡密 |
### 响应示例
```json
{
"code": 200,
"message": "success",
"data": {
"machine_code": "07f4d0fbcff99966c2b37b0c1fb7f01c",
"ip_exp_time": "2025-12-12 10:35:06",
"ip_thread": 5,
"ip_card_code": "DL_5_TK_021c06ac87434a66a857a55baac28494"
}
}
```
## 3.充值卡密--RechargeCard
### 请求信息
```gotemplate
dll.RechargeCard(tailCardSecret,machineCode)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----|
| tailCardSecret | string | 是 | 卡密 |
| machineCode | string | 是 | 机器码 |
### 响应示例
```json
{
"success": true,
"message": "充值成功",
"machine_code": "e88c2c4210a2667866aab824cdfd2a9c"
}
```
## 4.获取代理服务器列表--GetProxies
### 请求信息
```gotemplate
dll.GetProxies(machineCode)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|-----|
| machineCode | string | 是 | 机器码 |
### 响应示例
```json
{
"success": true,
"count": 4,
"proxies": ["",""]
}
```
## 5.检查卡密是否过期--CheckTailCardSecretExpired
### 请求信息
```gotemplate
dll.CheckTailCardSecretExpired(tailCardSecret)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----|
| tailCardSecret | string | 是 | 卡密 |
### 响应示例
```json
{
"is_valid": true,
"count": 4
}
```
## 6.释放C字符串内存--FreeCString
### 请求信息
```gotemplate
dll.FreeCString(str)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| str | string | 是 | 需要释放的字符串 |

615
monitor/main.go Normal file
View File

@ -0,0 +1,615 @@
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"sync"
"time"
)
// ==============================
// 事件发射器 (Event Emitter)
// ==============================
// 事件类型
type EventType string
const (
EventProgress EventType = "progress"
EventComplete EventType = "complete"
EventError EventType = "error"
EventStart EventType = "start"
EventPause EventType = "pause"
EventResume EventType = "resume"
EventCancel EventType = "cancel"
)
// 事件数据
type EventData struct {
Type EventType
Downloaded int64
Total int64
Percentage float64
Speed float64 // KB/s
Message string
Timestamp time.Time
Filename string
Error error
}
// 事件监听器函数类型
type EventListener func(data EventData)
// 事件发射器
type EventEmitter struct {
listeners map[EventType][]EventListener
mu sync.RWMutex
}
// 创建新的事件发射器
func NewEventEmitter() *EventEmitter {
return &EventEmitter{
listeners: make(map[EventType][]EventListener),
}
}
// 添加事件监听器
func (ee *EventEmitter) On(eventType EventType, listener EventListener) {
ee.mu.Lock()
defer ee.mu.Unlock()
ee.listeners[eventType] = append(ee.listeners[eventType], listener)
}
// 移除事件监听器
func (ee *EventEmitter) Off(eventType EventType, listener EventListener) {
ee.mu.Lock()
defer ee.mu.Unlock()
listeners := ee.listeners[eventType]
for i, l := range listeners {
if &l == &listener {
ee.listeners[eventType] = append(listeners[:i], listeners[i+1:]...)
break
}
}
}
// 发射事件 (类似 runtime.EventsEmit)
func (ee *EventEmitter) Emit(eventType EventType, data EventData) {
ee.mu.RLock()
listeners := ee.listeners[eventType]
ee.mu.RUnlock()
data.Type = eventType
data.Timestamp = time.Now()
for _, listener := range listeners {
listener(data)
}
}
// ==============================
// CSV下载器
// ==============================
// CSV下载器
type CSVDownloader struct {
emitter *EventEmitter
interval time.Duration
isPaused bool
isCanceled bool
mu sync.RWMutex
}
// 创建新的CSV下载器
func NewCSVDownloader() *CSVDownloader {
return &CSVDownloader{
emitter: NewEventEmitter(),
interval: 100 * time.Millisecond,
}
}
// 设置进度报告间隔
func (d *CSVDownloader) SetInterval(interval time.Duration) {
d.interval = interval
}
// 监听事件
func (d *CSVDownloader) On(eventType EventType, listener EventListener) {
d.emitter.On(eventType, listener)
}
// 暂停下载
func (d *CSVDownloader) Pause() {
d.mu.Lock()
d.isPaused = true
d.mu.Unlock()
d.emitter.Emit(EventPause, EventData{Message: "下载已暂停"})
}
// 恢复下载
func (d *CSVDownloader) Resume() {
d.mu.Lock()
d.isPaused = false
d.mu.Unlock()
d.emitter.Emit(EventResume, EventData{Message: "下载已恢复"})
}
// 取消下载
func (d *CSVDownloader) Cancel() {
d.mu.Lock()
d.isCanceled = true
d.mu.Unlock()
d.emitter.Emit(EventCancel, EventData{Message: "下载已取消"})
}
// 下载CSV文件
func (d *CSVDownloader) Download(url, filename string) error {
// 检查是否已取消
d.mu.RLock()
if d.isCanceled {
d.mu.RUnlock()
return fmt.Errorf("下载已取消")
}
d.mu.RUnlock()
// 开始下载事件
d.emitter.Emit(EventStart, EventData{
Filename: filename,
Message: "开始下载文件",
})
// 创建HTTP请求
resp, err := http.Get(url)
if err != nil {
errorData := EventData{
Filename: filename,
Message: fmt.Sprintf("无法开始下载: %v", err),
Error: err,
}
d.emitter.Emit(EventError, errorData)
return err
}
defer resp.Body.Close()
// 获取文件总大小
totalSize, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
if totalSize <= 0 {
totalSize = 0 // 未知大小
}
// 创建本地文件
file, err := os.Create(filename)
if err != nil {
errorData := EventData{
Filename: filename,
Message: fmt.Sprintf("无法创建文件: %v", err),
Error: err,
}
d.emitter.Emit(EventError, errorData)
return err
}
defer file.Close()
// 下载缓冲区
buffer := make([]byte, 32*1024) // 32KB
var downloaded int64 = 0
var lastReport time.Time
var lastDownloaded int64 = 0
startTime := time.Now()
// 开始下载循环
for {
// 检查是否暂停
d.mu.RLock()
if d.isPaused {
d.mu.RUnlock()
for {
time.Sleep(100 * time.Millisecond)
d.mu.RLock()
if !d.isPaused || d.isCanceled {
d.mu.RUnlock()
break
}
d.mu.RUnlock()
}
}
// 检查是否取消
if d.isCanceled {
d.mu.RUnlock()
file.Close()
os.Remove(filename)
return fmt.Errorf("下载已取消")
}
d.mu.RUnlock()
// 读取数据
n, err := resp.Body.Read(buffer)
if n > 0 {
// 写入文件
_, writeErr := file.Write(buffer[:n])
if writeErr != nil {
errorData := EventData{
Filename: filename,
Message: fmt.Sprintf("写入文件失败: %v", writeErr),
Error: writeErr,
}
d.emitter.Emit(EventError, errorData)
return writeErr
}
// 更新下载量
downloaded += int64(n)
// 计算下载速度
now := time.Now()
if lastReport.IsZero() {
lastReport = now
lastDownloaded = downloaded
}
// 定时报告进度
if time.Since(lastReport) >= d.interval || err == io.EOF {
elapsed := time.Since(lastReport).Seconds()
speed := 0.0
if elapsed > 0 {
speed = float64(downloaded-lastDownloaded) / elapsed / 1024 // KB/s
}
// 计算百分比
percentage := 0.0
if totalSize > 0 {
percentage = float64(downloaded) / float64(totalSize) * 100
}
// 发射进度事件
d.emitter.Emit(EventProgress, EventData{
Downloaded: downloaded,
Total: totalSize,
Percentage: percentage,
Speed: speed,
Filename: filename,
Message: fmt.Sprintf("下载中: %.2f%%", percentage),
})
// 重置计时器
lastReport = now
lastDownloaded = downloaded
}
}
// 检查是否完成
if err != nil {
if err == io.EOF {
// 下载完成
totalTime := time.Since(startTime).Seconds()
averageSpeed := 0.0
if totalTime > 0 {
averageSpeed = float64(downloaded) / totalTime / 1024
}
d.emitter.Emit(EventComplete, EventData{
Downloaded: downloaded,
Total: totalSize,
Percentage: 100.0,
Speed: averageSpeed,
Filename: filename,
Message: fmt.Sprintf("下载完成! 总共用时: %.2f秒", totalTime),
})
return nil
}
// 发生错误
errorData := EventData{
Filename: filename,
Message: fmt.Sprintf("下载错误: %v", err),
Error: err,
}
d.emitter.Emit(EventError, errorData)
return err
}
}
}
// ==============================
// 模拟文件生成器(用于测试)
// ==============================
type MockCSVGenerator struct {
emitter *EventEmitter
}
func NewMockCSVGenerator() *MockCSVGenerator {
return &MockCSVGenerator{
emitter: NewEventEmitter(),
}
}
func (g *MockCSVGenerator) On(eventType EventType, listener EventListener) {
g.emitter.On(eventType, listener)
}
func (g *MockCSVGenerator) Generate(filename string, totalRows int, columns []string) error {
// 开始事件
g.emitter.Emit(EventStart, EventData{
Filename: filename,
Message: "开始生成CSV文件",
})
file, err := os.Create(filename)
if err != nil {
g.emitter.Emit(EventError, EventData{
Filename: filename,
Error: err,
Message: "无法创建文件",
})
return err
}
defer file.Close()
// 写入头部
header := ""
for i, col := range columns {
if i > 0 {
header += ","
}
header += col
}
file.WriteString(header + "\n")
startTime := time.Now()
// 生成数据行
for i := 0; i < totalRows; i++ {
// 生成一行数据
row := ""
for j := 0; j < len(columns); j++ {
if j > 0 {
row += ","
}
row += fmt.Sprintf("data%d_%d", i, j)
}
file.WriteString(row + "\n")
// 模拟处理时间
time.Sleep(5 * time.Millisecond)
// 每100行报告一次进度
if (i+1)%100 == 0 || i == totalRows-1 {
percentage := float64(i+1) / float64(totalRows) * 100
elapsed := time.Since(startTime).Seconds()
rowsPerSecond := 0.0
if elapsed > 0 {
rowsPerSecond = float64(i+1) / elapsed
}
g.emitter.Emit(EventProgress, EventData{
Downloaded: int64(i + 1),
Total: int64(totalRows),
Percentage: percentage,
Speed: rowsPerSecond,
Filename: filename,
Message: fmt.Sprintf("已生成 %d/%d 行", i+1, totalRows),
})
}
}
// 完成事件
g.emitter.Emit(EventComplete, EventData{
Downloaded: int64(totalRows),
Total: int64(totalRows),
Percentage: 100.0,
Filename: filename,
Message: "CSV文件生成完成",
})
return nil
}
// ==============================
// 主程序
// ==============================
func main() {
fmt.Println("=== CSV文件下载器 (事件监听模式) ===\n")
// 创建控制台日志监听器
consoleLogger := func(data EventData) {
timestamp := data.Timestamp.Format("15:04:05")
switch data.Type {
case EventStart:
fmt.Printf("[%s] 🚀 %s: %s\n", timestamp, data.Filename, data.Message)
case EventProgress:
if data.Total > 0 {
fmt.Printf("[%s] 📥 %s: %s (%.2f%%) - %.2f KB/s\n",
timestamp, data.Filename, data.Message, data.Percentage, data.Speed)
} else {
fmt.Printf("[%s] 📥 %s: 已下载 %d bytes - %.2f KB/s\n",
timestamp, data.Filename, data.Downloaded, data.Speed)
}
case EventComplete:
fmt.Printf("[%s] ✅ %s: %s\n", timestamp, data.Filename, data.Message)
case EventError:
fmt.Printf("[%s] ❌ %s: %s (错误: %v)\n",
timestamp, data.Filename, data.Message, data.Error)
case EventPause:
fmt.Printf("[%s] ⏸️ %s\n", timestamp, data.Message)
case EventResume:
fmt.Printf("[%s] ▶️ %s\n", timestamp, data.Message)
case EventCancel:
fmt.Printf("[%s] ⏹️ %s\n", timestamp, data.Message)
}
}
// 创建进度条监听器(简单的文本进度条)
progressBar := func(data EventData) {
if data.Type == EventProgress && data.Total > 0 {
barWidth := 50
completed := int(float64(barWidth) * data.Percentage / 100)
remaining := barWidth - completed
bar := ""
for i := 0; i < completed; i++ {
bar += "█"
}
for i := 0; i < remaining; i++ {
bar += "░"
}
fmt.Printf("\r进度: [%s] %.2f%% | 速度: %.2f KB/s", bar, data.Percentage, data.Speed)
if data.Percentage >= 100 {
fmt.Println()
}
}
}
// 创建统计信息监听器
statsTracker := func(data EventData) {
// 在实际应用中,这里可以记录统计数据到数据库或文件
if data.Type == EventComplete {
fmt.Printf("[统计] 文件: %s, 大小: %d bytes, 平均速度: %.2f KB/s\n",
data.Filename, data.Downloaded, data.Speed)
}
}
// 示例1模拟CSV文件生成
fmt.Println("示例1: 模拟生成CSV文件")
fmt.Println("======================")
generator := NewMockCSVGenerator()
// 注册事件监听器
generator.On(EventStart, consoleLogger)
generator.On(EventProgress, progressBar)
generator.On(EventProgress, consoleLogger)
generator.On(EventComplete, consoleLogger)
generator.On(EventComplete, statsTracker)
generator.On(EventError, consoleLogger)
// 生成CSV文件
columns := []string{"ID", "Name", "Age", "Email", "City", "Salary", "Department"}
err := generator.Generate("employees.csv", 500, columns)
if err != nil {
fmt.Printf("生成失败: %v\n", err)
}
fmt.Println("\n示例2: 模拟文件下载")
fmt.Println("======================")
// 创建下载器
downloader := NewCSVDownloader()
downloader.SetInterval(200 * time.Millisecond)
// 注册事件监听器
downloader.On(EventStart, consoleLogger)
downloader.On(EventProgress, progressBar)
downloader.On(EventProgress, consoleLogger)
downloader.On(EventComplete, consoleLogger)
downloader.On(EventComplete, statsTracker)
downloader.On(EventError, consoleLogger)
downloader.On(EventPause, consoleLogger)
downloader.On(EventResume, consoleLogger)
downloader.On(EventCancel, consoleLogger)
// 模拟下载控制(在真实场景中,这可能是用户界面操作)
go func() {
time.Sleep(500 * time.Millisecond)
downloader.Pause()
time.Sleep(1 * time.Second)
downloader.Resume()
}()
// 注意这里使用了一个公开的测试CSV文件URL
// 在实际使用中请替换为真实的CSV文件URL
testURL := "https://raw.githubusercontent.com/datasets/covid-19/main/data/time-series-19-covid-combined.csv"
// 由于网络请求可能需要时间这里我们使用goroutine来演示
go func() {
err := downloader.Download(testURL, "covid-data.csv")
if err != nil {
fmt.Printf("下载失败: %v\n", err)
}
}()
// 等待演示完成
time.Sleep(5 * time.Second)
// 演示文件处理进度监控
fmt.Println("\n示例3: 文件处理进度监控")
fmt.Println("======================")
processor := NewEventEmitter()
// 模拟文件处理
go func() {
processor.Emit(EventStart, EventData{
Filename: "employees.csv",
Message: "开始处理文件",
})
totalRows := 500
for i := 0; i < totalRows; i++ {
time.Sleep(10 * time.Millisecond)
if (i+1)%50 == 0 {
percentage := float64(i+1) / float64(totalRows) * 100
processor.Emit(EventProgress, EventData{
Downloaded: int64(i + 1),
Total: int64(totalRows),
Percentage: percentage,
Filename: "employees.csv",
Message: fmt.Sprintf("已处理 %d/%d 行", i+1, totalRows),
})
}
}
processor.Emit(EventComplete, EventData{
Downloaded: int64(totalRows),
Total: int64(totalRows),
Percentage: 100.0,
Filename: "employees.csv",
Message: "文件处理完成",
})
}()
// 监听处理进度
processor.On(EventStart, consoleLogger)
processor.On(EventProgress, func(data EventData) {
barWidth := 30
completed := int(float64(barWidth) * data.Percentage / 100)
remaining := barWidth - completed
bar := ""
for i := 0; i < completed; i++ {
bar += "▊"
}
for i := 0; i < remaining; i++ {
bar += "░"
}
fmt.Printf("\r处理: [%s] %.2f%%", bar, data.Percentage)
if data.Percentage >= 100 {
fmt.Println()
}
})
processor.On(EventComplete, consoleLogger)
// 等待所有演示完成
time.Sleep(6 * time.Second)
fmt.Println("\n✅ 所有演示完成!")
fmt.Println("生成的文件:")
fmt.Println(" - employees.csv (示例数据)")
fmt.Println(" - covid-data.csv (从网络下载的数据)")
}

124
proxy/dll/proxy.h Normal file
View File

@ -0,0 +1,124 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "proxy.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
#include <complex>
typedef std::complex<float> GoComplex64;
typedef std::complex<double> GoComplex128;
#endif
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
// 导出函数:获取代理健康状态(用于调试)
//
extern __declspec(dllexport) char* GetProxyHealth(void);
// 导出函数:代理类型管理器
//
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
// 导出函数:查询机器码
//
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
// 导出函数:充值卡密
//
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
// 导出函数:获取代理服务器列表
//
extern __declspec(dllexport) char* GetProxies(char* machineCode);
// 导出函数:检查卡密是否过期
//
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
// 导出函数:初始化代理管理器
//
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
// 导出函数释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

View File

@ -1,6 +1,8 @@
package main package main
// #include <stdlib.h> /*
#include <stdlib.h>
*/
import "C" import "C"
import ( import (
"crypto/md5" "crypto/md5"
@ -13,6 +15,7 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"unsafe"
) )
// 代理类型常量 // 代理类型常量
@ -322,6 +325,7 @@ func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
return false, fmt.Errorf("时间格式错误: %v", err) return false, fmt.Errorf("时间格式错误: %v", err)
} }
currentTime := time.Now() currentTime := time.Now()
// 卡密日期在当前日期之后
if targetTime.After(currentTime) { if targetTime.After(currentTime) {
return true, nil return true, nil
} else { } else {
@ -450,6 +454,8 @@ func initProxy() {
} }
// =================== C 导出函数 =======================
// 导出函数:获取代理健康状态(用于调试) // 导出函数:获取代理健康状态(用于调试)
// //
//export GetProxyHealth //export GetProxyHealth
@ -498,13 +504,194 @@ func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.cha
return C.CString(proxyURL) return C.CString(proxyURL)
} }
//// 导出函数释放C字符串内存 // 导出函数:查询机器码
////
////export FreeCString
//func FreeCString(str *C.char) {
// C.free(unsafe.Pointer(str))
//}
// //
//func main() { //export GetMachineCode
func GetMachineCode(tailCardSecret *C.char) *C.char {
goTailCardSecret := C.GoString(tailCardSecret)
log.Printf("[DEBUG] 查询机器码调用: card=%s", goTailCardSecret)
resp, err := getMachineCode(goTailCardSecret)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: %v", err)
log.Printf("[ERROR] 查询机器码错误: %v", err)
return C.CString(errorMsg)
}
// 将响应转换为JSON
jsonData, err := json.Marshal(resp)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
log.Printf("[ERROR] 序列化机器码响应失败: %v", err)
return C.CString(errorMsg)
}
log.Printf("[DEBUG] 查询机器码成功: code=%d, machine_code=%s", resp.Code, resp.Data.MachineCode)
return C.CString(string(jsonData))
}
// 导出函数:充值卡密
// //
//} //export RechargeCard
func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
goTailCardSecret := C.GoString(tailCardSecret)
goMachineCode := C.GoString(machineCode)
log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode)
newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: %v", err)
log.Printf("[ERROR] 充值卡密错误: %v", err)
return C.CString(errorMsg)
}
// 构建成功响应
response := map[string]interface{}{
"success": true,
"machine_code": newMachineCode,
"message": "充值成功",
}
jsonData, err := json.Marshal(response)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
log.Printf("[ERROR] 序列化充值响应失败: %v", err)
return C.CString(errorMsg)
}
log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode)
return C.CString(string(jsonData))
}
// 导出函数:获取代理服务器列表
//
//export GetProxies
func GetProxies(machineCode *C.char) *C.char {
goMachineCode := C.GoString(machineCode)
log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode)
proxies, err := getProxies(goMachineCode)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: %v", err)
log.Printf("[ERROR] 获取代理服务器列表错误: %v", err)
return C.CString(errorMsg)
}
// 将代理列表转换为JSON
response := map[string]interface{}{
"success": true,
"count": len(proxies),
"proxies": proxies,
}
jsonData, err := json.Marshal(response)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
log.Printf("[ERROR] 序列化代理列表失败: %v", err)
return C.CString(errorMsg)
}
log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies))
return C.CString(string(jsonData))
}
// 导出函数:检查卡密是否过期
//
//export CheckTailCardSecretExpired
func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
goTailCardSecret := C.GoString(tailCardSecret)
log.Printf("[DEBUG] 检查卡密是否过期调用: card=%s", goTailCardSecret)
isValid, err := checkTailCardSecretExpired(goTailCardSecret)
if err != nil {
// 如果是过期错误,返回特定格式
if strings.Contains(err.Error(), "卡密已经过期") {
response := map[string]interface{}{
"is_valid": false,
"message": err.Error(),
}
jsonData, _ := json.Marshal(response)
return C.CString(string(jsonData))
}
errorMsg := fmt.Sprintf("ERROR: %v", err)
log.Printf("[ERROR] 检查卡密过期错误: %v", err)
return C.CString(errorMsg)
}
response := map[string]interface{}{
"is_valid": isValid,
"message": "卡密有效",
}
jsonData, err := json.Marshal(response)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
return C.CString(errorMsg)
}
log.Printf("[DEBUG] 检查卡密过期结果: is_valid=%v", isValid)
return C.CString(string(jsonData))
}
// 导出函数:初始化代理管理器
//
//export InitProxyManager
func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
goServersJson := C.GoString(serversJson)
goUsername := C.GoString(username)
goPassword := C.GoString(password)
goTailCardSecret := C.GoString(tailCardSecret)
goProxyType := C.GoString(proxyType)
log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType)
// 解析服务器列表
var serverList []string
if goServersJson != "" {
if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil {
errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err)
log.Printf("[ERROR] 解析服务器列表失败: %v", err)
return C.CString(errorMsg)
}
}
// 更新全局服务器列表
if len(serverList) > 0 {
randMutex.Lock()
servers = serverList
randMutex.Unlock()
log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers))
}
// 创建代理管理器
manager := ProxyManager{
servers: servers,
username: goUsername,
password: goPassword,
tailCardSecret: goTailCardSecret,
proxyType: goProxyType,
}
// 序列化管理器信息
jsonData, err := json.Marshal(manager)
if err != nil {
errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err)
log.Printf("[ERROR] 序列化代理管理器失败: %v", err)
return C.CString(errorMsg)
}
log.Printf("[DEBUG] 代理管理器初始化成功")
return C.CString(string(jsonData))
}
// 导出函数释放C字符串内存
//
//export FreeCString
func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str))
}
// 空main函数编译DLL时需要
func main() {
}

699
proxy/proxy_so.go Normal file
View File

@ -0,0 +1,699 @@
package main
///*
//#cgo LDFLAGS: -ldl
//
//#include <dlfcn.h>
//#include <stdlib.h>
//#include <stdio.h>
//*/
//import "C"
//import (
// "crypto/md5"
// "encoding/json"
// "fmt"
// "github.com/parnurzeal/gorequest"
// "log"
// "math/rand"
// "net/url"
// "strings"
// "sync"
// "time"
// "unsafe"
//)
//
//// 代理类型常量
//const (
// CalfElephantProxyType = "CALF_ELEPHANT_PROXY"
// TailProxyType = "TAIL_PROXY"
//)
//
//// 代理服务器列表
//var (
// servers = []string{
// "http-dynamic.xiaoxiangdaili.com",
// "http-dynamic-S02.xiaoxiangdaili.com",
// "http-dynamic-S03.xiaoxiangdaili.com",
// "http-dynamic-S04.xiaoxiangdaili.com",
// }
// randMutex sync.Mutex
// globalRand *rand.Rand
//
// // 代理健康状态管理
// proxyHealthMaps = make(map[string]*ProxyHealth)
// proxyHealthMutex sync.RWMutex
//)
//
//// ProxyManager 代理管理器结构体
//type ProxyManager struct {
// servers []string `json:"servers"` // 代理服务器列表
// username string `json:"username"` // 代理账号
// password string `json:"password"` // 代理密码
// tailCardSecret string `json:"tail_card_secret"` // 尾巴代理卡密
// proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
//}
//
//// 代理健康状态
//type ProxyHealth struct {
// SuccessCount int // 成功次数
// FailCount int // 失败次数
// LastCheck time.Time // 最后检查时间
// ResponseTime time.Duration // 响应时间
// IsHealthy bool // 是否健康
//}
//
//func init() {
// // 创建全局的随机数生成器
// globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
//}
//
//// 获取代理URL
//func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
// switch proxyType {
// case CalfElephantProxyType:
// return buildCalfElephantProxyURL(username, password)
// case TailProxyType:
// return buildTailProxyURL(machineCode)
// default:
// return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
// }
//}
//
//// 构建小象代理URL
//func buildCalfElephantProxyURL(username, password string) (string, error) {
// server := randomServer()
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
// url.QueryEscape(username),
// url.QueryEscape(password),
// server,
// 10030)
//
// // 检测代理可用性
// if err := checkProxyHealth(proxyURL); err != nil {
// log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
// // 尝试下一个代理服务器
// return tryNextCalfElephantProxy(username, password, server)
// }
//
// log.Printf("[INFO] 使用小象代理: %s", server)
// return proxyURL, nil
//}
//
//// 尝试下一个小象代理服务器
//func tryNextCalfElephantProxy(username, password, failedServer string) (string, error) {
// // 创建服务器副本并排除失败的服务器
// availableServers := make([]string, 0)
// for _, server := range servers {
// if server != failedServer {
// availableServers = append(availableServers, server)
// }
// }
//
// if len(availableServers) == 0 {
// return "", fmt.Errorf("所有小象代理服务器都不可用")
// }
//
// // 随机尝试可用服务器
// for _, server := range shuffleServers(availableServers) {
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
// url.QueryEscape(username),
// url.QueryEscape(password),
// server,
// 10030)
//
// if err := checkProxyHealth(proxyURL); err == nil {
// log.Printf("[INFO] 切换到可用代理: %s", server)
// return proxyURL, nil
// }
// log.Printf("[WARN] 代理 %s 检测失败", server)
// }
//
// return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
//}
//
//// 构建内置代理URL
//func buildTailProxyURL(machineCode string) (string, error) {
// proxies, err := getProxies(machineCode)
// if err != nil {
// return "", err
// }
// if len(proxies) == 0 {
// return "", fmt.Errorf("未获取到有效代理")
// }
//
// // 过滤并选择健康的代理
// healthyProxies := filterHealthyProxies(proxies)
// if len(healthyProxies) > 0 {
// proxyURL := "http://" + randomElement(healthyProxies)
// log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
// return proxyURL, nil
// }
//
// // 如果没有健康代理,检测所有代理
// log.Printf("[INFO] 未找到健康代理,开始检测代理可用性...")
// return findWorkingTailProxy(proxies)
//}
//
//// 过滤健康代理
//func filterHealthyProxies(proxies []string) []string {
// proxyHealthMutex.RLock()
// defer proxyHealthMutex.RUnlock()
//
// var healthy []string
// for _, proxy := range proxies {
// if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
// // 检查是否在最近检查过5分钟内
// if time.Since(health.LastCheck) < 5*time.Minute {
// healthy = append(healthy, proxy)
// }
// }
// }
// return healthy
//}
//
//// 查找可用的尾巴代理
//func findWorkingTailProxy(proxies []string) (string, error) {
// // 打乱代理顺序
// shuffledProxies := shuffleServers(proxies)
//
// // 并发检测代理
// type proxyResult struct {
// proxy string
// err error
// }
//
// ch := make(chan proxyResult, len(shuffledProxies))
// var wg sync.WaitGroup
//
// // 限制并发数
// sem := make(chan struct{}, 5)
//
// for _, proxy := range shuffledProxies {
// wg.Add(1)
// go func(p string) {
// defer wg.Done()
// sem <- struct{}{}
// defer func() { <-sem }()
//
// proxyURL := "http://" + p
// err := checkProxyHealth(proxyURL)
// ch <- proxyResult{proxy: p, err: err}
// }(proxy)
// }
//
// wg.Wait()
// close(ch)
//
// // 收集结果
// var workingProxies []string
// for result := range ch {
// if result.err == nil {
// workingProxies = append(workingProxies, result.proxy)
// // 更新健康状态
// updateProxyHealth(result.proxy, true, 0)
// } else {
// updateProxyHealth(result.proxy, false, 0)
// }
// }
//
// if len(workingProxies) > 0 {
// selected := randomElement(workingProxies)
// log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
// return "http://" + selected, nil
// }
//
// return "", fmt.Errorf("所有尾巴代理都不可用")
//}
//
//// 检测代理健康状态
//func checkProxyHealth(proxyURL string) error {
// start := time.Now()
//
// req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
// resp, _, errs := req.Get("https://shop.kongfz.com/").End()
//
// responseTime := time.Since(start)
//
// if len(errs) > 0 {
// updateProxyHealth(proxyURL, false, responseTime)
// return fmt.Errorf("代理连接失败: %v", errs)
// }
//
// if resp.StatusCode != 200 {
// updateProxyHealth(proxyURL, false, responseTime)
// return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
// }
//
// updateProxyHealth(proxyURL, true, responseTime)
// log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
// return nil
//}
//
//// 更新代理健康状态
//func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
// proxyHealthMutex.Lock()
// defer proxyHealthMutex.Unlock()
//
// host := getProxyHost(proxyURL)
// if _, exists := proxyHealthMaps[host]; !exists {
// proxyHealthMaps[host] = &ProxyHealth{}
// }
//
// health := proxyHealthMaps[host]
// health.LastCheck = time.Now()
//
// if success {
// health.SuccessCount++
// health.FailCount = 0
// health.ResponseTime = responseTime
// health.IsHealthy = true
// } else {
// health.FailCount++
// health.SuccessCount = 0
// // 连续失败3次标记为不健康
// if health.FailCount >= 3 {
// health.IsHealthy = false
// }
// }
//}
//
//// 获取代理主机名
//func getProxyHost(proxyURL string) string {
// if strings.HasPrefix(proxyURL, "http://") {
// proxyURL = proxyURL[7:]
// }
// // 去除认证信息
// if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
// proxyURL = proxyURL[atIndex+1:]
// }
// // 去除端口
// if colonIndex := strings.Index(proxyURL, ":"); colonIndex != -1 {
// proxyURL = proxyURL[:colonIndex]
// }
// return proxyURL
//}
//
//// 随机代理服务器
//func randomServer() string {
// return randomElement(servers)
//}
//
//// 线程安全的随机元素选择
//func randomElement(slice []string) string {
// randMutex.Lock()
// defer randMutex.Unlock()
// return slice[globalRand.Intn(len(slice))]
//}
//
//// 打乱服务器顺序
//func shuffleServers(servers []string) []string {
// randMutex.Lock()
// defer randMutex.Unlock()
//
// shuffled := make([]string, len(servers))
// copy(shuffled, servers)
// globalRand.Shuffle(len(shuffled), func(i, j int) {
// shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
// })
// return shuffled
//}
//
//// 检查卡密是否过期
//func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
// code, err := getMachineCode(tailCardSecret)
// if err != nil {
// return false, fmt.Errorf("请求错误: %v", err)
// }
// targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime)
// if err != nil {
// return false, fmt.Errorf("时间格式错误: %v", err)
// }
// currentTime := time.Now()
// if targetTime.After(currentTime) {
// return true, nil
// } else {
// return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
// }
//}
//
//// 定义响应结构体
//type getMachineCodeResp struct {
// Code int `json:"code"`
// Message string `json:"message"`
// Data struct {
// MachineCode string `json:"machine_code"`
// IpExpTime string `json:"ip_exp_time"`
// IpThread int `json:"ip_thread"`
// IpCardCode string `json:"ip_card_code"`
// } `json:"data"`
//}
//
//// 查询机器码
//func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
// url := "http://114.66.2.223:7842/api/proxies/ip_show"
// data := map[string]interface{}{
// "ip_card_code": tailCardSecret,
// "agent_id": 9999,
// }
// _, body, errs := gorequest.New().Post(url).Send(data).End()
// if len(errs) > 0 {
// return nil, fmt.Errorf("查询机器码失败: %v", errs)
// }
// var resp getMachineCodeResp
// if err := json.Unmarshal([]byte(body), &resp); err != nil {
// return nil, fmt.Errorf("解析响应失败: %v", err)
// }
//
// if resp.Code == 201 {
// machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode)
// if err != nil {
// return nil, err
// }
// resp.Data.MachineCode = machineCode
// return &resp, nil
// } else if resp.Code == 200 {
// return &resp, nil
// } else {
// return nil, fmt.Errorf("查询机器码失败: %s", resp.Message)
// }
//}
//
//// 充值卡密
//func rechargeCard(tailCardSecret, machineCode string) (string, error) {
// url := "http://114.66.2.223:7842/api/proxies/ip_recharge"
// data := map[string]interface{}{
// "machine_code": machineCode,
// "ip_card_code": tailCardSecret,
// "agent_id": 9999,
// }
// _, body, errs := gorequest.New().Post(url).Send(data).End()
// if len(errs) > 0 {
// return "", fmt.Errorf("充值卡密失败: %v", errs)
// }
// var resp struct {
// Code int `json:"code"` // 状态码
// Message string `json:"message"` // 返回消息
// Data struct {
// IpExpTime string `json:"ip_exp_time"` // 过期时间
// IpThread int `json:"ip_thread"` // 线程
// MachineCode string `json:"machine_code"` // 机器码
// } `json:"data"`
// }
// if err := json.Unmarshal([]byte(body), &resp); err != nil {
// return "", fmt.Errorf("解析响应失败: %v", err)
// }
// if resp.Code != 200 {
// return "", fmt.Errorf("充值卡密失败: %s", resp.Message)
// }
// return resp.Data.MachineCode, nil
//}
//
//// 获取代理服务器列表
//func getProxies(machineCode string) ([]string, error) {
// log.Printf("[INFO] 开始获取代理列表: %s", machineCode)
// // 生成签名
// sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
//
// GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
// machineCode, sign)
//
// req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
// _, body, errs := req.End()
// if len(errs) > 0 {
// return nil, fmt.Errorf("获取代理失败: %v", errs)
// }
//
// // 检查是否为JSON错误响应
// if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
// // 尝试解析为JSON错误响应
// var errorResp struct {
// Code int `json:"code"`
// Message string `json:"message"`
// }
// if err := json.Unmarshal([]byte(body), &errorResp); err == nil {
// return nil, fmt.Errorf("获取代理失败: %s (错误码: %d)", errorResp.Message, errorResp.Code)
// }
// }
//
// // 解析响应
// lines := strings.Split(strings.TrimSpace(body), "\n")
// var proxies []string
// for _, line := range lines {
// line = strings.TrimSpace(line)
// if line != "" && !strings.HasPrefix(line, "{") {
// proxies = append(proxies, line)
// }
// }
// if len(proxies) == 0 {
// return nil, fmt.Errorf("未获取到有效代理")
// }
//
// log.Printf("[INFO] 获取到 %d 个代理", len(proxies))
// return proxies, nil
//}
//
//// 初始化代理信息
//func initProxy() {
//
//}
//
//// =================== C 导出函数 =======================
//
//// 导出函数:获取代理健康状态(用于调试)
////
////export GetProxyHealth
//func GetProxyHealth() *C.char {
// proxyHealthMutex.RLock()
// defer proxyHealthMutex.RUnlock()
//
// healthInfo := make(map[string]interface{})
// for proxy, health := range proxyHealthMaps {
// healthInfo[proxy] = map[string]interface{}{
// "success_count": health.SuccessCount,
// "fail_count": health.FailCount,
// "last_check": health.LastCheck.Format(time.RFC3339),
// "response_time": health.ResponseTime.String(),
// "is_healthy": health.IsHealthy,
// }
// }
//
// jsonData, err := json.Marshal(healthInfo)
// if err != nil {
// return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err))
// }
//
// return C.CString(string(jsonData))
//}
//
//// 导出函数:代理类型管理器
////
////export ProxyTypeManager
//func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char {
// goProxyType := C.GoString(proxyType)
// goUsername := C.GoString(username)
// goPassword := C.GoString(password)
// goMachineCode := C.GoString(machineCode)
//
// log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType)
//
// proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: %v", err)
// log.Printf("[ERROR] 代理类型管理器错误: %v", err)
// return C.CString(errorMsg)
// }
//
// log.Printf("[DEBUG] 代理类型管理器返回: %s", proxyURL)
// return C.CString(proxyURL)
//}
//
//// 导出函数:查询机器码
////
////export GetMachineCode
//func GetMachineCode(tailCardSecret *C.char) *C.char {
// goTailCardSecret := C.GoString(tailCardSecret)
// log.Printf("[DEBUG] 查询机器码调用: card=%s", goTailCardSecret)
//
// resp, err := getMachineCode(goTailCardSecret)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: %v", err)
// log.Printf("[ERROR] 查询机器码错误: %v", err)
// return C.CString(errorMsg)
// }
//
// // 将响应转换为JSON
// jsonData, err := json.Marshal(resp)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
// log.Printf("[ERROR] 序列化机器码响应失败: %v", err)
// return C.CString(errorMsg)
// }
//
// log.Printf("[DEBUG] 查询机器码成功: code=%d, machine_code=%s", resp.Code, resp.Data.MachineCode)
// return C.CString(string(jsonData))
//}
//
//// 导出函数:充值卡密
////
////export RechargeCard
//func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
// goTailCardSecret := C.GoString(tailCardSecret)
// goMachineCode := C.GoString(machineCode)
// log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode)
//
// newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: %v", err)
// log.Printf("[ERROR] 充值卡密错误: %v", err)
// return C.CString(errorMsg)
// }
//
// // 构建成功响应
// response := map[string]interface{}{
// "success": true,
// "machine_code": newMachineCode,
// "message": "充值成功",
// }
//
// jsonData, err := json.Marshal(response)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
// log.Printf("[ERROR] 序列化充值响应失败: %v", err)
// return C.CString(errorMsg)
// }
//
// log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode)
// return C.CString(string(jsonData))
//}
//
//// 导出函数:获取代理服务器列表
////
////export GetProxies
//func GetProxies(machineCode *C.char) *C.char {
// goMachineCode := C.GoString(machineCode)
// log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode)
//
// proxies, err := getProxies(goMachineCode)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: %v", err)
// log.Printf("[ERROR] 获取代理服务器列表错误: %v", err)
// return C.CString(errorMsg)
// }
//
// // 将代理列表转换为JSON
// response := map[string]interface{}{
// "success": true,
// "count": len(proxies),
// "proxies": proxies,
// }
//
// jsonData, err := json.Marshal(response)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
// log.Printf("[ERROR] 序列化代理列表失败: %v", err)
// return C.CString(errorMsg)
// }
//
// log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies))
// return C.CString(string(jsonData))
//}
//
//// 导出函数:检查卡密是否过期
////
////export CheckTailCardSecretExpired
//func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
// goTailCardSecret := C.GoString(tailCardSecret)
// log.Printf("[DEBUG] 检查卡密是否过期调用: card=%s", goTailCardSecret)
//
// isValid, err := checkTailCardSecretExpired(goTailCardSecret)
// if err != nil {
// // 如果是过期错误,返回特定格式
// if strings.Contains(err.Error(), "卡密已经过期") {
// response := map[string]interface{}{
// "is_valid": false,
// "message": err.Error(),
// }
// jsonData, _ := json.Marshal(response)
// return C.CString(string(jsonData))
// }
//
// errorMsg := fmt.Sprintf("ERROR: %v", err)
// log.Printf("[ERROR] 检查卡密过期错误: %v", err)
// return C.CString(errorMsg)
// }
//
// response := map[string]interface{}{
// "is_valid": isValid,
// "message": "卡密有效",
// }
//
// jsonData, err := json.Marshal(response)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
// return C.CString(errorMsg)
// }
//
// log.Printf("[DEBUG] 检查卡密过期结果: is_valid=%v", isValid)
// return C.CString(string(jsonData))
//}
//
//// 导出函数:初始化代理管理器
////
////export InitProxyManager
//func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
// goServersJson := C.GoString(serversJson)
// goUsername := C.GoString(username)
// goPassword := C.GoString(password)
// goTailCardSecret := C.GoString(tailCardSecret)
// goProxyType := C.GoString(proxyType)
//
// log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType)
//
// // 解析服务器列表
// var serverList []string
// if goServersJson != "" {
// if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil {
// errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err)
// log.Printf("[ERROR] 解析服务器列表失败: %v", err)
// return C.CString(errorMsg)
// }
// }
//
// // 更新全局服务器列表
// if len(serverList) > 0 {
// randMutex.Lock()
// servers = serverList
// randMutex.Unlock()
// log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers))
// }
//
// // 创建代理管理器
// manager := ProxyManager{
// servers: servers,
// username: goUsername,
// password: goPassword,
// tailCardSecret: goTailCardSecret,
// proxyType: goProxyType,
// }
//
// // 序列化管理器信息
// jsonData, err := json.Marshal(manager)
// if err != nil {
// errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err)
// log.Printf("[ERROR] 序列化代理管理器失败: %v", err)
// return C.CString(errorMsg)
// }
//
// log.Printf("[DEBUG] 代理管理器初始化成功")
// return C.CString(string(jsonData))
//}
//
//// 导出函数释放C字符串内存
////
////export FreeCString
//func FreeCString(str *C.char) {
// C.free(unsafe.Pointer(str))
//}
//
//func main() {
//}

BIN
so/kongfz.so Normal file

Binary file not shown.

2386
zjdydll.go

File diff suppressed because it is too large Load Diff