修改所有

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~
#*.dll
*.so
#*.so
*.dylib
# 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,7 +21,8 @@ extern const char *_GoStringPtr(_GoString_ s);
/* Start of preamble from import "C" comments. */
#line 3 "proxyConfig.go"
#line 3 "csv.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
@ -87,17 +88,14 @@ extern "C" {
#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);
// 导出函数释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
extern __declspec(dllexport) char* UpdateCSVRowSafe(long long int handleID, int rowNum, char* newRow);
#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的都非官方标准接口)
//
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的都非官方标准接口)
//

Binary file not shown.

View File

@ -21,7 +21,7 @@ extern const char *_GoStringPtr(_GoString_ s);
/* Start of preamble from import "C" comments. */
#line 3 "proxyConfig.go"
#line 3 "proxy.go"
#include <stdlib.h>
#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* 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);

995
dyso.go
View File

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

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 (
github.com/PuerkitoBio/goquery v1.10.3
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/nfnt/resize v0.0.0-20180221191011-83c6a9932646
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 (
@ -15,15 +20,26 @@ require (
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // 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/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/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/moul/http2curl v1.0.0 // 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/stretchr/testify v1.10.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.34.0 // indirect
github.com/tiendc/go-deepcopy v1.7.1 // indirect
github.com/xuri/efp v0.0.1 // 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/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/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/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/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/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
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/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
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/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
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/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
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/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
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.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
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=
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-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.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.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.8.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.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.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
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-20220722155255-886fb9371eb4/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.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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
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/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=
@ -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.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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/parnurzeal/gorequest"
)
// 获取商品模版
func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) {
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
itemId, time.Now().Unix())
log.Printf("请求URL: %s", url)
// 创建HTTP客户端
request := gorequest.New()
// 设置代理如果有提供代理URL
if proxy != "" {
request.Proxy(proxy)
log.Printf("使用代理: %s", proxy)
}
resp, body, errs := request.Get(url).
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Set("Referer", "https://seller.kongfz.com/").
Set("Origin", "https://seller.kongfz.com").
Timeout(30 * time.Second).
End()
if len(errs) > 0 {
return nil, fmt.Errorf("请求失败: %v", errs)
}
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status)
}
var data map[string]interface{}
if err := json.Unmarshal([]byte(body), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 检查API响应状态
if val, ok := data["status"].(float64); ok && val == 1 {
return data, nil
}
return nil, fmt.Errorf("API返回错误: %+v", data)
}
//import (
// "crypto/md5"
// "encoding/json"
// "fmt"
// "log"
// "math/rand"
// "net/http"
// "net/url"
// "regexp"
// "strconv"
// "strings"
// "sync"
// "time"
//
// "github.com/parnurzeal/gorequest"
//)
//
//// 获取商品模版
//func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) {
// if token == "" {
// return nil, fmt.Errorf("请先登录获取Token")
// }
//
// url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
// itemId, time.Now().Unix())
//
// log.Printf("请求URL: %s", url)
//
// // 创建HTTP客户端
// request := gorequest.New()
//
// // 设置代理如果有提供代理URL
// if proxy != "" {
// request.Proxy(proxy)
// log.Printf("使用代理: %s", proxy)
// }
//
// resp, body, errs := request.Get(url).
// Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
// Set("Accept", "application/json, text/plain, */*").
// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
// Set("Referer", "https://seller.kongfz.com/").
// Set("Origin", "https://seller.kongfz.com").
// Timeout(30 * time.Second).
// End()
//
// if len(errs) > 0 {
// return nil, fmt.Errorf("请求失败: %v", errs)
// }
//
// // 检查HTTP状态码
// if resp.StatusCode != http.StatusOK {
// return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status)
// }
//
// var data map[string]interface{}
// if err := json.Unmarshal([]byte(body), &data); err != nil {
// return nil, fmt.Errorf("解析JSON失败: %w", err)
// }
//
// // 检查API响应状态
// if val, ok := data["status"].(float64); ok && val == 1 {
// return data, nil
// }
//
// return nil, fmt.Errorf("API返回错误: %+v", data)
//}
//
//func main() {
// fmt.Println("=== 孔夫子商品模板API服务 ===")
//
// http.HandleFunc("/api/goodsTemplate", getGoodsTemplateHandler)
// http.HandleFunc("/api/outGetImageByIsbn", getOutGetImageByIsbnHandler)
//
// port := "8989"
// server := &http.Server{
// Addr: ":" + port,
@ -76,60 +85,782 @@ func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}
// fmt.Printf("服务器启动失败: %v\n", err)
// }
//}
// 获取商品模板接口
func getGoodsTemplateHandler(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")
itemId := r.URL.Query().Get("itemId")
proxy := r.URL.Query().Get("proxy")
// 验证必需参数
if token == "" || itemId == "" {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"error": "缺少必需参数: token 和 itemid",
"usage": map[string]string{
"token": "登录Token (PHPSESSID)",
"itemid": "商品ID",
"proxy": "代理地址 (可选)",
},
"example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
})
return
}
log.Printf("收到请求 - 商品ID: %s", itemId)
// 调用函数获取商品模板
result, err := outGetGoodsTplMsgLinux(token, itemId, proxy)
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,
})
}
//
//// 获取商品模板接口
//func getGoodsTemplateHandler(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")
// itemId := r.URL.Query().Get("itemId")
// proxy := r.URL.Query().Get("proxy")
//
// // 验证必需参数
// if token == "" || itemId == "" {
// w.WriteHeader(http.StatusBadRequest)
// json.NewEncoder(w).Encode(map[string]interface{}{
// "success": false,
// "error": "缺少必需参数: token 和 itemid",
// "usage": map[string]string{
// "token": "登录Token (PHPSESSID)",
// "itemid": "商品ID",
// "proxy": "代理地址 (可选)",
// },
// "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
// })
// return
// }
//
// log.Printf("收到请求 - 商品ID: %s", itemId)
//
// // 调用函数获取商品模板
// result, err := outGetGoodsTplMsgLinux(token, itemId, proxy)
// 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,
// })
//}
//
//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)
}
// 获取商品列表
func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime int,
endCreateTime int, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) {
// 获取商品列表-已登的店铺
func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime string,
endCreateTime string, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) {
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
@ -472,8 +472,8 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p
ItemSn string `json:"itemSn"`
PriceMin string `json:"priceMin"`
PriceMax string `json:"priceMax"`
StartCreateTime int `json:"startCreateTime"`
EndCreateTime int `json:"endCreateTime"`
StartCreateTime string `json:"startCreateTime"`
EndCreateTime string `json:"endCreateTime"`
RequestType string `json:"requestType,omitempty"`
IsItemSnEqual int `json:"isItemSnEqual,omitempty"`
Page int `json:"page,omitempty"`
@ -655,7 +655,6 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error)
// 获取图片URL(官图和拍图)带有店铺过滤
func outGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (map[string]string, error) {
fmt.Println("[DEBUG] 使用的ISBN: ", isbn)
if isLiveImage == 0 {
gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn)
request := gorequest.New()
@ -2873,6 +2872,7 @@ func updateAccountToken(id int64, token string) error {
return err
}
// =================== C 导出函数 =======================
// 登录(带有Out的都非官方标准接口)
//
//export OutLogin
@ -2980,15 +2980,15 @@ func OutGetGoodsTplMsg(token, itemId, proxy *C.char) *C.char {
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
//
//export OutGetGoodsListMsgFromSelfShop
func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime C.int,
endCreateTime C.int, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char {
func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char,
endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goItemSn := C.GoString(itemSn)
goPriceMin := C.GoString(priceMin)
goPriceMax := C.GoString(priceMax)
goStartCreateTime := int(startCreateTime)
goEndCreateTime := int(endCreateTime)
goStartCreateTime := C.GoString(startCreateTime)
goEndCreateTime := C.GoString(endCreateTime)
goRequestType := C.GoString(requestType)
goIsItemSnEqual := int(isItemSnEqual)
goPage := int(page)
@ -3054,7 +3054,7 @@ func OutDelGoodsFromSelfShop(token, proxy, itemId *C.char) *C.char {
return C.CString(string(jsonData))
}
// 新增商品(带有Out的都非官方标准接口)
// 新增商品-已登的店铺(带有Out的都非官方标准接口)
//
//export OutAddGoods
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))
}
// 初始化配置
//
//export Initialize
func Initialize(configJSON *C.char) *C.char {
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
// #include <stdlib.h>
/*
#include <stdlib.h>
*/
import "C"
import (
"crypto/md5"
@ -13,6 +15,7 @@ import (
"strings"
"sync"
"time"
"unsafe"
)
// 代理类型常量
@ -322,6 +325,7 @@ func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
return false, fmt.Errorf("时间格式错误: %v", err)
}
currentTime := time.Now()
// 卡密日期在当前日期之后
if targetTime.After(currentTime) {
return true, nil
} else {
@ -450,6 +454,8 @@ func initProxy() {
}
// =================== C 导出函数 =======================
// 导出函数:获取代理健康状态(用于调试)
//
//export GetProxyHealth
@ -498,13 +504,194 @@ func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.cha
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.

2294
zjdydll.go

File diff suppressed because it is too large Load Diff