更新修改功能

This commit is contained in:
Cai1Cai1 2025-12-31 17:56:22 +08:00
parent bd241447c6
commit d41bfcb4ca
33 changed files with 8650 additions and 4450 deletions

View File

@ -181,7 +181,7 @@ func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bo
} }
// 获取总行数 // 获取总行数
rows, err := csvHandle.GetTotalRows() rows, err := csvHandle.getTotalRows()
if err != nil { if err != nil {
return -1, err return -1, err
} }
@ -190,7 +190,7 @@ func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bo
// 读取表头(如果需要) // 读取表头(如果需要)
if hasHeader { if hasHeader {
if err := mgr.readHeader(csvHandle); err != nil { if err := mgr.readHeader(csvHandle); err != nil {
csvHandle.Close() csvHandle.close()
return -1, fmt.Errorf("读取表头失败: %w", err) return -1, fmt.Errorf("读取表头失败: %w", err)
} }
} }
@ -206,7 +206,7 @@ func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bo
return handleID, nil return handleID, nil
} }
// openFile 打开文件 // 打开文件
func (mgr *CSVManager) openFile(handle *CSVHandle) error { func (mgr *CSVManager) openFile(handle *CSVHandle) error {
handle.mu.Lock() handle.mu.Lock()
defer handle.mu.Unlock() defer handle.mu.Unlock()
@ -222,6 +222,7 @@ func (mgr *CSVManager) openFile(handle *CSVHandle) error {
} }
fileSize := fileInfo.Size() fileSize := fileInfo.Size()
fmt.Println("文件大小:", fileSize)
// 根据文件大小选择打开策略 // 根据文件大小选择打开策略
if mgr.config.UseMMap && fileSize > mgr.config.MMapThreshold { if mgr.config.UseMMap && fileSize > mgr.config.MMapThreshold {
@ -231,7 +232,7 @@ func (mgr *CSVManager) openFile(handle *CSVHandle) error {
return mgr.openFileNormal(handle) return mgr.openFileNormal(handle)
} }
// openFileNormal 正常打开文件 // 正常打开文件
func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error { func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error {
file, err := os.Open(handle.Filename) file, err := os.Open(handle.Filename)
if err != nil { if err != nil {
@ -251,7 +252,7 @@ func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error {
return nil return nil
} }
// openFileWithMMap 使用内存映射打开大文件 // 使用内存映射打开大文件
func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error { func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error {
file, err := os.Open(handle.Filename) file, err := os.Open(handle.Filename)
if err != nil { if err != nil {
@ -271,7 +272,7 @@ func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error
return nil return nil
} }
// readHeader 读取CSV表头 // 读取CSV表头
func (mgr *CSVManager) readHeader(handle *CSVHandle) error { func (mgr *CSVManager) readHeader(handle *CSVHandle) error {
handle.mu.Lock() handle.mu.Lock()
defer handle.mu.Unlock() defer handle.mu.Unlock()
@ -299,8 +300,8 @@ func (mgr *CSVManager) readHeader(handle *CSVHandle) error {
return nil return nil
} }
// GetHandle 获取句柄对象 // 获取句柄对象
func (mgr *CSVManager) GetHandle(handleID int64) (*CSVHandle, error) { func (mgr *CSVManager) getHandle(handleID int64) (*CSVHandle, error) {
value, ok := mgr.handles.Load(handleID) value, ok := mgr.handles.Load(handleID)
if !ok { if !ok {
return nil, fmt.Errorf("句柄不存在: %d", handleID) return nil, fmt.Errorf("句柄不存在: %d", handleID)
@ -326,10 +327,114 @@ func (mgr *CSVManager) GetHandle(handleID int64) (*CSVHandle, error) {
return handle, nil return handle, nil
} }
// ========================== 读取操作 ========================== // 启动自动关闭计时器
func (h *CSVHandle) startAutoClose(timeout time.Duration, mgr *CSVManager) {
h.autoCloseTimer = time.AfterFunc(timeout, func() {
h.mu.Lock()
defer h.mu.Unlock()
// ReadRow 读取一行数据 // 检查是否长时间未访问
func (h *CSVHandle) ReadRow() ([]string, error) { if h.IsOpen && time.Since(h.AccessTime) > timeout {
h.close()
// 从管理器移除
mgr.handles.Delete(h.ID)
}
})
}
// 获取表头
func (h *CSVHandle) getHeader() []string {
h.mu.RLock()
defer h.mu.RUnlock()
return h.Header
}
//// 获取句柄信息
//func (h *CSVHandle) getInfo() map[string]interface{} {
// h.mu.RLock()
// defer h.mu.RUnlock()
//
// return map[string]interface{}{
// "id": h.ID,
// "filename": h.Filename,
// "delimiter": string(h.Delimiter),
// "has_header": h.HasHeader,
// "is_open": h.IsOpen,
// "open_time": h.OpenTime,
// "access_time": h.AccessTime,
// "access_count": h.AccessCount,
// "total_rows": h.TotalRows,
// "header": h.Header,
// }
//}
// ========================== 句柄管理 ==========================
// 关闭CSV句柄
func (h *CSVHandle) close() error {
h.mu.Lock()
defer h.mu.Unlock()
h.writeMu.Lock()
defer h.writeMu.Unlock()
if !h.IsOpen {
return nil
}
// 停止自动关闭计时器
if h.autoCloseTimer != nil {
h.autoCloseTimer.Stop()
h.autoCloseTimer = nil
}
// 关闭文件
if h.File != nil {
if err := h.File.Close(); err != nil {
return err
}
h.File = nil
}
h.CSVReader = nil
h.IsOpen = false
return nil
}
// 关闭指定句柄
func (mgr *CSVManager) closeHandle(handleID int64) error {
value, ok := mgr.handles.Load(handleID)
if !ok {
return fmt.Errorf("句柄不存在: %d", handleID)
}
handle := value.(*CSVHandle)
if err := handle.close(); err != nil {
return err
}
// 清理文件锁
mgr.fileLocks.Delete(handle.Filename)
mgr.handles.Delete(handleID)
return nil
}
// 关闭所有句柄
func (mgr *CSVManager) closeAllHandles() {
mgr.handles.Range(func(key, value interface{}) bool {
handle := value.(*CSVHandle)
handle.close()
mgr.fileLocks.Delete(handle.Filename)
mgr.handles.Delete(key)
return true
})
}
// ========================== 读取操作 ==========================
// 读取一行数据
func (h *CSVHandle) readRow() ([]string, error) {
h.mu.RLock() h.mu.RLock()
defer h.mu.RUnlock() defer h.mu.RUnlock()
@ -344,8 +449,8 @@ func (h *CSVHandle) ReadRow() ([]string, error) {
return h.CSVReader.Read() return h.CSVReader.Read()
} }
// ReadAllRows 读取所有行 // 读取所有行
func (h *CSVHandle) ReadAllRows() ([][]string, error) { func (h *CSVHandle) readAllRows() ([][]string, error) {
h.mu.Lock() h.mu.Lock()
defer h.mu.Unlock() defer h.mu.Unlock()
@ -373,8 +478,8 @@ func (h *CSVHandle) ReadAllRows() ([][]string, error) {
return h.CSVReader.ReadAll() return h.CSVReader.ReadAll()
} }
// ReadRows 读取指定数量的行 // 读取指定数量的行
func (h *CSVHandle) ReadRows(count int) ([][]string, error) { func (h *CSVHandle) readRows(count int) ([][]string, error) {
h.mu.Lock() h.mu.Lock()
defer h.mu.Unlock() defer h.mu.Unlock()
@ -398,8 +503,8 @@ func (h *CSVHandle) ReadRows(count int) ([][]string, error) {
return rows, nil return rows, nil
} }
// GetTotalRows 获取总行数 // 获取总行数
func (h *CSVHandle) GetTotalRows() (int64, error) { func (h *CSVHandle) getTotalRows() (int64, error) {
h.mu.Lock() h.mu.Lock()
defer h.mu.Unlock() defer h.mu.Unlock()
@ -450,8 +555,53 @@ func (h *CSVHandle) GetTotalRows() (int64, error) {
// ========================== 修改操作 ========================== // ========================== 修改操作 ==========================
// ModifyRow 修改指定行 // 写入和覆盖CSV文件
func (mgr *CSVManager) ModifyRow(handleID int64, rowNumber int64, newRow []string) (*ModifyResult, error) { func (mgr *CSVManager) writeCSVFile(handleID int64, filename string, data [][]string) (int64, error) {
// 1. 获取句柄
handle, err := mgr.getHandle(handleID)
if err != nil {
return -1, err
}
if len(data) == 0 {
return -1, fmt.Errorf("写入的数据不能为空")
}
fileLock := mgr.getFileLock(handle.Filename)
fileLock.Lock()
defer fileLock.Unlock()
var file *os.File
var fileMode int
// 检查文件是否存在和有内容
if info, err := os.Stat(handle.Filename); err == nil && info.Size() > 0 {
// 文件存在且有内容,追加模式
fileMode = os.O_APPEND | os.O_WRONLY
} else {
// 文件不存在或为空,创建模式(会清空已有内容)
fileMode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
}
// 打开或创建文件
file, err = os.OpenFile(filename, fileMode, 0755)
if err != nil {
return -1, err
}
defer file.Close()
// 写入CSV数据
writer := csv.NewWriter(file)
if err := writer.WriteAll(data); err != nil {
return -1, err
}
writer.Flush()
return -1, writer.Error()
}
// 修改指定行
func (mgr *CSVManager) modifyRow(handleID int64, rowNumber int64, newRow []string) (*ModifyResult, error) {
req := &ModifyRequest{ req := &ModifyRequest{
HandleID: handleID, HandleID: handleID,
RowNumber: rowNumber, RowNumber: rowNumber,
@ -459,26 +609,13 @@ func (mgr *CSVManager) ModifyRow(handleID int64, rowNumber int64, newRow []strin
NewRow: newRow, NewRow: newRow,
} }
return mgr.ModifyCSV(req) return mgr.modifyCSV(req)
} }
// ModifyCell 修改指定单元格 // 通用的CSV修改函数
func (mgr *CSVManager) ModifyCell(handleID int64, rowNumber int64, columnIndex int, newValue string) (*ModifyResult, error) { func (mgr *CSVManager) modifyCSV(req *ModifyRequest) (*ModifyResult, error) {
req := &ModifyRequest{
HandleID: handleID,
RowNumber: rowNumber,
ModifyType: ModifyCell,
ColumnIndex: columnIndex,
NewCellValue: newValue,
}
return mgr.ModifyCSV(req)
}
// ModifyCSV 通用的CSV修改函数
func (mgr *CSVManager) ModifyCSV(req *ModifyRequest) (*ModifyResult, error) {
// 1. 获取句柄 // 1. 获取句柄
handle, err := mgr.GetHandle(req.HandleID) handle, err := mgr.getHandle(req.HandleID)
if err != nil { if err != nil {
return &ModifyResult{ return &ModifyResult{
Success: false, Success: false,
@ -507,7 +644,7 @@ func (mgr *CSVManager) ModifyCSV(req *ModifyRequest) (*ModifyResult, error) {
defer handle.writeMu.Unlock() defer handle.writeMu.Unlock()
// 6. 执行修改操作 // 6. 执行修改操作
result, err := mgr.executeModifyWithRetry(handle, req, operationID) result, err := mgr.executeModifyOnce(handle, req, operationID)
if result != nil { if result != nil {
result.OperationID = operationID result.OperationID = operationID
} }
@ -533,13 +670,6 @@ func (mgr *CSVManager) validateModifyRequestBeforeLock(handle *CSVHandle, req *M
} }
} }
if req.ModifyType == ModifyCell {
expectedCols := len(header)
if req.ColumnIndex < 0 || req.ColumnIndex >= expectedCols {
return fmt.Errorf("列索引超出范围,有效范围: 0-%d", expectedCols-1)
}
}
return nil return nil
} }
@ -592,15 +722,6 @@ func (mgr *CSVManager) executeModifyOnce(handle *CSVHandle, req *ModifyRequest,
}, err }, err
} }
//// 2. 获取总行数用于验证
//totalRows, err := mgr.calculateTotalRowsWithLock(handle)
//if err != nil {
// return &ModifyResult{
// Success: false,
// Message: fmt.Sprintf("获取总行数失败: %v", err),
// }, err
//}
// 3. 验证行号范围 // 3. 验证行号范围
if err := mgr.validateRowNumber(handle, req, totalRows); err != nil { if err := mgr.validateRowNumber(handle, req, totalRows); err != nil {
return &ModifyResult{ return &ModifyResult{
@ -800,6 +921,7 @@ func (mgr *CSVManager) createBackup(handle *CSVHandle) (string, error) {
} }
handle.tempDir = tempDir handle.tempDir = tempDir
} }
fmt.Println("handle.tempDir", handle.tempDir)
// 生成备份文件名 // 生成备份文件名
backupFile := filepath.Join(handle.tempDir, backupFile := filepath.Join(handle.tempDir,
@ -1305,7 +1427,7 @@ func (mgr *CSVManager) restoreFromBackup(handle *CSVHandle) error {
// GetRow 获取指定行数据 // GetRow 获取指定行数据
func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) { func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) {
handle, err := mgr.GetHandle(handleID) handle, err := mgr.getHandle(handleID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1320,7 +1442,7 @@ func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error)
// UndoLastModify 撤销最后一次修改 // UndoLastModify 撤销最后一次修改
func (mgr *CSVManager) UndoLastModify(handleID int64) (*ModifyResult, error) { func (mgr *CSVManager) UndoLastModify(handleID int64) (*ModifyResult, error) {
handle, err := mgr.GetHandle(handleID) handle, err := mgr.getHandle(handleID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1383,7 +1505,7 @@ func (mgr *CSVManager) UndoLastModify(handleID int64) (*ModifyResult, error) {
// CleanupBackups 清理备份文件 // CleanupBackups 清理备份文件
func (mgr *CSVManager) CleanupBackups(handleID int64) error { func (mgr *CSVManager) CleanupBackups(handleID int64) error {
handle, err := mgr.GetHandle(handleID) handle, err := mgr.getHandle(handleID)
if err != nil { if err != nil {
return err return err
} }
@ -1406,156 +1528,9 @@ func (mgr *CSVManager) CleanupBackups(handleID int64) error {
return nil return nil
} }
// ========================== 句柄管理 ==========================
// Close 关闭CSV句柄
func (h *CSVHandle) Close() error {
h.mu.Lock()
defer h.mu.Unlock()
h.writeMu.Lock()
defer h.writeMu.Unlock()
if !h.IsOpen {
return nil
}
// 停止自动关闭计时器
if h.autoCloseTimer != nil {
h.autoCloseTimer.Stop()
h.autoCloseTimer = nil
}
// 关闭文件
if h.File != nil {
if err := h.File.Close(); err != nil {
return err
}
h.File = nil
}
h.CSVReader = nil
h.IsOpen = false
return nil
}
// startAutoClose 启动自动关闭计时器
func (h *CSVHandle) startAutoClose(timeout time.Duration, mgr *CSVManager) {
h.autoCloseTimer = time.AfterFunc(timeout, func() {
h.mu.Lock()
defer h.mu.Unlock()
// 检查是否长时间未访问
if h.IsOpen && time.Since(h.AccessTime) > timeout {
h.Close()
// 从管理器移除
mgr.handles.Delete(h.ID)
}
})
}
// GetHeader 获取表头
func (h *CSVHandle) GetHeader() []string {
h.mu.RLock()
defer h.mu.RUnlock()
return h.Header
}
// GetInfo 获取句柄信息
func (h *CSVHandle) GetInfo() map[string]interface{} {
h.mu.RLock()
defer h.mu.RUnlock()
return map[string]interface{}{
"id": h.ID,
"filename": h.Filename,
"delimiter": string(h.Delimiter),
"has_header": h.HasHeader,
"is_open": h.IsOpen,
"open_time": h.OpenTime,
"access_time": h.AccessTime,
"access_count": h.AccessCount,
"total_rows": h.TotalRows,
"header": h.Header,
}
}
// CloseHandle 关闭指定句柄
func (mgr *CSVManager) CloseHandle(handleID int64) error {
value, ok := mgr.handles.Load(handleID)
if !ok {
return fmt.Errorf("句柄不存在: %d", handleID)
}
handle := value.(*CSVHandle)
if err := handle.Close(); err != nil {
return err
}
// 清理文件锁
mgr.fileLocks.Delete(handle.Filename)
mgr.handles.Delete(handleID)
return nil
}
// CloseAllHandles 关闭所有句柄
func (mgr *CSVManager) CloseAllHandles() {
mgr.handles.Range(func(key, value interface{}) bool {
handle := value.(*CSVHandle)
handle.Close()
mgr.fileLocks.Delete(handle.Filename)
mgr.handles.Delete(key)
return true
})
}
// ========================== 简便函数 ==========================
//// OpenCSVFile 打开CSV文件
//func OpenCSVFile(filename string, delimiter rune, hasHeader bool) (int64, error) {
// return GetManager().OpenCSVFile(filename, delimiter, hasHeader, "utf-8")
//}
// GetCSVHandle 获取CSV句柄
func GetCSVHandle(handleID int64) (*CSVHandle, error) {
return GetManager().GetHandle(handleID)
}
// CloseCSVHandle 关闭CSV句柄
func CloseCSVHandle(handleID int64) error {
return GetManager().CloseHandle(handleID)
}
// ModifyCSVRow 修改CSV行
func ModifyCSVRow(handleID int64, rowNumber int64, newRow []string) (*ModifyResult, error) {
return GetManager().ModifyRow(handleID, rowNumber, newRow)
}
// ModifyCSVCell 修改CSV单元格
func ModifyCSVCell(handleID int64, rowNumber int64, columnIndex int, newValue string) (*ModifyResult, error) {
return GetManager().ModifyCell(handleID, rowNumber, columnIndex, newValue)
}
// GetCSVRow 获取CSV行
func GetCSVRow(handleID int64, rowNumber int64) ([]string, error) {
return GetManager().GetRow(handleID, rowNumber)
}
// UndoLastCSVModify 撤销最后修改
func UndoLastCSVModify(handleID int64) (*ModifyResult, error) {
return GetManager().UndoLastModify(handleID)
}
// CleanupCSVBackups 清理CSV备份
func CleanupCSVBackups(handleID int64) error {
return GetManager().CleanupBackups(handleID)
}
// UpdateCSVRowSafe 修改csv文件行数据 // UpdateCSVRowSafe 修改csv文件行数据
func (mgr *CSVManager) UpdateCSVRowSafe(handleID int64, rowNum int, newRow []string) error { func (mgr *CSVManager) UpdateCSVRowSafe(handleID int64, rowNum int, newRow []string) error {
getHandle, err := mgr.GetHandle(handleID) getHandle, err := mgr.getHandle(handleID)
if err != nil { if err != nil {
return fmt.Errorf("获取句柄信息失败: %v", err) return fmt.Errorf("获取句柄信息失败: %v", err)
} }
@ -1764,44 +1739,52 @@ func UpdateCSVRowSafe(handleID C.longlong, rowNum C.int, newRow *C.char) *C.char
// 主函数 // 主函数
func main() { func main() {
//fmt.Println("=== CSV句柄管理器测试 ===") fmt.Println("=== CSV句柄管理器测试 ===")
//
//filename := "csv/taskLog.csv" filename := "csv/taskLog1.csv"
//fmt.Printf("1. 创建测试文件: %s\n", filename) fmt.Printf("1. 创建测试文件: %s\n", filename)
//
//// 2. 打开CSV文件 // 2. 打开CSV文件
//handleID, err := OpenCSVFile(filename, ',', true) handleID, err := GetManager().OpenCSVFile(filename, ',', true)
//if err != nil { if err != nil {
// fmt.Printf("打开文件失败: %v\n", err) fmt.Printf("打开文件失败: %v\n", err)
// return return
//} }
//defer CloseCSVHandle(handleID) defer GetManager().closeHandle(handleID)
//
newStr := []string{"9787115524539", "20.00", "2", "上传成功", ""}
row, err := GetManager().modifyRow(handleID, 3, newStr)
if err != nil {
fmt.Println(err)
}
fmt.Println(row)
//fmt.Printf("2. 打开文件成功句柄ID: %d\n", handleID) //fmt.Printf("2. 打开文件成功句柄ID: %d\n", handleID)
// //
////// 3. 获取句柄信息 //// 3. 获取句柄信息
////handle, err := GetCSVHandle(handleID) //handle, err := GetCSVHandle(handleID)
////if err != nil { //if err != nil {
//// fmt.Printf("获取句柄失败: %v\n", err) // fmt.Printf("获取句柄失败: %v\n", err)
//// return // return
////} //}
////
////info := handle.GetInfo()
////fmt.Printf("3. 文件信息:\n")
////fmt.Printf(" - 文件名: %s\n", info["filename"])
////fmt.Printf(" - 总行数: %v\n", info["total_rows"])
////fmt.Printf(" - 表头: %v\n", info["header"])
// //
////// 准备新的行数据 //info := handle.GetInfo()
////newData := []string{"9787115524539", "20.00", "20", "上传成功", ""} //fmt.Printf("3. 文件信息:\n")
////err = UpdateCSVRowSafes(handleID, 6, newData) //fmt.Printf(" - 文件名: %s\n", info["filename"])
////if err != nil { //fmt.Printf(" - 总行数: %v\n", info["total_rows"])
//// fmt.Printf("错误: %v\n", err) //fmt.Printf(" - 表头: %v\n", info["header"])
////} else {
//// fmt.Println("CSV文件更新成功")
////}
// //
//// 4. 读取数据 //// 准备新的行数据
//newData := []string{"9787115524539", "20.00", "20", "上传成功", ""}
//err = UpdateCSVRowSafes(handleID, 6, newData)
//if err != nil {
// fmt.Printf("错误: %v\n", err)
//} else {
// fmt.Println("CSV文件更新成功")
//}
//
////4. 读取数据
//fmt.Println("4. 读取测试:") //fmt.Println("4. 读取测试:")
//row3, err := GetCSVRow(handleID, 100000) //row3, err := GetCSVRow(handleID, 100000)
//if err != nil { //if err != nil {
@ -1810,72 +1793,36 @@ func main() {
// fmt.Printf(" 第100000行数据: %v\n", row3) // fmt.Printf(" 第100000行数据: %v\n", row3)
//} //}
// //
////// 5. 修改测试 //// 5. 修改测试
////fmt.Println("5. 修改测试:") //fmt.Println("5. 修改测试:")
////newRow := []string{"9787115524539", "20.00", "1", "上传成功", ""} //newRow := []string{"9787115524539", "20.00", "1", "上传成功", ""}
////result, err := ModifyCSVRow(handleID, 3, newRow) //result, err := ModifyCSVRow(handleID, 3, newRow)
////if err != nil { //if err != nil {
//// fmt.Printf(" 修改失败: %v\n", err) // fmt.Printf(" 修改失败: %v\n", err)
////} else { //} else {
//// fmt.Printf(" 修改结果: %s (影响行数: %d)\n", result.Message, result.RowsAffected) // fmt.Printf(" 修改结果: %s (影响行数: %d)\n", result.Message, result.RowsAffected)
////
//// // 验证修改
//// modifiedRow, _ := GetCSVRow(handleID, 3)
//// fmt.Printf(" 修改后的第3行: %v\n", modifiedRow)
////}
// //
////// 6. 单元格修改测试 // // 验证修改
////fmt.Println("6. 单元格修改测试:") // modifiedRow, _ := GetCSVRow(handleID, 3)
////cellResult, err := ModifyCSVCell(handleID, 5, 1, "35") // fmt.Printf(" 修改后的第3行: %v\n", modifiedRow)
////if err != nil { //}
//// fmt.Printf(" 单元格修改失败: %v\n", err) //
////} else { //// 6. 单元格修改测试
//// fmt.Printf(" 单元格修改结果: %s\n", cellResult.Message) //fmt.Println("6. 单元格修改测试:")
//// //cellResult, err := ModifyCSVCell(handleID, 5, 1, "35")
//// // 验证修改 //if err != nil {
//// cellModifiedRow, _ := GetCSVRow(handleID, 5) // fmt.Printf(" 单元格修改失败: %v\n", err)
//// fmt.Printf(" 修改后的第5行: %v\n", cellModifiedRow) //} else {
////} // fmt.Printf(" 单元格修改结果: %s\n", cellResult.Message)
//// //
////// 7. 并发测试 // // 验证修改
////fmt.Println("7. 并发测试:") // cellModifiedRow, _ := GetCSVRow(handleID, 5)
////TestConcurrentOperations(handleID) // fmt.Printf(" 修改后的第5行: %v\n", cellModifiedRow)
//// //}
////fmt.Println("=== 测试完成 ===") //
} //// 7. 并发测试
//fmt.Println("7. 并发测试:")
// 下面是一个更简单的版本,根据你的具体需求可以选择使用 //TestConcurrentOperations(handleID)
func WriteCSVSimple(filename string, data [][]string) error { //
if len(data) == 0 { //fmt.Println("=== 测试完成 ===")
return fmt.Errorf("写入的数据不能为空")
}
var file *os.File
var err error
var fileMode int
// 检查文件是否存在和有内容
if info, err := os.Stat(filename); err == nil && info.Size() > 0 {
// 文件存在且有内容,追加模式
fileMode = os.O_APPEND | os.O_WRONLY
} else {
// 文件不存在或为空,创建模式(会清空已有内容)
fileMode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
}
// 打开或创建文件
file, err = os.OpenFile(filename, fileMode, 0755)
if err != nil {
return err
}
defer file.Close()
// 写入CSV数据
writer := csv.NewWriter(file)
if err := writer.WriteAll(data); err != nil {
return err
}
writer.Flush()
return writer.Error()
} }

View File

@ -320,58 +320,6 @@ func handleOpenCSVFile(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(response) 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读取的数据 // 解码从DLL读取的数据
func decodeRowData(buffer []byte, maxBytes int) [][]string { func decodeRowData(buffer []byte, maxBytes int) [][]string {
var rows [][]string var rows [][]string
@ -411,3 +359,91 @@ func decodeRowData(buffer []byte, maxBytes int) [][]string {
return rows return rows
} }
//func main() {
// 使用dll文件
//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))
//file, err := GetManager().OpenCSVFile("csv/taskLog1.csv", ',', true)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(file)
//handle, err := GetManager().getHandle(2)
//if err != nil {
// fmt.Println(err)
//}
// 获取指定数量的行
//row, err := handle.readRows(100)
//if err != nil {
// fmt.Println(err)
//}
//for _, i := range row {
// fmt.Println(i)
//}
//// 获取总行数
//row, err := handle.getTotalRows()
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(row)
//newRow := []string{
// "9787107267505", "20.00", "10", "上传成功", "877133619369",
//}
//
//row, err := GetManager().modifyRow(file, 1, newRow)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(row)
//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("服务器已关闭")
//}

File diff suppressed because it is too large Load Diff

View File

@ -657,6 +657,7 @@ package main
// } // }
// //
// fmt.Printf("✅ 成功更新第 %d 行,文件已直接更新\n", rowNum) // fmt.Printf("✅ 成功更新第 %d 行,文件已直接更新\n", rowNum)
//
// return nil // return nil
//} //}
// //
@ -1220,6 +1221,6 @@ package main
// C.free(unsafe.Pointer(str)) // C.free(unsafe.Pointer(str))
//} //}
// //
////// main 函数是必需的,即使为空 //// main 函数是必需的,即使为空
////func main() { //func main() {
////} //}

View File

@ -1,54 +0,0 @@
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
es/dll/es.dll Normal file

Binary file not shown.

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

@ -0,0 +1,141 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h>
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
extern size_t _GoStringLen(_GoString_ s);
extern const char *_GoStringPtr(_GoString_ s);
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "es.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
// ListAllIndices 查询所有索引
//
extern __declspec(dllexport) char* ListAllIndices(void);
// GetIndicesInfo 获取所有索引的详细信息
//
extern __declspec(dllexport) char* GetIndicesInfo(void);
// GetIndexDetail 获取单个索引的详细信息
//
extern __declspec(dllexport) char* GetIndexDetail(char* indexName);
// CreateIndex 创建索引(如果不存在)
//
extern __declspec(dllexport) char* CreateIndex(char* indexName, char* mapping);
// DeleteIndex 删除索引
//
extern __declspec(dllexport) char* DeleteIndex(char* indexName);
// GetDocumentCount 获取索引文档数量
//
extern __declspec(dllexport) char* GetDocumentCount(char* indexName);
// CreateDocument 创建文档
//
extern __declspec(dllexport) char* CreateDocument(char* indexName, char* id, char* doc);
// GetDocument 根据ID获取文档
//
extern __declspec(dllexport) char* GetDocument(char* indexName, char* id);
// UpdateDocument 更新文档
//
extern __declspec(dllexport) char* UpdateDocument(char* indexName, char* id, char* updateData);
// DeleteDocument 删除文档
//
extern __declspec(dllexport) char* DeleteDocument(char* indexName, char* id);
// SearchDocuments 搜索文档
//
extern __declspec(dllexport) char* SearchDocuments(char* indexName, char* query);
// 释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

1088
es/es.go

File diff suppressed because it is too large Load Diff

133
es/esDll.go Normal file
View File

@ -0,0 +1,133 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// EsDLL Elasticsearch工具DLL结构
type esDLL struct {
dll *syscall.DLL
listAllIndices *syscall.Proc // 查询所有索引
getIndicesInfo *syscall.Proc // 获取所有索引的详细信息
getIndexDetail *syscall.Proc // 获取单个索引的详细信息
createIndex *syscall.Proc // 创建索引
deleteIndex *syscall.Proc // 删除索引
getDocumentCount *syscall.Proc // 获取索引文档数量
createDocument *syscall.Proc // 创建文档
getDocument *syscall.Proc // 根据ID获取文档
updateDocument *syscall.Proc // 更新文档
deleteDocument *syscall.Proc // 删除文档
searchDocuments *syscall.Proc // 搜索文档
freeCString *syscall.Proc // 释放C字符串
}
// 初始化esDLL
func InitEsDLL() (*esDLL, error) {
dllPath := filepath.Join("es", "dll", "es.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("es DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载es DLL 失败: %s", err)
} else {
return &esDLL{
dll: dll,
listAllIndices: dll.MustFindProc("ListAllIndices"),
getIndicesInfo: dll.MustFindProc("GetIndicesInfo"),
getIndexDetail: dll.MustFindProc("GetIndexDetail"),
createIndex: dll.MustFindProc("CreateIndex"),
deleteIndex: dll.MustFindProc("DeleteIndex"),
getDocumentCount: dll.MustFindProc("GetDocumentCount"),
createDocument: dll.MustFindProc("CreateDocument"),
getDocument: dll.MustFindProc("GetDocument"),
updateDocument: dll.MustFindProc("UpdateDocument"),
deleteDocument: dll.MustFindProc("DeleteDocument"),
searchDocuments: dll.MustFindProc("SearchDocuments"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
// cStr 获取C字符串
func (m *esDLL) 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
}
// 查询所有索引
func (m *esDLL) ListAllIndices() (string, error) {
proc, err := m.dll.FindProc("ListAllIndices")
if err != nil {
return "", fmt.Errorf("找不到函数 ListAllIndices: %v", err)
}
resultPtr, _, _ := proc.Call()
result := m.cStr(resultPtr)
return result, nil
}
func (m *esDLL) CreateDocument(indexName string, id string, doc interface{}) (string, error) {
proc, err := m.dll.FindProc("CreateDocument")
if err != nil {
return "", fmt.Errorf("找不到函数 CreateDocument: %v", err)
}
docJson, err := json.Marshal(doc)
if err != nil {
return "", err
}
indexNamePtr, _ := syscall.BytePtrFromString(indexName)
idPtr, _ := syscall.BytePtrFromString(id)
docJsonPtr, _ := syscall.BytePtrFromString(string(docJson))
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(indexNamePtr)),
uintptr(unsafe.Pointer(idPtr)),
uintptr(unsafe.Pointer(docJsonPtr)),
)
result := m.cStr(resultPtr)
return result, nil
}
//func main() {
// dll, err := InitEsDLL()
// if err != nil {
// fmt.Println(err)
// }
// //indices, err := dll.ListAllIndices()
// //if err != nil {
// // fmt.Println(err)
// //}
// //fmt.Println(string(indices))
//
// doc := Document{
// ID: "10003",
// Title: "测试文档3",
// Content: "这是一个测试文档3",
// Author: "测试员3",
// CreatedAt: time.Now(),
// Tags: []string{"测试3", "文档3"},
// }
// document, err := dll.CreateDocument("test-cc", doc.ID, doc)
// if err != nil {
// fmt.Println(err)
// }
// fmt.Println(document)
//}

4172
es/main.go

File diff suppressed because it is too large Load Diff

207
es/test.go Normal file
View File

@ -0,0 +1,207 @@
package main
import (
"time"
)
//func main() {
//client, err := newESClient([]string{esAddress}, esUsername, esPassword)
//if err != nil {
// fmt.Println(err.Error())
//}
//fmt.Println(client)
//
//// 获取所有索引
//indices, err := client.listAllIndices()
//if err != nil {
// fmt.Println(err.Error())
//}
//for _, i := range indices {
// fmt.Println("索引:", i)
//}
//fmt.Println("所有索引:", indices)
// // 获取所有索引的详细信息
// //infos, err := client.GetIndicesInfo()
// //if err != nil {
// // fmt.Println(err.Error())
// //}
// //infosJson, _ := json.Marshal(infos)
// //fmt.Println("所有索引的详细信息:", string(infosJson))
//
// // 获取索引设置
// //settings, err := client.GetIndexSettings("test-go-index")
// //if err != nil {
// // fmt.Println(err.Error())
// //}
// //marshal, _ := json.Marshal(settings)
// //fmt.Println("marshal:", string(marshal))
//
// // 创建索引
// // 创建索引映射
// //mapping := `{
// // "settings": {
// // "number_of_shards": 1,
// // "number_of_replicas": 1,
// // "analysis": {
// // "analyzer": {
// // "default": {
// // "type": "standard"
// // }
// // }
// // }
// // },
// // "mappings": {
// // "properties": {
// // "id":{
// // "type":"integer"
// // },
// // "title": {
// // "type": "text",
// // "analyzer": "standard",
// // "search_analyzer": "standard"
// // },
// // "content": {
// // "type": "text",
// // "analyzer": "standard",
// // "search_analyzer": "standard"
// // },
// // "author": {
// // "type": "keyword"
// // },
// // "created_at": {
// // "type": "date"
// // },
// // "tags": {
// // "type": "keyword"
// // }
// // }
// // }
// //}`
// //index, err := client.CreateIndex("test-cc", mapping)
// //if err != nil {
// // fmt.Println(err.Error())
// //}
// //marshal, _ := json.Marshal(index)
// //fmt.Println("marshal:", string(marshal))
//
// 删除索引
//err = client.deleteIndex("test-go-index")
//if err != nil {
// fmt.Println(err.Error())
//}
//
// // 修改索引设置
// //newSettings := map[string]interface{}{
// // "index": map[string]interface{}{
// // "refresh_interval": "5s",
// // "max_result_window": 10000,
// // },
// //}
// //err = client.UpdateIndexSettings("test-cc", newSettings)
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// // 更新映射
// //newMappings := map[string]interface{}{
// // "views": map[string]interface{}{
// // "type": "integer",
// // },
// //}
// //err = client.UpdateIndexMappings("test-cc", newMappings)
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// // 关闭索引
// //err = client.CloseIndex("test-cc")
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// //// 打开索引
// //err = client.OpenIndex("test-cc")
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// // 获取文档数量
// //count, err := client.GetDocumentCount("test-cc")
// //if err != nil {
// // fmt.Println(err.Error())
// //}
// //fmt.Println("文档数量: ", count)
//
// // 创建文档
// //doc := Document{
// // ID: "10002",
// // Title: "测试文档2",
// // Content: "这是一个测试文档2",
// // Author: "测试员2",
// // CreatedAt: time.Now(),
// // Tags: []string{"测试2", "文档2"},
// //}
// //err = client.CreateDocument("test-cc", doc.ID, doc)
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// // 更新文档
// //err = client.UpdateDocument("test-cc", "10002", map[string]interface{}{
// // "title": "测试文档2",
// // "content": "这是一个测试文档2",
// //})
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// // 删除文档
// //err = client.DeleteDocument("test-cc", "10001")
// //if err != nil {
// // fmt.Println(err.Error())
// //}
//
// // 搜索文档
// //query := map[string]interface{}{
// // "query": map[string]interface{}{
// // "match": map[string]interface{}{
// // "id": "10000",
// // },
// // },
// // "size": 100,
// //}
// //documents, err := client.SearchDocuments("test-cc", query)
// //if err != nil {
// // fmt.Println(err.Error())
// //}
// //documentsJson, _ := json.Marshal(documents)
// //fmt.Println(string(documentsJson))
//
// // 获取文档信息
// //document, err := client.GetDocument("test-cc", "10000")
// //if err != nil {
// // fmt.Println(err.Error())
// //}
// //bytes, _ := json.Marshal(document)
// //fmt.Println("获取文档信息", string(bytes))
//
// // 获取单个索引
// detail, err := client.getIndexDetail("test-cc")
// if err != nil {
// fmt.Println(err.Error())
// }
// detailJson, _ := json.Marshal(detail)
// fmt.Println("单个索引的所有信息:", string(detailJson))
//}
// Document 示例文档结构
type Document struct {
ID string `json:"id,omitempty"`
Title string `json:"title"`
Content string `json:"content"`
Author string `json:"author"`
CreatedAt time.Time `json:"created_at"`
Tags []string `json:"tags"`
}

29
gin/gin.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"github.com/gin-gonic/gin"
//"net/http"
)
func main() {
// 创建 Gin 实例
r := gin.Default()
// 定义路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// XML 响应
r.GET("/xml", func(c *gin.Context) {
c.XML(200, gin.H{
"message": "hello",
"status": "success",
})
})
// 启动服务
r.Run(":8080") // 默认监听 8080 端口
}

31
go.mod
View File

@ -7,9 +7,12 @@ require (
github.com/chromedp/chromedp v0.14.2 github.com/chromedp/chromedp v0.14.2
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/elastic/go-elasticsearch/v8 v8.19.0 github.com/elastic/go-elasticsearch/v8 v8.19.0
github.com/gin-gonic/gin v1.11.0
github.com/go-sql-driver/mysql v1.9.3 github.com/go-sql-driver/mysql v1.9.3
github.com/makiuchi-d/gozxing v0.1.1
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 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/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/xuri/excelize/v2 v2.10.0 github.com/xuri/excelize/v2 v2.10.0
golang.org/x/image v0.25.0 golang.org/x/image v0.25.0
golang.org/x/sys v0.37.0 golang.org/x/sys v0.37.0
@ -18,28 +21,56 @@ require (
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // 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/cloudwego/base64x v0.1.6 // indirect
github.com/elastic/elastic-transport-go/v8 v8.7.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/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.1.0 // 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/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // 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/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/moul/http2curl v1.0.0 // indirect github.com/moul/http2curl v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps 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/tiendc/go-deepcopy v1.7.1 // indirect github.com/tiendc/go-deepcopy v1.7.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/xuri/efp v0.0.1 // indirect github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.43.0 // indirect golang.org/x/crypto v0.43.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.46.0 // indirect golang.org/x/net v0.46.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.36.9 // indirect
) )

78
go.sum
View File

@ -4,12 +4,19 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E= github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E=
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM= github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM=
github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo= github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
@ -20,6 +27,12 @@ github.com/elastic/go-elasticsearch/v8 v8.19.0 h1:VmfBLNRORY7RZL+9hTxBD97ehl9H8N
github.com/elastic/go-elasticsearch/v8 v8.19.0/go.mod h1:F3j9e+BubmKvzvLjNui/1++nJuJxbkhHefbaT0kFKGY= 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/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -27,6 +40,14 @@ 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/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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
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=
@ -35,14 +56,34 @@ 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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
@ -51,23 +92,42 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
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=
github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= 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/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.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 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/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 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= 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 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
@ -83,6 +143,10 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= 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 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
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=
@ -99,6 +163,8 @@ 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=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@ -117,6 +183,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -157,6 +225,14 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -6,6 +6,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
"github.com/nfnt/resize" "github.com/nfnt/resize"
"golang.org/x/image/draw" "golang.org/x/image/draw"
"image" "image"
@ -14,6 +16,7 @@ import (
_ "image/jpeg" _ "image/jpeg"
"image/png" "image/png"
_ "image/png" _ "image/png"
"math"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -22,16 +25,18 @@ import (
// Config 配置结构体 // Config 配置结构体
type Config struct { type Config struct {
OutputDir string // 输出目录路径 OutputDir string // 输出目录路径
FileName string // 文件名 FileName string // 文件名
MatchDir string // 满足条件的图片目录名 MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名 UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名 EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名 WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名 WhiteBorderPngDir string // 去白边转PNG的图片目录名
MinWhitePct float64 // 纯白占比下限0-1 WhiteHeightZoomDir string // 缩放的图片目录
MaxWhitePct float64 // 纯白占比上限0-1 CropDir string // 裁切的图片目录
Extensions []string // 支持的图片扩展名 MinWhitePct float64 // 纯白占比下限0-1
MaxWhitePct float64 // 纯白占比上限0-1
Extensions []string // 支持的图片扩展名
} }
// 检查图片 // 检查图片
@ -52,6 +57,7 @@ func validateConfig(config *Config) error {
return nil return nil
} }
// 创建目录功能
func createDirs(config *Config) error { func createDirs(config *Config) error {
// 创建输出根目录 // 创建输出根目录
if err := os.MkdirAll(config.OutputDir, 0755); err != nil { if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
@ -84,6 +90,16 @@ func createDirs(config *Config) error {
if err := os.MkdirAll(whiteBorderPngPath, 0755); err != nil { if err := os.MkdirAll(whiteBorderPngPath, 0755); err != nil {
return err return err
} }
whiteHeightZoomPath := filepath.Join(config.OutputDir, config.WhiteHeightZoomDir)
if err := os.MkdirAll(whiteHeightZoomPath, 0755); err != nil {
return err
}
cropPath := filepath.Join(config.OutputDir, config.CropDir)
if err := os.MkdirAll(cropPath, 0755); err != nil {
return err
}
return nil return nil
} }
@ -503,6 +519,596 @@ func (c *ImageToPNGConverter) isBackgroundColor(pixel color.Color, hasAlpha bool
b8 >= threshold8 b8 >= threshold8
} }
// 图片缩放
func resizeWTToHeightQuality(config *Config, dsWidth, dsHeight 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)
}
// 使用Lanczos3算法缩放图片
optimized := resizeImageOptimized(img, dsWidth, dsHeight)
// 保存图片到指定目录下
filename := filepath.Base(config.FileName)
destPath := filepath.Join(config.OutputDir, config.WhiteHeightZoomDir, filename)
err = saveImage(destPath, optimized, format)
if err != nil {
return "", fmt.Errorf("保存图片失败: %v", err)
}
return destPath, nil
}
// 使用Lanczos3算法缩放图片
func resizeImageOptimized(src image.Image, dstWidth, dstHeight int) image.Image {
srcBounds := src.Bounds()
srcWidth := srcBounds.Dx()
srcHeight := srcBounds.Dy()
// 创建目标图片
dst := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight))
// 计算缩放比例
xScale := float64(srcWidth) / float64(dstWidth)
yScale := float64(srcHeight) / float64(dstHeight)
// Lanczos3算法半径
radius := 3.0
// 预计算x方向的权重
xWeights := make([][]float64, dstWidth)
xIndices := make([][]int, dstWidth)
for x := 0; x < dstWidth; x++ {
srcX := float64(x) * xScale
startX := int(math.Max(0, math.Floor(srcX-radius+0.5)))
endX := int(math.Min(float64(srcWidth-1), math.Floor(srcX+radius)))
weights := make([]float64, 0, endX-startX+1)
indices := make([]int, 0, endX-startX+1)
for sx := startX; sx <= endX; sx++ {
xDist := float64(sx) + 0.5 - srcX
weight := lanczos3(xDist)
if weight != 0 {
weights = append(weights, weight)
indices = append(indices, sx)
}
}
xWeights[x] = weights
xIndices[x] = indices
}
// 处理每一行
for y := 0; y < dstHeight; y++ {
srcY := float64(y) * yScale
startY := int(math.Max(0, math.Floor(srcY-radius+0.5)))
endY := int(math.Min(float64(srcHeight-1), math.Floor(srcY+radius)))
// 预计算y方向的权重
yWeights := make([]float64, 0, endY-startY+1)
yIndices := make([]int, 0, endY-startY+1)
for sy := startY; sy <= endY; sy++ {
yDist := float64(sy) + 0.5 - srcY
weight := lanczos3(yDist)
if weight != 0 {
yWeights = append(yWeights, weight)
yIndices = append(yIndices, sy)
}
}
// 处理每一列
for x := 0; x < dstWidth; x++ {
var rSum, gSum, bSum, aSum, weightSum float64
// 应用预计算的权重
for i, sy := range yIndices {
yWeight := yWeights[i]
for j, sx := range xIndices[x] {
xWeight := xWeights[x][j]
weight := xWeight * yWeight
// 获取源像素颜色
srcColor := src.At(sx+srcBounds.Min.X, sy+srcBounds.Min.Y)
r, g, b, a := srcColor.RGBA()
// 累加加权颜色值
rSum += float64(r>>8) * weight
gSum += float64(g>>8) * weight
bSum += float64(b>>8) * weight
aSum += float64(a>>8) * weight
weightSum += weight
}
}
// 防止除以零
if weightSum == 0 {
weightSum = 1
}
// 计算最终颜色值
r := clamp(rSum / weightSum)
g := clamp(gSum / weightSum)
b := clamp(bSum / weightSum)
a := clamp(aSum / weightSum)
// 设置目标像素
dst.SetRGBA(x, y, color.RGBA{r, g, b, a})
}
}
return dst
}
// 函数计算Lanczos3核函数值
func lanczos3(x float64) float64 {
if x == 0 {
return 1.0
}
if x < -3 || x > 3 {
return 0.0
}
return (3 * math.Sin(math.Pi*x) * math.Sin(math.Pi*x/3)) / (math.Pi * math.Pi * x * x)
}
// 将值限制在0-255范围内
func clamp(v float64) uint8 {
if v < 0 {
return 0
}
if v > 255 {
return 255
}
return uint8(v + 0.5)
}
// 图片裁切
func cropImage(config *Config, x, y, 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)
}
fmt.Printf("输入图片: %s (%dx%d, 格式: %s)\n",
config.FileName, img.Bounds().Dx(), img.Bounds().Dy(), format)
imgFile, err := basicCrop(img, x, y, width, height)
if err != nil {
return "", err
}
//// 执行裁切
//var cropped image.Image
//if width > 0 && height > 0 {
// cropped, err = SmartCrop(img, x, y, width, height int)
//} else {
// // 如果未指定尺寸,使用中心裁切
// cropped, err = CropCenter(img, config.Width, config.Height)
//}
//
//if err != nil {
// return "", err
//}
//
// 保存图片到指定目录下
filename := filepath.Base(config.FileName)
destPath := filepath.Join(config.OutputDir, config.CropDir, filename)
err = saveImage(destPath, imgFile, format)
if err != nil {
return "", fmt.Errorf("保存图片失败: %v", err)
}
return destPath, nil
}
// basicCrop 基础裁切功能
func basicCrop(src image.Image, x, y, width, height int) (image.Image, error) {
srcBounds := src.Bounds()
srcWidth := srcBounds.Dx()
srcHeight := srcBounds.Dy()
// 验证裁切参数
if x < 0 || y < 0 || width <= 0 || height <= 0 {
return nil, fmt.Errorf("invalid crop parameters: x=%d, y=%d, width=%d, height=%d",
x, y, width, height)
}
if x >= srcWidth || y >= srcHeight {
return nil, fmt.Errorf("crop start point (%d, %d) is outside image bounds (%dx%d)",
x, y, srcWidth, srcHeight)
}
// 调整裁切尺寸以避免超出边界
if x+width > srcWidth {
width = srcWidth - x
}
if y+height > srcHeight {
height = srcHeight - y
}
// 从源图像中裁切
cropped := image.NewRGBA(image.Rect(0, 0, width, height))
for cy := 0; cy < height; cy++ {
for cx := 0; cx < width; cx++ {
cropped.Set(cx, cy, src.At(x+cx, y+cy))
}
}
return cropped, nil
}
//// smartCrop 智能裁切,处理边界和自动调整 -- 暂时没用
//func smartCrop(src image.Image, x, y, width, height int) (image.Image, error) {
// srcBounds := src.Bounds()
// srcWidth := srcBounds.Dx()
// srcHeight := srcBounds.Dy()
//
// // 如果宽度或高度为0使用图像的最大可能尺寸
// if width == 0 {
// width = srcWidth - x
// }
// if height == 0 {
// height = srcHeight - y
// }
//
// // 调整裁切区域以确保在边界内
// newX := max(0, min(x, srcWidth-1))
// newY := max(0, min(x, srcHeight-1))
// width = max(1, min(width, srcWidth-x))
// height = max(1, min(height, srcHeight-y))
//
// // 如果需要保持宽高比,调整裁切区域
// if width > 0 && height > 0 {
// currentRatio := float64(width) / float64(height)
// originalRatio := float64(srcWidth) / float64(srcHeight)
//
// if math.Abs(currentRatio-originalRatio) > 0.01 {
// // 调整宽度以匹配原始宽高比
// newWidth := int(float64(height) * originalRatio)
// if newWidth <= (srcWidth - newX) {
// width = newWidth
// } else {
// // 调整高度以匹配原始宽高比
// newHeight := int(float64(width) / originalRatio)
// if newHeight <= (srcHeight - newY) {
// height = newHeight
// }
// }
// }
// }
//
// // 执行裁切
// cropped, err := basicCrop(src, newX, newY, width, height)
// if err != nil {
// return nil, err
// }
//
// return cropped, nil
//}
//// CropCenter 中心裁切 -- 暂时没用
//func CropCenter(src image.Image, width, height int) (image.Image, error) {
// srcBounds := src.Bounds()
// srcWidth := srcBounds.Dx()
// srcHeight := srcBounds.Dy()
//
// // 如果裁切尺寸大于原图,返回原图或调整裁切尺寸
// if width >= srcWidth && height >= srcHeight {
// return src, nil
// }
//
// // 计算中心裁切的起始点
// x := (srcWidth - width) / 2
// y := (srcHeight - height) / 2
//
// // 确保不超出边界
// x = max(0, min(x, srcWidth-width))
// y = max(0, min(y, srcHeight-height))
//
// return BasicCrop(src, x, y, width, height)
//}
// 识别二维码
func scanQRCode(fileName string) (bool, string, error) {
file, err := os.Open(fileName)
if err != nil {
return false, "", fmt.Errorf("打开图片失败: %v", err)
}
defer file.Close()
// 解码图像
img, _, err := image.Decode(file)
if err != nil {
return false, "", fmt.Errorf("解码图片失败: %v", err)
}
// 创建二维码读取器
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
if err != nil {
return false, "", fmt.Errorf("创建位图失败: %v", err)
}
// 解码二维码
reader := qrcode.NewQRCodeReader()
result, err := reader.Decode(bmp, nil)
if err != nil {
return false, "", analyzeQRCodeError(err)
}
// 6. 打印二维码内容
fmt.Printf("二维码内容: %s\n", result.GetText())
// 7. 获取二维码位置点
points := result.GetResultPoints()
if len(points) < 3 {
fmt.Println("未找到足够的定位点")
}
return true, result.GetText(), nil
}
// 识别二维码
func scanQRCodeNew(fileName string) (bool, string, error) {
file, err := os.Open(fileName)
if err != nil {
return false, "", fmt.Errorf("打开图片失败: %v", err)
}
defer file.Close()
// 解码图像
img, _, err := image.Decode(file)
if err != nil {
return false, "", fmt.Errorf("解码图片失败: %v", err)
}
// 创建二维码读取器
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
if err != nil {
return false, "", fmt.Errorf("创建位图失败: %v", err)
}
// 解码二维码
reader := qrcode.NewQRCodeReader()
result, err := reader.Decode(bmp, nil)
if err != nil {
return false, "", analyzeQRCodeError(err)
}
// 6. 打印二维码内容
fmt.Printf("二维码内容: %s\n", result.GetText())
// 7. 获取二维码位置点
points := result.GetResultPoints()
if len(points) < 3 {
fmt.Println("未找到足够的定位点")
}
// 8. 计算二维码边界框
minX, minY := int(points[0].GetX()), int(points[0].GetY())
maxX, maxY := minX, minY
for _, point := range points {
x, y := int(point.GetX()), int(point.GetY())
if x < minX {
minX = x
}
if x > maxX {
maxX = x
}
if y < minY {
minY = y
}
if y > maxY {
maxY = y
}
}
// 9. 添加一些边距(可选)
margin := 10
minX -= margin
minY -= margin
maxX += margin
maxY += margin
// 确保坐标不超出图片范围
bounds := img.Bounds()
if minX < bounds.Min.X {
minX = bounds.Min.X
}
if minY < bounds.Min.Y {
minY = bounds.Min.Y
}
if maxX > bounds.Max.X {
maxX = bounds.Max.X
}
if maxY > bounds.Max.Y {
maxY = bounds.Max.Y
}
// 10. 创建裁剪区域
qrRect := image.Rect(minX, minY, maxX, maxY)
fmt.Printf("二维码位置: %v\n", qrRect)
// 11. 创建新图片并复制二维码区域
qrImg := image.NewRGBA(qrRect)
draw.Draw(qrImg, qrImg.Bounds(), img, qrRect.Min, draw.Src)
// 12. 保存二维码图片
outputFile, err := os.Create("image/qrcode.png")
if err != nil {
panic(err)
}
defer outputFile.Close()
err = png.Encode(outputFile, qrImg)
if err != nil {
panic(err)
}
return true, result.GetText(), nil
}
// 图像预处理
func preprocessImage(img image.Image) image.Image {
// 转为灰度图
gray := imaging.Grayscale(img)
// 增强对比度
enhanced := imaging.AdjustContrast(gray, 20)
// 高斯模糊去噪
blurred := imaging.Blur(enhanced, 1.0)
// 自适应二值化
binary := adaptiveThreshold(blurred)
return binary
}
// 二值化处理
func adaptiveThreshold(img image.Image) image.Image {
bounds := img.Bounds()
dst := image.NewGray(bounds)
// 局部自适应阈值
blockSize := 15
c := 2.0
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
// 计算局部均值
sum := 0.0
count := 0
for dy := -blockSize / 2; dy <= blockSize/2; dy++ {
for dx := -blockSize / 2; dx <= blockSize/2; dx++ {
nx, ny := x+dx, y+dy
if nx >= bounds.Min.X && nx < bounds.Max.X &&
ny >= bounds.Min.Y && ny < bounds.Max.Y {
r, _, _, _ := img.At(nx, ny).RGBA()
sum += float64(r >> 8)
count++
}
}
}
localMean := sum / float64(count)
r, _, _, _ := img.At(x, y).RGBA()
gray := float64(r >> 8)
if gray > localMean-c {
dst.SetGray(x, y, color.Gray{Y: 255})
} else {
dst.SetGray(x, y, color.Gray{Y: 0})
}
}
}
return dst
}
// 分析二维码错误类型
func analyzeQRCodeError(err error) error {
if err == nil {
return nil
}
// 检查不同类型的错误
switch e := err.(type) {
case gozxing.ChecksumException:
return fmt.Errorf("二维码校验失败(可能部分损坏或被遮挡): %v", e)
case gozxing.FormatException:
return fmt.Errorf("二维码格式错误(可能不是有效的二维码): %v", e)
case gozxing.NotFoundException:
return fmt.Errorf("二维码格式错误: %v", e)
default:
return fmt.Errorf("解码失败: %v", err)
}
}
// 生成二维码
func generateQRCode(content string, width int, height int, fileName string) (string, error) {
// 创建 QR 码写入器
writer := qrcode.NewQRCodeWriter()
// 设置编码参数
hints := make(map[gozxing.EncodeHintType]interface{})
// 纠错级别设置
hints[gozxing.EncodeHintType_ERROR_CORRECTION] = "H"
// 字符集
hints[gozxing.EncodeHintType_CHARACTER_SET] = "UTF-8"
// 边距
hints[gozxing.EncodeHintType_MARGIN] = 4
// 生成二维码
bitMatrix, err := writer.Encode(content, gozxing.BarcodeFormat_QR_CODE, width, height, hints)
if err != nil {
return "", fmt.Errorf("生成二维码失败: %v", err)
}
// 创建图像
img := image.NewRGBA(image.Rect(0, 0, width, height))
// 白色背景
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
img.Set(x, y, color.White)
}
}
// 黑色二维码点
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
if bitMatrix.Get(x, y) {
img.Set(x, y, color.Black)
}
}
}
// 保存文件
file, err := os.Create(fileName)
if err != nil {
return "", fmt.Errorf("保存二维码失败: %v", err)
}
defer file.Close()
png.Encode(file, img)
return fmt.Sprintf("生成二维码成功: %v", fileName), nil
}
// =================== 辅助函数 =======================
// 辅助函数 // 辅助函数
func absDiff(a, b uint8) uint8 { func absDiff(a, b uint8) uint8 {
if a > b { if a > b {
@ -578,6 +1184,44 @@ func RemoveWhiteBorderAndPNG(jsonConfig *C.char) *C.char {
return C.CString(fileName) return C.CString(fileName)
} }
// ResizeWTToHeightQuality 图片缩放
//
//export ResizeWTToHeightQuality
func ResizeWTToHeightQuality(jsonConfig *C.char, dsWidth, dsHeight C.int) *C.char {
configStr := C.GoString(jsonConfig)
dsWidthStr := int(dsWidth)
dsHeightStr := int(dsHeight)
var config *Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
}
fileName, err := resizeWTToHeightQuality(config, dsWidthStr, dsHeightStr)
if err != nil {
return C.CString(fmt.Sprintf("%v", err))
}
return C.CString(fileName)
}
// CropImage 图片裁切
//
//export CropImage
func CropImage(jsonConfig *C.char, x, y, width, height C.int) *C.char {
configStr := C.GoString(jsonConfig)
xStr := int(x)
yStr := int(y)
widthStr := int(width)
heightStr := int(height)
var config *Config
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
}
fileName, err := cropImage(config, xStr, yStr, widthStr, heightStr)
if err != nil {
return C.CString(fmt.Sprintf("%v", err))
}
return C.CString(fileName)
}
// 导出函数释放C字符串内存 // 导出函数释放C字符串内存
// //
//export FreeCString //export FreeCString
@ -585,6 +1229,6 @@ func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str)) C.free(unsafe.Pointer(str))
} }
// main 函数是必需的,即使为空
//func main() { //func main() {
//
//} //}

View File

@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/skip2/go-qrcode"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -80,30 +81,75 @@ func cStr(ptr uintptr) string {
func main() { func main() {
config := &Config{ //config := &Config{
OutputDir: "D:\\isbn_images\\result", // 输出根目录 // OutputDir: "D:\\isbn_images\\result", // 输出根目录
FileName: "D:\\isbn_images\\result\\9771671688095.jpg", // FileName: "D:\\isbn_images\\result\\97800079351851.jpg",
MatchDir: "matched", // 满足条件的图片目录 // //MatchDir: "matched", // 满足条件的图片目录
UnmatchDir: "unmatched", // 不满足条件的图片目录 // //UnmatchDir: "unmatched", // 不满足条件的图片目录
WhiteDir: "white", // //WhiteDir: "white",
EqualHeightDir: "equalHeight", // //EqualHeightDir: "equalHeight",
MinWhitePct: 0.1, // 纯白占比下限 10% // //WhiteHeightZoomDir: "whiteHeightZoom",
MaxWhitePct: 0.65, // 纯白占比上限 90% // CropDir: "crop",
Extensions: []string{"jpg", "jpeg", "png", "gif", "bmp", "webp"}, // MinWhitePct: 0.1, // 纯白占比下限 10%
} // MaxWhitePct: 0.65, // 纯白占比上限 90%
// Extensions: []string{"jpg", "jpeg", "png", "gif", "bmp", "webp"},
//}
dll, err := InitImageDll() //dll, err := InitImageDll()
if err != nil {
fmt.Println(err)
}
//err = dll.ProcessImage(config)
//if err != nil { //if err != nil {
// fmt.Println(err) // fmt.Println(err)
//} //}
////err = dll.ProcessImage(config)
////if err != nil {
//// fmt.Println(err)
////}
//
//image, err := dll.CreateWhiteBottomCenteredImage(config, 800, 800)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(image)
image, err := dll.CreateWhiteBottomCenteredImage(config, 800, 800) // 图片缩放
//quality, err := resizeWTToHeightQuality(config, 800, 500)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(quality)
//// 图片裁切
//image, err := cropImage(config, 100, 0, 300, 300)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(image)
//file := "D:\\isbn_images\\result\\123\\qrcode.jpg"
//
//code, s, err := scanQRCodeNew(file)
//if err != nil {
// fmt.Println(err)
//}
//if code {
// fmt.Println(s)
//}
code, err := generateQRCode("你好", 500, 500, "D:\\isbn_images\\result\\123\\qrcode.jpg")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
fmt.Println(image) fmt.Println(code)
}
// 生成二维码
func generateQRCode1() {
// 一行代码生成二维码
err := qrcode.WriteFile("https://example.com", qrcode.Medium, 256, "qrcode.png")
if err != nil {
panic(err)
}
//// 更多控制
//q, _ := qrcode.New("https://github.com", qrcode.High)
//q.WriteFile(256, "custom_qr.png")
} }

View File

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

Binary file not shown.

View File

@ -88,55 +88,67 @@ extern "C" {
#endif #endif
// 孔网登录 // OutLogin 孔网登录
// //
extern __declspec(dllexport) char* OutLogin(char* username, char* password); extern __declspec(dllexport) char* OutLogin(char* username, char* password);
// 获取孔网用户信息 // OutGetUserMsg 获取孔网用户信息
// //
extern __declspec(dllexport) char* OutGetUserMsg(char* token); extern __declspec(dllexport) char* OutGetUserMsg(char* token);
// 获取商品模版--已登的店铺 // OutGetGoodsTplMsg 获取商品模版--已登的店铺
// //
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId); extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
// 获取商品列表-已登的店铺 // OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺
// //
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); 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的都非官方标准接口) // OutAddGoods 新增商品-已登的店铺
// //
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData); extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
// 删除商品-已登的店铺 // OutDelGoodsFromSelfShop 删除商品-已登的店铺
// //
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId); extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
// 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤 // OutGetImageFilterShopId 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
// //
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg); extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
// 获取孔网商品图片和信息(官图和拍图) // OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图)
// //
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg); extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg);
// 获取商品列表通过店铺ID // OutGetGoodsListMsgByShopId 获取商品列表通过店铺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* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
// 获取商品信息通过商品详情链接 // OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接
// //
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy); extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
// 获取销量榜商品列表 // OutGetTopGoodsListMsg 获取销量榜商品列表
// //
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy); extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
// 初始化配置 // KongfzDeliveryMethodList 获取配送方式列表
//
extern __declspec(dllexport) char* KongfzDeliveryMethodList(int appId, char* appSecret, char* accessToken);
// KongfzOrderDeliver 订单发货
//
extern __declspec(dllexport) char* KongfzOrderDeliver(int appId, char* appSecret, char* accessToken, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
// KongfzOrderSynchronization 孔网订单同步
//
extern __declspec(dllexport) char* KongfzOrderSynchronization(int appId, char* appSecret, char* accessToken, char* shippingComName, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
// Initialize 初始化配置
// //
extern __declspec(dllexport) char* Initialize(char* configJSON); extern __declspec(dllexport) char* Initialize(char* configJSON);
// 释放C字符串内存 // FreeCString 释放C字符串内存
// //
extern __declspec(dllexport) void FreeCString(char* str); extern __declspec(dllexport) void FreeCString(char* str);

File diff suppressed because it is too large Load Diff

346
md/es.md Normal file
View File

@ -0,0 +1,346 @@
# es.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件~~~~
```gotemplate
// EsDLL Elasticsearch工具DLL结构
type esDLL struct {
dll *syscall.DLL
listAllIndices *syscall.Proc // 查询所有索引
getIndicesInfo *syscall.Proc // 获取所有索引的详细信息
getIndexDetail *syscall.Proc // 获取单个索引的详细信息
createIndex *syscall.Proc // 创建索引
deleteIndex *syscall.Proc // 删除索引
getDocumentCount *syscall.Proc // 获取索引文档数量
createDocument *syscall.Proc // 创建文档
getDocument *syscall.Proc // 根据ID获取文档
updateDocument *syscall.Proc // 更新文档
deleteDocument *syscall.Proc // 删除文档
searchDocuments *syscall.Proc // 搜索文档
freeCString *syscall.Proc // 释放C字符串
}
// 初始化esDLL
func InitEsDLL() (*esDLL, error) {
dllPath := filepath.Join("dll", "es.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("es DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载es DLL 失败: %s", err)
} else {
return &esDLL{
dll: dll,
listAllIndices: dll.MustFindProc("ListAllIndices"),
getIndicesInfo: dll.MustFindProc("GetIndicesInfo"),
getIndexDetail: dll.MustFindProc("GetIndexDetail"),
createIndex: dll.MustFindProc("CreateIndex"),
deleteIndex: dll.MustFindProc("DeleteIndex"),
getDocumentCount: dll.MustFindProc("GetDocumentCount"),
createDocument: dll.MustFindProc("CreateDocument"),
getDocument: dll.MustFindProc("GetDocument"),
updateDocument: dll.MustFindProc("UpdateDocument"),
deleteDocument: dll.MustFindProc("DeleteDocument"),
searchDocuments: dll.MustFindProc("SearchDocuments"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
dll, err := InitEsDLL()
```
### 获取C字符串
```gotemplate
// cStr 获取C字符串
func (m *esDLL) 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
// 查询所有索引
func (m *esDLL) ListAllIndices() (string, error) {
proc, err := m.dll.FindProc("ListAllIndices")
if err != nil {
return "", fmt.Errorf("找不到函数 ListAllIndices: %v", err)
}
resultPtr, _, _ := proc.Call()
result := m.cStr(resultPtr)
return result, nil
}
```
# 接口详情
## 1.查询所有索引--ListAllIndices
### 请求信息
```gotemplate
dll.ListAllIndices()
```
### 请求参数
### 响应示例
```json
{
"success": true,
"message": "",
"data": ["index1", "index2", "index3"]
}
```
## 2. 获取所有索引的详细信息--GetIndicesInfo
### 请求信息
```gotemplate
dll.GetIndicesInfo()
```
### 请求参数
### 响应示例
```json
{
"success": true,
"message": "",
"data": [
{
"index": "index1",
"health": "green",
"status": "open",
"uuid": "abc123",
"pri": "1",
"rep": "1",
"docs.count": "100",
"docs.deleted": "0",
"store.size": "1.2kb",
"pri.store.size": "600b"
}
]
}
```
## 3. 获取单个索引的详细信息--GetIndexDetail
### 请求信息
```gotemplate
dll.GetIndexDetail(indexName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| indexName | string | 是 | 索引名称 |
### 响应示例
```json
{
"success": true,
"message": "",
"data": {
"index1": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1234567890000",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "abc123",
"version": {
"created": "7080099"
}
}
}
}
}
}
```
## 4. 创建索引--CreateIndex
### 请求信息
```gotemplate
dll.CreateIndex(indexName, mapping)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
| mapping | string | 是 | 索引映射的JSON字符串 |
### 响应示例
```json
{
"success": true,
"message": "",
"data": {
}
}
```
## 5. 删除索引--DeleteIndex
### 请求信息
```gotemplate
dll.DeleteIndex(indexName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
### 响应示例
```json
{
"success": true,
"message": ""
}
```
## 6. 获取索引文档数量--GetDocumentCount
### 请求信息
```gotemplate
dll.GetDocumentCount(indexName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
### 响应示例
```json
{
"success": true,
"message": "",
"data": 100
}
```
## 7. 创建文档--CreateDocument
### 请求信息
```gotemplate
dll.CreateDocument(indexName, id, doc)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
| id | string | 是 | 文档ID |
| doc | string | 是 | 文档内容的JSON字符串 |
### 响应示例
```json
{
"success": true,
"message": ""
}
```
## 8. 根据ID获取文档--GetDocument
### 请求信息
```gotemplate
dll.GetDocument(indexName, id)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
| id | string | 是 | 文档ID |
### 响应示例
```json
{
"success": true,
"message": "",
"data": {
"_id": "1",
"_index": "index1",
"_source": {
"title": "示例文档",
"content": "这是文档内容"
}
}
}
```
## 9. 更新文档--UpdateDocument
### 请求信息
```gotemplate
dll.UpdateDocument(indexName, id, updateData)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
| id | string | 是 | 文档ID |
| updateData | string | 是 | 更新数据的JSON字符串 |
### 响应示例
```json
{
"success": true,
"message": ""
}
```
## 10. 删除文档--DeleteDocument
### 请求信息
```gotemplate
dll.DeleteDocument(indexName, id)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|---------------|
| indexName | string | 是 | 索引名称 |
| id | string | 是 | 文档ID |
### 响应示例
```json
{
"success": true,
"message": ""
}
```
## 11. 搜索文档--SearchDocuments
### 请求信息
```gotemplate
dll.SearchDocuments(indexName, query)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|--------------|
| indexName | string | 是 | 索引名称 |
| query | string | 是 | 查询条件的JSON字符串|
#### query示例
```gotemplate
query := map[string]interface{}{
"query": map[string]interface{}{
"term": map[string]interface{}{
fieldName: fieldValue,
},
},
"size": size,
}
```
### 响应示例
```json
{
"success": true,
"message": "",
"data": [
{
"_id": "1",
"title": "示例文档",
"content": "这是文档内容"
},
{
"_id": "2",
"title": "另一个文档",
"content": "更多内容"
}
]
}
```

View File

@ -1567,17 +1567,165 @@ dll.OutGetTopGoodsListMsg(catId,proxy)
} }
``` ```
## 12.获取配送方式列表--KongfzDeliveryMethodList
### 请求信息
```gotemplate
dll.KongfzDeliveryMethodList(appId C.int, appSecret, accessToken *C.char)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|----|----------------------------|
| appId | int | 是 | 开放平台分配给应用的AppId |
| appSecret | string | 是 | App密钥 |
| accessToken | string | 否 | 用户登录授权成功后,开放平台颁发给应用的授权信息。 |
### 响应示例
```json
{
"requestId": "EGLJzOwutBCR9RDE",
"requestMethod": "kongfz.delivery.method.list",
"successResponse": [
{
"shippingId": "registerPost",
"shippingName": "挂号印刷品",
"isDefault": false,
"companies": [
{
"shippingCom": "registeredPrint",
"shippingComName": "邮局",
"isDefault": false
}
]
},
{
"shippingId": "express",
"shippingName": "快递",
"isDefault": false,
"companies": [
{
"shippingCom": "huitongkuaidi",
"shippingComName": "百世快递",
"isDefault": true
},
{
"shippingCom": "yunda",
"shippingComName": "韵达快递",
"isDefault": false,
},
]
},
{
"shippingId": "generalParcel",
"shippingName": "普通包裹",
"isDefault": false,
"companies": [
{
"shippingCom": "generalParcel",
"shippingComName": "邮局",
"isDefault": false
}
]
},
{
"shippingId": "ems",
"shippingName": "EMS",
"isDefault": false,
"companies": [
{
"shippingCom": "ems",
"shippingComName": "邮局",
"isDefault": false
}
]
},
{
"shippingId": "noLogistics",
"shippingName": "无需物流",
"isDefault": false,
"companies": []
}
],
"errorResponse": null
}
```
## 13.订单发货--KongfzOrderDeliver
### 请求信息
```gotemplate
dll.KongfzOrderDeliver(appId, appSecret, accessToken ,
orderId , shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum )
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--------|----|-------------|
| appId | int | 是 | 开放平台分配给应用的AppId |
| appSecret | string | 是 | App密钥 |
| accessToken | string | 否 | 用户登录授权成功后,开放平台颁发给应用的授权信息 |
| orderId | int | 是 | 订单编号 |
| shippingId | string | 是 | 配送方式 |
| shippingCom | string | 否 | 快递公司。当shippingId!=noLogistics时此参数为必填。取值参考kongfz.delivery.method.list接口的返回值 |
| shipmentNum | string | 否 | 快递单号。当shippingId!=noLogistics时此参数为必填。 |
| userDefined | string | 否 | 用户自定义物流公司。当shippingCom=other时此参数为必填。 |
| moreShipmentNum | string | 否 | 填写更多的快递单号,以逗号分隔。 |
### 响应示例
```json
{
"requestId": "bbXrGb2dOBRRDRBL",
"requestMethod": "kongfz.order.deliver",
"successResponse": {
"order": {
"orderId": 73609014,
"remark": "订单发货成功",
"updateTime": "2019-05-22 16:32:08"
}
},
"errorResponse": null
}
```
## 14.孔网订单同步--KongfzOrderSynchronization
### 请求信息
```gotemplate
result, err := dll.KongfzOrderSynchronization(appId, appSecret, accessToken, shippingComName,
orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|----|-----------------------------------------------------------------------------|
| appId | int | 是 | 开放平台分配给应用的AppId |
| appSecret | string | 是 | App密钥 |
| accessToken | string | 否 | 用户登录授权成功后,开放平台颁发给应用的授权信息 |
| shippingComName | string | 是 | 快递名称 |
| orderId | int | 是 | 订单编号 |
| shippingId | string | 是 | 配送方式 |
| shippingCom | string | 否 | 快递公司。当shippingId!=noLogistics时此参数为必填。取值参考kongfz.delivery.method.list接口的返回值 |
| shipmentNum | string | 否 | 快递单号。当shippingId!=noLogistics时此参数为必填。 |
| userDefined | string | 否 | 用户自定义物流公司。当shippingCom=other时此参数为必填。 |
| moreShipmentNum | string | 否 | 填写更多的快递单号,以逗号分隔。 |
### 响应示例
```json
{
"requestId": "bbXrGb2dOBRRDRBL",
"requestMethod": "kongfz.order.deliver",
"successResponse": {
"order": {
"orderId": 73609014,
"remark": "订单发货成功",
"updateTime": "2019-05-22 16:32:08"
}
},
"errorResponse": null
}
```
## 12.初始化--Initialize(可以不调用) ## 12.初始化--Initialize(可以不调用)
### 请求信息 ### 请求信息
```gotemplate ```gotemplate
result, err := dll.Initialize(configJSON) result, err := dll.Initialize(configJSON)
``` ```
### 请求参数 ### 请求参数
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------------| |--|--|--|----------------|
| configJSON | string | 是 | 配置信息的 JSON 字符串 | | configJSON | string | 是 | 配置信息的 JSON 字符串 |
### 响应示例 ### 响应示例
```json ```json
{ {

495
md/pdd.md Normal file
View File

@ -0,0 +1,495 @@
# pdd.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
```gotemplate
// PddDLL 拼多多工具DLL结构
type pddDLL struct {
dll *syscall.DLL
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
freeCString *syscall.Proc // 释放C字符串
}
// 初始化pddDLL
func InitPddDLL() (*pddDLL, error) {
dllPath := filepath.Join("dll", "pdd.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
} else {
return &pddDLL{
dll: dll,
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
dll, err := InitPddDLL()
```
### 获取C字符串
```gotemplate
// cStr 获取C字符串
func (m *pddDLL) 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
// 类目预测
func (m *pddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
proc, err := m.dll.FindProc("PddGoodsOuterCatMappingGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
resultPtr, _, _ := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(outerCatIdPtr)),
uintptr(unsafe.Pointer(outerCatNamePtr)),
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
)
result := m.cStr(resultPtr)
return result, nil
}
```
# 接口详情
## 1. 类目预测--PddGoodsOuterCatMappingGet
### 请求信息
```gotemplate
dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| outerCatId | string | 是 | 外部平台类目ID |
| outerCatName | string | 是 | 外部平台类目名称 |
| outerGoodsName | string | 是 | 外部商品名称 |
### 响应示例
```json
{
"outer_cat_mapping_get_response": {
"cat_id2": 16028,
"cat_id3": 16031,
"cat_id1": 15543,
"request_id": "17666480184871649",
"cat_id4": 0
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 2. 快递公司查看--PddLogisticsCompaniesGet
### 请求信息
```gotemplate
dll.PddLogisticsCompaniesGet(clientId, clientSecret)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
### 响应示例
```json
{
"logistics_companies_get_response": {
"logistics_companies": [
{
"available": 1,
"code": "SF",
"id": 1,
"logistics_company": "顺丰速运"
},
{
"available": 1,
"code": "STO",
"id": 2,
"logistics_company": "申通快递"
}
]
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 3. erp打单信息同步--PddErpOrderSync
### 请求信息
```gotemplate
dll.PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
orderSn, orderState, waybillNo)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| logisticsId | string | 是 | 物流公司ID |
| orderSn | string | 是 | 拼多多订单号 |
| orderState | string | 是 | 订单状态 |
| waybillNo | string | 是 | 运单号 |
### 响应示例
```json
{
"erp_order_sync_response": {
"is_success": true,
"request_id": "17666480184871650"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 4. 拼多多订单同步--PddOrderSynchronization
### 请求信息
```gotemplate
dll.PddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId,
orderSn, orderState, waybillNo)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| logisticsCompany | string | 是 | 物流公司名称 |
| logisticsId | string | 是 | 物流公司ID |
| orderSn | string | 是 | 拼多多订单号 |
| orderState | string | 是 | 订单状态 |
| waybillNo | string | 是 | 运单号 |
### 响应示例
```json
{
"erp_order_sync_response": {
"is_success": true,
"request_id": "17666480184871651"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 5. 商品图片上传接口--PddGoodsImgUpload
### 请求信息
```gotemplate
dll.PddGoodsImgUpload(clientId, clientSecret, accessToken, filePath)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| filePath | string | 是 | 图片文件路径 |
### 响应示例
```json
{
"goods_img_upload_response": {
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"request_id": "17666480184871652"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 6. 商品新增接口--PddGoodsAdd
### 请求信息
```gotemplate
dll.PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| goodsAddJson | string | 是 | 商品信息JSON字符串 |
#### 商品信息JSON结构示例
```json
{
"goods_name": "测试商品",
"goods_desc": "商品描述",
"cat_id": 20111,
"goods_type": 1,
"market_price": 9900,
"is_folt": false,
"is_pre_sale": false,
"is_refundable": true,
"shipment_limit_second": 86400,
"cost_template_id": 10001,
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"carousel_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"detail_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"sku_list": [
{
"out_sku_sn": "SKU001",
"price": 8900,
"quantity": 100,
"spec_id_list": "1001:10001",
"sku_properties": [
{
"ref_pid": 1001,
"value": "红色",
"vid": 10001,
"punit": "个"
}
],
"is_onsale": 1,
"limit_quantity": 10,
"multi_price": 8500,
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"weight": 500
}
]
}
```
### 响应示例
```json
{
"goods_add_response": {
"goods_id": 123456789,
"goods_name": "测试商品",
"goods_sn": "G202501200001",
"request_id": "17666480184871653"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 7. 联合拼多多图片上传的商品新增--SelfPddGoodsAdd
### 请求信息
```gotemplate
dll.SelfPddGoodsAdd(clientId, clientSecret, accessToken, filePath, goodsAddJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| filePath | string | 是 | 图片文件路径 |
| goodsAddJson | string | 是 | 商品信息JSON字符串不需包含image_url|
#### 接口说明
此接口为组合接口,内部执行以下步骤:
1.上传商品主图文件到拼多多服务器
2.获取图片URL并自动填充到商品信息中
3.调用商品新增接口创建商品
#### 商品信息JSON结构示例
```json
{
"goods_name": "测试商品",
"goods_desc": "商品描述",
"cat_id": 20111,
"goods_type": 1,
"market_price": 9900,
"is_folt": false,
"is_pre_sale": false,
"is_refundable": true,
"shipment_limit_second": 86400,
"cost_template_id": 10001,
"image_url": "",
"carousel_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"detail_gallery": [
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
],
"sku_list": [
{
"out_sku_sn": "SKU001",
"price": 8900,
"quantity": 100,
"spec_id_list": "1001:10001",
"sku_properties": [
{
"ref_pid": 1001,
"value": "红色",
"vid": 10001,
"punit": "个"
}
],
"is_onsale": 1,
"limit_quantity": 10,
"multi_price": 8500,
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
"weight": 500
}
]
}
```
### 响应示例
```json
{
"goods_add_response": {
"goods_id": 123456790,
"goods_name": "测试商品",
"goods_sn": "G202501200002",
"request_id": "17666480184871654"
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```
## 8. 批量数据解密脱敏接口--PddOpenDecryptMaskBatch
### 请求信息
```gotemplate
dll.PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|--|--|----------|
| clientId | string | 是 | 拼多多开放平台ClientID |
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
| accessToken | string | 是 | 授权令牌 |
| reqJson | string | 是 | 信息JSON字符串 |
#### 信息JSON结构示例
```json
[
{
"data_tag": "251229-272441044622514",
"encrypted_data": "~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"
}
]
```
### 响应示例
```json
{
"open_decrypt_mask_batch_response": {
"data_decrypt_list": [
{
"data_tag": "str",
"data_type": 0,
"decrypted_data": "str",
"encrypted_data": "str",
"error_code": 0,
"error_msg": "str"
}
]
}
}
```
### 错误响应示例
```json
{
"error_response": {
"error_msg": "公共参数错误:type",
"sub_msg": "",
"sub_code": null,
"error_code": 10001,
"request_id": "15440104776643887"
}
}
```

655
md/xy.md Normal file
View File

@ -0,0 +1,655 @@
# pdd.dll 使用教程
## 1.创建DLL工具实例
### 加载DLL文件
# 接口详情
## 1. 查询快递公司--ExecuteOpenExpressCompanies
### 请求信息
```gotemplate
dll.ExecuteOpenExpressCompanies(appid, timestamp, sign)
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|---------|----|----------|
| appid | string | 是 | 开放平台的AppKey |
| timestamp | integer | 否 | 当前时间戳单位秒5分钟内有效 |
| sign | string | 是 | 签名MD5值参考签名说明 |
### 响应示例
```json
{
"code": 0,
"msg": "OK",
"data": {
"list": [
{
"code": "shentong",
"express_alias": "申通",
"express_name": "申通快递",
"is_hot": true
},
{
"code": "yunda",
"express_alias": "韵达",
"express_name": "韵达快递",
"is_hot": true
},
{
"code": "htky",
"express_alias": "极兔",
"express_name": "极兔-原百世快递",
"is_hot": true
},
{
"code": "youzhengguonei",
"express_alias": "",
"express_name": "邮政快递包裹",
"is_hot": true
},
{
"code": "yuantong",
"express_alias": "圆通",
"express_name": "圆通速递",
"is_hot": true
},
{
"code": "zhongtong",
"express_alias": "中通",
"express_name": "中通快递",
"is_hot": true
},
{
"code": "zhaijisong",
"express_alias": "",
"express_name": "宅急送",
"is_hot": true
},
{
"code": "tiantian",
"express_alias": "天天",
"express_name": "天天快递",
"is_hot": true
},
{
"code": "shunfeng",
"express_alias": "顺丰",
"express_name": "顺丰速运",
"is_hot": true
},
{
"code": "ems",
"express_alias": "",
"express_name": "EMS",
"is_hot": true
},
{
"code": "other",
"express_alias": "",
"express_name": "其他",
"is_hot": true
},
{
"code": "baishikuaidi",
"express_alias": "",
"express_name": "百世快递",
"is_hot": false
},
{
"code": "sxjdfreight",
"express_alias": "",
"express_name": "顺心捷达",
"is_hot": false
},
{
"code": "wanjiawuliu",
"express_alias": "",
"express_name": "万家物流",
"is_hot": false
},
{
"code": "taijin",
"express_alias": "",
"express_name": "泰进物流",
"is_hot": false
},
{
"code": "tcat",
"express_alias": "",
"express_name": "黑猫宅急便",
"is_hot": false
},
{
"code": "tiandihuayu",
"express_alias": "",
"express_name": "天地华宇",
"is_hot": false
},
{
"code": "usps",
"express_alias": "USPS",
"express_name": "美国邮政",
"is_hot": false
},
{
"code": "yafengsudi",
"express_alias": "",
"express_name": "亚风速递",
"is_hot": false
},
{
"code": "sut56",
"express_alias": "",
"express_name": "速通物流",
"is_hot": false
},
{
"code": "suning",
"express_alias": "苏宁",
"express_name": "苏宁快递",
"is_hot": false
},
{
"code": "suer",
"express_alias": "速尔",
"express_name": "速尔快运",
"is_hot": false
},
{
"code": "shenweizhaipei",
"express_alias": "",
"express_name": "神威宅配",
"is_hot": false
},
{
"code": "shenghuiwuliu",
"express_alias": "",
"express_name": "盛辉物流",
"is_hot": false
},
{
"code": "shangqiao56",
"express_alias": "",
"express_name": "商桥物流",
"is_hot": false
},
{
"code": "rufengda",
"express_alias": "如风达",
"express_name": "如风达配送",
"is_hot": false
},
{
"code": "rrs",
"express_alias": "日日顺",
"express_name": "日日顺物流",
"is_hot": false
},
{
"code": "youzhengbk",
"express_alias": "",
"express_name": "邮政标准快递",
"is_hot": false
},
{
"code": "ztky",
"express_alias": "",
"express_name": "中铁快运",
"is_hot": false
},
{
"code": "zhongtongkuaiyun",
"express_alias": "",
"express_name": "中通快运",
"is_hot": false
},
{
"code": "zhongtiewuliu",
"express_alias": "中铁飞豹",
"express_name": "中铁物流",
"is_hot": false
},
{
"code": "zengyisudi",
"express_alias": "",
"express_name": "增益速递",
"is_hot": false
},
{
"code": "yundatongcheng",
"express_alias": "",
"express_name": "韵达同城",
"is_hot": false
},
{
"code": "yundakuaiyun",
"express_alias": "",
"express_name": "韵达快运",
"is_hot": false
},
{
"code": "yujiawl",
"express_alias": "",
"express_name": "山东宇佳物流",
"is_hot": false
},
{
"code": "yuantongcainiancang",
"express_alias": "",
"express_name": "圆通菜鸟仓",
"is_hot": false
},
{
"code": "yuanshuochengnuoda",
"express_alias": "",
"express_name": "圆硕承诺达特快",
"is_hot": false
},
{
"code": "wanxiangwuliu",
"express_alias": "万象物流",
"express_name": "A1万象物流",
"is_hot": false
},
{
"code": "youxinwuliu",
"express_alias": "",
"express_name": "优信物流",
"is_hot": false
},
{
"code": "youshuwuliu",
"express_alias": "优速",
"express_name": "优速快递",
"is_hot": false
},
{
"code": "yimidida",
"express_alias": "壹米滴答",
"express_name": "壹米滴答快运",
"is_hot": false
},
{
"code": "ycgky",
"express_alias": "",
"express_name": "远成快运",
"is_hot": false
},
{
"code": "post",
"express_alias": "",
"express_name": "中国邮政",
"is_hot": false
},
{
"code": "xinzebangwuliu",
"express_alias": "",
"express_name": "鑫泽邦物流",
"is_hot": false
},
{
"code": "xinfengwuliu",
"express_alias": "",
"express_name": "信丰物流",
"is_hot": false
},
{
"code": "xinbangwuliu",
"express_alias": "",
"express_name": "新邦物流",
"is_hot": false
},
{
"code": "debangwuliu",
"express_alias": "",
"express_name": "德邦物流",
"is_hot": false
},
{
"code": "huayuwuliu",
"express_alias": "",
"express_name": "重庆华宇物流",
"is_hot": false
},
{
"code": "haoyaoshizijian",
"express_alias": "",
"express_name": "好药师自建物流",
"is_hot": false
},
{
"code": "guotongkuaidi",
"express_alias": "国通",
"express_name": "国通快递",
"is_hot": false
},
{
"code": "ganzhongnengda",
"express_alias": "",
"express_name": "能达速递",
"is_hot": false
},
{
"code": "fushisudi",
"express_alias": "",
"express_name": "服饰速递",
"is_hot": false
},
{
"code": "fengwang",
"express_alias": "丰网",
"express_name": "丰网速运",
"is_hot": false
},
{
"code": "exfresh",
"express_alias": "安鲜达",
"express_name": "安鲜达快递",
"is_hot": false
},
{
"code": "esb",
"express_alias": "",
"express_name": "E速宝",
"is_hot": false
},
{
"code": "dsukuaidi",
"express_alias": "D速快递",
"express_name": "D速物流",
"is_hot": false
},
{
"code": "diandiansong",
"express_alias": "",
"express_name": "点点送",
"is_hot": false
},
{
"code": "jd",
"express_alias": "京东",
"express_name": "京东物流",
"is_hot": false
},
{
"code": "debangkuaidi",
"express_alias": "",
"express_name": "德邦快递",
"is_hot": false
},
{
"code": "cszx",
"express_alias": "",
"express_name": "城市之星",
"is_hot": false
},
{
"code": "canpostfr",
"express_alias": "",
"express_name": "加拿大邮政",
"is_hot": false
},
{
"code": "cainiaodj-woaijia",
"express_alias": "",
"express_name": "菜鸟大件-沃埃家",
"is_hot": false
},
{
"code": "baishiyp",
"express_alias": "",
"express_name": "百世云配",
"is_hot": false
},
{
"code": "baishikuaiyun",
"express_alias": "",
"express_name": "百世快运",
"is_hot": false
},
{
"code": "astexpress",
"express_alias": "安世通",
"express_name": "安世通国际快递",
"is_hot": false
},
{
"code": "anxl",
"express_alias": "",
"express_name": "安迅物流",
"is_hot": false
},
{
"code": "annto",
"express_alias": "",
"express_name": "安得物流",
"is_hot": false
},
{
"code": "jiuyescm",
"express_alias": "",
"express_name": "九曳鲜配",
"is_hot": false
},
{
"code": "quanfengkuaidi",
"express_alias": "",
"express_name": "全峰快递",
"is_hot": false
},
{
"code": "annengwuliu",
"express_alias": "",
"express_name": "安能物流",
"is_hot": false
},
{
"code": "pingandatengfei",
"express_alias": "平安达腾飞",
"express_name": "平安达腾飞快递",
"is_hot": false
},
{
"code": "menduimen",
"express_alias": "",
"express_name": "门对门",
"is_hot": false
},
{
"code": "linshiwuliu",
"express_alias": "",
"express_name": "林氏物流",
"is_hot": false
},
{
"code": "lianhaowuliu",
"express_alias": "",
"express_name": "联昊通",
"is_hot": false
},
{
"code": "lianbangkuaidi",
"express_alias": "",
"express_name": "联邦快递",
"is_hot": false
},
{
"code": "kuayue",
"express_alias": "跨越",
"express_name": "跨越速运",
"is_hot": false
},
{
"code": "kahangtianxia",
"express_alias": "",
"express_name": "卡行天下",
"is_hot": false
},
{
"code": "jtexpress",
"express_alias": "极兔",
"express_name": "极兔速递",
"is_hot": false
},
{
"code": "quanyikuaidi",
"express_alias": "",
"express_name": "全一快递",
"is_hot": false
},
{
"code": "jinguangsudikuaijian",
"express_alias": "",
"express_name": "京广速递",
"is_hot": false
},
{
"code": "jindouyunjiaoche",
"express_alias": "",
"express_name": "筋斗云轿车物流",
"is_hot": false
},
{
"code": "jiazhuang-zhengjia",
"express_alias": "",
"express_name": "家装-正佳",
"is_hot": false
},
{
"code": "jiazhuang-zhelian",
"express_alias": "",
"express_name": "家装-浙联",
"is_hot": false
},
{
"code": "jiazhuang-sfc",
"express_alias": "",
"express_name": "家装-顺风车",
"is_hot": false
},
{
"code": "jiayunmeiwuliu",
"express_alias": "加运美",
"express_name": "加运美速递",
"is_hot": false
},
{
"code": "jiayiwuliu",
"express_alias": "",
"express_name": "佳怡物流",
"is_hot": false
},
{
"code": "jiajiwuliu",
"express_alias": "",
"express_name": "佳吉快运",
"is_hot": false
},
{
"code": "jgwl",
"express_alias": "",
"express_name": "景光物流",
"is_hot": false
}
]
}
}
```
## 2. 订单物流发货--ExecuteOpenOrderShip
### 请求信息
```gotemplate
dll.ExecuteOpenOrderShip(json)
```
### json样式
```json
{
"order_no": "1339920336328048683",
"ship_name": "张三",
"ship_mobile": "13800138000",
"ship_district_id": 440305,
"ship_prov_name": "广东省",
"ship_city_name": "深圳市",
"ship_area_name": "南山区",
"ship_address": "侨香路西丽街道丰泽园仓储中心",
"waybill_no": "25051016899982",
"express_name": "其他",
"express_code": "qita"
}
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|---------|----|-------------------|
| appid | string | 是 | 开放平台的AppKey |
| timestamp | integer | 否 | 当前时间戳单位秒5分钟内有效 |
| seller_id | integer | 否 | 商家ID |
| sign | string | 是 | 签名MD5值参考签名说明 |
| order_no | string | 是 | 闲鱼订单号 |
| ship_name | string | 否 | 寄件方姓名 |
| ship_mobile | string | 否 | 寄件方号码 |
| ship_district_id | int | 否 | 寄件方所在地区ID |
| ship_prov_name | string | 否 | 寄件方所在省份 |
| ship_city_name | string | 否 | 寄件方所在城市 |
| ship_area_name | string | 否 | 寄件方所在地区 |
| ship_address | string | 否 | 寄件方详细地址 |
| waybill_no | string | 否 | 快递单号 |
| express_code | string | 否 | 快递公司代码 |
| express_name | string | 否 | 快递公司名称 |
### 响应示例
```json
{
"code": 0,
"msg": "ok",
"data": {}
}
```
## 3. 闲鱼订单同步--ExecuteXyOrderSynchronization
### 请求信息
```gotemplate
dll.ExecuteXyOrderSynchronization(json)
```
### json样式
```json
{
"order_no": "1339920336328048683",
"ship_name": "张三",
"ship_mobile": "13800138000",
"ship_district_id": 440305,
"ship_prov_name": "广东省",
"ship_city_name": "深圳市",
"ship_area_name": "南山区",
"ship_address": "侨香路西丽街道丰泽园仓储中心",
"waybill_no": "25051016899982",
"express_name": "其他",
"express_code": "qita"
}
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--|---------|----|-------------------|
| appid | string | 是 | 开放平台的AppKey |
| timestamp | integer | 否 | 当前时间戳单位秒5分钟内有效 |
| seller_id | integer | 否 | 商家ID |
| sign | string | 是 | 签名MD5值参考签名说明 |
| order_no | string | 是 | 闲鱼订单号 |
| ship_name | string | 否 | 寄件方姓名 |
| ship_mobile | string | 否 | 寄件方号码 |
| ship_district_id | int | 否 | 寄件方所在地区ID |
| ship_prov_name | string | 否 | 寄件方所在省份 |
| ship_city_name | string | 否 | 寄件方所在城市 |
| ship_area_name | string | 否 | 寄件方所在地区 |
| ship_address | string | 否 | 寄件方详细地址 |
| waybill_no | string | 否 | 快递单号 |
| express_code | string | 否 | 快递公司代码 |
| express_name | string | 否 | 快递公司名称 |
### 响应示例
```json
{
"code": 0,
"msg": "ok",
"data": {}
}
```

BIN
pdd/dll/pdd.dll Normal file

Binary file not shown.

125
pdd/dll/pdd.h Normal file
View File

@ -0,0 +1,125 @@
/* 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 "pdd.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
// PddGoodsOuterCatMappingGet 类目预测
//
extern __declspec(dllexport) char* PddGoodsOuterCatMappingGet(char* clientId, char* clientSecret, char* accessToken, char* outerCatId, char* outerCatName, char* outerGoodsName);
// PddLogisticsCompaniesGet 快递公司查看
//
extern __declspec(dllexport) char* PddLogisticsCompaniesGet(char* clientId, char* clientSecret);
// PddErpOrderSync erp打单信息同步
//
extern __declspec(dllexport) char* PddErpOrderSync(char* clientId, char* clientSecret, char* accessToken, char* logisticsId, char* orderSn, char* orderState, char* waybillNo);
// PddOrderSynchronization 拼多多订单同步
//
extern __declspec(dllexport) char* PddOrderSynchronization(char* clientId, char* clientSecret, char* accessToken, char* logisticsCompany, char* logisticsId, char* orderSn, char* orderState, char* waybillNo);
// PddGoodsImgUpload 商品图片上传接口
//
extern __declspec(dllexport) char* PddGoodsImgUpload(char* clientId, char* clientSecret, char* accessToken, char* filePath);
// PddGoodsAdd 商品新增接口
//
extern __declspec(dllexport) char* PddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* goodsAddJson);
// SelfPddGoodsAdd 联合拼多多图片上传的商品新增
//
extern __declspec(dllexport) char* SelfPddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* filePath, char* goodsAddJson);
// 释放C字符串内存
//
extern __declspec(dllexport) void FreeCString(char* str);
#ifdef __cplusplus
}
#endif

886
pdd/pdd.go Normal file
View File

@ -0,0 +1,886 @@
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/parnurzeal/gorequest"
"io"
"mime/multipart"
"os"
"path/filepath"
"sort"
"strings"
"time"
"unsafe"
)
// ErrorResponse 错误响应结构
type ErrorResponse struct {
ErrorMsg string `json:"error_msg"` // 错误信息
SubMsg string `json:"sub_msg"` // 子错误信息
SubCode interface{} `json:"sub_code"` // 使用json.RawMessage处理null和不同类型
ErrorCode int `json:"error_code"` // 错误代码
RequestID string `json:"request_id"` // 请求ID
}
// ErrorWrapper 最外层错误响应包装
type ErrorWrapper struct {
ErrorResponse ErrorResponse `json:"error_response"` // 错误响应
}
// generateSign 生成签名
func generateSign(params map[string]interface{}, clientSecret string) string {
// 获取所有键并排序
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
// 按键名升序排序
sort.Strings(keys)
// 拼接签名字符串
var signStr strings.Builder
for _, k := range keys {
// 跳过 sign 参数(签名本身不参与签名)
if strings.ToLower(k) == "sign" {
continue
}
value := ""
if params[k] != nil {
value = fmt.Sprintf("%v", params[k]) // 将任意值转为字符串
}
// 按照文档格式:参数名+参数值
signStr.WriteString(k + value)
}
signString := signStr.String()
// 拼接client_secret: client_secret + 参数串 + client_secret
data := clientSecret + signString + clientSecret
// MD5加密并转大写
hasher := md5.New()
hasher.Write([]byte(data))
return strings.ToUpper(hex.EncodeToString(hasher.Sum(nil)))
}
// 类目预测
func pddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
// API地址
url := fmt.Sprint("https://gw-api.pinduoduo.com/api/router")
// 当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 生成签名参数
params := map[string]interface{}{
"type": "pdd.goods.outer.cat.mapping.get", // API类型
"data_type": "JSON", // 数据类型
"client_id": clientId, // 客户端ID
"access_token": accessToken, // 访问令牌
"outer_cat_id": outerCatId, // 外部类目ID
"outer_cat_name": outerCatName, // 外部类目名称
"outer_goods_name": outerGoodsName, // 外部商品名称
"timestamp": timestamp, // 时间戳
}
// 生成签名
sign := generateSign(params, clientSecret)
// 请求体
formData := map[string]interface{}{
"type": "pdd.goods.outer.cat.mapping.get",
"data_type": "JSON",
"client_id": clientId,
"access_token": accessToken,
"outer_cat_id": outerCatId,
"outer_cat_name": outerCatName,
"outer_goods_name": outerGoodsName,
"timestamp": timestamp,
"sign": sign, // 签名
}
request := gorequest.New()
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(30 * time.Second). // 30秒超时
Send(formData). // 发送表单数据
End() // 结束请求
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// 快递公司查看
func pddLogisticsCompaniesGet(clientId, clientSecret string) (string, error) {
url := fmt.Sprint("https://gw-api.pinduoduo.com/api/router")
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 生成签名参数
params := map[string]interface{}{
"type": "pdd.logistics.companies.get", // API类型获取物流公司
"data_type": "JSON",
"client_id": clientId,
"timestamp": timestamp,
}
sign := generateSign(params, clientSecret)
// 请求体
formData := map[string]interface{}{
"type": "pdd.logistics.companies.get",
"data_type": "JSON",
"client_id": clientId,
"timestamp": timestamp,
"sign": sign,
}
request := gorequest.New()
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(30 * time.Second).
Send(formData).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理:检查是否有错误响应
if response["error_response"] != nil {
var errorWrapper ErrorWrapper
// 解析响应
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// erp打单信息同步
func pddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
orderSn, orderState, waybillNo string) (string, error) {
url := fmt.Sprint("https://gw-api.pinduoduo.com/api/router")
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 生成签名
params := map[string]interface{}{
"type": "pdd.erp.order.sync", // API类型ERP订单同步
"data_type": "JSON",
"client_id": clientId,
"access_token": accessToken,
"logistics_id": logisticsId, // 物流公司ID
"order_sn": orderSn, // 订单号
"order_state": orderState, // 订单状态
"waybill_no": waybillNo, // 运单号
"timestamp": timestamp,
}
sign := generateSign(params, clientSecret)
// 请求体
formData := map[string]interface{}{
"type": "pdd.erp.order.sync",
"data_type": "JSON",
"client_id": clientId,
"access_token": accessToken,
"logistics_id": logisticsId,
"order_sn": orderSn,
"order_state": orderState,
"waybill_no": waybillNo,
"timestamp": timestamp,
"sign": sign,
}
request := gorequest.New()
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(30 * time.Second).
Send(formData).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理
if response["error_response"] != nil {
var errorWrapper ErrorWrapper
// 解析响应
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// LogisticsCompany 物流公司信息结构体
type LogisticsCompany struct {
Available int `json:"available"` // 是否可用
Code string `json:"code"` // 物流公司代码
ID int `json:"id"` // 物流公司ID
LogisticsCompany string `json:"logistics_company"` // 物流公司名称
}
// LogisticsCompaniesGetResponse 物流公司列表响应结构体
type LogisticsCompaniesGetResponse struct {
LogisticsCompanies []LogisticsCompany `json:"logistics_companies"` // 物流公司列表
}
// LogisticsResponse 最外层响应结构体
type LogisticsResponse struct {
LogisticsCompaniesGetResponse LogisticsCompaniesGetResponse `json:"logistics_companies_get_response"` // 物流公司响应
}
// 拼多多订单同步(组合接口:先查物流公司,再同步订单)
func pddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId,
orderSn, orderState, waybillNo string) (string, error) {
// 1. 获取物流公司列表
logisticsCompaniesGet, err := pddLogisticsCompaniesGet(clientId, clientSecret)
if err != nil {
return "", err
}
// 2. 解析物流公司响应
var response LogisticsResponse
if err := json.Unmarshal([]byte(logisticsCompaniesGet), &response); err != nil {
return "", fmt.Errorf("解析JSON失败: %v", err)
}
// 3. 根据物流公司名称查找对应的物流公司ID
for _, lc := range response.LogisticsCompaniesGetResponse.LogisticsCompanies {
if lc.LogisticsCompany == logisticsCompany {
logisticsId = fmt.Sprintf("%d", lc.ID)
break
}
}
// 4. 执行ERP订单同步
erpOrderSync, err := pddErpOrderSync(clientId, clientSecret, accessToken,
logisticsId, orderSn, orderState, waybillNo)
if err != nil {
return "", err
}
return erpOrderSync, nil
}
// 商品图片上传接口
func pddGoodsImgUpload(clientId, clientSecret, accessToken string, filePath string) (string, error) {
url := fmt.Sprint("https://gw-upload.pinduoduo.com/api/upload") // 上传专用地址
// 1. 打开文件
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("打开文件失败: %v", err)
}
defer file.Close() // 确保文件关闭
// 当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 生成签名参数
params := map[string]interface{}{
"type": "pdd.goods.img.upload", // API类型商品图片上传
"data_type": "JSON",
"client_id": clientId,
"access_token": accessToken,
"timestamp": timestamp,
}
sign := generateSign(params, clientSecret)
// 创建multipart/form-data请求体
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 读取文件内容
fileData, err := os.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("读取文件失败: %v", err)
}
// 添加文件字段
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
if err != nil {
return "", fmt.Errorf("创建表单文件失败: %v", err)
}
_, err = io.Copy(part, bytes.NewReader(fileData))
if err != nil {
return "", fmt.Errorf("复制文件内容失败: %v", err)
}
// 添加其他参数到multipart表单
for key, value := range params {
writer.WriteField(key, fmt.Sprintf("%v", value))
}
writer.WriteField("sign", sign) // 添加签名
writer.Close() // 关闭writer完成表单构建
// 发送POST请求
request := gorequest.New()
resp, respBody, errs := request.Post(url).
Timeout(30*time.Second).
Set("Content-Type", writer.FormDataContentType()). // 设置Content-Type
Send(body.String()). // 发送请求体
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(respBody), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理
if response["error_response"] != nil {
var errorWrapper ErrorWrapper
// 解析响应
if err := json.Unmarshal([]byte(respBody), &errorWrapper); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// GoodsAddRequest 商品添加/编辑请求结构体
type GoodsAddRequest struct {
AutoFillSpuProperty bool `json:"auto_fill_spu_property,omitempty"` // 是否自动补充标品属性
BadFruitClaim int `json:"bad_fruit_claim,omitempty"` // 坏果包赔
BuyLimit int64 `json:"buy_limit,omitempty"` // 限购次数
CarouselGallery []string `json:"carousel_gallery"` // 商品轮播图
CarouselVideo []CarouselVideo `json:"carousel_video,omitempty"` // 商品视频
CarouselVideoURL string `json:"carousel_video_url,omitempty"` // 轮播视频
CatID int64 `json:"cat_id"` // 叶子类目ID
CostTemplateID int64 `json:"cost_template_id"` // 物流运费模板ID
CountryID int `json:"country_id"` // 地区/国家ID
CustomerNum int64 `json:"customer_num,omitempty"` // 团购人数
Customs string `json:"customs,omitempty"` // 海关名称
DeliveryOneDay int `json:"delivery_one_day,omitempty"` // 是否当日发货
DeliveryType int `json:"delivery_type,omitempty"` // 发货方式
DetailGallery []string `json:"detail_gallery"` // 商品详情图
ElecGoodsAttributes ElecGoodsAttributes `json:"elec_goods_attributes,omitempty"` // 卡券类商品属性
GoodsDesc string `json:"goods_desc,omitempty"` // 商品描述
GoodsName string `json:"goods_name"` // 商品标题
GoodsProperties []GoodsProperty `json:"goods_properties,omitempty"` // 商品属性列表
GoodsTradeAttr GoodsTradeAttr `json:"goods_trade_attr,omitempty"` // 日历商品交易相关信息
GoodsTravelAttr GoodsTravelAttr `json:"goods_travel_attr,omitempty"` // 商品出行信息
GoodsType int `json:"goods_type"` // 商品类型
IgnoreEditWarn bool `json:"ignore_edit_warn,omitempty"` // 是否获取商品发布警告信息
ImageURL string `json:"image_url,omitempty"` // 商品主图
InvoiceStatus bool `json:"invoice_status,omitempty"` // 是否支持开票
IsCustoms bool `json:"is_customs,omitempty"` // 是否需要上报海关
IsFolt bool `json:"is_folt"` // 是否支持假一赔十
IsGroupPreSale int `json:"is_group_pre_sale,omitempty"` // 是否成团预售
IsPreSale bool `json:"is_pre_sale"` // 是否预售
IsRefundable bool `json:"is_refundable"` // 是否7天无理由退换货
IsSkuPreSale int `json:"is_sku_pre_sale,omitempty"` // 是否sku预售
LackOfWeightClaim int `json:"lack_of_weight_claim,omitempty"` // 缺重包退
LocalServiceIDList []int `json:"local_service_id_list,omitempty"` // 本地服务id
MaiJiaZiTi string `json:"mai_jia_zi_ti,omitempty"` // 买家自提模版id
MarketPrice int64 `json:"market_price"` // 参考价格(分)
OrderLimit int `json:"order_limit,omitempty"` // 单次限量
OriginCountryID int `json:"origin_country_id,omitempty"` // 原产地id
OutGoodsID string `json:"out_goods_id,omitempty"` // 商品外部编码
OutSourceGoodsID string `json:"out_source_goods_id,omitempty"` // 第三方商品Id
OutSourceType int `json:"out_source_type,omitempty"` // 第三方商品来源
OverseaGoods OverseaGoods `json:"oversea_goods,omitempty"` // 海淘商品信息
OverseaType int `json:"oversea_type,omitempty"` // oversea_type
PreSaleTime int64 `json:"pre_sale_time,omitempty"` // 预售时间
PrivacyDelivery int `json:"privacy_delivery,omitempty"` // 保密发货
QuanGuoLianBao int `json:"quan_guo_lian_bao,omitempty"` // 是否支持全国联保
SecondHand bool `json:"second_hand"` // 是否二手商品
ShangMenAnZhuang string `json:"shang_men_an_zhuang,omitempty"` // 上门安装模版id
ShipmentLimitSecond int64 `json:"shipment_limit_second"` // 承诺发货时间(秒)
ShopGroupID int64 `json:"shop_group_id,omitempty"` // 门店组id
SizeSpecID int64 `json:"size_spec_id,omitempty"` // 尺码表id
SkuList []Sku `json:"sku_list"` // sku对象列表
SkuType int `json:"sku_type,omitempty"` // 库存方式
SongHuoAnZhuang string `json:"song_huo_an_zhuang,omitempty"` // 送货入户并安装模版id
SongHuoRuHu string `json:"song_huo_ru_hu,omitempty"` // 送货入户模版id
TinyName string `json:"tiny_name,omitempty"` // 短标题
TwoPiecesDiscount int `json:"two_pieces_discount,omitempty"` // 满2件折扣
Warehouse string `json:"warehouse,omitempty"` // 保税仓
WarmTips string `json:"warm_tips,omitempty"` // 水果类目温馨提示
ZhiHuanBuXiu int `json:"zhi_huan_bu_xiu,omitempty"` // 只换不修的天数
}
// CarouselVideo 轮播视频
type CarouselVideo struct {
FileID int64 `json:"file_id,omitempty"` // 商品视频id
VideoURL string `json:"video_url,omitempty"` // 商品视频url
}
// ElecGoodsAttributes 卡券类商品属性
type ElecGoodsAttributes struct {
BeginTime int64 `json:"begin_time,omitempty"` // 开始时间
DaysTime int `json:"days_time,omitempty"` // 天数内有效
EndTime int64 `json:"end_time,omitempty"` // 截止时间
TimeType int `json:"time_type,omitempty"` // 卡券核销类型
}
// GoodsProperty 商品属性
type GoodsProperty struct {
GroupID int `json:"group_id,omitempty"` // 属性值分组ID
ImgURL string `json:"img_url,omitempty"` // 图片url
Note string `json:"note,omitempty"` // 备注
ParentSpecID int64 `json:"parent_spec_id,omitempty"` // 父规格ID
RefPid int64 `json:"ref_pid,omitempty"` // 引用属性id
SpecID int64 `json:"spec_id,omitempty"` // 规格ID
TemplatePid int64 `json:"template_pid,omitempty"` // 模板属性id
Value string `json:"value,omitempty"` // 属性值
ValueUnit string `json:"value_unit,omitempty"` // 属性单位
Vid int64 `json:"vid,omitempty"` // 属性值id
//TableInfo []TableValueList `json:"table_info,omitempty"` // 成分表表单信息
}
//// TableValueList 表单内容列表
//type TableValueList struct {
// ColumnType int `json:"column_type,omitempty"` // 列类型 1-材质成分百分比
// Unit string `json:"unit,omitempty"` // 表单单位,材质成分表时为:%
// Value string `json:"value,omitempty"` // 表单值,材质成分表时为:占比百分值
//}
// GoodsTradeAttr 日历商品交易相关信息
type GoodsTradeAttr struct {
AdvancesDays int `json:"advances_days,omitempty"` // 提前预定天数
BookingNotes BookingNotes `json:"booking_notes,omitempty"` // 预订须知
LifeSpan int `json:"life_span,omitempty"` // 卡券有效期
}
// BookingNotes 预订须知
type BookingNotes struct {
URL *string `json:"url,omitempty"` // 预定须知图片地址
}
// GoodsTravelAttr 商品出行信息
type GoodsTravelAttr struct {
NeedTourist bool `json:"need_tourist,omitempty"` // 出行人是否必填
Type int `json:"type,omitempty"` // 日历商品类型
}
// OverseaGoods 海淘商品信息
type OverseaGoods struct {
BondedWarehouseKey string `json:"bonded_warehouse_key"` // 保税仓唯一标识
ConsumptionTaxRate int `json:"consumption_tax_rate,omitempty"` // 消费税率
CustomsBroker string `json:"customs_broker,omitempty"` // 清关服务商
HsCode string `json:"hs_code,omitempty"` // 海关编号
ValueAddedTaxRate int `json:"value_added_tax_rate,omitempty"` // 增值税率
}
// Sku SKU信息
type Sku struct {
IsOnsale int `json:"is_onsale"` // sku上架状态
Length int64 `json:"length,omitempty"` // sku送装参数长度
LimitQuantity int64 `json:"limit_quantity"` // sku购买限制
MultiPrice int64 `json:"multi_price"` // 商品团购价格
OutSkuSn string `json:"out_sku_sn,omitempty"` // 商品sku外部编码
OutSourceSkuID string `json:"out_source_sku_id,omitempty"` // 第三方sku Id
OverseaSku OverseaSku `json:"oversea_sku,omitempty"` // 海淘sku信息
Price int64 `json:"price"` // 商品单买价格
Quantity int64 `json:"quantity"` // 商品sku库存数量
SkuPreSaleTime int `json:"sku_pre_sale_time,omitempty"` // sku预售时间戳
SkuProperties []SkuProperty `json:"sku_properties"` // sku属性
SpecIDList string `json:"spec_id_list"` // 商品规格列表
ThumbURL string `json:"thumb_url"` // sku缩略图
Weight int64 `json:"weight"` // 重量g
}
// OverseaSku 海淘SKU信息
type OverseaSku struct {
MeasurementCode string `json:"measurement_code"` // 计量单位编码
Specifications string `json:"specifications"` // 规格
Taxation int `json:"taxation"` // 税费
}
// SkuProperty SKU属性
type SkuProperty struct {
Punit string `json:"punit"` // 属性单位
RefPid int64 `json:"ref_pid"` // 属性id
Value string `json:"value"` // 属性值
Vid int64 `json:"vid"` // 属性值id
}
// 商品新增接口
func pddGoodsAdd(clientId, clientSecret, accessToken string, goodsAddJson string) (string, error) {
url := fmt.Sprint("http://gw-api.pinduoduo.com/api/router")
// 当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 生成签名参数
params := map[string]interface{}{
"type": "pdd.goods.add", // API类型商品新增接口
"data_type": "JSON",
"client_id": clientId,
"access_token": accessToken,
"timestamp": timestamp,
}
if goodsAddJson == "" {
return "", fmt.Errorf("goodsAddJson 参数为空!")
}
// 将JSON参数合并到params中
toParams, err := addStructToParams(goodsAddJson, params)
if err != nil {
return "", err
}
sign := generateSign(toParams, clientSecret)
if sign == "" {
return "", fmt.Errorf("生成 sign 签名错误!")
}
params["sign"] = sign
request := gorequest.New()
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(30 * time.Second).
Send(params).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理
if response["error_response"] != nil {
var errorWrapper ErrorWrapper
// 解析响应
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// 联合拼多多图片上传的商品新增(组合接口)
func selfPddGoodsAdd(clientId, clientSecret, accessToken string, filePath string, goodsAddJson string) (string, error) {
// 1. 上传商品图片
upload, err := pddGoodsImgUpload(clientId, clientSecret, accessToken, filePath)
if err != nil {
return "", err
}
// 2. 解析商品添加请求JSON
var goodsAddRequest GoodsAddRequest
if err := json.Unmarshal([]byte(goodsAddJson), &goodsAddRequest); err != nil {
return "", fmt.Errorf("解析 goodsAddJson 失败: %v", err)
}
// 3. 设置图片URL
goodsAddRequest.ImageURL = upload
// 4. 重新序列化为JSON
goodsAddRequestStr, err := json.Marshal(goodsAddRequest)
if err != nil {
return "", fmt.Errorf("序列化 goodsAddRequest 失败:%v", err)
}
// 5. 调用商品添加接口
add, err := pddGoodsAdd(clientId, clientSecret, accessToken, string(goodsAddRequestStr))
if err != nil {
return "", err
}
return add, nil
}
// 批量数据解密脱敏接口
func pddOpenDecryptMaskBatch(clientId, clientSecret, accessToken string, reqJson string) (string, error) {
url := fmt.Sprint("http://gw-api.pinduoduo.com/api/router")
// 当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 生成签名参数
params := map[string]interface{}{
"type": "pdd.open.decrypt.mask.batch", // API类型批量解密脱敏
"data_type": "JSON",
"client_id": clientId,
"access_token": accessToken,
"timestamp": timestamp,
}
params["data_list"] = reqJson // 添加数据列表
sign := generateSign(params, clientSecret)
if sign == "" {
return "", fmt.Errorf("生成 sign 签名错误!")
}
params["sign"] = sign
request := gorequest.New()
resp, body, errs := request.Get(url).
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
Set("Accept", "application/json, text/plain, */*").
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
Timeout(30 * time.Second).
Send(params).
End()
if len(errs) > 0 {
return "", fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 异常处理
if response["error_response"] != nil {
var errorWrapper ErrorWrapper
// 解析响应
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
}
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// =========================== 辅助函数 ============================
// 将结构体的字段添加到 params 映射中
func addStructToParams(req string, params map[string]interface{}) (map[string]interface{}, error) {
// 将JSON字符串解析为map
var tempMap map[string]interface{}
if err := json.Unmarshal([]byte(req), &tempMap); err != nil {
return nil, fmt.Errorf("解析 req json失败%v ", err)
}
// 合并到params
for k, v := range tempMap {
// 只添加非nil的值
if v != nil {
params[k] = v
}
}
return params, nil
}
// ========================== C 导入函数 ===================
// PddGoodsOuterCatMappingGet 类目预测
//
//export PddGoodsOuterCatMappingGet
func PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName *C.char) *C.char {
// 将C字符串转换为Go字符串
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goOuterCatId := C.GoString(outerCatId)
goOuterCatName := C.GoString(outerCatName)
goOuterGoodsName := C.GoString(outerGoodsName)
// 调用Go函数
info, err := pddGoodsOuterCatMappingGet(goClientId, goClientSecret, goAccessToken, goOuterCatId, goOuterCatName, goOuterGoodsName)
if err != nil {
return C.CString(err.Error()) // 返回错误信息
}
return C.CString(info) // 返回成功信息
}
// PddLogisticsCompaniesGet 快递公司查看
//
//export PddLogisticsCompaniesGet
func PddLogisticsCompaniesGet(clientId, clientSecret *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
info, err := pddLogisticsCompaniesGet(goClientId, goClientSecret)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// PddErpOrderSync erp打单信息同步
//
//export PddErpOrderSync
func PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
orderSn, orderState, waybillNo *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goLogisticsId := C.GoString(logisticsId)
goOrderSn := C.GoString(orderSn)
goOrderState := C.GoString(orderState)
goWaybillNo := C.GoString(waybillNo)
info, err := pddErpOrderSync(goClientId, goClientSecret, goAccessToken, goLogisticsId, goOrderSn, goOrderState, goWaybillNo)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// PddOrderSynchronization 拼多多订单同步
//
//export PddOrderSynchronization
func PddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId,
orderSn, orderState, waybillNo *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goLogisticsCompany := C.GoString(logisticsCompany)
goLogisticsId := C.GoString(logisticsId)
goOrderSn := C.GoString(orderSn)
goOrderState := C.GoString(orderState)
goWaybillNo := C.GoString(waybillNo)
info, err := pddOrderSynchronization(goClientId, goClientSecret, goAccessToken, goLogisticsCompany, goLogisticsId, goOrderSn, goOrderState, goWaybillNo)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// PddGoodsImgUpload 商品图片上传接口
//
//export PddGoodsImgUpload
func PddGoodsImgUpload(clientId, clientSecret, accessToken, filePath *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goFilePath := C.GoString(filePath)
info, err := pddGoodsImgUpload(goClientId, goClientSecret, goAccessToken, goFilePath)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// PddGoodsAdd 商品新增接口
//
//export PddGoodsAdd
func PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goGoodsAddJson := C.GoString(goodsAddJson)
info, err := pddGoodsAdd(goClientId, goClientSecret, goAccessToken, goGoodsAddJson)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// SelfPddGoodsAdd 联合拼多多图片上传的商品新增
//
//export SelfPddGoodsAdd
func SelfPddGoodsAdd(clientId, clientSecret, accessToken, filePath, goodsAddJson *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goFilePath := C.GoString(filePath)
goGoodsAddJson := C.GoString(goodsAddJson)
info, err := selfPddGoodsAdd(goClientId, goClientSecret, goAccessToken, goFilePath, goGoodsAddJson)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// PddOpenDecryptMaskBatch 批量数据解密脱敏接口
//
//export PddOpenDecryptMaskBatch
func PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson *C.char) *C.char {
goClientId := C.GoString(clientId)
goClientSecret := C.GoString(clientSecret)
goAccessToken := C.GoString(accessToken)
goReqJson := C.GoString(reqJson)
info, err := pddOpenDecryptMaskBatch(goClientId, goClientSecret, goAccessToken, goReqJson)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// FreeCString 释放C字符串内存
//
//export FreeCString
func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str))
}
// main函数
func main() {
}

232
pdd/pddDll.go Normal file
View File

@ -0,0 +1,232 @@
package main
import "C"
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
)
// pddDLL 拼多多API调用结构体
type pddDLL struct {
dll *syscall.DLL
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
pddLogisticsCompaniesGet *syscall.Proc // 快递公司查看
pddErpOrderSync *syscall.Proc // erp打单信息同步
freeCString *syscall.Proc // 释放C字符串
}
// InitPddDLL 初始化pddDLL
func InitPddDLL() (*pddDLL, error) {
dllPath := filepath.Join("pdd", "dll", "pdd.dll")
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
}
if dll, err := syscall.LoadDLL(dllPath); err != nil {
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
} else {
return &pddDLL{
dll: dll,
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
pddLogisticsCompaniesGet: dll.MustFindProc("PddLogisticsCompaniesGet"),
pddErpOrderSync: dll.MustFindProc("PddErpOrderSync"),
freeCString: dll.MustFindProc("FreeCString"),
}, nil
}
}
// cStr 获取C字符串
func (m *pddDLL) 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
}
// PddGoodsOuterCatMappingGet 类目预测
func (m *pddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
outerCatId, outerCatName, outerGoodsName string) (string, error) {
proc, err := m.dll.FindProc("PddGoodsOuterCatMappingGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet 函数: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
info, _, err := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(outerCatIdPtr)),
uintptr(unsafe.Pointer(outerCatNamePtr)),
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return "", fmt.Errorf("调用函数 PddGoodsOuterCatMappingGet 失败: %v", err)
}
return m.cStr(info), nil
}
// PddLogisticsCompaniesGet 快递公司查看
func (m *pddDLL) PddLogisticsCompaniesGet(clientId, clientSecret string) (string, error) {
proc, err := m.dll.FindProc("PddLogisticsCompaniesGet")
if err != nil {
return "", fmt.Errorf("找不到函数 PddLogisticsCompaniesGet 函数: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
info, _, err := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return "", fmt.Errorf("调用函数 PddGoodsOuterCatMappingGet 失败: %v", err)
}
return m.cStr(info), nil
}
// PddErpOrderSync erp打单信息同步
func (m *pddDLL) PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
orderSn, orderState, waybillNo string) (string, error) {
proc, err := m.dll.FindProc("PddErpOrderSync")
if err != nil {
return "", fmt.Errorf("找不到函数 PddErpOrderSync 函数: %v", err)
}
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
logisticsIdPtr, _ := syscall.BytePtrFromString(logisticsId)
orderSnPtr, _ := syscall.BytePtrFromString(orderSn)
orderStatePtr, _ := syscall.BytePtrFromString(orderState)
waybillNoPtr, _ := syscall.BytePtrFromString(waybillNo)
info, _, err := proc.Call(
uintptr(unsafe.Pointer(clientIdPtr)),
uintptr(unsafe.Pointer(clientSecretPtr)),
uintptr(unsafe.Pointer(accessTokenPtr)),
uintptr(unsafe.Pointer(logisticsIdPtr)),
uintptr(unsafe.Pointer(orderSnPtr)),
uintptr(unsafe.Pointer(orderStatePtr)),
uintptr(unsafe.Pointer(waybillNoPtr)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return "", fmt.Errorf("调用函数 PddGoodsOuterCatMappingGet 失败: %v", err)
}
return m.cStr(info), nil
}
func main() {
clientId := "203c5a7ba8bd4b8488d5e26f93052642"
clientSecret := "892ffaa86e12b7a3d8d2942b669d9aa520ad8179"
accessToken := "bd96218bb2a146779701506dc1e5e5c478692539"
//outerCatId := "15543"
//outerCatName := "书籍/杂志/报纸"
//outerGoodsName := "书籍医家金鉴 妇产科学卷"
//logisticsId := 0
//orderSn := ""
//orderState := ""
//waybillNo := ""
//logisticsCompany := "德邦"
// 初始化
//dll, err := InitPddDLL()
//if err != nil {
// fmt.Println(err)
//}
// 类目预测
//info, err := dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken, outerCatId, outerCatName, outerGoodsName)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(info)
// 快递公司查看
//get, err := dll.PddLogisticsCompaniesGet(clientId, clientSecret)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(get)
//var logisticsResponse LogisticsResponse
//if err := json.Unmarshal([]byte(get), &logisticsResponse); err != nil {
// fmt.Println(err)
//}
//
//var company string
//var available int
//var code string
//for _, logisticsCompanies := range logisticsResponse.LogisticsCompaniesGetResponse.LogisticsCompanies {
// if strings.Contains(logisticsCompanies.LogisticsCompany, logisticsCompany) {
// company = logisticsCompanies.LogisticsCompany
// logisticsId = logisticsCompanies.ID
// available = logisticsCompanies.Available
// code = logisticsCompanies.Code
// break
// }
//}
//fmt.Println("快递公司名称: ", company)
//fmt.Println("快递公司编码: ", logisticsId)
//fmt.Println("是否有效: ", available)
//fmt.Println("物流公司代码: ", code)
//file := "D:\\isbn_images\\result\\9780007935192.jpg"
//open, err := os.Open(file)
//if err != nil {
// fmt.Println(err)
//}
//defer open.Close()
////base := filepath.Base(file)
//// 商品图片上传接口
//upload, err := pddGoodsImgUpload(clientId, clientSecret, accessToken, file)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(upload)
//get, err := pddLogisticsCompaniesGet(clientId, clientSecret)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println(get)
// 脱敏
jsonStr := `[{"data_tag":"251229-272441044622514","encrypted_data":"~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"}]`
//var records []DataList
//err := json.Unmarshal([]byte(jsonStr), &records)
//if err != nil {
// log.Fatal("解析JSON失败:", err)
//}
batch, err := pddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, jsonStr)
if err != nil {
fmt.Println(err)
}
fmt.Println(batch)
}
type DataList struct {
DataTag string `json:"data_tag"`
EncryptedData string `json:"encrypted_data"`
}

Binary file not shown.

View File

@ -22,7 +22,8 @@ extern const char *_GoStringPtr(_GoString_ s);
#line 3 "proxy.go" #line 3 "proxy.go"
#include <stdlib.h>
#include <stdlib.h>
#line 1 "cgo-generated-wrapper" #line 1 "cgo-generated-wrapper"
@ -87,35 +88,35 @@ extern "C" {
#endif #endif
// 导出函数:获取代理健康状态(用于调试) // GetProxyHealth 导出函数:获取代理健康状态(用于调试)
// //
extern __declspec(dllexport) char* GetProxyHealth(void); extern __declspec(dllexport) char* GetProxyHealth(void);
// 导出函数:代理类型管理器 // ProxyTypeManager 导出函数:代理类型管理器
// //
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);
// 导出函数:查询机器码 // GetMachineCode 导出函数:查询机器码
// //
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret); extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
// 导出函数:充值卡密 // RechargeCard 导出函数:充值卡密
// //
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode); extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
// 导出函数:获取代理服务器列表 // GetProxies 导出函数:获取代理服务器列表
// //
extern __declspec(dllexport) char* GetProxies(char* machineCode); extern __declspec(dllexport) char* GetProxies(char* machineCode);
// 导出函数:检查卡密是否过期 // CheckTailCardSecretExpired 导出函数:检查卡密是否过期
// //
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret); extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
// 导出函数:初始化代理管理器 // InitProxyManager 导出函数:初始化代理管理器
// //
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType); extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
// 导出函数释放C字符串内存 // FreeCString 导出函数释放C字符串内存
// //
extern __declspec(dllexport) void FreeCString(char* str); extern __declspec(dllexport) void FreeCString(char* str);

View File

@ -20,11 +20,11 @@ import (
// 代理类型常量 // 代理类型常量
const ( const (
CalfElephantProxyType = "CALF_ELEPHANT_PROXY" CalfElephantProxyType = "CALF_ELEPHANT_PROXY" // 小象代理类型
TailProxyType = "TAIL_PROXY" TailProxyType = "TAIL_PROXY" // 尾巴代理类型
) )
// 代理服务器列表 // 小象代理服务器列表
var ( var (
servers = []string{ servers = []string{
"http-dynamic.xiaoxiangdaili.com", "http-dynamic.xiaoxiangdaili.com",
@ -32,15 +32,16 @@ var (
"http-dynamic-S03.xiaoxiangdaili.com", "http-dynamic-S03.xiaoxiangdaili.com",
"http-dynamic-S04.xiaoxiangdaili.com", "http-dynamic-S04.xiaoxiangdaili.com",
} }
// 互斥锁,用于保护全局随机数生成器的并发访问
randMutex sync.Mutex randMutex sync.Mutex
globalRand *rand.Rand globalRand *rand.Rand // 全局随机数生成器
// 代理健康状态管理 // 代理健康状态管理
// 代理健康状态映射表key为代理主机名value为健康状态
proxyHealthMaps = make(map[string]*ProxyHealth) proxyHealthMaps = make(map[string]*ProxyHealth)
proxyHealthMutex sync.RWMutex proxyHealthMutex sync.RWMutex
) )
// ProxyManager 代理管理器结构体 // ProxyManager 代理管理器结构体,用于管理代理配置信息
type ProxyManager struct { type ProxyManager struct {
servers []string `json:"servers"` // 代理服务器列表 servers []string `json:"servers"` // 代理服务器列表
username string `json:"username"` // 代理账号 username string `json:"username"` // 代理账号
@ -49,7 +50,7 @@ type ProxyManager struct {
proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
} }
// 代理健康状态 // 代理健康状态结构体,用于跟踪代理的健康状况
type ProxyHealth struct { type ProxyHealth struct {
SuccessCount int // 成功次数 SuccessCount int // 成功次数
FailCount int // 失败次数 FailCount int // 失败次数
@ -58,39 +59,46 @@ type ProxyHealth struct {
IsHealthy bool // 是否健康 IsHealthy bool // 是否健康
} }
// 初始化函数,在程序启动时自动执行
func init() { func init() {
// 创建全局的随机数生成器 // 创建全局的随机数生成器,使用当前时间作为种子
globalRand = rand.New(rand.NewSource(time.Now().UnixNano())) globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
} }
// 获取代理URL // 获取代理URL代理类型管理器根据代理类型构建不同的代理URL
func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) { func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
switch proxyType { switch proxyType {
case CalfElephantProxyType: case CalfElephantProxyType:
// 小象代理使用用户名和密码构建代理URL
return buildCalfElephantProxyURL(username, password) return buildCalfElephantProxyURL(username, password)
case TailProxyType: case TailProxyType:
// 尾巴代理使用机器码构建代理URL
return buildTailProxyURL(machineCode) return buildTailProxyURL(machineCode)
default: default:
// 不支持的代理类型,返回错误
return "", fmt.Errorf("不支持的代理类型: %s", proxyType) return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
} }
} }
// 构建小象代理URL // 构建小象代理URL
func buildCalfElephantProxyURL(username, password string) (string, error) { func buildCalfElephantProxyURL(username, password string) (string, error) {
// 随机选择一个代理服务器
server := randomServer() server := randomServer()
// 构建代理URL格式http://用户名:密码@服务器:端口
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d", proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
url.QueryEscape(username), url.QueryEscape(username), // URL编码用户名防止特殊字符问题
url.QueryEscape(password), url.QueryEscape(password), // URL编码密码防止特殊字符问题
server, server, // 服务器地址
10030) 10030) // 固定端口号
// 检测代理可用性 // 检测代理可用性
if err := checkProxyHealth(proxyURL); err != nil { if err := checkProxyHealth(proxyURL); err != nil {
// 代理检测失败,记录警告日志
log.Printf("[WARN] 代理 %s 检测失败: %v", server, err) log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
// 尝试下一个代理服务器 // 尝试下一个代理服务器
return tryNextCalfElephantProxy(username, password, server) return tryNextCalfElephantProxy(username, password, server)
} }
// 代理检测成功,记录信息日志
log.Printf("[INFO] 使用小象代理: %s", server) log.Printf("[INFO] 使用小象代理: %s", server)
return proxyURL, nil return proxyURL, nil
} }
@ -104,35 +112,39 @@ func tryNextCalfElephantProxy(username, password, failedServer string) (string,
availableServers = append(availableServers, server) availableServers = append(availableServers, server)
} }
} }
// 如果没有可用的服务器,返回错误
if len(availableServers) == 0 { if len(availableServers) == 0 {
return "", fmt.Errorf("所有小象代理服务器都不可用") return "", fmt.Errorf("所有小象代理服务器都不可用")
} }
// 随机尝试可用服务器 // 随机尝试可用服务器
for _, server := range shuffleServers(availableServers) { for _, server := range shuffleServers(availableServers) {
// 构建代理URL
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d", proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
url.QueryEscape(username), url.QueryEscape(username),
url.QueryEscape(password), url.QueryEscape(password),
server, server,
10030) 10030)
// 检测代理可用性
if err := checkProxyHealth(proxyURL); err == nil { if err := checkProxyHealth(proxyURL); err == nil {
log.Printf("[INFO] 切换到可用代理: %s", server) log.Printf("[INFO] 切换到可用代理: %s", server)
return proxyURL, nil return proxyURL, nil
} }
// 代理不可用,记录警告日志
log.Printf("[WARN] 代理 %s 检测失败", server) log.Printf("[WARN] 代理 %s 检测失败", server)
} }
// 所有服务器都检测失败,返回错误
return "", fmt.Errorf("所有可用的小象代理服务器都检测失败") return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
} }
// 构建内置代理URL // 构建内置代理URL
func buildTailProxyURL(machineCode string) (string, error) { func buildTailProxyURL(machineCode string) (string, error) {
// 获取代理列表
proxies, err := getProxies(machineCode) proxies, err := getProxies(machineCode)
if err != nil { if err != nil {
return "", err return "", err
} }
// 检查是否获取到有效代理
if len(proxies) == 0 { if len(proxies) == 0 {
return "", fmt.Errorf("未获取到有效代理") return "", fmt.Errorf("未获取到有效代理")
} }
@ -140,6 +152,7 @@ func buildTailProxyURL(machineCode string) (string, error) {
// 过滤并选择健康的代理 // 过滤并选择健康的代理
healthyProxies := filterHealthyProxies(proxies) healthyProxies := filterHealthyProxies(proxies)
if len(healthyProxies) > 0 { if len(healthyProxies) > 0 {
// 从健康代理中随机选择一个
proxyURL := "http://" + randomElement(healthyProxies) proxyURL := "http://" + randomElement(healthyProxies)
log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL) log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
return proxyURL, nil return proxyURL, nil
@ -150,15 +163,16 @@ func buildTailProxyURL(machineCode string) (string, error) {
return findWorkingTailProxy(proxies) return findWorkingTailProxy(proxies)
} }
// 过滤健康代理 // 过滤健康代理,返回当前健康的代理列表
func filterHealthyProxies(proxies []string) []string { func filterHealthyProxies(proxies []string) []string {
// 获取读锁允许多个goroutine同时读取
proxyHealthMutex.RLock() proxyHealthMutex.RLock()
defer proxyHealthMutex.RUnlock() defer proxyHealthMutex.RUnlock() // 确保函数结束时释放锁
var healthy []string var healthy []string
for _, proxy := range proxies { for _, proxy := range proxies {
if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy { if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
// 检查是否在最近检查过5分钟内 // 检查是否在最近检查过5分钟内,避免使用过时的健康状态
if time.Since(health.LastCheck) < 5*time.Minute { if time.Since(health.LastCheck) < 5*time.Minute {
healthy = append(healthy, proxy) healthy = append(healthy, proxy)
} }
@ -177,69 +191,73 @@ func findWorkingTailProxy(proxies []string) (string, error) {
proxy string proxy string
err error err error
} }
// 创建带缓冲的通道,用于收集检测结果
ch := make(chan proxyResult, len(shuffledProxies)) ch := make(chan proxyResult, len(shuffledProxies))
var wg sync.WaitGroup var wg sync.WaitGroup // 等待组用于等待所有检测goroutine完成
// 限制并发数 // 限制并发数,避免同时检测太多代理导致网络拥塞
sem := make(chan struct{}, 5) sem := make(chan struct{}, 5)
// 并发检测每个代理
for _, proxy := range shuffledProxies { for _, proxy := range shuffledProxies {
wg.Add(1) wg.Add(1) // 增加等待组计数
go func(p string) { go func(p string) {
defer wg.Done() defer wg.Done() // 函数结束时减少等待组计数
sem <- struct{}{} sem <- struct{}{} // 获取信号量,限制并发数
defer func() { <-sem }() defer func() { <-sem }() // 释放信号量
proxyURL := "http://" + p proxyURL := "http://" + p
err := checkProxyHealth(proxyURL) err := checkProxyHealth(proxyURL)
// 将检测结果发送到通道
ch <- proxyResult{proxy: p, err: err} ch <- proxyResult{proxy: p, err: err}
}(proxy) }(proxy)
} }
// 等待所有检测goroutine完成
wg.Wait() wg.Wait()
close(ch) close(ch) // 关闭通道,表示不会再有数据发送
// 收集结果 // 收集结果,查找可用的代理
var workingProxies []string var workingProxies []string
for result := range ch { for result := range ch {
if result.err == nil { if result.err == nil {
// 代理可用,添加到工作代理列表
workingProxies = append(workingProxies, result.proxy) workingProxies = append(workingProxies, result.proxy)
// 更新健康状态 // 更新健康状态
updateProxyHealth(result.proxy, true, 0) updateProxyHealth(result.proxy, true, 0)
} else { } else {
// 代理不可用,更新健康状态
updateProxyHealth(result.proxy, false, 0) updateProxyHealth(result.proxy, false, 0)
} }
} }
// 如果有可用的代理,随机选择一个
if len(workingProxies) > 0 { if len(workingProxies) > 0 {
selected := randomElement(workingProxies) selected := randomElement(workingProxies)
log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies)) log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
return "http://" + selected, nil return "http://" + selected, nil
} }
// 所有代理都不可用,返回错误
return "", fmt.Errorf("所有尾巴代理都不可用") return "", fmt.Errorf("所有尾巴代理都不可用")
} }
// 检测代理健康状态 // 检测代理健康状态,通过访问测试网站验证代理可用性
func checkProxyHealth(proxyURL string) error { func checkProxyHealth(proxyURL string) error {
start := time.Now() start := time.Now() // 记录开始时间,用于计算响应时间
// 创建HTTP请求设置代理和超时
req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second) req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
// 发送GET请求到测试网站
resp, _, errs := req.Get("https://shop.kongfz.com/").End() resp, _, errs := req.Get("https://shop.kongfz.com/").End()
responseTime := time.Since(start) responseTime := time.Since(start) // 计算响应时间
// 检查请求错误
if len(errs) > 0 { if len(errs) > 0 {
updateProxyHealth(proxyURL, false, responseTime) updateProxyHealth(proxyURL, false, responseTime)
return fmt.Errorf("代理连接失败: %v", errs) return fmt.Errorf("代理连接失败: %v", errs)
} }
// 检查HTTP状态码
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
updateProxyHealth(proxyURL, false, responseTime) updateProxyHealth(proxyURL, false, responseTime)
return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode) return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
} }
// 代理检测成功
updateProxyHealth(proxyURL, true, responseTime) updateProxyHealth(proxyURL, true, responseTime)
log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime) log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
return nil return nil
@ -247,25 +265,29 @@ func checkProxyHealth(proxyURL string) error {
// 更新代理健康状态 // 更新代理健康状态
func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) { func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
// 获取写锁,确保更新操作的互斥性
proxyHealthMutex.Lock() proxyHealthMutex.Lock()
defer proxyHealthMutex.Unlock() defer proxyHealthMutex.Unlock() // 确保函数结束时释放锁
// 获取代理主机名作为键
host := getProxyHost(proxyURL) host := getProxyHost(proxyURL)
// 如果代理健康状态不存在,创建新的
if _, exists := proxyHealthMaps[host]; !exists { if _, exists := proxyHealthMaps[host]; !exists {
proxyHealthMaps[host] = &ProxyHealth{} proxyHealthMaps[host] = &ProxyHealth{}
} }
health := proxyHealthMaps[host] health := proxyHealthMaps[host]
health.LastCheck = time.Now() health.LastCheck = time.Now() // 更新最后检查时间
if success { if success {
health.SuccessCount++ // 成功的情况
health.FailCount = 0 health.SuccessCount++ // 增加成功次数
health.ResponseTime = responseTime health.FailCount = 0 // 重置失败次数
health.IsHealthy = true health.ResponseTime = responseTime // 记录响应时间
health.IsHealthy = true // 标记为健康
} else { } else {
health.FailCount++ // 失败的情况
health.SuccessCount = 0 health.FailCount++ // 增加失败次数
health.SuccessCount = 0 // 重置成功次数
// 连续失败3次标记为不健康 // 连续失败3次标记为不健康
if health.FailCount >= 3 { if health.FailCount >= 3 {
health.IsHealthy = false health.IsHealthy = false
@ -275,10 +297,11 @@ func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration
// 获取代理主机名 // 获取代理主机名
func getProxyHost(proxyURL string) string { func getProxyHost(proxyURL string) string {
// 去除协议前缀
if strings.HasPrefix(proxyURL, "http://") { if strings.HasPrefix(proxyURL, "http://") {
proxyURL = proxyURL[7:] proxyURL = proxyURL[7:]
} }
// 去除认证信息 // 去除认证信息(用户名:密码@部分)
if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 { if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
proxyURL = proxyURL[atIndex+1:] proxyURL = proxyURL[atIndex+1:]
} }
@ -296,18 +319,19 @@ func randomServer() string {
// 线程安全的随机元素选择 // 线程安全的随机元素选择
func randomElement(slice []string) string { func randomElement(slice []string) string {
randMutex.Lock() randMutex.Lock() // 获取互斥锁
defer randMutex.Unlock() defer randMutex.Unlock() // 确保函数结束时释放锁
return slice[globalRand.Intn(len(slice))] return slice[globalRand.Intn(len(slice))] // 随机选择一个元素
} }
// 打乱服务器顺序 // 打乱服务器顺序
func shuffleServers(servers []string) []string { func shuffleServers(servers []string) []string {
randMutex.Lock() randMutex.Lock()
defer randMutex.Unlock() defer randMutex.Unlock()
// 创建服务器副本,避免修改原始切片
shuffled := make([]string, len(servers)) shuffled := make([]string, len(servers))
copy(shuffled, servers) copy(shuffled, servers)
// 使用Fisher-Yates算法打乱顺序
globalRand.Shuffle(len(shuffled), func(i, j int) { globalRand.Shuffle(len(shuffled), func(i, j int) {
shuffled[i], shuffled[j] = shuffled[j], shuffled[i] shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
}) })
@ -316,52 +340,59 @@ func shuffleServers(servers []string) []string {
// 检查卡密是否过期 // 检查卡密是否过期
func checkTailCardSecretExpired(tailCardSecret string) (bool, error) { func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
// 获取机器码信息
code, err := getMachineCode(tailCardSecret) code, err := getMachineCode(tailCardSecret)
if err != nil { if err != nil {
return false, fmt.Errorf("请求错误: %v", err) return false, fmt.Errorf("请求错误: %v", err)
} }
// 解析过期时间字符串
targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime) targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime)
if err != nil { if err != nil {
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 {
// 卡密已过期
return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime) return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
} }
} }
// 定义响应结构体 // 定义响应结构体
type getMachineCodeResp struct { type getMachineCodeResp struct {
Code int `json:"code"` Code int `json:"code"` // 状态码
Message string `json:"message"` Message string `json:"message"` // 消息
Data struct { Data struct {
MachineCode string `json:"machine_code"` MachineCode string `json:"machine_code"` // 机器码
IpExpTime string `json:"ip_exp_time"` IpExpTime string `json:"ip_exp_time"` // IP过期时间
IpThread int `json:"ip_thread"` IpThread int `json:"ip_thread"` // IP线程数
IpCardCode string `json:"ip_card_code"` IpCardCode string `json:"ip_card_code"` // IP卡密
} `json:"data"` } `json:"data"`
} }
// 查询机器码 // 查询机器码
func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) { func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
url := "http://114.66.2.223:7842/api/proxies/ip_show" url := "http://114.66.2.223:7842/api/proxies/ip_show"
// 构建请求数据
data := map[string]interface{}{ data := map[string]interface{}{
"ip_card_code": tailCardSecret, "ip_card_code": tailCardSecret,
"agent_id": 9999, "agent_id": 9999,
} }
// 发送POST请求
_, body, errs := gorequest.New().Post(url).Send(data).End() _, body, errs := gorequest.New().Post(url).Send(data).End()
if len(errs) > 0 { if len(errs) > 0 {
return nil, fmt.Errorf("查询机器码失败: %v", errs) return nil, fmt.Errorf("查询机器码失败: %v", errs)
} }
// 解析响应
var resp getMachineCodeResp var resp getMachineCodeResp
if err := json.Unmarshal([]byte(body), &resp); err != nil { if err := json.Unmarshal([]byte(body), &resp); err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err) return nil, fmt.Errorf("解析响应失败: %v", err)
} }
// 处理不同的响应码
if resp.Code == 201 { if resp.Code == 201 {
// 需要充值卡密
machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode) machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode)
if err != nil { if err != nil {
return nil, err return nil, err
@ -369,8 +400,10 @@ func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
resp.Data.MachineCode = machineCode resp.Data.MachineCode = machineCode
return &resp, nil return &resp, nil
} else if resp.Code == 200 { } else if resp.Code == 200 {
// 成功获取机器码
return &resp, nil return &resp, nil
} else { } else {
// 其他错误
return nil, fmt.Errorf("查询机器码失败: %s", resp.Message) return nil, fmt.Errorf("查询机器码失败: %s", resp.Message)
} }
} }
@ -378,15 +411,18 @@ func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
// 充值卡密 // 充值卡密
func rechargeCard(tailCardSecret, machineCode string) (string, error) { func rechargeCard(tailCardSecret, machineCode string) (string, error) {
url := "http://114.66.2.223:7842/api/proxies/ip_recharge" url := "http://114.66.2.223:7842/api/proxies/ip_recharge"
// 构建请求数据
data := map[string]interface{}{ data := map[string]interface{}{
"machine_code": machineCode, "machine_code": machineCode,
"ip_card_code": tailCardSecret, "ip_card_code": tailCardSecret,
"agent_id": 9999, "agent_id": 9999,
} }
// 发送POST请求
_, body, errs := gorequest.New().Post(url).Send(data).End() _, body, errs := gorequest.New().Post(url).Send(data).End()
if len(errs) > 0 { if len(errs) > 0 {
return "", fmt.Errorf("充值卡密失败: %v", errs) return "", fmt.Errorf("充值卡密失败: %v", errs)
} }
// 解析响应
var resp struct { var resp struct {
Code int `json:"code"` // 状态码 Code int `json:"code"` // 状态码
Message string `json:"message"` // 返回消息 Message string `json:"message"` // 返回消息
@ -399,6 +435,8 @@ func rechargeCard(tailCardSecret, machineCode string) (string, error) {
if err := json.Unmarshal([]byte(body), &resp); err != nil { if err := json.Unmarshal([]byte(body), &resp); err != nil {
return "", fmt.Errorf("解析响应失败: %v", err) return "", fmt.Errorf("解析响应失败: %v", err)
} }
// 检查响应状态
if resp.Code != 200 { if resp.Code != 200 {
return "", fmt.Errorf("充值卡密失败: %s", resp.Message) return "", fmt.Errorf("充值卡密失败: %s", resp.Message)
} }
@ -411,16 +449,18 @@ func getProxies(machineCode string) ([]string, error) {
// 生成签名 // 生成签名
sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode)))) sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
// 构建请求URL
GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999", GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
machineCode, sign) machineCode, sign)
// 发送GET请求
req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second) req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
_, body, errs := req.End() _, body, errs := req.End()
if len(errs) > 0 { if len(errs) > 0 {
return nil, fmt.Errorf("获取代理失败: %v", errs) return nil, fmt.Errorf("获取代理失败: %v", errs)
} }
// 检查是否为JSON错误响应 // 检查是否为JSON错误响应(响应以{开头,以}结尾)
if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") { if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
// 尝试解析为JSON错误响应 // 尝试解析为JSON错误响应
var errorResp struct { var errorResp struct {
@ -432,15 +472,17 @@ func getProxies(machineCode string) ([]string, error) {
} }
} }
// 解析响应 // 解析响应(每行一个代理地址)
lines := strings.Split(strings.TrimSpace(body), "\n") lines := strings.Split(strings.TrimSpace(body), "\n")
var proxies []string var proxies []string
for _, line := range lines { for _, line := range lines {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
// 过滤空行和JSON格式的行
if line != "" && !strings.HasPrefix(line, "{") { if line != "" && !strings.HasPrefix(line, "{") {
proxies = append(proxies, line) proxies = append(proxies, line)
} }
} }
// 检查是否获取到有效代理
if len(proxies) == 0 { if len(proxies) == 0 {
return nil, fmt.Errorf("未获取到有效代理") return nil, fmt.Errorf("未获取到有效代理")
} }
@ -456,13 +498,13 @@ func initProxy() {
// =================== C 导出函数 ======================= // =================== C 导出函数 =======================
// 导出函数:获取代理健康状态(用于调试) // GetProxyHealth 导出函数:获取代理健康状态(用于调试)
// //
//export GetProxyHealth //export GetProxyHealth
func GetProxyHealth() *C.char { func GetProxyHealth() *C.char {
proxyHealthMutex.RLock() proxyHealthMutex.RLock()
defer proxyHealthMutex.RUnlock() defer proxyHealthMutex.RUnlock()
// 构建健康信息映射
healthInfo := make(map[string]interface{}) healthInfo := make(map[string]interface{})
for proxy, health := range proxyHealthMaps { for proxy, health := range proxyHealthMaps {
healthInfo[proxy] = map[string]interface{}{ healthInfo[proxy] = map[string]interface{}{
@ -473,7 +515,7 @@ func GetProxyHealth() *C.char {
"is_healthy": health.IsHealthy, "is_healthy": health.IsHealthy,
} }
} }
// 序列化为JSON
jsonData, err := json.Marshal(healthInfo) jsonData, err := json.Marshal(healthInfo)
if err != nil { if err != nil {
return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err)) return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err))
@ -482,17 +524,18 @@ func GetProxyHealth() *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 导出函数:代理类型管理器 // ProxyTypeManager 导出函数:代理类型管理器
// //
//export ProxyTypeManager //export ProxyTypeManager
func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char { func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char {
// C字符串转换为Go字符串
goProxyType := C.GoString(proxyType) goProxyType := C.GoString(proxyType)
goUsername := C.GoString(username) goUsername := C.GoString(username)
goPassword := C.GoString(password) goPassword := C.GoString(password)
goMachineCode := C.GoString(machineCode) goMachineCode := C.GoString(machineCode)
log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType) log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType)
// 调用代理类型管理器
proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode) proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode)
if err != nil { if err != nil {
errorMsg := fmt.Sprintf("ERROR: %v", err) errorMsg := fmt.Sprintf("ERROR: %v", err)
@ -504,7 +547,7 @@ func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.cha
return C.CString(proxyURL) return C.CString(proxyURL)
} }
// 导出函数:查询机器码 // GetMachineCode 导出函数:查询机器码
// //
//export GetMachineCode //export GetMachineCode
func GetMachineCode(tailCardSecret *C.char) *C.char { func GetMachineCode(tailCardSecret *C.char) *C.char {
@ -530,7 +573,7 @@ func GetMachineCode(tailCardSecret *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 导出函数:充值卡密 // RechargeCard 导出函数:充值卡密
// //
//export RechargeCard //export RechargeCard
func RechargeCard(tailCardSecret, machineCode *C.char) *C.char { func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
@ -563,7 +606,7 @@ func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 导出函数:获取代理服务器列表 // GetProxies 导出函数:获取代理服务器列表
// //
//export GetProxies //export GetProxies
func GetProxies(machineCode *C.char) *C.char { func GetProxies(machineCode *C.char) *C.char {
@ -595,7 +638,7 @@ func GetProxies(machineCode *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 导出函数:检查卡密是否过期 // CheckTailCardSecretExpired 导出函数:检查卡密是否过期
// //
//export CheckTailCardSecretExpired //export CheckTailCardSecretExpired
func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char { func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
@ -634,7 +677,7 @@ func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 导出函数:初始化代理管理器 // InitProxyManager 导出函数:初始化代理管理器
// //
//export InitProxyManager //export InitProxyManager
func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char { func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
@ -685,7 +728,7 @@ func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType
return C.CString(string(jsonData)) return C.CString(string(jsonData))
} }
// 导出函数释放C字符串内存 // FreeCString 导出函数释放C字符串内存
// //
//export FreeCString //export FreeCString
func FreeCString(str *C.char) { func FreeCString(str *C.char) {

BIN
xy/dll/xy.dll Normal file

Binary file not shown.

114
xy/dll/xy.h Normal file
View File

@ -0,0 +1,114 @@
/* 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>
#include <stdio.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) void FreeCString(char* str);
extern __declspec(dllexport) char* StartServer(char* configFile);
extern __declspec(dllexport) char* StopServer(void);
extern __declspec(dllexport) char* GetServerStatus(void);
extern __declspec(dllexport) char* GetServerAddress(void);
extern __declspec(dllexport) char* ReloadConfig(char* configFile);
extern __declspec(dllexport) char* ExecuteGoodsCreat(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteOtherTypeGoods(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteGoodsPublish(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteGoodsDownShelf(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteGoodsFlash(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteGoodsEditPrice(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteGoodsEditStock(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteSelectGoodsListPrice(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteSelectShopListPrice(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteOpenExpressCompanies(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteOpenOrderShip(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteXyOrderSynchronization(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteGetGoodsDetail(char* bodyJson, char* configFile);
extern __declspec(dllexport) char* ExecuteCountOuterId(char* bodyJson, char* configFile);
#ifdef __cplusplus
}
#endif