修改所有
This commit is contained in:
parent
8a111054a6
commit
905da936db
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,7 +5,7 @@
|
|||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
#*.dll
|
#*.dll
|
||||||
*.so
|
#*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
|
|||||||
202
bookImage.go
202
bookImage.go
@ -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
|
|
||||||
}
|
|
||||||
299
bookNameError.go
299
bookNameError.go
@ -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
BIN
csv/csv.dll
Normal file
Binary file not shown.
1882
csv/csv.go
Normal file
1882
csv/csv.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -21,8 +21,9 @@ extern const char *_GoStringPtr(_GoString_ s);
|
|||||||
/* Start of preamble from import "C" comments. */
|
/* Start of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
#line 3 "proxyConfig.go"
|
#line 3 "csv.go"
|
||||||
#include <stdlib.h>
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#line 1 "cgo-generated-wrapper"
|
#line 1 "cgo-generated-wrapper"
|
||||||
|
|
||||||
@ -87,17 +88,14 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// 导出函数:获取代理健康状态(用于调试)
|
// ===================== C 函数导入 ==================
|
||||||
|
// OpenCSVFile 打开/创建CSV文件
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* GetProxyHealth(void);
|
extern __declspec(dllexport) char* OpenCSVFile(char* filename, char delimiter, int hasHeader);
|
||||||
|
|
||||||
// 导出函数:代理类型管理器
|
// UpdateCSVRowSafe 修改csv文件行数据
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
extern __declspec(dllexport) char* UpdateCSVRowSafe(long long int handleID, int rowNum, char* newRow);
|
||||||
|
|
||||||
// 导出函数:释放C字符串内存
|
|
||||||
//
|
|
||||||
extern __declspec(dllexport) void FreeCString(char* str);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
413
csv/csvDllTest.go
Normal file
413
csv/csvDllTest.go
Normal 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
1050
csv/csvTool.go
Normal file
File diff suppressed because it is too large
Load Diff
BIN
csv/dll/csv.dll
Normal file
BIN
csv/dll/csv.dll
Normal file
Binary file not shown.
139
csv/dll/csv.h
Normal file
139
csv/dll/csv.h
Normal 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
1225
csv/newcsv.go
Normal file
File diff suppressed because it is too large
Load Diff
8
csv/taskLog1.csv
Normal file
8
csv/taskLog1.csv
Normal 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"
|
||||||
|
11
csv/test.csv
Normal file
11
csv/test.csv
Normal 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
|
||||||
|
|
||||||
|
54
csv/test.go
Normal file
54
csv/test.go
Normal 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
BIN
dll/csv.dll
Normal file
Binary file not shown.
134
dll/csv.h
Normal file
134
dll/csv.h
Normal 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
BIN
dll/excel.dll
Normal file
Binary file not shown.
141
dll/excel.h
Normal file
141
dll/excel.h
Normal 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
|
||||||
BIN
dll/kongfz.dll
BIN
dll/kongfz.dll
Binary file not shown.
@ -106,7 +106,7 @@ extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* itemId,
|
|||||||
|
|
||||||
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
|
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, int startCreateTime, int endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
||||||
|
|
||||||
// 删除商品-已登的店铺(带有Out的都非官方标准接口)
|
// 删除商品-已登的店铺(带有Out的都非官方标准接口)
|
||||||
//
|
//
|
||||||
|
|||||||
BIN
dll/proxy.dll
BIN
dll/proxy.dll
Binary file not shown.
22
dll/proxy.h
22
dll/proxy.h
@ -21,7 +21,7 @@ extern const char *_GoStringPtr(_GoString_ s);
|
|||||||
/* Start of preamble from import "C" comments. */
|
/* Start of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
#line 3 "proxyConfig.go"
|
#line 3 "proxy.go"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#line 1 "cgo-generated-wrapper"
|
#line 1 "cgo-generated-wrapper"
|
||||||
@ -95,6 +95,26 @@ extern __declspec(dllexport) char* GetProxyHealth(void);
|
|||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
||||||
|
|
||||||
|
// 导出函数:查询机器码
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
|
||||||
|
|
||||||
|
// 导出函数:充值卡密
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
|
||||||
|
|
||||||
|
// 导出函数:获取代理服务器列表
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* GetProxies(char* machineCode);
|
||||||
|
|
||||||
|
// 导出函数:检查卡密是否过期
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
|
||||||
|
|
||||||
|
// 导出函数:初始化代理管理器
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
|
||||||
|
|
||||||
// 导出函数:释放C字符串内存
|
// 导出函数:释放C字符串内存
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) void FreeCString(char* str);
|
extern __declspec(dllexport) void FreeCString(char* str);
|
||||||
|
|||||||
995
dyso.go
995
dyso.go
@ -1,496 +1,503 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
/*
|
///*
|
||||||
#cgo linux LDFLAGS: -ldl
|
//#cgo linux LDFLAGS: -ldl
|
||||||
#cgo windows LDFLAGS: -lkernel32
|
//#cgo windows LDFLAGS: -lkernel32
|
||||||
|
//
|
||||||
#include <stdlib.h>
|
//#include <stdlib.h>
|
||||||
|
//
|
||||||
#ifdef _WIN32
|
//#ifdef _WIN32
|
||||||
#include <windows.h>
|
//#include <windows.h>
|
||||||
static void* dlopen(const char* filename, int flags) {
|
//static void* dlopen(const char* filename, int flags) {
|
||||||
return (void*)LoadLibraryA(filename);
|
// return (void*)LoadLibraryA(filename);
|
||||||
}
|
//}
|
||||||
static void dlclose(void* handle) {
|
//static void dlclose(void* handle) {
|
||||||
FreeLibrary((HMODULE)handle);
|
// FreeLibrary((HMODULE)handle);
|
||||||
}
|
//}
|
||||||
static void* dlsym(void* handle, const char* name) {
|
//static void* dlsym(void* handle, const char* name) {
|
||||||
return (void*)GetProcAddress((HMODULE)handle, name);
|
// return (void*)GetProcAddress((HMODULE)handle, name);
|
||||||
}
|
//}
|
||||||
static const char* dlerror() {
|
//static const char* dlerror() {
|
||||||
static char buf[256];
|
// static char buf[256];
|
||||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
// FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
NULL, GetLastError(), 0, buf, sizeof(buf), NULL);
|
// NULL, GetLastError(), 0, buf, sizeof(buf), NULL);
|
||||||
return buf;
|
// return buf;
|
||||||
}
|
//}
|
||||||
#define RTLD_LAZY 0
|
//#define RTLD_LAZY 0
|
||||||
#else
|
//#else
|
||||||
#include <dlfcn.h>
|
//#include <dlfcn.h>
|
||||||
#endif
|
//#endif
|
||||||
*/
|
//*/
|
||||||
import "C"
|
//import "C"
|
||||||
import (
|
//import (
|
||||||
"encoding/json"
|
// "encoding/json"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"log"
|
// "log"
|
||||||
"net/http"
|
// "net/http"
|
||||||
"os"
|
// "os"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"runtime"
|
// "runtime"
|
||||||
"unsafe"
|
// "unsafe"
|
||||||
)
|
//)
|
||||||
|
//
|
||||||
// 配置结构
|
//// 配置结构
|
||||||
type Configs struct {
|
//type Configs struct {
|
||||||
App struct {
|
// App struct {
|
||||||
MaxRetryTimes int `json:"max_retry_times"`
|
// MaxRetryTimes int `json:"max_retry_times"`
|
||||||
RateLimitDelay int `json:"rate_limit_delay"`
|
// RateLimitDelay int `json:"rate_limit_delay"`
|
||||||
Size int `json:"size"`
|
// Size int `json:"size"`
|
||||||
DefaultUserAgent string `json:"default_user_agent"`
|
// DefaultUserAgent string `json:"default_user_agent"`
|
||||||
} `json:"app"`
|
// } `json:"app"`
|
||||||
|
//
|
||||||
API struct {
|
// API struct {
|
||||||
LoginURL string `json:"login_url"`
|
// LoginURL string `json:"login_url"`
|
||||||
BookSearchURL string `json:"book_search_url"`
|
// BookSearchURL string `json:"book_search_url"`
|
||||||
ProductSearchURL string `json:"product_search_url"`
|
// ProductSearchURL string `json:"product_search_url"`
|
||||||
} `json:"api"`
|
// } `json:"api"`
|
||||||
|
//
|
||||||
Proxy struct {
|
// Proxy struct {
|
||||||
Servers string `json:"servers"`
|
// Servers string `json:"servers"`
|
||||||
Username string `json:"username"`
|
// Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
// Password string `json:"password"`
|
||||||
TailMachineCode string `json:"tail_machine_code"`
|
// TailMachineCode string `json:"tail_machine_code"`
|
||||||
TailCardKey string `json:"tail_card_key"`
|
// TailCardKey string `json:"tail_card_key"`
|
||||||
ProxyFilePath string `json:"proxy_file_path"`
|
// ProxyFilePath string `json:"proxy_file_path"`
|
||||||
} `json:"proxy"`
|
// } `json:"proxy"`
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// API响应结构
|
//// API响应结构
|
||||||
type APIResponseSO struct {
|
//type APIResponseSO struct {
|
||||||
Success bool `json:"success"`
|
// Success bool `json:"success"`
|
||||||
Message string `json:"message,omitempty"`
|
// Message string `json:"message,omitempty"`
|
||||||
GoodsNum string `json:"goods_num,omitempty"`
|
// GoodsNum string `json:"goods_num,omitempty"`
|
||||||
PNum string `json:"pnum,omitempty"`
|
// PNum string `json:"pnum,omitempty"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
// Data interface{} `json:"data,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
// Error string `json:"error,omitempty"`
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// APIResp 简化的响应结构体
|
//// APIResp 简化的响应结构体
|
||||||
type APIRespSO struct {
|
//type APIRespSO struct {
|
||||||
Success bool `json:"success"`
|
// Success bool `json:"success"`
|
||||||
Message string `json:"message"`
|
// Message string `json:"message"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
// Data interface{} `json:"data,omitempty"`
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
type SOManager struct {
|
//type SOManager struct {
|
||||||
handle unsafe.Pointer
|
// handle unsafe.Pointer
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func NewSOManager(soPath string) (*SOManager, error) {
|
//func NewSOManager(soPath string) (*SOManager, error) {
|
||||||
// 根据平台调整库文件扩展名
|
// // 根据平台调整库文件扩展名
|
||||||
if runtime.GOOS == "windows" {
|
// if runtime.GOOS == "windows" {
|
||||||
soPath = changeExtensionToDLL(soPath)
|
// soPath = changeExtensionToDLL(soPath)
|
||||||
}
|
// }
|
||||||
// 使用 dlopen 加载 .so 文件
|
// // 使用 dlopen 加载 .so 文件
|
||||||
cSoPath := C.CString(soPath)
|
// cSoPath := C.CString(soPath)
|
||||||
defer C.free(unsafe.Pointer(cSoPath))
|
// defer C.free(unsafe.Pointer(cSoPath))
|
||||||
|
//
|
||||||
handle := C.dlopen(cSoPath, C.RTLD_LAZY)
|
// handle := C.dlopen(cSoPath, C.RTLD_LAZY)
|
||||||
if handle == nil {
|
// if handle == nil {
|
||||||
return nil, fmt.Errorf("加载SO文件失败: %s", C.GoString(C.dlerror()))
|
// return nil, fmt.Errorf("加载SO文件失败: %s", C.GoString(C.dlerror()))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return &SOManager{handle: handle}, nil
|
// return &SOManager{handle: handle}, nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func (m *SOManager) Close() {
|
//func (m *SOManager) Close() {
|
||||||
if m.handle != nil {
|
// if m.handle != nil {
|
||||||
C.dlclose(m.handle)
|
// C.dlclose(m.handle)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func changeExtensionToDLL(path string) string {
|
//func changeExtensionToDLL(path string) string {
|
||||||
ext := filepath.Ext(path)
|
// ext := filepath.Ext(path)
|
||||||
if ext == ".so" {
|
// if ext == ".so" {
|
||||||
return path[:len(path)-len(ext)] + ".dll"
|
// return path[:len(path)-len(ext)] + ".dll"
|
||||||
}
|
// }
|
||||||
return path
|
// return path
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取函数指针
|
//// 获取函数指针
|
||||||
func (m *SOManager) getFunction(funcName string) (unsafe.Pointer, error) {
|
//func (m *SOManager) getFunction(funcName string) (unsafe.Pointer, error) {
|
||||||
cFuncName := C.CString(funcName)
|
// cFuncName := C.CString(funcName)
|
||||||
defer C.free(unsafe.Pointer(cFuncName))
|
// defer C.free(unsafe.Pointer(cFuncName))
|
||||||
|
//
|
||||||
symbol := C.dlsym(m.handle, cFuncName)
|
// symbol := C.dlsym(m.handle, cFuncName)
|
||||||
if symbol == nil {
|
// if symbol == nil {
|
||||||
return nil, fmt.Errorf("找不到函数 %s: %s", funcName, C.GoString(C.dlerror()))
|
// return nil, fmt.Errorf("找不到函数 %s: %s", funcName, C.GoString(C.dlerror()))
|
||||||
}
|
// }
|
||||||
return symbol, nil
|
// return symbol, nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 初始化
|
//// 初始化
|
||||||
func (m *SOManager) Initialize(configJSON string) (string, error) {
|
//func (m *SOManager) Initialize(configJSON string) (string, error) {
|
||||||
function, err := m.getFunction("Initialize")
|
// function, err := m.getFunction("Initialize")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
configJSONC := C.CString(configJSON)
|
// configJSONC := C.CString(configJSON)
|
||||||
defer C.free(unsafe.Pointer(configJSONC))
|
// defer C.free(unsafe.Pointer(configJSONC))
|
||||||
result := (*funcPtr)(configJSONC)
|
// result := (*funcPtr)(configJSONC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 登录
|
//// 登录
|
||||||
func (m *SOManager) OutLogin(username, password string) (string, error) {
|
//func (m *SOManager) OutLogin(username, password string) (string, error) {
|
||||||
function, err := m.getFunction("OutLogin")
|
// function, err := m.getFunction("OutLogin")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
usernameC := C.CString(username)
|
// usernameC := C.CString(username)
|
||||||
passwordC := C.CString(password)
|
// passwordC := C.CString(password)
|
||||||
defer C.free(unsafe.Pointer(usernameC))
|
// defer C.free(unsafe.Pointer(usernameC))
|
||||||
defer C.free(unsafe.Pointer(passwordC))
|
// defer C.free(unsafe.Pointer(passwordC))
|
||||||
result := (*funcPtr)(usernameC, passwordC)
|
// result := (*funcPtr)(usernameC, passwordC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取用户信息
|
//// 获取用户信息
|
||||||
func (m *SOManager) OutGetUserMsg(token string) (string, error) {
|
//func (m *SOManager) OutGetUserMsg(token string) (string, error) {
|
||||||
function, err := m.getFunction("OutGetUserMsg")
|
// function, err := m.getFunction("OutGetUserMsg")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
result := (*funcPtr)(tokenC)
|
// result := (*funcPtr)(tokenC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取商品模版
|
//// 获取商品模版
|
||||||
func (m *SOManager) OutGetGoodsTplMsg(token, itemId, proxy string) (string, error) {
|
//func (m *SOManager) OutGetGoodsTplMsg(token, itemId, proxy string) (string, error) {
|
||||||
function, err := m.getFunction("OutGetGoodsTplMsg")
|
// function, err := m.getFunction("OutGetGoodsTplMsg")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
itemIdC := C.CString(itemId)
|
// itemIdC := C.CString(itemId)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
defer C.free(unsafe.Pointer(itemIdC))
|
// defer C.free(unsafe.Pointer(itemIdC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
result := (*funcPtr)(tokenC, itemIdC, proxyC)
|
// result := (*funcPtr)(tokenC, itemIdC, proxyC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取商品列表-已登的店铺
|
//// 获取商品列表-已登的店铺
|
||||||
func (m *SOManager) OutGetGoodsListMsgFromSelfShop(token string, proxy string,
|
//func (m *SOManager) OutGetGoodsListMsgFromSelfShop(token string, proxy string,
|
||||||
itemSn string, priceMin string, priceMax string, startCreateTime int,
|
// itemSn string, priceMin string, priceMax string, startCreateTime int,
|
||||||
endCreateTime int, requestType string, isItemSnEqual int, page int, size int) (string, error) {
|
// endCreateTime int, requestType string, isItemSnEqual int, page int, size int) (string, error) {
|
||||||
function, err := m.getFunction("OutGetGoodsListMsgFromSelfShop")
|
// function, err := m.getFunction("OutGetGoodsListMsgFromSelfShop")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char, *C.char, *C.char, *C.char, C.int, C.int, *C.char, C.int, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char, *C.char, *C.char, *C.char, C.int, C.int, *C.char, C.int, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
itemSnC := C.CString(itemSn)
|
// itemSnC := C.CString(itemSn)
|
||||||
priceMinC := C.CString(priceMin)
|
// priceMinC := C.CString(priceMin)
|
||||||
priceMaxC := C.CString(priceMax)
|
// priceMaxC := C.CString(priceMax)
|
||||||
startCreateTimeC := C.int(startCreateTime)
|
// startCreateTimeC := C.int(startCreateTime)
|
||||||
endCreateTimeC := C.int(endCreateTime)
|
// endCreateTimeC := C.int(endCreateTime)
|
||||||
requestTypeC := C.CString(requestType)
|
// requestTypeC := C.CString(requestType)
|
||||||
isItemSnEqualC := C.int(isItemSnEqual)
|
// isItemSnEqualC := C.int(isItemSnEqual)
|
||||||
pageC := C.int(page)
|
// pageC := C.int(page)
|
||||||
sizeC := C.int(size)
|
// sizeC := C.int(size)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
defer C.free(unsafe.Pointer(itemSnC))
|
// defer C.free(unsafe.Pointer(itemSnC))
|
||||||
defer C.free(unsafe.Pointer(priceMinC))
|
// defer C.free(unsafe.Pointer(priceMinC))
|
||||||
defer C.free(unsafe.Pointer(priceMaxC))
|
// defer C.free(unsafe.Pointer(priceMaxC))
|
||||||
defer C.free(unsafe.Pointer(requestTypeC))
|
// defer C.free(unsafe.Pointer(requestTypeC))
|
||||||
result := (*funcPtr)(tokenC, proxyC, itemSnC, priceMinC, priceMaxC, startCreateTimeC,
|
// result := (*funcPtr)(tokenC, proxyC, itemSnC, priceMinC, priceMaxC, startCreateTimeC,
|
||||||
endCreateTimeC, requestTypeC, isItemSnEqualC, pageC, sizeC)
|
// endCreateTimeC, requestTypeC, isItemSnEqualC, pageC, sizeC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 删除商品-已登的店铺
|
//// 删除商品-已登的店铺
|
||||||
func (m *SOManager) OutDelGoodsFromSelfShop(token, itemId, proxy string) (string, error) {
|
//func (m *SOManager) OutDelGoodsFromSelfShop(token, itemId, proxy string) (string, error) {
|
||||||
function, err := m.getFunction("OutDelGoodsFromSelfShop")
|
// function, err := m.getFunction("OutDelGoodsFromSelfShop")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
itemIdC := C.CString(itemId)
|
// itemIdC := C.CString(itemId)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
defer C.free(unsafe.Pointer(itemIdC))
|
// defer C.free(unsafe.Pointer(itemIdC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
result := (*funcPtr)(tokenC, itemIdC, proxyC)
|
// result := (*funcPtr)(tokenC, itemIdC, proxyC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 新增商品
|
//// 新增商品
|
||||||
func (m *SOManager) OutAddGoods(token, proxy, formData string) (string, error) {
|
//func (m *SOManager) OutAddGoods(token, proxy, formData string) (string, error) {
|
||||||
function, err := m.getFunction("OutAddGoods")
|
// function, err := m.getFunction("OutAddGoods")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
formDataC := C.CString(formData)
|
// formDataC := C.CString(formData)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
defer C.free(unsafe.Pointer(formDataC))
|
// defer C.free(unsafe.Pointer(formDataC))
|
||||||
result := (*funcPtr)(tokenC, proxyC, formDataC)
|
// result := (*funcPtr)(tokenC, proxyC, formDataC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取图片URL(官图和拍图)带有店铺过滤
|
//// 获取图片URL(官图和拍图)带有店铺过滤
|
||||||
func (m *SOManager) OutGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (string, error) {
|
//func (m *SOManager) OutGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (string, error) {
|
||||||
function, err := m.getFunction("OutGetImageFilterShopId")
|
// function, err := m.getFunction("OutGetImageFilterShopId")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char, C.int, *C.char, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char, C.int, *C.char, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
isbnC := C.CString(isbn)
|
// isbnC := C.CString(isbn)
|
||||||
shopIdC := C.int(shopId)
|
// shopIdC := C.int(shopId)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
isLiveImageC := C.int(isLiveImage)
|
// isLiveImageC := C.int(isLiveImage)
|
||||||
isReturnMsgC := C.int(isReturnMsg)
|
// isReturnMsgC := C.int(isReturnMsg)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
defer C.free(unsafe.Pointer(isbnC))
|
// defer C.free(unsafe.Pointer(isbnC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
result := (*funcPtr)(tokenC, isbnC, shopIdC, proxyC, isLiveImageC, isReturnMsgC)
|
// result := (*funcPtr)(tokenC, isbnC, shopIdC, proxyC, isLiveImageC, isReturnMsgC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取商品图片
|
//// 获取商品图片
|
||||||
func (m *SOManager) OutGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (string, error) {
|
//func (m *SOManager) OutGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (string, error) {
|
||||||
function, err := m.getFunction("OutGetImageByIsbn")
|
// function, err := m.getFunction("OutGetImageByIsbn")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char, *C.char, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char, *C.char, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
||||||
tokenC := C.CString(token)
|
// tokenC := C.CString(token)
|
||||||
isbnC := C.CString(isbn)
|
// isbnC := C.CString(isbn)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
isLiveImageC := C.int(isLiveImage)
|
// isLiveImageC := C.int(isLiveImage)
|
||||||
isReturnMsgC := C.int(isReturnMsg)
|
// isReturnMsgC := C.int(isReturnMsg)
|
||||||
defer C.free(unsafe.Pointer(tokenC))
|
// defer C.free(unsafe.Pointer(tokenC))
|
||||||
defer C.free(unsafe.Pointer(isbnC))
|
// defer C.free(unsafe.Pointer(isbnC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
result := (*funcPtr)(tokenC, isbnC, proxyC, isLiveImageC, isReturnMsgC)
|
// result := (*funcPtr)(tokenC, isbnC, proxyC, isLiveImageC, isReturnMsgC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取商品列表通过店铺ID
|
//// 获取商品列表通过店铺ID
|
||||||
func (m *SOManager) OutGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage int, sortType string,
|
//func (m *SOManager) OutGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage int, sortType string,
|
||||||
sort string, priceMin float32, priceMax float32, pageNum, returnNum int) (string, error) {
|
// sort string, priceMin float32, priceMax float32, pageNum, returnNum int) (string, error) {
|
||||||
function, err := m.getFunction("OutGetGoodsListMsgByShopId")
|
// function, err := m.getFunction("OutGetGoodsListMsgByShopId")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(C.int, *C.char, C.int, C.int, *C.char, *C.char, C.double, C.double, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(C.int, *C.char, C.int, C.int, *C.char, *C.char, C.double, C.double, C.int, C.int) *C.char)(unsafe.Pointer(&function))
|
||||||
shopIdC := C.int(shopId)
|
// shopIdC := C.int(shopId)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
retPriceC := C.int(retPrice)
|
// retPriceC := C.int(retPrice)
|
||||||
isImageC := C.int(isImage)
|
// isImageC := C.int(isImage)
|
||||||
sortTypeC := C.CString(sortType)
|
// sortTypeC := C.CString(sortType)
|
||||||
sortC := C.CString(sort)
|
// sortC := C.CString(sort)
|
||||||
priceMinC := C.double(priceMin)
|
// priceMinC := C.double(priceMin)
|
||||||
priceMaxC := C.double(priceMax)
|
// priceMaxC := C.double(priceMax)
|
||||||
pageNumC := C.int(pageNum)
|
// pageNumC := C.int(pageNum)
|
||||||
returnNumC := C.int(returnNum)
|
// returnNumC := C.int(returnNum)
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
defer C.free(unsafe.Pointer(sortTypeC))
|
// defer C.free(unsafe.Pointer(sortTypeC))
|
||||||
defer C.free(unsafe.Pointer(sortC))
|
// defer C.free(unsafe.Pointer(sortC))
|
||||||
result := (*funcPtr)(shopIdC, proxyC, retPriceC, isImageC, sortTypeC, sortC, priceMinC, priceMaxC, pageNumC, returnNumC)
|
// result := (*funcPtr)(shopIdC, proxyC, retPriceC, isImageC, sortTypeC, sortC, priceMinC, priceMaxC, pageNumC, returnNumC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取商品信息通过商品详情链接
|
//// 获取商品信息通过商品详情链接
|
||||||
func (m *SOManager) OutGetGoodsMsgByDetailUrl(detailUrl, proxy string) (string, error) {
|
//func (m *SOManager) OutGetGoodsMsgByDetailUrl(detailUrl, proxy string) (string, error) {
|
||||||
function, err := m.getFunction("OutGetGoodsMsgByDetailUrl")
|
// function, err := m.getFunction("OutGetGoodsMsgByDetailUrl")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(*C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(*C.char, *C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
detailUrlC := C.CString(detailUrl)
|
// detailUrlC := C.CString(detailUrl)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
defer C.free(unsafe.Pointer(detailUrlC))
|
// defer C.free(unsafe.Pointer(detailUrlC))
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
result := (*funcPtr)(detailUrlC, proxyC)
|
// result := (*funcPtr)(detailUrlC, proxyC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取销量榜商品列表
|
//// 获取销量榜商品列表
|
||||||
func (m *SOManager) OutGetTopGoodsListMsg(catId int, proxy string) (string, error) {
|
//func (m *SOManager) OutGetTopGoodsListMsg(catId int, proxy string) (string, error) {
|
||||||
function, err := m.getFunction("OutGetTopGoodsListMsg")
|
// function, err := m.getFunction("OutGetTopGoodsListMsg")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
funcPtr := (*func(C.int, *C.char) *C.char)(unsafe.Pointer(&function))
|
// funcPtr := (*func(C.int, *C.char) *C.char)(unsafe.Pointer(&function))
|
||||||
catIdC := C.int(catId)
|
// catIdC := C.int(catId)
|
||||||
proxyC := C.CString(proxy)
|
// proxyC := C.CString(proxy)
|
||||||
defer C.free(unsafe.Pointer(proxyC))
|
// defer C.free(unsafe.Pointer(proxyC))
|
||||||
result := (*funcPtr)(catIdC, proxyC)
|
// result := (*funcPtr)(catIdC, proxyC)
|
||||||
defer C.free(unsafe.Pointer(result))
|
// defer C.free(unsafe.Pointer(result))
|
||||||
return C.GoString(result), nil
|
// return C.GoString(result), nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 创建默认配置
|
//// 创建默认配置
|
||||||
func createDefaultConfig() Configs {
|
//func createDefaultConfig() Configs {
|
||||||
var configs Configs
|
// var configs Configs
|
||||||
// App配置
|
// // App配置
|
||||||
configs.App.MaxRetryTimes = 3
|
// configs.App.MaxRetryTimes = 3
|
||||||
configs.App.RateLimitDelay = 1000
|
// configs.App.RateLimitDelay = 1000
|
||||||
configs.App.Size = 10
|
// configs.App.Size = 10
|
||||||
configs.App.DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
// configs.App.DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||||
// API配置
|
// // API配置
|
||||||
configs.API.LoginURL = "https://login.kongfz.com/Pc/Login/account"
|
// configs.API.LoginURL = "https://login.kongfz.com/Pc/Login/account"
|
||||||
configs.API.BookSearchURL = "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list"
|
// configs.API.BookSearchURL = "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list"
|
||||||
configs.API.ProductSearchURL = "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list"
|
// configs.API.ProductSearchURL = "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list"
|
||||||
// 代理配置
|
// // 代理配置
|
||||||
configs.Proxy.Servers = "http-dynamic.xiaoxiangdaili.com,http-dynamic-S02.xiaoxiangdaili.com,http-dynamic-S03.xiaoxiangdaili.com,http-dynamic-S04.xiaoxiangdaili.com"
|
// configs.Proxy.Servers = "http-dynamic.xiaoxiangdaili.com,http-dynamic-S02.xiaoxiangdaili.com,http-dynamic-S03.xiaoxiangdaili.com,http-dynamic-S04.xiaoxiangdaili.com"
|
||||||
configs.Proxy.Username = "1297757178467602432"
|
// configs.Proxy.Username = "1297757178467602432"
|
||||||
configs.Proxy.Password = "QgQBvP7f"
|
// configs.Proxy.Password = "QgQBvP7f"
|
||||||
configs.Proxy.TailMachineCode = "b7bf22a237ec692f13fcc2c43ee63252"
|
// configs.Proxy.TailMachineCode = "b7bf22a237ec692f13fcc2c43ee63252"
|
||||||
configs.Proxy.TailCardKey = "DL_20_YK_1920acb2129844c2aabade3896560a9b"
|
// configs.Proxy.TailCardKey = "DL_20_YK_1920acb2129844c2aabade3896560a9b"
|
||||||
configs.Proxy.ProxyFilePath = "so/proxyConfig.so"
|
// configs.Proxy.ProxyFilePath = "so/proxyConfig.so"
|
||||||
return configs
|
// return configs
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 获取当前可执行文件所在目录
|
//// 获取当前可执行文件所在目录
|
||||||
func getExecutableDir() string {
|
//func getExecutableDir() string {
|
||||||
exePath, err := os.Executable()
|
// exePath, err := os.Executable()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "."
|
// return "."
|
||||||
}
|
// }
|
||||||
return filepath.Dir(exePath)
|
// return filepath.Dir(exePath)
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func main() {
|
//func main() {
|
||||||
http.HandleFunc("/api/kfzShopBookInfo", handleGetKFZShopBookInfo)
|
// fmt.Println("ccc")
|
||||||
|
// http.HandleFunc("/api/kfzShopBookInfo", handleGetKFZShopBookInfo)
|
||||||
port := "8080"
|
//
|
||||||
log.Printf("🚀 服务器启动在端口 %s", port)
|
// port := "8080"
|
||||||
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
// log.Printf("🚀 服务器启动在端口 %s", port)
|
||||||
log.Fatalf("服务器启动失败: %v", err)
|
// if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||||||
}
|
// log.Fatalf("服务器启动失败: %v", err)
|
||||||
}
|
// }
|
||||||
|
//}
|
||||||
func handleGetKFZShopBookInfo(w http.ResponseWriter, r *http.Request) {
|
//
|
||||||
// 设置响应头
|
//func handleGetKFZShopBookInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
// // 设置响应头
|
||||||
|
// w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
// 只支持GET请求
|
//
|
||||||
if r.Method != http.MethodGet {
|
// // 只支持GET请求
|
||||||
sendErrorResponse(w, http.StatusMethodNotAllowed, "只支持GET方法")
|
// if r.Method != http.MethodGet {
|
||||||
return
|
// sendErrorResponse(w, http.StatusMethodNotAllowed, "只支持GET方法")
|
||||||
}
|
// return
|
||||||
|
// }
|
||||||
// 加载SO
|
// soPath := filepath.Join("so", "kongfz.so")
|
||||||
manager, err := NewSOManager("so/kongfz.so")
|
// if _, err := os.Stat(soPath); os.IsNotExist(err) {
|
||||||
if err != nil {
|
// log.Printf("❌ SO文件不存在: %s", soPath)
|
||||||
log.Printf("初始化SO管理器失败: %v", err)
|
// sendErrorResponse(w, http.StatusInternalServerError,
|
||||||
sendErrorResponse(w, http.StatusInternalServerError, "初始化SO管理器失败")
|
// fmt.Sprintf("SO文件不存在: %s", soPath))
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
defer manager.Close()
|
// // 加载SO
|
||||||
log.Println("✅ SO加载成功")
|
// manager, err := NewSOManager(soPath)
|
||||||
// 使用默认配置初始化
|
// if err != nil {
|
||||||
config := createDefaultConfig()
|
// log.Printf("初始化SO管理器失败: %v", err)
|
||||||
configJSON, err := json.Marshal(config)
|
// sendErrorResponse(w, http.StatusInternalServerError, "初始化SO管理器失败")
|
||||||
if err != nil {
|
// return
|
||||||
log.Printf("序列化配置失败: %v", err)
|
// }
|
||||||
sendErrorResponse(w, http.StatusInternalServerError, "序列化配置失败")
|
// defer manager.Close()
|
||||||
return
|
// log.Println("✅ SO加载成功")
|
||||||
}
|
// // 使用默认配置初始化
|
||||||
result, err := manager.Initialize(string(configJSON))
|
// config := createDefaultConfig()
|
||||||
if err != nil {
|
// configJSON, err := json.Marshal(config)
|
||||||
log.Printf("初始化失败: %v", err)
|
// if err != nil {
|
||||||
sendErrorResponse(w, http.StatusInternalServerError, "初始化失败")
|
// log.Printf("序列化配置失败: %v", err)
|
||||||
return
|
// sendErrorResponse(w, http.StatusInternalServerError, "序列化配置失败")
|
||||||
}
|
// return
|
||||||
var initResp APIResponseSO
|
// }
|
||||||
if err := json.Unmarshal([]byte(result), &initResp); err != nil {
|
// result, err := manager.Initialize(string(configJSON))
|
||||||
log.Printf("解析初始化响应失败: %v", err)
|
// if err != nil {
|
||||||
sendErrorResponse(w, http.StatusInternalServerError, "解析初始化响应失败")
|
// log.Printf("初始化失败: %v", err)
|
||||||
return
|
// sendErrorResponse(w, http.StatusInternalServerError, "初始化失败")
|
||||||
}
|
// return
|
||||||
if !initResp.Success {
|
// }
|
||||||
log.Printf("初始化失败: %s", initResp.Message)
|
// var initResp APIResponseSO
|
||||||
sendErrorResponse(w, http.StatusInternalServerError, "初始化失败: "+initResp.Message)
|
// if err := json.Unmarshal([]byte(result), &initResp); err != nil {
|
||||||
return
|
// log.Printf("解析初始化响应失败: %v", err)
|
||||||
}
|
// sendErrorResponse(w, http.StatusInternalServerError, "解析初始化响应失败")
|
||||||
log.Println("✅ SO初始化成功")
|
// return
|
||||||
|
// }
|
||||||
// 登录示例
|
// if !initResp.Success {
|
||||||
user, err := manager.OutLogin("18904056801", "Long6166@@")
|
// log.Printf("初始化失败: %s", initResp.Message)
|
||||||
if err != nil {
|
// sendErrorResponse(w, http.StatusInternalServerError, "初始化失败: "+initResp.Message)
|
||||||
log.Printf("登录失败: %v", err)
|
// return
|
||||||
} else {
|
// }
|
||||||
log.Printf("登录结果: %s", user)
|
// log.Println("✅ SO初始化成功")
|
||||||
}
|
//
|
||||||
|
// // 登录示例
|
||||||
// 解析token
|
// user, err := manager.OutLogin("18904056801", "Long6166@@")
|
||||||
var data APIResponseSO
|
// if err != nil {
|
||||||
if err := json.Unmarshal([]byte(user), &data); err != nil {
|
// log.Printf("登录失败: %v", err)
|
||||||
log.Printf("解析登录响应失败: %v", err)
|
// } else {
|
||||||
} else {
|
// log.Printf("登录结果: %s", user)
|
||||||
var token string
|
// }
|
||||||
if dataMap, ok := data.Data.(map[string]interface{}); ok {
|
//
|
||||||
if tk, exists := dataMap["token"]; exists {
|
// // 解析token
|
||||||
token = tk.(string)
|
// var data APIResponseSO
|
||||||
log.Printf("获取到Token: %s", token)
|
// if err := json.Unmarshal([]byte(user), &data); err != nil {
|
||||||
|
// log.Printf("解析登录响应失败: %v", err)
|
||||||
// 使用token获取用户信息
|
// } else {
|
||||||
msg, err := manager.OutGetUserMsg(token)
|
// var token string
|
||||||
if err != nil {
|
// if dataMap, ok := data.Data.(map[string]interface{}); ok {
|
||||||
log.Printf("获取用户信息失败: %v", err)
|
// if tk, exists := dataMap["token"]; exists {
|
||||||
} else {
|
// token = tk.(string)
|
||||||
log.Printf("用户信息: %s", msg)
|
// log.Printf("获取到Token: %s", token)
|
||||||
}
|
//
|
||||||
} else {
|
// // 使用token获取用户信息
|
||||||
log.Println("Token 不存在")
|
// msg, err := manager.OutGetUserMsg(token)
|
||||||
}
|
// if err != nil {
|
||||||
} else {
|
// log.Printf("获取用户信息失败: %v", err)
|
||||||
log.Println("Data 格式不正确")
|
// } else {
|
||||||
}
|
// log.Printf("用户信息: %s", msg)
|
||||||
}
|
// }
|
||||||
// 返回成功响应
|
// } else {
|
||||||
response := APIRespSO{
|
// log.Println("Token 不存在")
|
||||||
Success: true,
|
// }
|
||||||
Message: "SO调用成功",
|
// } else {
|
||||||
}
|
// log.Println("Data 格式不正确")
|
||||||
json.NewEncoder(w).Encode(response)
|
// }
|
||||||
}
|
// }
|
||||||
|
// // 返回成功响应
|
||||||
// 发送错误响应
|
// response := APIRespSO{
|
||||||
func sendErrorResponse(w http.ResponseWriter, statusCode int, message string) {
|
// Success: true,
|
||||||
response := APIRespSO{
|
// Message: "SO调用成功",
|
||||||
Success: false,
|
// }
|
||||||
Message: message,
|
// json.NewEncoder(w).Encode(response)
|
||||||
}
|
//}
|
||||||
w.WriteHeader(statusCode)
|
//
|
||||||
json.NewEncoder(w).Encode(response)
|
//// 发送错误响应
|
||||||
}
|
//func sendErrorResponse(w http.ResponseWriter, statusCode int, message string) {
|
||||||
|
// response := APIRespSO{
|
||||||
|
// Success: false,
|
||||||
|
// Message: message,
|
||||||
|
// }
|
||||||
|
// w.WriteHeader(statusCode)
|
||||||
|
// json.NewEncoder(w).Encode(response)
|
||||||
|
//}
|
||||||
|
|||||||
2088
es/main.go
Normal file
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
150418
es/sale_isbns_empty_pic.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
excel/3128518632404838050.xlsx
Normal file
BIN
excel/3128518632404838050.xlsx
Normal file
Binary file not shown.
BIN
excel/3128518632404838051.xlsx
Normal file
BIN
excel/3128518632404838051.xlsx
Normal file
Binary file not shown.
BIN
excel/dll/excel.dll
Normal file
BIN
excel/dll/excel.dll
Normal file
Binary file not shown.
141
excel/dll/excel.h
Normal file
141
excel/dll/excel.h
Normal 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
516
excel/excelDllTest.go
Normal 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
1425
excel/excel_so.go
Normal file
File diff suppressed because it is too large
Load Diff
1451
excel/main.go
Normal file
1451
excel/main.go
Normal file
File diff suppressed because it is too large
Load Diff
BIN
excel/new_file.xlsx
Normal file
BIN
excel/new_file.xlsx
Normal file
Binary file not shown.
621
excel/test.go
Normal file
621
excel/test.go
Normal 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
24
go.mod
@ -5,9 +5,14 @@ go 1.25
|
|||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.10.3
|
github.com/PuerkitoBio/goquery v1.10.3
|
||||||
github.com/chromedp/chromedp v0.14.2
|
github.com/chromedp/chromedp v0.14.2
|
||||||
github.com/go-ini/ini v1.67.0
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
github.com/elastic/go-elasticsearch/v8 v8.19.0
|
||||||
github.com/go-sql-driver/mysql v1.9.3
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/parnurzeal/gorequest v0.3.0
|
github.com/parnurzeal/gorequest v0.3.0
|
||||||
|
github.com/xuri/excelize/v2 v2.10.0
|
||||||
|
golang.org/x/image v0.25.0
|
||||||
|
golang.org/x/sys v0.37.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -15,15 +20,26 @@ require (
|
|||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect
|
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect
|
||||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||||
|
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
|
||||||
github.com/elazarl/goproxy v1.7.2 // indirect
|
github.com/elazarl/goproxy v1.7.2 // indirect
|
||||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
|
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.4.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
github.com/moul/http2curl v1.0.0 // indirect
|
github.com/moul/http2curl v1.0.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
|
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
github.com/xuri/efp v0.0.1 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||||
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
|
golang.org/x/net v0.46.0 // indirect
|
||||||
|
golang.org/x/text v0.30.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
58
go.sum
58
go.sum
@ -12,12 +12,21 @@ github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipw
|
|||||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/elastic/elastic-transport-go/v8 v8.7.0 h1:OgTneVuXP2uip4BA658Xi6Hfw+PeIOod2rY3GVMGoVE=
|
||||||
|
github.com/elastic/elastic-transport-go/v8 v8.7.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
||||||
|
github.com/elastic/go-elasticsearch/v8 v8.19.0 h1:VmfBLNRORY7RZL+9hTxBD97ehl9H8Nxf2QigDh6HuMU=
|
||||||
|
github.com/elastic/go-elasticsearch/v8 v8.19.0/go.mod h1:F3j9e+BubmKvzvLjNui/1++nJuJxbkhHefbaT0kFKGY=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
|
||||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
|
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
@ -26,6 +35,7 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
@ -35,6 +45,8 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL
|
|||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
|
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
|
||||||
@ -43,19 +55,45 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||||
|
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||||
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||||
|
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||||
|
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||||
|
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||||
|
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
|
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
||||||
|
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
||||||
|
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||||
|
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||||
|
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||||
|
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
@ -70,8 +108,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -91,8 +129,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@ -111,8 +149,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
|||||||
BIN
image/dll/image.dll
Normal file
BIN
image/dll/image.dll
Normal file
Binary file not shown.
113
image/dll/image.h
Normal file
113
image/dll/image.h
Normal 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
590
image/image.go
Normal 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
87
image/imageDllTest.go
Normal 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
43
image/imageTest.go
Normal 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
644
image/imageTool.go
Normal 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"])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
BIN
kfzgw_so_linux
BIN
kfzgw_so_linux
Binary file not shown.
969
kfztp.go
969
kfztp.go
@ -1,71 +1,80 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
//import (
|
||||||
"encoding/json"
|
// "crypto/md5"
|
||||||
"fmt"
|
// "encoding/json"
|
||||||
"log"
|
// "fmt"
|
||||||
"net/http"
|
// "log"
|
||||||
"time"
|
// "math/rand"
|
||||||
|
// "net/http"
|
||||||
"github.com/parnurzeal/gorequest"
|
// "net/url"
|
||||||
)
|
// "regexp"
|
||||||
|
// "strconv"
|
||||||
// 获取商品模版
|
// "strings"
|
||||||
func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) {
|
// "sync"
|
||||||
if token == "" {
|
// "time"
|
||||||
return nil, fmt.Errorf("请先登录获取Token")
|
//
|
||||||
}
|
// "github.com/parnurzeal/gorequest"
|
||||||
|
//)
|
||||||
url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
|
//
|
||||||
itemId, time.Now().Unix())
|
//// 获取商品模版
|
||||||
|
//func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) {
|
||||||
log.Printf("请求URL: %s", url)
|
// if token == "" {
|
||||||
|
// return nil, fmt.Errorf("请先登录获取Token")
|
||||||
// 创建HTTP客户端
|
// }
|
||||||
request := gorequest.New()
|
//
|
||||||
|
// url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
|
||||||
// 设置代理(如果有提供代理URL)
|
// itemId, time.Now().Unix())
|
||||||
if proxy != "" {
|
//
|
||||||
request.Proxy(proxy)
|
// log.Printf("请求URL: %s", url)
|
||||||
log.Printf("使用代理: %s", proxy)
|
//
|
||||||
}
|
// // 创建HTTP客户端
|
||||||
|
// request := gorequest.New()
|
||||||
resp, body, errs := request.Get(url).
|
//
|
||||||
Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
|
// // 设置代理(如果有提供代理URL)
|
||||||
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
// if proxy != "" {
|
||||||
Set("Accept", "application/json, text/plain, */*").
|
// request.Proxy(proxy)
|
||||||
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
// log.Printf("使用代理: %s", proxy)
|
||||||
Set("Referer", "https://seller.kongfz.com/").
|
// }
|
||||||
Set("Origin", "https://seller.kongfz.com").
|
//
|
||||||
Timeout(30 * time.Second).
|
// resp, body, errs := request.Get(url).
|
||||||
End()
|
// Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
|
||||||
|
// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
if len(errs) > 0 {
|
// Set("Accept", "application/json, text/plain, */*").
|
||||||
return nil, fmt.Errorf("请求失败: %v", errs)
|
// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
}
|
// Set("Referer", "https://seller.kongfz.com/").
|
||||||
|
// Set("Origin", "https://seller.kongfz.com").
|
||||||
// 检查HTTP状态码
|
// Timeout(30 * time.Second).
|
||||||
if resp.StatusCode != http.StatusOK {
|
// End()
|
||||||
return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status)
|
//
|
||||||
}
|
// if len(errs) > 0 {
|
||||||
|
// return nil, fmt.Errorf("请求失败: %v", errs)
|
||||||
var data map[string]interface{}
|
// }
|
||||||
if err := json.Unmarshal([]byte(body), &data); err != nil {
|
//
|
||||||
return nil, fmt.Errorf("解析JSON失败: %w", err)
|
// // 检查HTTP状态码
|
||||||
}
|
// if resp.StatusCode != http.StatusOK {
|
||||||
|
// return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status)
|
||||||
// 检查API响应状态
|
// }
|
||||||
if val, ok := data["status"].(float64); ok && val == 1 {
|
//
|
||||||
return data, nil
|
// var data map[string]interface{}
|
||||||
}
|
// if err := json.Unmarshal([]byte(body), &data); err != nil {
|
||||||
|
// return nil, fmt.Errorf("解析JSON失败: %w", err)
|
||||||
return nil, fmt.Errorf("API返回错误: %+v", data)
|
// }
|
||||||
}
|
//
|
||||||
|
// // 检查API响应状态
|
||||||
|
// if val, ok := data["status"].(float64); ok && val == 1 {
|
||||||
|
// return data, nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil, fmt.Errorf("API返回错误: %+v", data)
|
||||||
|
//}
|
||||||
|
//
|
||||||
//func main() {
|
//func main() {
|
||||||
// fmt.Println("=== 孔夫子商品模板API服务 ===")
|
// fmt.Println("=== 孔夫子商品模板API服务 ===")
|
||||||
//
|
//
|
||||||
// http.HandleFunc("/api/goodsTemplate", getGoodsTemplateHandler)
|
// http.HandleFunc("/api/goodsTemplate", getGoodsTemplateHandler)
|
||||||
|
// http.HandleFunc("/api/outGetImageByIsbn", getOutGetImageByIsbnHandler)
|
||||||
|
//
|
||||||
// port := "8989"
|
// port := "8989"
|
||||||
// server := &http.Server{
|
// server := &http.Server{
|
||||||
// Addr: ":" + port,
|
// Addr: ":" + port,
|
||||||
@ -76,60 +85,782 @@ func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}
|
|||||||
// fmt.Printf("服务器启动失败: %v\n", err)
|
// fmt.Printf("服务器启动失败: %v\n", err)
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
//
|
||||||
// 获取商品模板接口
|
//// 获取商品模板接口
|
||||||
func getGoodsTemplateHandler(w http.ResponseWriter, r *http.Request) {
|
//func getGoodsTemplateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// 设置响应头
|
// // 设置响应头
|
||||||
w.Header().Set("Content-Type", "application/json")
|
// w.Header().Set("Content-Type", "application/json")
|
||||||
|
//
|
||||||
// 只处理GET请求
|
// // 只处理GET请求
|
||||||
if r.Method != http.MethodGet {
|
// if r.Method != http.MethodGet {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
// w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"success": false,
|
// "success": false,
|
||||||
"error": "只支持GET请求",
|
// "error": "只支持GET请求",
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// 获取查询参数
|
// // 获取查询参数
|
||||||
token := r.URL.Query().Get("token")
|
// token := r.URL.Query().Get("token")
|
||||||
itemId := r.URL.Query().Get("itemId")
|
// itemId := r.URL.Query().Get("itemId")
|
||||||
proxy := r.URL.Query().Get("proxy")
|
// proxy := r.URL.Query().Get("proxy")
|
||||||
|
//
|
||||||
// 验证必需参数
|
// // 验证必需参数
|
||||||
if token == "" || itemId == "" {
|
// if token == "" || itemId == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
// w.WriteHeader(http.StatusBadRequest)
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"success": false,
|
// "success": false,
|
||||||
"error": "缺少必需参数: token 和 itemid",
|
// "error": "缺少必需参数: token 和 itemid",
|
||||||
"usage": map[string]string{
|
// "usage": map[string]string{
|
||||||
"token": "登录Token (PHPSESSID)",
|
// "token": "登录Token (PHPSESSID)",
|
||||||
"itemid": "商品ID",
|
// "itemid": "商品ID",
|
||||||
"proxy": "代理地址 (可选)",
|
// "proxy": "代理地址 (可选)",
|
||||||
},
|
// },
|
||||||
"example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
|
// "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
log.Printf("收到请求 - 商品ID: %s", itemId)
|
// log.Printf("收到请求 - 商品ID: %s", itemId)
|
||||||
|
//
|
||||||
// 调用函数获取商品模板
|
// // 调用函数获取商品模板
|
||||||
result, err := outGetGoodsTplMsgLinux(token, itemId, proxy)
|
// result, err := outGetGoodsTplMsgLinux(token, itemId, proxy)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Printf("获取商品模板失败: %v", err)
|
// log.Printf("获取商品模板失败: %v", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
// w.WriteHeader(http.StatusInternalServerError)
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"success": false,
|
// "success": false,
|
||||||
"error": err.Error(),
|
// "error": err.Error(),
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// 返回成功响应
|
// // 返回成功响应
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"success": true,
|
// "success": true,
|
||||||
"data": result,
|
// "data": result,
|
||||||
})
|
// })
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//func getOutGetImageByIsbnHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// // 设置响应头
|
||||||
|
// w.Header().Set("Content-Type", "application/json")
|
||||||
|
//
|
||||||
|
// // 只处理GET请求
|
||||||
|
// if r.Method != http.MethodGet {
|
||||||
|
// w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
// "success": false,
|
||||||
|
// "error": "只支持GET请求",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 获取查询参数
|
||||||
|
// token := r.URL.Query().Get("token")
|
||||||
|
// isbn := r.URL.Query().Get("isbn")
|
||||||
|
// proxyType := r.URL.Query().Get("proxyType")
|
||||||
|
// username := r.URL.Query().Get("username")
|
||||||
|
// password := r.URL.Query().Get("password")
|
||||||
|
// machineCode := r.URL.Query().Get("machineCode")
|
||||||
|
// //proxy := r.URL.Query().Get("proxy")
|
||||||
|
//
|
||||||
|
// // 验证必需参数
|
||||||
|
// if token == "" || isbn == "" {
|
||||||
|
// w.WriteHeader(http.StatusBadRequest)
|
||||||
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
// "success": false,
|
||||||
|
// "error": "缺少必需参数: token 和 isbn",
|
||||||
|
// "usage": map[string]string{
|
||||||
|
// "token": "登录Token (PHPSESSID)",
|
||||||
|
// "isbn": "isbn",
|
||||||
|
// "proxy": "代理地址 (可选)",
|
||||||
|
// },
|
||||||
|
// "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// manager, err2 := proxyTypeManager(proxyType, username, password, machineCode)
|
||||||
|
// if err2 != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// log.Printf("收到请求 - 商品ID: %s", isbn)
|
||||||
|
//
|
||||||
|
// // 调用函数获取商品模板
|
||||||
|
// result, err := OutGetImageByIsbns(token, isbn, manager, 0, 0)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("获取商品模板失败: %v", err)
|
||||||
|
// w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
// "success": false,
|
||||||
|
// "error": err.Error(),
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// // 返回成功响应
|
||||||
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
// "success": true,
|
||||||
|
// "data": result,
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 条目详情结构体
|
||||||
|
//type BookInfo struct {
|
||||||
|
// BookName string `json:"book_name"` // 书名
|
||||||
|
// Author string `json:"author"` // 作者
|
||||||
|
// Publisher string `json:"publisher"` // 出版社
|
||||||
|
// ISBN string `json:"isbn"` // ISBN
|
||||||
|
// PublicationTime int64 `json:"publication_time"` // 出版时间
|
||||||
|
// Edition string `json:"edition"` // 版次
|
||||||
|
// PrintTime string `json:"print_time"` // 印刷时间
|
||||||
|
// FixPrice string `json:"fix_price"` // 定价
|
||||||
|
// BindingLayout string `json:"binding_layout"` // 装帧
|
||||||
|
// Format string `json:"format"` // 开本
|
||||||
|
// Paper string `json:"paper"` // 纸张
|
||||||
|
// Pages string `json:"pages"` // 页数
|
||||||
|
// Wordage string `json:"wordage"` // 字数
|
||||||
|
// Languages string `json:"languages"` // 语种
|
||||||
|
// Era string `json:"era"` // 年代
|
||||||
|
// EngravingMethod string `json:"engraving_method"` // 刻印方式
|
||||||
|
// Dimensions string `json:"dimensions"` // 尺寸
|
||||||
|
// VolumeNumber string `json:"volume_number"` // 册数
|
||||||
|
// BookPic string `json:"book_pic"` // 图书封面图(官图)
|
||||||
|
// BookPicS string `json:"book_pic_s"` // 图书封面图(实拍图)
|
||||||
|
// SellingPrice string `json:"selling_price"` // 售价
|
||||||
|
// Condition string `json:"condition"` // 品相
|
||||||
|
// ExpressDeliveryFee string `json:"express_delivery_fee"` // 快递费
|
||||||
|
// Editor string `json:"editor"` // 编辑
|
||||||
|
// Category string `json:"category"` // 分类
|
||||||
|
// BuyCount string `json:"buy_count"` // 买过
|
||||||
|
// SellCount string `json:"sell_count"` // 在卖
|
||||||
|
// Content string `json:"content"` // 内容
|
||||||
|
// Mid int64 `json:"mid"` // 商家id
|
||||||
|
// ItemId int64 `json:"item_id"` // 商品id
|
||||||
|
// ShopId int64 `json:"shop_id"` // 店铺id
|
||||||
|
// DetailUrl string `json:"detail_url"` // 商品详情url
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 获取图片URL(官图和拍图)
|
||||||
|
//func OutGetImageByIsbns(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (*BookInfo, error) {
|
||||||
|
// fmt.Println("[DEBUG] 使用的ISBN: ", isbn)
|
||||||
|
// // isLiveImage 1实拍图 0官图 ,isReturnMsg 0商品信息
|
||||||
|
// bookInfo := &BookInfo{}
|
||||||
|
// //if isLiveImage == 0 {
|
||||||
|
// // 孔网官图请求
|
||||||
|
// gtUrl := fmt.Sprintf("%s?keyword=%s", "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", isbn)
|
||||||
|
// // 创建HTTP客户端
|
||||||
|
// requestGt := gorequest.New()
|
||||||
|
// // 设置代理(如果有提供代理URL)
|
||||||
|
// if proxy != "" {
|
||||||
|
// requestGt.Proxy(proxy)
|
||||||
|
// }
|
||||||
|
// // 发送请求
|
||||||
|
// respGt, bodyGt, errsGt := requestGt.Get(gtUrl).
|
||||||
|
// Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)).
|
||||||
|
// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
// Set("Accept", "application/json, text/plain, */*").
|
||||||
|
// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
// Set("Referer", "https://item.kongfz.com/").
|
||||||
|
// Timeout(30 * time.Second).
|
||||||
|
// End()
|
||||||
|
// if len(errsGt) > 0 {
|
||||||
|
// // 检查是否是代理相关错误
|
||||||
|
// var isProxyError bool
|
||||||
|
// var errorDetails []string
|
||||||
|
// for _, e := range errsGt {
|
||||||
|
// errorStr := e.Error()
|
||||||
|
// errorDetails = append(errorDetails, errorStr)
|
||||||
|
// if strings.Contains(errorStr, "Proxy Authentication Required") ||
|
||||||
|
// strings.Contains(errorStr, "connectex: A connection attempt failed") ||
|
||||||
|
// strings.Contains(errorStr, "connectex: No connection could be made") ||
|
||||||
|
// strings.Contains(errorStr, "proxyconnect tcp") ||
|
||||||
|
// strings.Contains(errorStr, "timeout") ||
|
||||||
|
// strings.Contains(errorStr, "connection refused") {
|
||||||
|
// isProxyError = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// log.Printf("[ERROR] 请求错误详情: %v", errorDetails)
|
||||||
|
// if isProxyError {
|
||||||
|
// // 处理代理失败
|
||||||
|
// return nil, fmt.Errorf("代理连接失败")
|
||||||
|
// }
|
||||||
|
// return nil, fmt.Errorf("查询请求失败: %v", errsGt)
|
||||||
|
// }
|
||||||
|
// //检查HTTP状态码
|
||||||
|
// if respGt.StatusCode != http.StatusOK {
|
||||||
|
// return nil, fmt.Errorf("HTTP错误: %s", respGt.Status)
|
||||||
|
// }
|
||||||
|
// // 解析响应
|
||||||
|
// var apiGtResp struct {
|
||||||
|
// Status int `json:"status"`
|
||||||
|
// ErrType string `json:"errType"`
|
||||||
|
// Message string `json:"message"`
|
||||||
|
// SystemTime int64 `json:"systemTime"`
|
||||||
|
// Data struct {
|
||||||
|
// ItemResponse struct {
|
||||||
|
// Total int `json:"total"`
|
||||||
|
// List []struct {
|
||||||
|
// BookName string `json:"bookName"`
|
||||||
|
// Mid int64 `json:"mid"`
|
||||||
|
// ImgUrlEntity struct {
|
||||||
|
// BigImgUrl string `json:"bigImgUrl"`
|
||||||
|
// } `json:"imgUrlEntity"`
|
||||||
|
// Isbn string `json:"isbn"`
|
||||||
|
// BookShowInfo []string `json:"bookShowInfo"`
|
||||||
|
// } `json:"list"`
|
||||||
|
// } `json:"itemResponse"`
|
||||||
|
// } `json:"data"`
|
||||||
|
// }
|
||||||
|
// if err := json.Unmarshal([]byte(bodyGt), &apiGtResp); err != nil {
|
||||||
|
// return nil, fmt.Errorf("解析JSON失败: %w", err)
|
||||||
|
// }
|
||||||
|
// if apiGtResp.ErrType == "102" {
|
||||||
|
// return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiGtResp.Message, apiGtResp.ErrType)
|
||||||
|
// }
|
||||||
|
// // 如果找到条目,返回图片URL
|
||||||
|
// if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 {
|
||||||
|
// list := apiGtResp.Data.ItemResponse.List[0]
|
||||||
|
// bookShowInfo := list.BookShowInfo
|
||||||
|
// bookInfo.BookName = list.BookName
|
||||||
|
// bookInfo.BookPic = list.ImgUrlEntity.BigImgUrl
|
||||||
|
// bookInfo.ISBN = list.Isbn
|
||||||
|
// // 根据长度安全填充字段
|
||||||
|
// if isReturnMsg == 0 {
|
||||||
|
// if len(bookShowInfo) > 0 {
|
||||||
|
// bookInfo.Author = bookShowInfo[0]
|
||||||
|
// }
|
||||||
|
// if len(bookShowInfo) > 1 {
|
||||||
|
// bookInfo.Publisher = bookShowInfo[1]
|
||||||
|
// }
|
||||||
|
// if len(bookShowInfo) > 2 {
|
||||||
|
// bookInfo.PublicationTime = validateDateFormat(bookShowInfo[2])
|
||||||
|
// }
|
||||||
|
// if len(bookShowInfo) > 3 {
|
||||||
|
// bookInfo.BindingLayout = bookShowInfo[3]
|
||||||
|
// }
|
||||||
|
// if len(bookShowInfo) > 4 {
|
||||||
|
// bookInfo.FixPrice = bookShowInfo[4]
|
||||||
|
// } else {
|
||||||
|
// log.Printf("[WARN] BookShowInfo 长度不足 (仅 %d 项): %v", len(bookShowInfo), bookShowInfo)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// //return bookInfo, nil
|
||||||
|
//
|
||||||
|
// //}
|
||||||
|
// //if isLiveImage == 1 {
|
||||||
|
//
|
||||||
|
// size := 10
|
||||||
|
// // 实拍图
|
||||||
|
// sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000",
|
||||||
|
// "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, size)
|
||||||
|
// //创建HTTP客户端
|
||||||
|
// requestSpt := gorequest.New()
|
||||||
|
// //设置代理(如果有提供代理URL)
|
||||||
|
// if proxy != "" {
|
||||||
|
// requestSpt.Proxy(proxy)
|
||||||
|
// }
|
||||||
|
// // 发送请求
|
||||||
|
// respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl).
|
||||||
|
// Proxy(proxy).
|
||||||
|
// Set("Cookie", token).
|
||||||
|
// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
// Set("Accept", "application/json, text/plain, */*").
|
||||||
|
// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
// Set("Referer", "https://item.kongfz.com/").
|
||||||
|
// Timeout(30 * time.Second).
|
||||||
|
// End()
|
||||||
|
// // 错误处理
|
||||||
|
// if len(errsSpt) > 0 {
|
||||||
|
// return nil, fmt.Errorf("请求失败: %v", errsSpt)
|
||||||
|
// }
|
||||||
|
// // 检查HTTP状态码
|
||||||
|
// if respSpt.StatusCode != http.StatusOK {
|
||||||
|
// return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status)
|
||||||
|
// }
|
||||||
|
// // 解析响应
|
||||||
|
// var apiSptResp struct {
|
||||||
|
// Status int `json:"status"`
|
||||||
|
// ErrType string `json:"errType"`
|
||||||
|
// Message string `json:"message"`
|
||||||
|
// SystemTime int64 `json:"systemTime"`
|
||||||
|
// Data struct {
|
||||||
|
// ItemResponse struct {
|
||||||
|
// Total int `json:"total"`
|
||||||
|
// List []struct {
|
||||||
|
// ItemId int64 `json:"itemId"`
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// ImgUrl string `json:"imgUrl"`
|
||||||
|
// ImgBigUrl string `json:"imgBigUrl"`
|
||||||
|
// Author string `json:"author"`
|
||||||
|
// PubDateText string `json:"pubDateText"`
|
||||||
|
// Isbn string `json:"isbn"`
|
||||||
|
// Press string `json:"press"`
|
||||||
|
// ShopId int64 `json:"shopId"`
|
||||||
|
// TplRecords []struct {
|
||||||
|
// Key string `json:"key"`
|
||||||
|
// Value string `json:"value"`
|
||||||
|
// } `json:"tplRecords"`
|
||||||
|
// } `json:"list"`
|
||||||
|
// } `json:"itemResponse"`
|
||||||
|
// } `json:"data"`
|
||||||
|
// }
|
||||||
|
// // 解析JSON
|
||||||
|
// if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil {
|
||||||
|
// return nil, fmt.Errorf("解析JSON失败: %w", err)
|
||||||
|
// }
|
||||||
|
// if apiSptResp.ErrType == "102" {
|
||||||
|
// return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiSptResp.Message, apiSptResp.ErrType)
|
||||||
|
// }
|
||||||
|
// if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 {
|
||||||
|
// // 确定其实索引
|
||||||
|
// var startIndex int
|
||||||
|
// if size >= apiSptResp.Data.ItemResponse.Total {
|
||||||
|
// startIndex = apiSptResp.Data.ItemResponse.Total - 1
|
||||||
|
// } else {
|
||||||
|
// startIndex = size - 1
|
||||||
|
// }
|
||||||
|
// for attempt := 0; attempt < 3; attempt++ {
|
||||||
|
// currentIndex := startIndex - attempt
|
||||||
|
// // 检查索引是否有效
|
||||||
|
// if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) {
|
||||||
|
// log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// randomNum := rand.Intn(currentIndex + 1)
|
||||||
|
// item := apiSptResp.Data.ItemResponse.List[randomNum]
|
||||||
|
// // 检查图片URL是否存在
|
||||||
|
// if item.ImgBigUrl == "" {
|
||||||
|
// log.Printf("[DEBUG] 索引 %d 的图片URL为空,跳过", randomNum)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// // 响应信息
|
||||||
|
// bookInfo.BookPicS = item.ImgUrl
|
||||||
|
// bookInfo.ISBN = item.Isbn
|
||||||
|
// if bookInfo.BookName == "" {
|
||||||
|
// bookInfo.BookName = item.Title
|
||||||
|
// if isReturnMsg == 0 {
|
||||||
|
// // 安全地获取TplRecords中的值
|
||||||
|
// if len(item.TplRecords) > 0 {
|
||||||
|
// bookInfo.Author = item.TplRecords[0].Value
|
||||||
|
// }
|
||||||
|
// if len(item.TplRecords) > 1 {
|
||||||
|
// bookInfo.Publisher = item.TplRecords[1].Value
|
||||||
|
// }
|
||||||
|
// if len(item.TplRecords) > 2 {
|
||||||
|
// bookInfo.PublicationTime = validateDateFormat(item.TplRecords[2].Value)
|
||||||
|
// }
|
||||||
|
// if len(item.TplRecords) > 3 {
|
||||||
|
// bookInfo.BindingLayout = item.TplRecords[3].Value
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return bookInfo, nil
|
||||||
|
// }
|
||||||
|
// //}
|
||||||
|
// }
|
||||||
|
// return nil, fmt.Errorf("查询失败,没有数据!")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 验证日期格式
|
||||||
|
//func validateDateFormat(dateStr string) int64 {
|
||||||
|
// // 去除前后空格
|
||||||
|
// dateStr = strings.TrimSpace(dateStr)
|
||||||
|
//
|
||||||
|
// // 替换各种分隔符为统一的分隔符"-"
|
||||||
|
// dateStr = regexp.MustCompile(`[/_\\.,\s]+`).ReplaceAllString(dateStr, "-")
|
||||||
|
//
|
||||||
|
// // 处理纯年份格式 (4位数字)
|
||||||
|
// if regexp.MustCompile(`^\d{4}$`).MatchString(dateStr) {
|
||||||
|
// dateStr += "-01-01"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 处理年月格式 (4位数字-1或2位数字)
|
||||||
|
// if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})$`).FindStringSubmatch(dateStr); len(matches) == 3 {
|
||||||
|
// year := matches[1]
|
||||||
|
// month := matches[2]
|
||||||
|
// if len(month) == 1 {
|
||||||
|
// month = "0" + month
|
||||||
|
// }
|
||||||
|
// if monthNum, _ := strconv.Atoi(month); monthNum >= 1 && monthNum <= 12 {
|
||||||
|
// dateStr = year + "-" + month + "-01"
|
||||||
|
// } else {
|
||||||
|
// return 0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 处理年月日格式 (4位数字-1或2位数字-1或2位数字)
|
||||||
|
// if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})-(\d{1,2})`).FindStringSubmatch(dateStr); len(matches) >= 4 {
|
||||||
|
// year := matches[1]
|
||||||
|
// month := matches[2]
|
||||||
|
// day := matches[3]
|
||||||
|
//
|
||||||
|
// // 标准化月份和日期为两位数
|
||||||
|
// if len(month) == 1 {
|
||||||
|
// month = "0" + month
|
||||||
|
// }
|
||||||
|
// if len(day) == 1 {
|
||||||
|
// day = "0" + day
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// dateStr = year + "-" + month + "-" + day
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 加载上海时区
|
||||||
|
// loc, err := time.LoadLocation("Asia/Shanghai")
|
||||||
|
// if err != nil {
|
||||||
|
// // 如果加载时区失败,使用UTC+8作为后备方案
|
||||||
|
// loc = time.FixedZone("CST", 8*60*60)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 尝试解析为标准日期格式,并指定上海时区
|
||||||
|
// parsedTime, err := time.ParseInLocation("2006-01-02", dateStr, loc)
|
||||||
|
// if err != nil {
|
||||||
|
// return 0
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 返回秒级时间戳(UTC时间,但解析时已经考虑了时区偏移)
|
||||||
|
// return parsedTime.Unix()
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 代理类型常量
|
||||||
|
//const (
|
||||||
|
// CalfElephantProxyType = "CALF_ELEPHANT_PROXY"
|
||||||
|
// TailProxyType = "TAIL_PROXY"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//// 代理服务器列表
|
||||||
|
//var (
|
||||||
|
// servers = []string{
|
||||||
|
// "http-dynamic.xiaoxiangdaili.com",
|
||||||
|
// "http-dynamic-S02.xiaoxiangdaili.com",
|
||||||
|
// "http-dynamic-S03.xiaoxiangdaili.com",
|
||||||
|
// "http-dynamic-S04.xiaoxiangdaili.com",
|
||||||
|
// }
|
||||||
|
// randMutex sync.Mutex
|
||||||
|
// globalRand *rand.Rand
|
||||||
|
//
|
||||||
|
// // 代理健康状态管理
|
||||||
|
// proxyHealthMaps = make(map[string]*ProxyHealth)
|
||||||
|
// proxyHealthMutex sync.RWMutex
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//// ProxyManager 代理管理器结构体
|
||||||
|
//type ProxyManager struct {
|
||||||
|
// servers []string `json:"servers"` // 代理服务器列表
|
||||||
|
// username string `json:"username"` // 代理账号
|
||||||
|
// password string `json:"password"` // 代理密码
|
||||||
|
// tailCardSecret string `json:"tail_card_secret"` // 尾巴代理卡密
|
||||||
|
// proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 代理健康状态
|
||||||
|
//type ProxyHealth struct {
|
||||||
|
// SuccessCount int // 成功次数
|
||||||
|
// FailCount int // 失败次数
|
||||||
|
// LastCheck time.Time // 最后检查时间
|
||||||
|
// ResponseTime time.Duration // 响应时间
|
||||||
|
// IsHealthy bool // 是否健康
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func init() {
|
||||||
|
// // 创建全局的随机数生成器
|
||||||
|
// globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 获取代理URL
|
||||||
|
//func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
|
||||||
|
// switch proxyType {
|
||||||
|
// case CalfElephantProxyType:
|
||||||
|
// return buildCalfElephantProxyURL(username, password)
|
||||||
|
// case TailProxyType:
|
||||||
|
// return buildTailProxyURL(machineCode)
|
||||||
|
// default:
|
||||||
|
// return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 构建小象代理URL
|
||||||
|
//func buildCalfElephantProxyURL(username, password string) (string, error) {
|
||||||
|
// server := randomServer()
|
||||||
|
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||||
|
// url.QueryEscape(username),
|
||||||
|
// url.QueryEscape(password),
|
||||||
|
// server,
|
||||||
|
// 10030)
|
||||||
|
//
|
||||||
|
// // 检测代理可用性
|
||||||
|
// if err := checkProxyHealth(proxyURL); err != nil {
|
||||||
|
// log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
|
||||||
|
// // 尝试下一个代理服务器
|
||||||
|
// return tryNextCalfElephantProxy(username, password, server)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.Printf("[INFO] 使用小象代理: %s", server)
|
||||||
|
// return proxyURL, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 尝试下一个小象代理服务器
|
||||||
|
//func tryNextCalfElephantProxy(username, password, failedServer string) (string, error) {
|
||||||
|
// // 创建服务器副本并排除失败的服务器
|
||||||
|
// availableServers := make([]string, 0)
|
||||||
|
// for _, server := range servers {
|
||||||
|
// if server != failedServer {
|
||||||
|
// availableServers = append(availableServers, server)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if len(availableServers) == 0 {
|
||||||
|
// return "", fmt.Errorf("所有小象代理服务器都不可用")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 随机尝试可用服务器
|
||||||
|
// for _, server := range shuffleServers(availableServers) {
|
||||||
|
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||||
|
// url.QueryEscape(username),
|
||||||
|
// url.QueryEscape(password),
|
||||||
|
// server,
|
||||||
|
// 10030)
|
||||||
|
//
|
||||||
|
// if err := checkProxyHealth(proxyURL); err == nil {
|
||||||
|
// log.Printf("[INFO] 切换到可用代理: %s", server)
|
||||||
|
// return proxyURL, nil
|
||||||
|
// }
|
||||||
|
// log.Printf("[WARN] 代理 %s 检测失败", server)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 构建内置代理URL
|
||||||
|
//func buildTailProxyURL(machineCode string) (string, error) {
|
||||||
|
// proxies, err := getProxies(machineCode)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
// if len(proxies) == 0 {
|
||||||
|
// return "", fmt.Errorf("未获取到有效代理")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 过滤并选择健康的代理
|
||||||
|
// healthyProxies := filterHealthyProxies(proxies)
|
||||||
|
// if len(healthyProxies) > 0 {
|
||||||
|
// proxyURL := "http://" + randomElement(healthyProxies)
|
||||||
|
// log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
|
||||||
|
// return proxyURL, nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 如果没有健康代理,检测所有代理
|
||||||
|
// log.Printf("[INFO] 未找到健康代理,开始检测代理可用性...")
|
||||||
|
// return findWorkingTailProxy(proxies)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 获取代理服务器列表
|
||||||
|
//func getProxies(machineCode string) ([]string, error) {
|
||||||
|
// log.Printf("[INFO] 开始获取代理列表: %s", machineCode)
|
||||||
|
// // 生成签名
|
||||||
|
// sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
|
||||||
|
//
|
||||||
|
// GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
|
||||||
|
// machineCode, sign)
|
||||||
|
//
|
||||||
|
// req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
|
||||||
|
// _, body, errs := req.End()
|
||||||
|
// if len(errs) > 0 {
|
||||||
|
// return nil, fmt.Errorf("获取代理失败: %v", errs)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 检查是否为JSON错误响应
|
||||||
|
// if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
|
||||||
|
// // 尝试解析为JSON错误响应
|
||||||
|
// var errorResp struct {
|
||||||
|
// Code int `json:"code"`
|
||||||
|
// Message string `json:"message"`
|
||||||
|
// }
|
||||||
|
// if err := json.Unmarshal([]byte(body), &errorResp); err == nil {
|
||||||
|
// return nil, fmt.Errorf("获取代理失败: %s (错误码: %d)", errorResp.Message, errorResp.Code)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 解析响应
|
||||||
|
// lines := strings.Split(strings.TrimSpace(body), "\n")
|
||||||
|
// var proxies []string
|
||||||
|
// for _, line := range lines {
|
||||||
|
// line = strings.TrimSpace(line)
|
||||||
|
// if line != "" && !strings.HasPrefix(line, "{") {
|
||||||
|
// proxies = append(proxies, line)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if len(proxies) == 0 {
|
||||||
|
// return nil, fmt.Errorf("未获取到有效代理")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.Printf("[INFO] 获取到 %d 个代理", len(proxies))
|
||||||
|
// return proxies, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 过滤健康代理
|
||||||
|
//func filterHealthyProxies(proxies []string) []string {
|
||||||
|
// proxyHealthMutex.RLock()
|
||||||
|
// defer proxyHealthMutex.RUnlock()
|
||||||
|
//
|
||||||
|
// var healthy []string
|
||||||
|
// for _, proxy := range proxies {
|
||||||
|
// if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
|
||||||
|
// // 检查是否在最近检查过(5分钟内)
|
||||||
|
// if time.Since(health.LastCheck) < 5*time.Minute {
|
||||||
|
// healthy = append(healthy, proxy)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return healthy
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 查找可用的尾巴代理
|
||||||
|
//func findWorkingTailProxy(proxies []string) (string, error) {
|
||||||
|
// // 打乱代理顺序
|
||||||
|
// shuffledProxies := shuffleServers(proxies)
|
||||||
|
//
|
||||||
|
// // 并发检测代理
|
||||||
|
// type proxyResult struct {
|
||||||
|
// proxy string
|
||||||
|
// err error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ch := make(chan proxyResult, len(shuffledProxies))
|
||||||
|
// var wg sync.WaitGroup
|
||||||
|
//
|
||||||
|
// // 限制并发数
|
||||||
|
// sem := make(chan struct{}, 5)
|
||||||
|
//
|
||||||
|
// for _, proxy := range shuffledProxies {
|
||||||
|
// wg.Add(1)
|
||||||
|
// go func(p string) {
|
||||||
|
// defer wg.Done()
|
||||||
|
// sem <- struct{}{}
|
||||||
|
// defer func() { <-sem }()
|
||||||
|
//
|
||||||
|
// proxyURL := "http://" + p
|
||||||
|
// err := checkProxyHealth(proxyURL)
|
||||||
|
// ch <- proxyResult{proxy: p, err: err}
|
||||||
|
// }(proxy)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// wg.Wait()
|
||||||
|
// close(ch)
|
||||||
|
//
|
||||||
|
// // 收集结果
|
||||||
|
// var workingProxies []string
|
||||||
|
// for result := range ch {
|
||||||
|
// if result.err == nil {
|
||||||
|
// workingProxies = append(workingProxies, result.proxy)
|
||||||
|
// // 更新健康状态
|
||||||
|
// updateProxyHealth(result.proxy, true, 0)
|
||||||
|
// } else {
|
||||||
|
// updateProxyHealth(result.proxy, false, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if len(workingProxies) > 0 {
|
||||||
|
// selected := randomElement(workingProxies)
|
||||||
|
// log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
|
||||||
|
// return "http://" + selected, nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return "", fmt.Errorf("所有尾巴代理都不可用")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 检测代理健康状态
|
||||||
|
//func checkProxyHealth(proxyURL string) error {
|
||||||
|
// start := time.Now()
|
||||||
|
//
|
||||||
|
// req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
|
||||||
|
// resp, _, errs := req.Get("https://shop.kongfz.com/").End()
|
||||||
|
//
|
||||||
|
// responseTime := time.Since(start)
|
||||||
|
//
|
||||||
|
// if len(errs) > 0 {
|
||||||
|
// updateProxyHealth(proxyURL, false, responseTime)
|
||||||
|
// return fmt.Errorf("代理连接失败: %v", errs)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if resp.StatusCode != 200 {
|
||||||
|
// updateProxyHealth(proxyURL, false, responseTime)
|
||||||
|
// return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// updateProxyHealth(proxyURL, true, responseTime)
|
||||||
|
// log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 更新代理健康状态
|
||||||
|
//func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
|
||||||
|
// proxyHealthMutex.Lock()
|
||||||
|
// defer proxyHealthMutex.Unlock()
|
||||||
|
//
|
||||||
|
// host := getProxyHost(proxyURL)
|
||||||
|
// if _, exists := proxyHealthMaps[host]; !exists {
|
||||||
|
// proxyHealthMaps[host] = &ProxyHealth{}
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// health := proxyHealthMaps[host]
|
||||||
|
// health.LastCheck = time.Now()
|
||||||
|
//
|
||||||
|
// if success {
|
||||||
|
// health.SuccessCount++
|
||||||
|
// health.FailCount = 0
|
||||||
|
// health.ResponseTime = responseTime
|
||||||
|
// health.IsHealthy = true
|
||||||
|
// } else {
|
||||||
|
// health.FailCount++
|
||||||
|
// health.SuccessCount = 0
|
||||||
|
// // 连续失败3次标记为不健康
|
||||||
|
// if health.FailCount >= 3 {
|
||||||
|
// health.IsHealthy = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 获取代理主机名
|
||||||
|
//func getProxyHost(proxyURL string) string {
|
||||||
|
// if strings.HasPrefix(proxyURL, "http://") {
|
||||||
|
// proxyURL = proxyURL[7:]
|
||||||
|
// }
|
||||||
|
// // 去除认证信息
|
||||||
|
// if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
|
||||||
|
// proxyURL = proxyURL[atIndex+1:]
|
||||||
|
// }
|
||||||
|
// // 去除端口
|
||||||
|
// if colonIndex := strings.Index(proxyURL, ":"); colonIndex != -1 {
|
||||||
|
// proxyURL = proxyURL[:colonIndex]
|
||||||
|
// }
|
||||||
|
// return proxyURL
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 随机代理服务器
|
||||||
|
//func randomServer() string {
|
||||||
|
// return randomElement(servers)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 线程安全的随机元素选择
|
||||||
|
//func randomElement(slice []string) string {
|
||||||
|
// randMutex.Lock()
|
||||||
|
// defer randMutex.Unlock()
|
||||||
|
// return slice[globalRand.Intn(len(slice))]
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 打乱服务器顺序
|
||||||
|
//func shuffleServers(servers []string) []string {
|
||||||
|
// randMutex.Lock()
|
||||||
|
// defer randMutex.Unlock()
|
||||||
|
//
|
||||||
|
// shuffled := make([]string, len(servers))
|
||||||
|
// copy(shuffled, servers)
|
||||||
|
// globalRand.Shuffle(len(shuffled), func(i, j int) {
|
||||||
|
// shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
||||||
|
// })
|
||||||
|
// return shuffled
|
||||||
|
//}
|
||||||
|
|||||||
BIN
kongfz/dll/kongfz.dll
Normal file
BIN
kongfz/dll/kongfz.dll
Normal file
Binary file not shown.
145
kongfz/dll/kongfz.h
Normal file
145
kongfz/dll/kongfz.h
Normal 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
2089
kongfz/kongfz.go
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
24
main.go
24
main.go
@ -461,9 +461,9 @@ func outGetGoodsTplMsg(token, itemId, proxy string) (map[string]interface{}, err
|
|||||||
return nil, fmt.Errorf("API返回错误: %+v", data)
|
return nil, fmt.Errorf("API返回错误: %+v", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取商品列表
|
// 获取商品列表-已登的店铺
|
||||||
func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime int,
|
func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime string,
|
||||||
endCreateTime int, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) {
|
endCreateTime string, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) {
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return nil, fmt.Errorf("请先登录获取Token")
|
return nil, fmt.Errorf("请先登录获取Token")
|
||||||
}
|
}
|
||||||
@ -472,8 +472,8 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p
|
|||||||
ItemSn string `json:"itemSn"`
|
ItemSn string `json:"itemSn"`
|
||||||
PriceMin string `json:"priceMin"`
|
PriceMin string `json:"priceMin"`
|
||||||
PriceMax string `json:"priceMax"`
|
PriceMax string `json:"priceMax"`
|
||||||
StartCreateTime int `json:"startCreateTime"`
|
StartCreateTime string `json:"startCreateTime"`
|
||||||
EndCreateTime int `json:"endCreateTime"`
|
EndCreateTime string `json:"endCreateTime"`
|
||||||
RequestType string `json:"requestType,omitempty"`
|
RequestType string `json:"requestType,omitempty"`
|
||||||
IsItemSnEqual int `json:"isItemSnEqual,omitempty"`
|
IsItemSnEqual int `json:"isItemSnEqual,omitempty"`
|
||||||
Page int `json:"page,omitempty"`
|
Page int `json:"page,omitempty"`
|
||||||
@ -655,7 +655,6 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error)
|
|||||||
|
|
||||||
// 获取图片URL(官图和拍图)带有店铺过滤
|
// 获取图片URL(官图和拍图)带有店铺过滤
|
||||||
func outGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (map[string]string, error) {
|
func outGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (map[string]string, error) {
|
||||||
fmt.Println("[DEBUG] 使用的ISBN: ", isbn)
|
|
||||||
if isLiveImage == 0 {
|
if isLiveImage == 0 {
|
||||||
gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn)
|
gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn)
|
||||||
request := gorequest.New()
|
request := gorequest.New()
|
||||||
@ -2873,6 +2872,7 @@ func updateAccountToken(id int64, token string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================== C 导出函数 =======================
|
||||||
// 登录(带有Out的都非官方标准接口)
|
// 登录(带有Out的都非官方标准接口)
|
||||||
//
|
//
|
||||||
//export OutLogin
|
//export OutLogin
|
||||||
@ -2980,15 +2980,15 @@ func OutGetGoodsTplMsg(token, itemId, proxy *C.char) *C.char {
|
|||||||
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
|
// 获取商品列表-已登的店铺(带有Out的都非官方标准接口)
|
||||||
//
|
//
|
||||||
//export OutGetGoodsListMsgFromSelfShop
|
//export OutGetGoodsListMsgFromSelfShop
|
||||||
func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime C.int,
|
func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char,
|
||||||
endCreateTime C.int, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char {
|
endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char {
|
||||||
goToken := C.GoString(token)
|
goToken := C.GoString(token)
|
||||||
goProxy := C.GoString(proxy)
|
goProxy := C.GoString(proxy)
|
||||||
goItemSn := C.GoString(itemSn)
|
goItemSn := C.GoString(itemSn)
|
||||||
goPriceMin := C.GoString(priceMin)
|
goPriceMin := C.GoString(priceMin)
|
||||||
goPriceMax := C.GoString(priceMax)
|
goPriceMax := C.GoString(priceMax)
|
||||||
goStartCreateTime := int(startCreateTime)
|
goStartCreateTime := C.GoString(startCreateTime)
|
||||||
goEndCreateTime := int(endCreateTime)
|
goEndCreateTime := C.GoString(endCreateTime)
|
||||||
goRequestType := C.GoString(requestType)
|
goRequestType := C.GoString(requestType)
|
||||||
goIsItemSnEqual := int(isItemSnEqual)
|
goIsItemSnEqual := int(isItemSnEqual)
|
||||||
goPage := int(page)
|
goPage := int(page)
|
||||||
@ -3054,7 +3054,7 @@ func OutDelGoodsFromSelfShop(token, proxy, itemId *C.char) *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增商品(带有Out的都非官方标准接口)
|
// 新增商品-已登的店铺(带有Out的都非官方标准接口)
|
||||||
//
|
//
|
||||||
//export OutAddGoods
|
//export OutAddGoods
|
||||||
func OutAddGoods(token, proxy, formData *C.char) *C.char {
|
func OutAddGoods(token, proxy, formData *C.char) *C.char {
|
||||||
@ -3280,6 +3280,8 @@ func OutGetTopGoodsListMsg(catId C.int, proxy *C.char) *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化配置
|
||||||
|
//
|
||||||
//export Initialize
|
//export Initialize
|
||||||
func Initialize(configJSON *C.char) *C.char {
|
func Initialize(configJSON *C.char) *C.char {
|
||||||
configStr := C.GoString(configJSON)
|
configStr := C.GoString(configJSON)
|
||||||
|
|||||||
249
md/csv.md
Normal file
249
md/csv.md
Normal 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
199
md/image.md
Normal 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
1597
md/kongfz.md
Normal file
File diff suppressed because it is too large
Load Diff
736
md/main.md
736
md/main.md
@ -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
197
md/proxy.md
Normal 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
615
monitor/main.go
Normal 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 (从网络下载的数据)")
|
||||||
|
}
|
||||||
Binary file not shown.
124
proxy/dll/proxy.h
Normal file
124
proxy/dll/proxy.h
Normal 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
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// #include <stdlib.h>
|
/*
|
||||||
|
#include <stdlib.h>
|
||||||
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
@ -13,6 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 代理类型常量
|
// 代理类型常量
|
||||||
@ -322,6 +325,7 @@ func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
|
|||||||
return false, fmt.Errorf("时间格式错误: %v", err)
|
return false, fmt.Errorf("时间格式错误: %v", err)
|
||||||
}
|
}
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
// 卡密日期在当前日期之后
|
||||||
if targetTime.After(currentTime) {
|
if targetTime.After(currentTime) {
|
||||||
return true, nil
|
return true, nil
|
||||||
} else {
|
} else {
|
||||||
@ -450,6 +454,8 @@ func initProxy() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================== C 导出函数 =======================
|
||||||
|
|
||||||
// 导出函数:获取代理健康状态(用于调试)
|
// 导出函数:获取代理健康状态(用于调试)
|
||||||
//
|
//
|
||||||
//export GetProxyHealth
|
//export GetProxyHealth
|
||||||
@ -498,13 +504,194 @@ func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.cha
|
|||||||
return C.CString(proxyURL)
|
return C.CString(proxyURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
//// 导出函数:释放C字符串内存
|
// 导出函数:查询机器码
|
||||||
////
|
|
||||||
////export FreeCString
|
|
||||||
//func FreeCString(str *C.char) {
|
|
||||||
// C.free(unsafe.Pointer(str))
|
|
||||||
//}
|
|
||||||
//
|
//
|
||||||
//func main() {
|
//export GetMachineCode
|
||||||
|
func GetMachineCode(tailCardSecret *C.char) *C.char {
|
||||||
|
goTailCardSecret := C.GoString(tailCardSecret)
|
||||||
|
log.Printf("[DEBUG] 查询机器码调用: card=%s", goTailCardSecret)
|
||||||
|
|
||||||
|
resp, err := getMachineCode(goTailCardSecret)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||||
|
log.Printf("[ERROR] 查询机器码错误: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将响应转换为JSON
|
||||||
|
jsonData, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||||
|
log.Printf("[ERROR] 序列化机器码响应失败: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] 查询机器码成功: code=%d, machine_code=%s", resp.Code, resp.Data.MachineCode)
|
||||||
|
return C.CString(string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数:充值卡密
|
||||||
//
|
//
|
||||||
//}
|
//export RechargeCard
|
||||||
|
func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
|
||||||
|
goTailCardSecret := C.GoString(tailCardSecret)
|
||||||
|
goMachineCode := C.GoString(machineCode)
|
||||||
|
log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode)
|
||||||
|
|
||||||
|
newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||||
|
log.Printf("[ERROR] 充值卡密错误: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建成功响应
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"machine_code": newMachineCode,
|
||||||
|
"message": "充值成功",
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||||
|
log.Printf("[ERROR] 序列化充值响应失败: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode)
|
||||||
|
return C.CString(string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数:获取代理服务器列表
|
||||||
|
//
|
||||||
|
//export GetProxies
|
||||||
|
func GetProxies(machineCode *C.char) *C.char {
|
||||||
|
goMachineCode := C.GoString(machineCode)
|
||||||
|
log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode)
|
||||||
|
|
||||||
|
proxies, err := getProxies(goMachineCode)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||||
|
log.Printf("[ERROR] 获取代理服务器列表错误: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将代理列表转换为JSON
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"count": len(proxies),
|
||||||
|
"proxies": proxies,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||||
|
log.Printf("[ERROR] 序列化代理列表失败: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies))
|
||||||
|
return C.CString(string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数:检查卡密是否过期
|
||||||
|
//
|
||||||
|
//export CheckTailCardSecretExpired
|
||||||
|
func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
||||||
|
goTailCardSecret := C.GoString(tailCardSecret)
|
||||||
|
log.Printf("[DEBUG] 检查卡密是否过期调用: card=%s", goTailCardSecret)
|
||||||
|
|
||||||
|
isValid, err := checkTailCardSecretExpired(goTailCardSecret)
|
||||||
|
if err != nil {
|
||||||
|
// 如果是过期错误,返回特定格式
|
||||||
|
if strings.Contains(err.Error(), "卡密已经过期") {
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"is_valid": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(response)
|
||||||
|
return C.CString(string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||||
|
log.Printf("[ERROR] 检查卡密过期错误: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"is_valid": isValid,
|
||||||
|
"message": "卡密有效",
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] 检查卡密过期结果: is_valid=%v", isValid)
|
||||||
|
return C.CString(string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数:初始化代理管理器
|
||||||
|
//
|
||||||
|
//export InitProxyManager
|
||||||
|
func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
|
||||||
|
goServersJson := C.GoString(serversJson)
|
||||||
|
goUsername := C.GoString(username)
|
||||||
|
goPassword := C.GoString(password)
|
||||||
|
goTailCardSecret := C.GoString(tailCardSecret)
|
||||||
|
goProxyType := C.GoString(proxyType)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType)
|
||||||
|
|
||||||
|
// 解析服务器列表
|
||||||
|
var serverList []string
|
||||||
|
if goServersJson != "" {
|
||||||
|
if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err)
|
||||||
|
log.Printf("[ERROR] 解析服务器列表失败: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新全局服务器列表
|
||||||
|
if len(serverList) > 0 {
|
||||||
|
randMutex.Lock()
|
||||||
|
servers = serverList
|
||||||
|
randMutex.Unlock()
|
||||||
|
log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建代理管理器
|
||||||
|
manager := ProxyManager{
|
||||||
|
servers: servers,
|
||||||
|
username: goUsername,
|
||||||
|
password: goPassword,
|
||||||
|
tailCardSecret: goTailCardSecret,
|
||||||
|
proxyType: goProxyType,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化管理器信息
|
||||||
|
jsonData, err := json.Marshal(manager)
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err)
|
||||||
|
log.Printf("[ERROR] 序列化代理管理器失败: %v", err)
|
||||||
|
return C.CString(errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] 代理管理器初始化成功")
|
||||||
|
return C.CString(string(jsonData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数:释放C字符串内存
|
||||||
|
//
|
||||||
|
//export FreeCString
|
||||||
|
func FreeCString(str *C.char) {
|
||||||
|
C.free(unsafe.Pointer(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空main函数,编译DLL时需要
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
699
proxy/proxy_so.go
Normal file
699
proxy/proxy_so.go
Normal 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
BIN
so/kongfz.so
Normal file
Binary file not shown.
2386
zjdydll.go
2386
zjdydll.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user