diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..cc0215d --- /dev/null +++ b/config/config.go @@ -0,0 +1,271 @@ +package main + +/* +#include +*/ +import "C" +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "unsafe" + + "gopkg.in/ini.v1" + "gopkg.in/yaml.v3" +) + +// ConfigReader 配置文件读取器接口 +type ConfigReader interface { + ReadConfig(data []byte) (string, error) +} + +// JSONConfigReader JSON配置文件读取器 +type JSONConfigReader struct{} + +func (r *JSONConfigReader) ReadConfig(data []byte) (string, error) { + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + if strings.Contains(err.Error(), "cannot unmarshal array into Go value of type map[string]interface {}") { + // 尝试解析为数组 + var arrayResult []map[string]interface{} + if arrayErr := json.Unmarshal(data, &arrayResult); arrayErr != nil { + return "", fmt.Errorf("JSON解析错误: %v", err) + } + arrayResultStr, err := json.Marshal(arrayResult) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(arrayResultStr), nil + } + return "", fmt.Errorf("JSON解析错误: %v", err) + } + resultStr, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(resultStr), nil +} + +// YAMLConfigReader YAML配置文件读取器 +type YAMLConfigReader struct{} + +func (r *YAMLConfigReader) ReadConfig(data []byte) (string, error) { + var result map[string]interface{} + if err := yaml.Unmarshal(data, &result); err != nil { + return "", fmt.Errorf("YAML解析错误: %v", err) + } + resultStr, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(resultStr), nil +} + +// INIConfigReader INI配置文件读取器 +type INIConfigReader struct{} + +func (r *INIConfigReader) ReadConfig(data []byte) (string, error) { + cfg, err := ini.Load(data) + if err != nil { + return "", fmt.Errorf("INI解析错误: %v", err) + } + + result := make(map[string]interface{}) + + // 将INI结构转换为map + for _, section := range cfg.Sections() { + sectionName := section.Name() + if sectionName == "DEFAULT" { + sectionName = "default" + } + + sectionMap := make(map[string]interface{}) + for _, key := range section.Keys() { + sectionMap[key.Name()] = key.Value() + } + + // 如果是默认节,直接合并到顶层 + if sectionName == "default" { + for k, v := range sectionMap { + result[k] = v + } + } else { + result[sectionName] = sectionMap + } + } + resultStr, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(resultStr), nil +} + +// ConfigProcessor 配置处理器 +type ConfigProcessor struct { + readers map[string]ConfigReader +} + +// NewConfigProcessor 创建配置处理器 +func NewConfigProcessor() *ConfigProcessor { + return &ConfigProcessor{ + readers: map[string]ConfigReader{ + "json": &JSONConfigReader{}, + "yaml": &YAMLConfigReader{}, + "yml": &YAMLConfigReader{}, + "ini": &INIConfigReader{}, + "conf": &INIConfigReader{}, + }, + } +} + +// GetFileExtension 获取文件扩展名 +func (p *ConfigProcessor) GetFileExtension(fileName string) string { + ext := strings.ToLower(filepath.Ext(fileName)) + if ext != "" { + return ext[1:] // 去掉点号 + } + return "" +} + +// GetReader 根据文件扩展名获取对应的读取器 +func (p *ConfigProcessor) GetReader(fileName string) (ConfigReader, error) { + ext := p.GetFileExtension(fileName) + if ext == "" { + return nil, fmt.Errorf("无法识别文件类型: %s", fileName) + } + + reader, exists := p.readers[ext] + if !exists { + return nil, fmt.Errorf("不支持的文件类型: %s", ext) + } + + return reader, nil +} + +// ReadConfigFile 读取配置文件 +// 参数: filePath:文件路径 fileName:文件名 +func (p *ConfigProcessor) readConfigFile(filePath, fileName string) (string, error) { + // 构建完整文件路径 + fullPath := p.buildFullPath(filePath, fileName) + // 读取文件内容 + data, err := p.readFileContent(fullPath) + if err != nil { + return "", err + } + + reader, err := p.GetReader(fileName) + if err != nil { + return "", err + } + + return reader.ReadConfig(data) +} + +// buildFullPath 构建完整文件路径 +func (p *ConfigProcessor) buildFullPath(filePath, fileName string) string { + // 如果文件路径为空,使用当前目录 + if filePath == "" { + return fileName + } + + // 检查是否为HTTP/HTTPS URL + if strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://") { + // 确保URL以斜杠结尾 + if !strings.HasSuffix(filePath, "/") { + filePath += "/" + } + return filePath + fileName + } + + // 本地文件路径 + return filepath.Join(filePath, fileName) +} + +// readFileContent 读取文件内容 +func (p *ConfigProcessor) readFileContent(fullPath string) ([]byte, error) { + // 检查是否为HTTP/HTTPS URL + if strings.HasPrefix(fullPath, "http://") || strings.HasPrefix(fullPath, "https://") { + return p.readFromHTTP(fullPath) + } + + // 本地文件 + return p.readFromLocal(fullPath) +} + +// readFromLocal 从本地文件读取内容 +func (p *ConfigProcessor) readFromLocal(filePath string) ([]byte, error) { + // 检查文件是否存在 + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return nil, fmt.Errorf("文件不存在: %s", filePath) + } + + return os.ReadFile(filePath) +} + +// readFromHTTP 从HTTP URL读取内容 +func (p *ConfigProcessor) readFromHTTP(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("HTTP请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + return io.ReadAll(resp.Body) +} + +// PrettyPrint 美化打印配置内容 +func PrettyPrint(config map[string]interface{}, indent string) { + for key, value := range config { + switch v := value.(type) { + case map[string]interface{}: + fmt.Printf("%s%s:\n", indent, key) + PrettyPrint(v, indent+" ") + default: + fmt.Printf("%s%s: %v\n", indent, key, v) + } + } +} + +// ReadConfigFile 读取配置文件 +// +//export ReadConfigFile +func ReadConfigFile(filePath, fileName *C.char) *C.char { + filePathStr := C.GoString(filePath) + fileNameStr := C.GoString(fileName) + info, err := NewConfigProcessor().readConfigFile(filePathStr, fileNameStr) + 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)) +} + +// CSV_VERSION 版本号 +const ( + CSV_VERSION = "v1" +) + +// 获取版本信息 +// +//export GetVersion +func GetVersion() *C.char { + return C.CString(CSV_VERSION) +} + +// 主函数 +func main() { +} diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..90a69d1 --- /dev/null +++ b/config/config.json @@ -0,0 +1,24 @@ +{ + "app": { + "name": "My Application", + "version": "1.0.0", + "debug": false + }, + "server": { + "host": "localhost", + "port": 8080, + "timeout": 30 + }, + "database": { + "host": "localhost", + "port": 5432, + "name": "mydb", + "user": "admin", + "password": "secret" + }, + "features": { + "enable_logging": true, + "max_file_size": 1048576, + "allowed_extensions": ["jpg", "png", "pdf"] + } +} \ No newline at end of file diff --git a/config/configDll.go b/config/configDll.go new file mode 100644 index 0000000..0b6a00e --- /dev/null +++ b/config/configDll.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + // 解析命令行参数 + filePath := "https://newverifyprice.buzhiyushu.cn" + fileName := "verify_price_config.yaml" + + // 创建配置处理器 + processor := NewConfigProcessor() + + // 读取配置文件 + config, err := processor.readConfigFile(filePath, fileName) + if err != nil { + fmt.Printf("读取配置文件失败: %v\n", err) + os.Exit(1) + } + + fmt.Println(config) + // 输出配置内容 + fmt.Printf("成功读取配置文件: %s\n", fileName) + +} diff --git a/config/dll/config.dll b/config/dll/config.dll new file mode 100644 index 0000000..4e1d91b Binary files /dev/null and b/config/dll/config.dll differ diff --git a/config/dll/config.h b/config/dll/config.h new file mode 100644 index 0000000..e18440a --- /dev/null +++ b/config/dll/config.h @@ -0,0 +1,105 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#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 "config.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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 + + +// ReadConfigFile 读取配置文件 +// +extern __declspec(dllexport) char* ReadConfigFile(char* filePath, char* fileName); + +// FreeCString 释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +// 获取版本信息 +// +extern __declspec(dllexport) char* GetVersion(void); + +#ifdef __cplusplus +} +#endif diff --git a/csv/csv.dll b/csv/csv.dll new file mode 100644 index 0000000..bb2c996 Binary files /dev/null and b/csv/csv.dll differ diff --git a/csv/csv.go b/csv/csv.go index 7969910..f51efd9 100644 --- a/csv/csv.go +++ b/csv/csv.go @@ -493,7 +493,7 @@ func (mgr *CSVManager) createEmptyCSVHandle(filename string, delimiter rune, has } // 以读写模式打开文件 - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) if err != nil { return -1, fmt.Errorf("打开文件失败: %w", err) } @@ -766,7 +766,7 @@ func (mgr *CSVManager) openFile(handle *CSVHandle) error { // 正常打开文件 func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error { // 以读写模式打开文件 - file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0644) + file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0755) if err != nil { return err } @@ -794,7 +794,7 @@ func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error { // 使用内存映射打开大文件 func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error { // 对于大文件,使用只读模式打开,写入需要特殊处理 - file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0644) + file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0755) if err != nil { return err } @@ -1023,6 +1023,83 @@ func (mgr *CSVManager) WriteRows(handleID int64, rows [][]string) (int64, error) return handle.TotalRows, nil } +// WriteRowsNum 批量写入多行数据到CSV文件 +// 返回每行数据存储的行号数组(从1开始) +func (mgr *CSVManager) WriteRowsNum(handleID int64, rows [][]string) ([]int64, error) { + + // 获取句柄 + handle, err := mgr.getHandle(handleID) + if err != nil { + return nil, fmt.Errorf("WriteRows 获取句柄失败: %w", err) + } + defer mgr.releaseHandle(handleID) + + // 检查句柄状态 + if !handle.beginOperation() { + return nil, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) + } + defer handle.endOperation() + + handle.mu.Lock() + defer handle.mu.Unlock() + + if !handle.IsOpen { + return nil, fmt.Errorf("文件未打开") + } + + // 检查是否有表头 + if handle.HasHeader && len(handle.Header) > 0 { + // 验证每行的列数 + for i, row := range rows { + if len(row) != len(handle.Header) { + return nil, fmt.Errorf("第%d行列数(%d)与表头列数(%d)不匹配", i+1, len(row), len(handle.Header)) + } + } + } + + // 移动到文件末尾 + if _, err := handle.File.Seek(0, 2); err != nil { + return nil, fmt.Errorf("移动到文件末尾失败: %w", err) + } + + // 计算起始行号 - 关键修正部分 + var startRow int64 + + // 先获取当前文件的实际行数(包括表头) + currentLineCount := handle.TotalRows + if handle.HasHeader && currentLineCount == 0 { + // 如果有表头但还没有数据行,表头算第1行,数据从第2行开始 + startRow = 2 + } else if handle.HasHeader { + // 有表头且已有数据行,表头是第1行,TotalRows不包括表头 + // 所以下一行应该是 TotalRows + 2 + startRow = currentLineCount + 2 + } else { + // 没有表头,直接累加 + startRow = currentLineCount + 1 + } + + // 创建行号数组 + rowNumbers := make([]int64, len(rows)) + for i := 0; i < len(rows); i++ { + rowNumbers[i] = startRow + int64(i) + } + + // 批量写入行数据 + for _, row := range rows { + if err := handle.CSVWriter.Write(row); err != nil { + return nil, fmt.Errorf("写入行失败: %w", err) + } + } + + // 更新行数统计 + handle.TotalRows += int64(len(rows)) + handle.cachedRowCount = handle.TotalRows + handle.rowCountCached = true + + return rowNumbers, nil +} + // logWriteRows 记录行数据到日志文件 func (mgr *CSVManager) logWriteRows(handleID int64, rows [][]string) error { // 获取句柄信息(但不锁定,因为我们只是读取元数据) @@ -1077,6 +1154,61 @@ func (mgr *CSVManager) logWriteRows(handleID int64, rows [][]string) error { return nil } +// logWriteRows 记录行数据到日志文件 +func (mgr *CSVManager) logWriteRowsNum(handleID int64, rows [][]string, rowsNum []int64) error { + // 获取句柄信息(但不锁定,因为我们只是读取元数据) + handle, err := mgr.getHandle(handleID) + if err != nil { + return fmt.Errorf("获取句柄信息失败: %w", err) + } + defer mgr.releaseHandle(handleID) + + // 创建日志目录 + logDir := filepath.Join(filepath.Dir("csv"), "logs") + if err := os.MkdirAll(logDir, 0755); err != nil { + return fmt.Errorf("创建日志目录失败: %w", err) + } + + // 生成日志文件名(基于CSV文件名和时间) + baseName := filepath.Base("csv") + logFileName := fmt.Sprintf("%s_%s_write.log", + baseName[:len(baseName)-len(filepath.Ext(baseName))], + time.Now().Format("20060102")) + logFilePath := filepath.Join(logDir, logFileName) + + // 打开或创建日志文件 + logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("打开日志文件失败: %w", err) + } + defer logFile.Close() + + // 创建日志条目 + logEntry := map[string]interface{}{ + "timestamp": time.Now().Format("2006-01-02 15:04:05.000"), + "handle_id": handleID, + "filename": "csv", + "operation": "write_rows", + "row_count": len(rows), + "total_rows": handle.TotalRows + int64(len(rows)), // 预计的总行数 + "data": rows, + "rowsNum": rowsNum, + } + + // 序列化为JSON + logData, err := json.Marshal(logEntry) + if err != nil { + return fmt.Errorf("序列化日志数据失败: %w", err) + } + + // 写入日志(每行一个JSON对象) + if _, err := logFile.Write(append(logData, '\n')); err != nil { + return fmt.Errorf("写入日志文件失败: %w", err) + } + + return nil +} + // Flush 将缓冲区数据写入文件 func (mgr *CSVManager) Flush(handleID int64) error { // 获取句柄 @@ -1135,6 +1267,24 @@ func (mgr *CSVManager) AppendRows(handleID int64, rows [][]string) (int64, error return totalRows, nil } +// AppendRows 批量追加行数据(自动Flush) +func (mgr *CSVManager) AppendRowsNum(handleID int64, rows [][]string) ([]int64, error) { + rowsNum, err := mgr.WriteRowsNum(handleID, rows) + if err != nil { + return nil, err + } + err = mgr.Flush(handleID) + if err != nil { + return nil, err + } + // 首先记录日志 + if err := mgr.logWriteRowsNum(handleID, rows, rowsNum); err != nil { + // 日志记录失败不影响主流程,但可以打印警告 + fmt.Printf("警告:记录日志失败: %v\n", err) + } + return rowsNum, nil +} + // GetHeader 获取CSV文件表头 func (mgr *CSVManager) GetHeader(handleID int64) ([]string, error) { // 获取句柄 @@ -1469,23 +1619,36 @@ func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) return nil, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) } - // 保存当前文件位置 - currentPos, err := handle.File.Seek(0, 1) // 当前位置 + //// 保存当前文件位置 + //currentPos, err := handle.File.Seek(0, 1) // 当前位置 + //if err != nil { + // return nil, fmt.Errorf("获取当前文件位置失败: %w", err) + //} + + //// 重置到文件开始 + //if _, err := handle.File.Seek(0, 0); err != nil { + // return nil, fmt.Errorf("重置文件指针失败: %w", err) + //} + + // 创建文件副本用于读取,避免影响其他goroutine + file, err := os.Open(handle.Filename) if err != nil { - return nil, fmt.Errorf("获取当前文件位置失败: %w", err) + return nil, fmt.Errorf("打开文件失败: %w", err) } + defer file.Close() - // 重置到文件开始 - if _, err := handle.File.Seek(0, 0); err != nil { - return nil, fmt.Errorf("重置文件指针失败: %w", err) - } - - // 重置CSV阅读器 - reader := csv.NewReader(bufio.NewReader(handle.File)) + // 创建CSV阅读器 + reader := csv.NewReader(file) reader.Comma = handle.Delimiter reader.LazyQuotes = true reader.TrimLeadingSpace = true + //// 重置CSV阅读器 + //reader := csv.NewReader(bufio.NewReader(handle.File)) + //reader.Comma = handle.Delimiter + //reader.LazyQuotes = true + //reader.TrimLeadingSpace = true + // 定位到指定行 var currentRow int64 = 0 var targetRow []string @@ -1496,7 +1659,7 @@ func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) break } // 恢复文件位置 - handle.File.Seek(currentPos, 0) + //handle.File.Seek(currentPos, 0) return nil, fmt.Errorf("读取行失败: %w", err) } @@ -1517,10 +1680,10 @@ func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) currentRow++ } - // 恢复文件位置 - if _, err := handle.File.Seek(currentPos, 0); err != nil { - return nil, fmt.Errorf("恢复文件位置失败: %w", err) - } + //// 恢复文件位置 + //if _, err := handle.File.Seek(currentPos, 0); err != nil { + // return nil, fmt.Errorf("恢复文件位置失败: %w", err) + //} if targetRow == nil { return nil, fmt.Errorf("未找到行号 %d", rowNumber) @@ -1944,6 +2107,46 @@ func WriteRows(handleID C.int, rowsData *C.char) *C.char { return C.CString(string(csvResponseStr)) } +// WriteRowsNum 写入/覆盖行数据 +// +//export WriteRowsNum +func WriteRowsNum(handleID C.int, rowsData *C.char) *C.char { + var csvResponse CSVResponse + goHandleID := int64(handleID) + goRowsData := C.GoString(rowsData) + // json解析 + var data [][]string + err := json.Unmarshal([]byte(goRowsData), &data) + if err != nil { + csvResponse = CSVResponse{ + Success: false, + Message: fmt.Sprintf("rowsData JSON解析失败: %v", err), + } + csvResponseStr, _ := json.Marshal(csvResponse) + return C.CString(string(csvResponseStr)) + } + // 写入数据 + rowsNum, err := GetManager().AppendRowsNum(goHandleID, data) + response := struct { + RowsNum []int64 `json:"rowsNum"` + }{ + RowsNum: rowsNum, + } + if err != nil { + csvResponse = CSVResponse{ + Success: false, + Message: err.Error(), + } + } else { + csvResponse = CSVResponse{ + Success: true, + Data: response, + } + } + csvResponseStr, _ := json.Marshal(csvResponse) + return C.CString(string(csvResponseStr)) +} + // UpdateRow C导出函数 - 修改指定行数据 // //export UpdateRow @@ -2085,5 +2288,5 @@ func FreeCString(str *C.char) { } // 主函数 -func main() { -} +//func main() { +//} diff --git a/csv/csv.h b/csv/csv.h new file mode 100644 index 0000000..583a859 --- /dev/null +++ b/csv/csv.h @@ -0,0 +1,133 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +extern size_t _GoStringLen(_GoString_ s); +extern const char *_GoStringPtr(_GoString_ s); +#endif + +#endif + +/* Start of preamble from import "C" comments. */ + + +#line 3 "csv.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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 + + +// OpenCSVFile 打开CSV文件 +// +extern __declspec(dllexport) char* OpenCSVFile(char* filename, char delimiter, int hasHeader); + +// WriteHeader 写入表头 +// +extern __declspec(dllexport) char* WriteHeader(int handleID, char* header); + +// WriteRows 写入/覆盖行数据 +// +extern __declspec(dllexport) char* WriteRows(int handleID, char* rowsData); + +// WriteRowsNum 写入/覆盖行数据 +// +extern __declspec(dllexport) char* WriteRowsNum(int handleID, char* rowsData); + +// UpdateRow C导出函数 - 修改指定行数据 +// +extern __declspec(dllexport) char* UpdateRow(int handleID, int rowNumber, char* rowData); + +// GetRow C导出函数 - 获取指定行数据 +// +extern __declspec(dllexport) char* GetRow(int handleID, int rowNumber); + +// MergeCSVFilesSimple 合并两个csv文件 +// +extern __declspec(dllexport) char* MergeCSVFilesSimple(int srcHandleID, int dstHandleID, int appendMode); + +// CloseHandles 关闭指定句柄(使用优雅关闭) +// +extern __declspec(dllexport) char* CloseHandles(int handleID); + +// CloseAllHandles 关闭所有句柄 +// +extern __declspec(dllexport) char* CloseAllHandles(void); + +// FreeCString 释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +#ifdef __cplusplus +} +#endif diff --git a/csv/csvDll.go b/csv/csvDll.go index 17b4599..01edc59 100644 --- a/csv/csvDll.go +++ b/csv/csvDll.go @@ -1,10 +1,8 @@ package main import ( - "context" "fmt" - "github.com/redis/go-redis/v9" - "log" + "sync" ) // @@ -584,260 +582,493 @@ import ( //} // 主函数 - 测试代码 +//func main() { +// fmt.Println("=== CSV句柄管理器测试 ===") //7984 +// +// filename := "csv/2006557053525397505_2013842964755726337_20260121_1.csv" +// ////dstFile := "csv/taskLog3.csv" +// //fmt.Printf("1. 创建测试文件: %s\n", filename) +// // +// // 创建 Redis 客户端 +// //rdb := redis.NewClient(&redis.Options{ +// // Addr: "192.168.101.209:6379", // Redis 地址 +// // Password: "", // 密码,没有则为空 +// // DB: 0, // 使用的数据库编号 +// //}) +// // +// //// 创建上下文 +// //ctx := context.Background() +// // +// //// 测试连接 +// //pong, err := rdb.Ping(ctx).Result() +// //if err != nil { +// // log.Fatal("连接 Redis 失败:", err) +// //} +// //fmt.Println("连接成功:", pong) +// +// //key := "2006557053525397505_2010589232836288513_20260112_1" +// +// //// 获取值 +// //val, err := rdb.Get(ctx, "1995373681100910593_2010521216979214337_20260112_1").Result() +// //if err != nil { +// // log.Fatal("获取键值失败:", err) +// //} +// //fmt.Println("key:", val) +// +// handleID, err := GetManager().OpenCSVFile(filename, ',', true) +// if err != nil { +// fmt.Printf("打开文件失败: %v\n", err) +// } +// +// fmt.Printf("2. 打开文件成功,句柄ID: %d\n", handleID) +// +// //// 测试写入表头 +// //header := []string{"ID", "data"} +// //err = GetManager().WriteHeader(handleID, header) +// //if err != nil { +// // fmt.Printf("写入表头失败: %v\n", err) +// //} else { +// // fmt.Println("写入表头成功") +// //} +// +// //// 获取列表所有元素 +// //listValues, err := rdb.LRange(ctx, key, 0, -1).Result() +// //if err != nil { +// // log.Fatal("获取列表失败:", err) +// //} +// // +// //fmt.Printf("列表包含 %d 个元素:\n", len(listValues)) +// //for i, value := range listValues { +// // +// // fmt.Printf(" [%d] %s\n", i, value) +// // +// // rowsData := [][]string{ +// // {fmt.Sprintf("%d", i), value}, +// // } +// // +// // totalRows, err := GetManager().WriteRows(handleID, rowsData) +// // if err != nil { +// // fmt.Printf("批量写入数据失败: %v\n", err) +// // } else { +// // fmt.Printf("批量写入数据成功,行数: %d\n", totalRows) +// // } +// // +// //} +// // +// //// 3. 也可以检查键是否存在 +// //exists, err := rdb.Exists(ctx, key).Result() +// //if err != nil { +// // log.Fatal("检查键是否存在失败:", err) +// //} +// //if exists == 0 { +// // fmt.Printf("键 '%s' 不存在\n", key) +// //} else { +// // fmt.Printf("键 '%s' 存在\n", key) +// //} +// // +// //// 关闭连接 +// //defer rdb.Close() +// +// // +// //// 2. 创建目标文件(部分数据) +// //dstHandle, err := GetManager().OpenCSVFile(dstFile, ',', true) +// //if err != nil { +// // fmt.Printf("创建目标文件失败: %v", err) +// //} +// //// 合并 +// //totalRows, err := GetManager().MergeCSVFilesSimple(handleID, dstHandle, true) +// //if err != nil { +// // fmt.Printf("合并文件失败: %v", err) +// //} +// //fmt.Println(totalRows) +// +// //for i := 0; i < 10000; i++ { +// // // 打开CSV文件 +// // handleID, err := GetManager().OpenCSVFile(filename, ',', true) +// // if err != nil { +// // fmt.Printf("打开文件失败: %v\n", err) +// // } +// // +// // fmt.Printf("2. 打开文件成功,句柄ID: %d\n", handleID) +// // +// // // 测试写入表头 +// // header := []string{"ID", "Name", "Age", "Email"} +// // err = GetManager().WriteHeader(handleID, header) +// // if err != nil { +// // fmt.Printf("写入表头失败: %v\n", err) +// // } else { +// // fmt.Println("写入表头成功") +// // } +// // +// // //// 写入测试数据 +// // //rowsData := [][]string{ +// // // {"1", fmt.Sprintf("张三", i), "25", "zhangsan@example.com"}, +// // // {"2", "李四", "30", "lisi@example.com"}, +// // // {"3", "王五", "28", "wangwu@example.com"}, +// // // {"4", "赵六", "35", "zhaoliu@example.com"}, +// // //} +// // +// // // 写入测试数据 +// // rowsData := [][]string{ +// // {fmt.Sprintf("%d", i+100), "张三", fmt.Sprintf("%d", i+1000), fmt.Sprintf("%d", i+10000)}, +// // } +// // +// // totalRows, err := GetManager().WriteRows(handleID, rowsData) +// // if err != nil { +// // fmt.Printf("批量写入数据失败: %v\n", err) +// // } else { +// // fmt.Printf("批量写入数据成功,行数: %d\n", totalRows) +// // } +// // +// // GetManager().CloseHandle(handleID) +// //} +// +// //// 测试写入表头 +// //header := []string{"ID", "Name", "Age", "Email"} +// //err = GetManager().WriteHeader(handleID, header) +// //if err != nil { +// // fmt.Printf("写入表头失败: %v\n", err) +// //} else { +// // fmt.Println("写入表头成功") +// //} +// +// //// 写入测试数据 +// //rowsData := [][]string{ +// // {"1", "张三", "25", "zhangsan@example.com"}, +// // {"2", "李四", "30", "lisi@example.com"}, +// // {"3", "王五", "28", "wangwu@example.com"}, +// // {"4", "赵六", "35", "zhaoliu@example.com"}, +// //} +// // +// //totalRows, err := GetManager().WriteRows(handleID, rowsData) +// //if err != nil { +// // fmt.Printf("批量写入数据失败: %v\n", err) +// //} else { +// // fmt.Printf("批量写入数据成功,行数: %d\n", totalRows) +// //} +// // +// //// 获取写入后的总行数 +// //totalRows, err = GetManager().GetTotalRows(handleID) +// //if err != nil { +// // fmt.Printf("获取总行数失败: %v\n", err) +// //} else { +// // fmt.Printf("写入后总行数: %d\n", totalRows) +// //} +// +// //// ============ 测试修改行功能 ============ +// // +// //fmt.Println("\n3. 测试修改行功能") +// // +// //// 修改第1行数据(索引0) +// //newRow1 := []string{"1", "张三(已修改1)", "26", "zhangsan_updated@example.com"} +// //err = GetManager().UpdateRow(handleID, 0, newRow1) +// //if err != nil { +// // fmt.Printf("修改第1行数据失败: %v\n", err) +// //} else { +// // fmt.Println("修改第1行数据成功") +// //} +// // +// //// 修改第3行数据(索引2) +// //newRow3 := []string{"3", "王五(已修改)", "29", "wangwu_updated@example.com"} +// //err = GetManager().UpdateRow(handleID, 2, newRow3) +// //if err != nil { +// // fmt.Printf("修改第3行数据失败: %v\n", err) +// //} else { +// // fmt.Println("修改第3行数据成功") +// //} +// // +// //// 获取修改后的行数据 +// //fmt.Println("\n4. 获取修改后的行数据") +// +// for i := 0; i < 10000; i++ { +// row1, err := GetManager().GetRow(handleID, int64(i)) +// if err != nil { +// fmt.Printf("获取第 %d 行失败: %v\n", i, err) +// } else { +// fmt.Printf("第 %d 行数据: %v\n", i, row1) +// } +// } +// //获取第1行 +// //row1, err := GetManager().GetRow(handleID, 10) +// //if err != nil { +// // fmt.Printf("获取第1行失败: %v\n", err) +// //} else { +// // fmt.Printf("第1行数据: %v\n", row1) +// //} +// +// //// 获取第3行 +// //row3, err := GetManager().GetRow(handleID, 2) +// //if err != nil { +// // fmt.Printf("获取第3行失败: %v\n", err) +// //} else { +// // fmt.Printf("第3行数据: %v\n", row3) +// //} +// +// //// ============ 测试批量修改功能 ============ +// // +// //fmt.Println("\n5. 测试批量修改功能") +// // +// //updates := map[int64][]string{ +// // 1: {"2", "李四(批量修改)", "31", "lisi_batch@example.com"}, +// // 3: {"4", "赵六(批量修改)", "36", "zhaoliu_batch@example.com"}, +// //} +// // +// //updatedCount, err := GetManager().UpdateRows(handleID, updates) +// //if err != nil { +// // fmt.Printf("批量修改失败: %v\n", err) +// //} else { +// // fmt.Printf("批量修改成功,修改了%d行\n", updatedCount) +// //} +// // +// //// 验证批量修改结果 +// //row2, err := GetManager().GetRow(handleID, 1) +// //if err != nil { +// // fmt.Printf("获取第2行失败: %v\n", err) +// //} else { +// // fmt.Printf("第2行数据(批量修改后): %v\n", row2) +// //} +// // +// //row4, err := GetManager().GetRow(handleID, 3) +// //if err != nil { +// // fmt.Printf("获取第4行失败: %v\n", err) +// //} else { +// // fmt.Printf("第4行数据(批量修改后): %v\n", row4) +// //} +// // +// //// 获取最终总行数 +// //finalTotalRows, err := GetManager().GetTotalRows(handleID) +// //if err != nil { +// // fmt.Printf("获取最终总行数失败: %v\n", err) +// //} else { +// // fmt.Printf("最终总行数: %d\n", finalTotalRows) +// //} +// +// // 清理 +// GetManager().closeAllHandles() +// fmt.Println("\n测试完成!") +//} + func main() { - fmt.Println("=== CSV句柄管理器测试 ===") //7984 - - filename := "csv/test1.csv" - ////dstFile := "csv/taskLog3.csv" - //fmt.Printf("1. 创建测试文件: %s\n", filename) - // - // 创建 Redis 客户端 - rdb := redis.NewClient(&redis.Options{ - Addr: "192.168.101.209:6379", // Redis 地址 - Password: "", // 密码,没有则为空 - DB: 0, // 使用的数据库编号 - }) - - // 创建上下文 - ctx := context.Background() - - // 测试连接 - pong, err := rdb.Ping(ctx).Result() - if err != nil { - log.Fatal("连接 Redis 失败:", err) - } - fmt.Println("连接成功:", pong) - - key := "2006557053525397505_2010589232836288513_20260112_1" - - //// 获取值 - //val, err := rdb.Get(ctx, "1995373681100910593_2010521216979214337_20260112_1").Result() - //if err != nil { - // log.Fatal("获取键值失败:", err) - //} - //fmt.Println("key:", val) - + filename := "csv/taskLog.csv" handleID, err := GetManager().OpenCSVFile(filename, ',', true) if err != nil { - fmt.Printf("打开文件失败: %v\n", err) + fmt.Println(err.Error()) } - - fmt.Printf("2. 打开文件成功,句柄ID: %d\n", handleID) - - // 测试写入表头 - header := []string{"ID", "data"} - err = GetManager().WriteHeader(handleID, header) + fmt.Println(handleID) + headers := []string{"ID", "序号", "姓名", "年龄"} + err = GetManager().WriteHeader(handleID, headers) if err != nil { - fmt.Printf("写入表头失败: %v\n", err) - } else { - fmt.Println("写入表头成功") + fmt.Println(err.Error()) } - // 获取列表所有元素 - listValues, err := rdb.LRange(ctx, key, 0, -1).Result() - if err != nil { - log.Fatal("获取列表失败:", err) - } + //rowss, i, err := GetManager().AppendRowss(handleID, rows) + //if err != nil { + // fmt.Println(err.Error()) + //} + //fmt.Println(rowss) + //fmt.Println(i) - fmt.Printf("列表包含 %d 个元素:\n", len(listValues)) - for i, value := range listValues { + var wg sync.WaitGroup + results := make([][]int64, 100) - fmt.Printf(" [%d] %s\n", i, value) - - rowsData := [][]string{ - {fmt.Sprintf("%d", i), value}, + for i := 0; i < 100; i++ { + rows := [][]string{ + {fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), fmt.Sprintf("张三%d", i), fmt.Sprintf("3%d", i)}, } - - totalRows, err := GetManager().WriteRows(handleID, rowsData) - if err != nil { - fmt.Printf("批量写入数据失败: %v\n", err) - } else { - fmt.Printf("批量写入数据成功,行数: %d\n", totalRows) - } - + wg.Add(1) + go func(idx int) { + defer wg.Done() + rowsss, _ := GetManager().AppendRowsNum(handleID, rows) + fmt.Println(rows, ":", rowsss) + results[idx] = rowsss + }(i) } - // 3. 也可以检查键是否存在 - exists, err := rdb.Exists(ctx, key).Result() - if err != nil { - log.Fatal("检查键是否存在失败:", err) - } - if exists == 0 { - fmt.Printf("键 '%s' 不存在\n", key) - } else { - fmt.Printf("键 '%s' 存在\n", key) - } + wg.Wait() - // 关闭连接 - defer rdb.Close() + //rowsss, err := GetManager().AppendRowsss(handleID, rows) + //if err != nil { + // fmt.Println(err.Error()) + //} + //fmt.Println(rowsss) - // - //// 2. 创建目标文件(部分数据) - //dstHandle, err := GetManager().OpenCSVFile(dstFile, ',', true) - //if err != nil { - // fmt.Printf("创建目标文件失败: %v", err) - //} - //// 合并 - //totalRows, err := GetManager().MergeCSVFilesSimple(handleID, dstHandle, true) - //if err != nil { - // fmt.Printf("合并文件失败: %v", err) - //} - //fmt.Println(totalRows) + GetManager().CloseHandle(handleID) - //for i := 0; i < 10000; i++ { - // // 打开CSV文件 - // handleID, err := GetManager().OpenCSVFile(filename, ',', true) - // if err != nil { - // fmt.Printf("打开文件失败: %v\n", err) - // } - // - // fmt.Printf("2. 打开文件成功,句柄ID: %d\n", handleID) - // - // // 测试写入表头 - // header := []string{"ID", "Name", "Age", "Email"} - // err = GetManager().WriteHeader(handleID, header) - // if err != nil { - // fmt.Printf("写入表头失败: %v\n", err) - // } else { - // fmt.Println("写入表头成功") - // } - // - // //// 写入测试数据 - // //rowsData := [][]string{ - // // {"1", fmt.Sprintf("张三", i), "25", "zhangsan@example.com"}, - // // {"2", "李四", "30", "lisi@example.com"}, - // // {"3", "王五", "28", "wangwu@example.com"}, - // // {"4", "赵六", "35", "zhaoliu@example.com"}, - // //} - // - // // 写入测试数据 - // rowsData := [][]string{ - // {fmt.Sprintf("%d", i+100), "张三", fmt.Sprintf("%d", i+1000), fmt.Sprintf("%d", i+10000)}, - // } - // - // totalRows, err := GetManager().WriteRows(handleID, rowsData) - // if err != nil { - // fmt.Printf("批量写入数据失败: %v\n", err) - // } else { - // fmt.Printf("批量写入数据成功,行数: %d\n", totalRows) - // } - // - // GetManager().CloseHandle(handleID) - //} - - //// 测试写入表头 - //header := []string{"ID", "Name", "Age", "Email"} - //err = GetManager().WriteHeader(handleID, header) - //if err != nil { - // fmt.Printf("写入表头失败: %v\n", err) - //} else { - // fmt.Println("写入表头成功") - //} - - //// 写入测试数据 - //rowsData := [][]string{ - // {"1", "张三", "25", "zhangsan@example.com"}, - // {"2", "李四", "30", "lisi@example.com"}, - // {"3", "王五", "28", "wangwu@example.com"}, - // {"4", "赵六", "35", "zhaoliu@example.com"}, - //} - // - //totalRows, err := GetManager().WriteRows(handleID, rowsData) - //if err != nil { - // fmt.Printf("批量写入数据失败: %v\n", err) - //} else { - // fmt.Printf("批量写入数据成功,行数: %d\n", totalRows) - //} - // - //// 获取写入后的总行数 - //totalRows, err = GetManager().GetTotalRows(handleID) - //if err != nil { - // fmt.Printf("获取总行数失败: %v\n", err) - //} else { - // fmt.Printf("写入后总行数: %d\n", totalRows) - //} - - //// ============ 测试修改行功能 ============ - // - //fmt.Println("\n3. 测试修改行功能") - // - //// 修改第1行数据(索引0) - //newRow1 := []string{"1", "张三(已修改1)", "26", "zhangsan_updated@example.com"} - //err = GetManager().UpdateRow(handleID, 0, newRow1) - //if err != nil { - // fmt.Printf("修改第1行数据失败: %v\n", err) - //} else { - // fmt.Println("修改第1行数据成功") - //} - // - //// 修改第3行数据(索引2) - //newRow3 := []string{"3", "王五(已修改)", "29", "wangwu_updated@example.com"} - //err = GetManager().UpdateRow(handleID, 2, newRow3) - //if err != nil { - // fmt.Printf("修改第3行数据失败: %v\n", err) - //} else { - // fmt.Println("修改第3行数据成功") - //} - // - //// 获取修改后的行数据 - //fmt.Println("\n4. 获取修改后的行数据") - - //获取第1行 - //row1, err := GetManager().GetRow(handleID, 10) - //if err != nil { - // fmt.Printf("获取第1行失败: %v\n", err) - //} else { - // fmt.Printf("第1行数据: %v\n", row1) - //} - - //// 获取第3行 - //row3, err := GetManager().GetRow(handleID, 2) - //if err != nil { - // fmt.Printf("获取第3行失败: %v\n", err) - //} else { - // fmt.Printf("第3行数据: %v\n", row3) - //} - - //// ============ 测试批量修改功能 ============ - // - //fmt.Println("\n5. 测试批量修改功能") - // - //updates := map[int64][]string{ - // 1: {"2", "李四(批量修改)", "31", "lisi_batch@example.com"}, - // 3: {"4", "赵六(批量修改)", "36", "zhaoliu_batch@example.com"}, - //} - // - //updatedCount, err := GetManager().UpdateRows(handleID, updates) - //if err != nil { - // fmt.Printf("批量修改失败: %v\n", err) - //} else { - // fmt.Printf("批量修改成功,修改了%d行\n", updatedCount) - //} - // - //// 验证批量修改结果 - //row2, err := GetManager().GetRow(handleID, 1) - //if err != nil { - // fmt.Printf("获取第2行失败: %v\n", err) - //} else { - // fmt.Printf("第2行数据(批量修改后): %v\n", row2) - //} - // - //row4, err := GetManager().GetRow(handleID, 3) - //if err != nil { - // fmt.Printf("获取第4行失败: %v\n", err) - //} else { - // fmt.Printf("第4行数据(批量修改后): %v\n", row4) - //} - // - //// 获取最终总行数 - //finalTotalRows, err := GetManager().GetTotalRows(handleID) - //if err != nil { - // fmt.Printf("获取最终总行数失败: %v\n", err) - //} else { - // fmt.Printf("最终总行数: %d\n", finalTotalRows) - //} - - // 清理 - //GetManager().closeAllHandles() - fmt.Println("\n测试完成!") +} + +// WriteRowss 批量写入多行数据到CSV文件 +// 返回写入的起始行号和写入的行数,错误时返回(-1, -1, error) +func (mgr *CSVManager) WriteRowss(handleID int64, rows [][]string) (int64, int64, error) { + // 首先记录日志 + if err := mgr.logWriteRows(handleID, rows); err != nil { + // 日志记录失败不影响主流程,但可以打印警告 + fmt.Printf("警告:记录日志失败: %v\n", err) + } + + // 获取句柄 + handle, err := mgr.getHandle(handleID) + if err != nil { + return -1, -1, fmt.Errorf("WriteRows 获取句柄失败: %w", err) + } + defer mgr.releaseHandle(handleID) + + // 检查句柄状态 + if !handle.beginOperation() { + return -1, -1, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) + } + defer handle.endOperation() + + handle.mu.Lock() + defer handle.mu.Unlock() + + if !handle.IsOpen { + return -1, -1, fmt.Errorf("文件未打开") + } + + // 检查是否有表头 + if handle.HasHeader && len(handle.Header) > 0 { + // 验证每行的列数 + for i, row := range rows { + if len(row) != len(handle.Header) { + return -1, -1, fmt.Errorf("第%d行列数(%d)与表头列数(%d)不匹配", i+1, len(row), len(handle.Header)) + } + } + } + + // 移动到文件末尾 + if _, err := handle.File.Seek(0, 2); err != nil { + return -1, -1, fmt.Errorf("移动到文件末尾失败: %w", err) + } + + // 记录写入前的行数作为起始行号 + // 注意:行号从1开始,如果是首次写入且没有表头,TotalRows=0,起始行号就是1 + startRow := handle.TotalRows + 1 + if handle.HasHeader && startRow == 1 { + // 如果有表头,数据从第2行开始 + startRow = 2 + } + + // 批量写入行数据 + for _, row := range rows { + if err := handle.CSVWriter.Write(row); err != nil { + return -1, -1, fmt.Errorf("写入行失败: %w", err) + } + } + + // 更新行数统计 + rowsWritten := int64(len(rows)) + handle.TotalRows += rowsWritten + handle.cachedRowCount = handle.TotalRows + handle.rowCountCached = true + + return startRow, rowsWritten, nil +} + +// AppendRows 批量追加行数据(自动Flush) +func (mgr *CSVManager) AppendRowss(handleID int64, rows [][]string) (int64, int64, error) { + totalRows, rowss, err := mgr.WriteRowss(handleID, rows) + if err != nil { + return -1, -1, err + } + err = mgr.Flush(handleID) + if err != nil { + return -1, -1, err + } + return totalRows, rowss, nil +} + +// AppendRows 批量追加行数据(自动Flush) +func (mgr *CSVManager) AppendRowsss(handleID int64, rows [][]string) ([]int64, error) { + rowsss, err := mgr.WriteRowsss(handleID, rows) + + if err != nil { + return nil, err + } + err = mgr.Flush(handleID) + if err != nil { + return nil, err + } + return rowsss, nil +} + +// WriteRows 批量写入多行数据到CSV文件 +// 返回每行数据存储的行号数组(从1开始) +func (mgr *CSVManager) WriteRowsss(handleID int64, rows [][]string) ([]int64, error) { + // 首先记录日志 + if err := mgr.logWriteRows(handleID, rows); err != nil { + // 日志记录失败不影响主流程,但可以打印警告 + fmt.Printf("警告:记录日志失败: %v\n", err) + } + + // 获取句柄 + handle, err := mgr.getHandle(handleID) + if err != nil { + return nil, fmt.Errorf("WriteRows 获取句柄失败: %w", err) + } + defer mgr.releaseHandle(handleID) + + // 检查句柄状态 + if !handle.beginOperation() { + return nil, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) + } + defer handle.endOperation() + + handle.mu.Lock() + defer handle.mu.Unlock() + + if !handle.IsOpen { + return nil, fmt.Errorf("文件未打开") + } + + // 检查是否有表头 + if handle.HasHeader && len(handle.Header) > 0 { + // 验证每行的列数 + for i, row := range rows { + if len(row) != len(handle.Header) { + return nil, fmt.Errorf("第%d行列数(%d)与表头列数(%d)不匹配", i+1, len(row), len(handle.Header)) + } + } + } + + // 移动到文件末尾 + if _, err := handle.File.Seek(0, 2); err != nil { + return nil, fmt.Errorf("移动到文件末尾失败: %w", err) + } + + // 计算起始行号 - 关键修正部分 + var startRow int64 + + // 先获取当前文件的实际行数(包括表头) + currentLineCount := handle.TotalRows + if handle.HasHeader && currentLineCount == 0 { + // 如果有表头但还没有数据行,表头算第1行,数据从第2行开始 + startRow = 2 + } else if handle.HasHeader { + // 有表头且已有数据行,表头是第1行,TotalRows不包括表头 + // 所以下一行应该是 TotalRows + 2 + startRow = currentLineCount + 2 + } else { + // 没有表头,直接累加 + startRow = currentLineCount + 1 + } + + // 创建行号数组 + rowNumbers := make([]int64, len(rows)) + for i := 0; i < len(rows); i++ { + rowNumbers[i] = startRow + int64(i) + } + + // 批量写入行数据 + for _, row := range rows { + if err := handle.CSVWriter.Write(row); err != nil { + return nil, fmt.Errorf("写入行失败: %w", err) + } + } + + // 更新行数统计 + handle.TotalRows += int64(len(rows)) + handle.cachedRowCount = handle.TotalRows + handle.rowCountCached = true + + return rowNumbers, nil } diff --git a/csv/dll/csv.dll b/csv/dll/csv.dll index 5ebdcba..44f085e 100644 Binary files a/csv/dll/csv.dll and b/csv/dll/csv.dll differ diff --git a/csv/dll/csv.h b/csv/dll/csv.h index 94186f5..350cbf8 100644 --- a/csv/dll/csv.h +++ b/csv/dll/csv.h @@ -134,6 +134,10 @@ extern __declspec(dllexport) int GetError(char* buffer, int bufferSize); // extern __declspec(dllexport) void FreeCString(char* str); +// 获取版本信息 +// +extern __declspec(dllexport) char* GetVersion(void); + #ifdef __cplusplus } #endif diff --git a/csv/newcsv.go b/csv/newcsv.go index 72fc84f..ac3b05f 100644 --- a/csv/newcsv.go +++ b/csv/newcsv.go @@ -1,6 +1,5 @@ package main -// ///* //#include //*/ @@ -1256,39 +1255,18 @@ package main // C.free(unsafe.Pointer(str)) //} // +//// CSV_VERSION 版本号 +//const ( +// CSV_VERSION = "v1" +//) +// +//// 获取版本信息 +//// +////export GetVersion +//func GetVersion() *C.char { +// return C.CString(CSV_VERSION) +//} +// //// main 函数是必需的,即使为空 //func main() { //} -// -//// 编码多行数据 -//func encodeRowsData(rows [][]string) []byte { -// var result []byte -// for _, row := range rows { -// rowData := encodeRowData(row) -// result = append(result, rowData...) -// } -// return result -//} -// -//// 编码行数据为DLL期望的格式 -//func encodeRowData(row []string) []byte { -// var result []byte -// -// for _, cell := range row { -// // 写入4字节长度 -// length := len(cell) -// result = append(result, -// byte(length&0xFF), -// byte((length>>8)&0xFF), -// byte((length>>16)&0xFF), -// byte((length>>24)&0xFF)) -// -// // 写入单元格数据 -// result = append(result, []byte(cell)...) -// } -// -// // 写入行结束标记 (4个0字节) -// result = append(result, 0, 0, 0, 0) -// -// return result -//} diff --git a/csv/taskLog.csv b/csv/taskLog.csv new file mode 100644 index 0000000..3c42588 --- /dev/null +++ b/csv/taskLog.csv @@ -0,0 +1,101 @@ +ID,序号,姓名,年龄 +0,0,张三0,30 +1,1,张三1,31 +12,12,张三12,312 +3,3,张三3,33 +22,22,张三22,322 +15,15,张三15,315 +75,75,张三75,375 +91,91,张三91,391 +20,20,张三20,320 +90,90,张三90,390 +21,21,张三21,321 +23,23,张三23,323 +24,24,张三24,324 +25,25,张三25,325 +26,26,张三26,326 +27,27,张三27,327 +28,28,张三28,328 +29,29,张三29,329 +30,30,张三30,330 +6,6,张三6,36 +64,64,张三64,364 +9,9,张三9,39 +2,2,张三2,32 +33,33,张三33,333 +17,17,张三17,317 +16,16,张三16,316 +18,18,张三18,318 +4,4,张三4,34 +7,7,张三7,37 +13,13,张三13,313 +45,45,张三45,345 +63,63,张三63,363 +43,43,张三43,343 +8,8,张三8,38 +14,14,张三14,314 +40,40,张三40,340 +10,10,张三10,310 +19,19,张三19,319 +11,11,张三11,311 +5,5,张三5,35 +31,31,张三31,331 +34,34,张三34,334 +35,35,张三35,335 +36,36,张三36,336 +37,37,张三37,337 +41,41,张三41,341 +38,38,张三38,338 +32,32,张三32,332 +53,53,张三53,353 +42,42,张三42,342 +49,49,张三49,349 +50,50,张三50,350 +47,47,张三47,347 +51,51,张三51,351 +48,48,张三48,348 +46,46,张三46,346 +52,52,张三52,352 +57,57,张三57,357 +54,54,张三54,354 +59,59,张三59,359 +55,55,张三55,355 +58,58,张三58,358 +56,56,张三56,356 +44,44,张三44,344 +65,65,张三65,365 +61,61,张三61,361 +39,39,张三39,339 +62,62,张三62,362 +68,68,张三68,368 +69,69,张三69,369 +67,67,张三67,367 +60,60,张三60,360 +70,70,张三70,370 +71,71,张三71,371 +66,66,张三66,366 +72,72,张三72,372 +74,74,张三74,374 +78,78,张三78,378 +76,76,张三76,376 +73,73,张三73,373 +79,79,张三79,379 +77,77,张三77,377 +83,83,张三83,383 +82,82,张三82,382 +86,86,张三86,386 +87,87,张三87,387 +81,81,张三81,381 +88,88,张三88,388 +84,84,张三84,384 +80,80,张三80,380 +85,85,张三85,385 +89,89,张三89,389 +92,92,张三92,392 +94,94,张三94,394 +96,96,张三96,396 +99,99,张三99,399 +97,97,张三97,397 +98,98,张三98,398 +93,93,张三93,393 +95,95,张三95,395 diff --git a/erp/dll/erp.dll b/erp/dll/erp.dll new file mode 100644 index 0000000..cd535dd Binary files /dev/null and b/erp/dll/erp.dll differ diff --git a/erp/dll/erp.h b/erp/dll/erp.h new file mode 100644 index 0000000..e091b39 --- /dev/null +++ b/erp/dll/erp.h @@ -0,0 +1,101 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#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 "erp.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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 + + +// InterfaceForward 接口转发 +// +extern __declspec(dllexport) char* InterfaceForward(char* requestMethod, char* erpUrl, char* requestJson); + +// FreeCString 释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +#ifdef __cplusplus +} +#endif diff --git a/expressDeliveryOrder/dll/expressDeliveryOrder.dll b/expressDeliveryOrder/dll/expressDeliveryOrder.dll new file mode 100644 index 0000000..1e09076 Binary files /dev/null and b/expressDeliveryOrder/dll/expressDeliveryOrder.dll differ diff --git a/expressDeliveryOrder/dll/expressDeliveryOrder.h b/expressDeliveryOrder/dll/expressDeliveryOrder.h new file mode 100644 index 0000000..d4ee74e --- /dev/null +++ b/expressDeliveryOrder/dll/expressDeliveryOrder.h @@ -0,0 +1,125 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#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 "expressDeliveryOrder.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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 + + +// ZtoOpenCreateOrder 中通快递--创建订单接口 +// +extern __declspec(dllexport) char* ZtoOpenCreateOrder(char* requestJSON, char* appKey, char* appSecret); + +// JtOrderAddOrder 极兔快递--创建订单接口 +// +extern __declspec(dllexport) char* JtOrderAddOrder(char* requestJSON, char* apiAccount, char* privateKey); + +// EmsAmpApiOpen 邮政快递--订单接入接口 +// +extern __declspec(dllexport) char* EmsAmpApiOpen(char* requestJSON, char* secretKey); + +// StoOmsExpressOrderCreate 申通快递--订单接入接口 +// +extern __declspec(dllexport) char* StoOmsExpressOrderCreate(char* requestJSON, char* fromAppkey, char* secretKey, char* fromCode); + +// YdCreateBmOrder 韵达快递--电子面单下单 +// +extern __declspec(dllexport) char* YdCreateBmOrder(char* requestJSON, char* appKey, char* appSecret); + +// YdBmGetPdfInfo 韵达快递--电子面单打印 +// +extern __declspec(dllexport) char* YdBmGetPdfInfo(char* requestJSON, char* appKey, char* appSecret); + +// IntegrationOrderCreate 整合所有快递--订单接口 +// +extern __declspec(dllexport) char* IntegrationOrderCreate(char* orderType, char* requestJSON, char* key, char* secret, char* fromCode); + +// FreeCString 释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +#ifdef __cplusplus +} +#endif diff --git a/expressDeliveryOrder/expressDeliveryOrder.go b/expressDeliveryOrder/expressDeliveryOrder.go new file mode 100644 index 0000000..b986f05 --- /dev/null +++ b/expressDeliveryOrder/expressDeliveryOrder.go @@ -0,0 +1,1293 @@ +package main + +/* +#include +*/ +import "C" +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/parnurzeal/gorequest" + "github.com/tjfoc/gmsm/sm4" + "net/http" + "sort" + "strings" + "time" + "unsafe" +) + +// ZtoOrderRequest 中通订单请求结构体 +type ZtoOrderRequest struct { + PartnerType string `json:"partnerType"` // 合作模式 ,1:集团客户;2:非集团客户 + OrderType string `json:"orderType"` // partnerType为1时,orderType:1:全网件 2:预约件。partnerType为2时,orderType:1:全网件 2:预约件(返回运单号) 3:预约件(不返回运单号) 4:星联全网件 + PartnerOrderCode string `json:"partnerOrderCode"` // 合作商订单号 + AccountInfo AccountInfo `json:"accountInfo"` // 账号信息 + BillCode string `json:"billCode"` // 运单号 + SenderInfo SenderInfo `json:"senderInfo"` // 发件人信息 + ReceiveInfo ReceiveInfo `json:"receiveInfo"` // 收件人信息 + OrderVasList []OrderVas `json:"orderVasList"` // 增值服务信息 + HallCode string `json:"hallCode"` // 门店/仓库编码(partnerType为1时可使用) + SiteCode string `json:"siteCode"` // 网点code(orderVasList.vasType为receiveReturnService必填) + SiteName string `json:"siteName"` // 网点名称(orderVasList.vasType为receiveReturnService必填) + SummaryInfo SummaryInfo `json:"summaryInfo"` // 汇总信息 + Remark string `json:"remark"` // 备注 + OrderItems []OrderItem `json:"orderItems"` // 物品信息 + Cabinet Cabinet `json:"cabinet"` // 机柜信息 +} + +// AccountInfo 账号信息 +type AccountInfo struct { + AccountId string `json:"accountId"` // 电子面单账号(partnerType为2,orderType传1,2,4时必传) + AccountPassword string `json:"accountPassword"` // 电子面单密码(测试环境传ZTO123) + Type int `json:"type"` // 单号类型:1.普通电子面单;74.星联电子面单;默认是1 + CustomerId string `json:"customerId"` // 集团客户编码(partnerType传1时必传) +} + +// SenderInfo 发件人信息 +type SenderInfo struct { + SenderId string `json:"senderId"` // 发件人ID + SenderName string `json:"senderName"` // 发件人姓名 + SenderPhone string `json:"senderPhone"` // 发件人座机(与senderMobile二者不能同时为空) + SenderMobile string `json:"senderMobile"` // 发件人手机号(与senderPhone二者不能同时为空) + SenderProvince string `json:"senderProvince"` // 发件人省 + SenderCity string `json:"senderCity"` // 发件人市 + SenderDistrict string `json:"senderDistrict"` // 发件人区 + SenderAddress string `json:"senderAddress"` // 发件人详细地址 +} + +// ReceiveInfo 收件人信息 +type ReceiveInfo struct { + ReceiverName string `json:"receiverName"` // 收件人姓名 + ReceiverPhone string `json:"receiverPhone"` // 收件人座机(与receiverMobile二者不能同时为空) + ReceiverMobile string `json:"receiverMobile"` // 收件人手机号(与 receiverPhone二者不能同时为空) + ReceiverProvince string `json:"receiverProvince"` // 收件人省 + ReceiverCity string `json:"receiverCity"` // 收件人市 + ReceiverDistrict string `json:"receiverDistrict"` // 收件人区 + ReceiverAddress string `json:"receiverAddress"` // 收件人详细地址 +} + +// OrderVas 增值服务 +type OrderVas struct { + VasType string `json:"vasType"` // 增值类型(COD:代收;vip:中通标快;insured:保价;receiveReturnService:签单返回;twoHour:两小时;standardExpress:中通好快) + VasAmount int64 `json:"vasAmount"` // 增值价格,如果增值类型涉及金额会校验,vasType为COD、insured时不能为空,单位:分 + VasPrice int64 `json:"vasPrice"` // 增值价格(暂时不用) + VasDetail string `json:"vasDetail"` // 增值详情 + AccountNo string `json:"accountNo"` // 代收账号(有代收货款增值时必填) +} + +// SummaryInfo 汇总信息 +type SummaryInfo struct { + Size string `json:"size"` // 订单包裹大小(单位:厘米、格式:"长,宽,高",用半角的逗号来分隔) + Quantity int `json:"quantity"` // 订单包裹内货物总数量 + Price int `json:"price"` // 商品总价值(单位:元) + Freight int `json:"freight"` // 运输费(单位:元) + Premium int `json:"premium"` // 险费(单位:元) + StartTime string `json:"startTime"` // 取件开始时间(传值此字段则需必传vasType:twoHour) + EndTime string `json:"endTime"` // 取件截止时间(传值此字段则需必传vasType:twoHour) +} + +// OrderItem 物品信息 +type OrderItem struct { + Name string `json:"name"` // 货品名称 + Category string `json:"category"` // 商品分类 + Material string `json:"material"` // 商品材质 + Size string `json:"size"` // 大小(长,宽,高)(单位:厘米), 用半角的逗号来分隔长宽高 + Weight int64 `json:"weight"` // 重量(单位:克) + Unitprice int `json:"unitprice"` // 单价(单位:元) + Quantity int `json:"quantity"` // 货品数量 + Remark string `json:"remark"` // 货品备注 +} + +// Cabinet 机柜信息 +type Cabinet struct { + Address string `json:"address"` // 地址 + Specification int `json:"specification"` // 格口规格 格口大小(1 大 2 中 3 小) + Code string `json:"code"` // 开箱码 +} + +/* + 中通快递--创建订单接口 + 请求参数: + requestJSON 创建订单参数JSON字符串 + appKey app值 + appSecret 密钥 +*/ +func ztoOpenCreateOrder(requestJSON, appKey, appSecret string) (string, error) { + fmt.Printf("进入中通订单创建接口: %v \n", requestJSON) + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") + } + // 正式环境 + ztoUrl := "https://japi.zto.com/zto.open.createOrder" + // 测试环境 + //ztoUrl := "https://japi-test.zto.com/zto.open.createOrder" + + // 生成签名 + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + fmt.Println(dataDigest) + + // 创建gorequest实例 + req := gorequest.New() + // 使用gorequest发送POST请求 + resp, body, errs := req.Post(ztoUrl). + Set("Content-Type", "application/json"). + Set("x-appKey", appKey). + Set("x-datadigest", dataDigest). + Set("x-timestamp", fmt.Sprintf("%d", time.Now().Unix())). + Send(requestJSON). + End() + + // 处理错误 + if len(errs) > 0 { + errStr := "" + for _, e := range errs { + errStr += e.Error() + "; " + } + return "", fmt.Errorf("发送HTTP请求失败: %s", errStr) + } + + // 检查HTTP状态码 + if resp.StatusCode >= 400 { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + // 解析响应体 + 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 ztoGenerateSign(data, appSecret string) (string, error) { + // 中通签名生成函数(修正版) + // 根据文档,签名流程应该是: + // 1. 请求参数json字符串 + appSecret + // 2. 进行MD5计算 + // 3. 对MD5结果进行Base64编码 + + // 注意:中通可能要求特定的格式,比如参数需要排序 + var requestData map[string]interface{} + if err := json.Unmarshal([]byte(data), &requestData); err != nil { + return "", fmt.Errorf("解析请求参数失败: %w", err) + } + + // 将map转换为排序后的JSON字符串(中通可能要求按key排序) + keys := make([]string, 0, len(requestData)) + for k := range requestData { + keys = append(keys, k) + } + sort.Strings(keys) + + var buf strings.Builder + buf.WriteString("{") + for i, k := range keys { + if i > 0 { + buf.WriteString(",") + } + buf.WriteString(fmt.Sprintf(`"%s":`, k)) + + // 根据值的类型进行格式化 + switch v := requestData[k].(type) { + case string: + buf.WriteString(fmt.Sprintf(`"%s"`, v)) + case float64: + buf.WriteString(fmt.Sprintf(`%.0f`, v)) // 整数可能以float64形式存在 + default: + // 其他类型转换为JSON字符串 + valBytes, _ := json.Marshal(v) + buf.Write(valBytes) + } + } + buf.WriteString("}") + + sortedJSON := buf.String() + fmt.Printf("排序后的JSON: %s\n", sortedJSON) + + // 按文档要求的格式 + signString := sortedJSON + appSecret + + // 计算MD5 + md5Sum := md5.Sum([]byte(signString)) + + // Base64编码 + sign := base64.StdEncoding.EncodeToString(md5Sum[:]) + fmt.Printf("Base64签名: %s\n", sign) + return sign, nil +} + +/* + 极兔快递--创建订单接口 + 请求参数: + requestJSON 创建订单参数JSON字符串 + appKey app值 + appSecret 密钥 +*/ +func jtOrderAddOrder(requestJSON, apiAccount, privateKey string) (string, error) { + fmt.Printf("进入极兔订单创建接口: %v \n", requestJSON) + + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder" + // 测试环境 + //jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名 + digest, err := jtGenerateDigest(requestJSON, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 创建gorequest实例 + req := gorequest.New() + // 使用gorequest发送POST请求 + resp, body, errs := req.Post(jtUrl). + Type("form"). + Set("Content-Type", "application/x-www-form-urlencoded"). + Set("apiAccount", apiAccount). + Set("digest", digest). + Set("timestamp", timestamp). + Set("User-Agent", "ZTO-API-Client/1.0"). + Send(requestJSON). + End() + + // 处理错误 + if len(errs) > 0 { + errStr := "" + for _, e := range errs { + errStr += e.Error() + "; " + } + return "", fmt.Errorf("发送HTTP请求失败: %s", errStr) + } + + // 检查HTTP状态码 + if resp.StatusCode >= 400 { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + // 解析响应体 + 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 +} + +// GenerateHeaderDigest 极兔生成Headers中的digest签名 +// bizContent: 业务参数的JSON对象,如map[string]interface{}或结构体 +// privateKey: 平台分配的私钥 +func jtGenerateDigest(bizContent interface{}, privateKey string) (string, error) { + // 1. 将业务参数转换为JSON字符串 + jsonBytes, err := json.Marshal(bizContent) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + jsonStr := string(jsonBytes) + + // 2. 拼接JSON字符串和私钥 + signStr := jsonStr + privateKey + + // 3. 计算MD5 + hash := md5.Sum([]byte(signStr)) + + // 4. Base64编码 + digest := base64.StdEncoding.EncodeToString(hash[:]) + + return digest, nil +} + +// GenerateBusinessDigest 极兔生成业务参数中的digest签名 +// customerCode: 客户编码,如"J0086474299" +// plainPassword: 明文密码,如"H5CD3zE6" +// privateKey: 平台分配的私钥 +func GenerateBusinessDigest(customerCode, plainPassword, privateKey string) (string, error) { + // 1. 计算加密后的密码 pwd = MD5(明文密码 + "jadada236t2"),32位大写 + pwdStr := plainPassword + "jadada236t2" + pwdHash := md5.Sum([]byte(pwdStr)) + pwd := strings.ToUpper(hex.EncodeToString(pwdHash[:])) + + // 2. 拼接 customerCode + pwd + privateKey + signStr := customerCode + pwd + privateKey + + // 3. 计算MD5 + hash := md5.Sum([]byte(signStr)) + + // 4. Base64编码 + digest := base64.StdEncoding.EncodeToString(hash[:]) + + return digest, nil +} + +/* + 邮政快递--订单接入接口 + 请求参数: + requestJSON 创建订单参数JSON字符串 + appKey app值 + appSecret 密钥 +*/ +func emsAmpApiOpen(requestJSON, secretKey string) (string, error) { + // 正式环境 + emsUrl := "https://api.ems.com.cn/amp-prod-api/f/amp/api/open" + // 测试环境 + //emsUrl := "https://api.ems.com.cn/amp-prod-api/f/amp/api/test" + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + contentToEncrypt := requestJSON + secretKey + + signature, err := GenerateSM4Signature(contentToEncrypt, secretKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %v", err) + } + + // 准备请求参数 + params := map[string]interface{}{ + "logisticsInterface": string(requestJSON), + } + + // 创建gorequest实例 + req := gorequest.New() + + // 设置超时和重试策略 + req = req.Timeout(30*time.Second). + Retry(3, 5*time.Second, http.StatusInternalServerError, http.StatusBadGateway) + + // 使用gorequest发送POST请求 + resp, body, errs := req.Post(emsUrl). + Set("Content-Type", "application/json"). + Set("apiCode", "020003"). + Set("senderNo", "EMS"). + Set("timestamp", timestamp). + Set("authorization", signature). + Set("User-Agent", "ZTO-API-Client/1.0"). + Send(params). + End() + + // 处理错误 + if len(errs) > 0 { + errStr := "" + for _, e := range errs { + errStr += e.Error() + "; " + } + return "", fmt.Errorf("发送HTTP请求失败: %s", errStr) + } + + // 检查HTTP状态码 + if resp.StatusCode >= 400 { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + // 解析响应体 + 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 +} + +const ( + CIPHERTEXT_PREFIX_THIRD_SM4ECB = "|$4|" +) + +// GenerateSM4Signature 生成SM4签名 +// params: 业务报文JSON字符串 +// key: 从页面生成的秘钥(base64格式) +// 返回值: 签名后的字符串,格式为 |$4| + base64(SM4加密结果) +func GenerateSM4Signature(params, key string) (string, error) { + // 检查输入参数 + if params == "" || key == "" { + return params, nil + } + + // 检查是否已经加密 + if strings.HasPrefix(params, CIPHERTEXT_PREFIX_THIRD_SM4ECB) { + return params, nil + } + + // 拼接待签名的内容:业务报文 + key + content := params + key + + // 解码base64格式的密钥 + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", fmt.Errorf("failed to decode base64 key: %v", err) + } + + // SM4 ECB模式加密 + encryptedData, err := sm4ECBEncrypt([]byte(content), keyBytes) + if err != nil { + return "", fmt.Errorf("failed to encrypt with SM4: %v", err) + } + + // 对加密结果进行base64编码 + encryptedBase64 := base64.StdEncoding.EncodeToString(encryptedData) + + // 添加前缀 + result := CIPHERTEXT_PREFIX_THIRD_SM4ECB + encryptedBase64 + + return result, nil +} + +// SM4ECBDecrypt 验证签名(解密) +func SM4ECBDecrypt(ciphertext, key string) (string, error) { + if ciphertext == "" || key == "" { + return ciphertext, nil + } + + // 检查是否包含SM4前缀 + if !strings.HasPrefix(ciphertext, CIPHERTEXT_PREFIX_THIRD_SM4ECB) { + return ciphertext, nil + } + + // 移除前缀 + encryptedBase64 := strings.TrimPrefix(ciphertext, CIPHERTEXT_PREFIX_THIRD_SM4ECB) + + // 解码base64密文 + encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64) + if err != nil { + return "", fmt.Errorf("failed to decode base64 ciphertext: %v", err) + } + + // 解码base64密钥 + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", fmt.Errorf("failed to decode base64 key: %v", err) + } + + // SM4 ECB模式解密 + decryptedData, err := sm4ECBDecrypt(encryptedData, keyBytes) + if err != nil { + return "", fmt.Errorf("failed to decrypt with SM4: %v", err) + } + + return string(decryptedData), nil +} + +// sm4ECBEncrypt SM4 ECB模式加密 +func sm4ECBEncrypt(plaintext, key []byte) ([]byte, error) { + block, err := sm4.NewCipher(key) + if err != nil { + return nil, err + } + + // 使用PKCS5Padding填充 + plaintext = pkcs5Padding(plaintext, block.BlockSize()) + + // ECB模式不需要IV + blockSize := block.BlockSize() + ciphertext := make([]byte, len(plaintext)) + + // 分块加密 + for start := 0; start < len(plaintext); start += blockSize { + end := start + blockSize + block.Encrypt(ciphertext[start:end], plaintext[start:end]) + } + + return ciphertext, nil +} + +// sm4ECBDecrypt SM4 ECB模式解密 +func sm4ECBDecrypt(ciphertext, key []byte) ([]byte, error) { + block, err := sm4.NewCipher(key) + if err != nil { + return nil, err + } + + // 检查密文长度 + if len(ciphertext)%block.BlockSize() != 0 { + return nil, fmt.Errorf("ciphertext length is not a multiple of block size") + } + + plaintext := make([]byte, len(ciphertext)) + blockSize := block.BlockSize() + + // 分块解密 + for start := 0; start < len(ciphertext); start += blockSize { + end := start + blockSize + block.Decrypt(plaintext[start:end], ciphertext[start:end]) + } + + // 去除PKCS5Padding填充 + plaintext = pkcs5UnPadding(plaintext) + + return plaintext, nil +} + +// pkcs5Padding PKCS5填充 +func pkcs5Padding(src []byte, blockSize int) []byte { + padding := blockSize - len(src)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +// pkcs5UnPadding 去除PKCS5填充 +func pkcs5UnPadding(src []byte) []byte { + length := len(src) + if length == 0 { + return src + } + + unpadding := int(src[length-1]) + if unpadding > length { + return src + } + + return src[:(length - unpadding)] +} + +// VerifySignature 验证签名是否正确 +func VerifySignature(ciphertext, params, key string) (bool, error) { + // 解密签名 + decrypted, err := SM4ECBDecrypt(ciphertext, key) + if err != nil { + return false, err + } + + // 验证解密后的内容是否等于 params + key + expected := params + key + return decrypted == expected, nil +} + +/* + 申通快递--订单创建接口 + 请求参数: + requestJSON content参数JSON字符串 + fromAppkey 订阅方/请求发起方的应用key + secretKey 密钥 + fromCode 订阅方/请求发起方的应用资源code +*/ +func stoOmsExpressOrderCreate(requestJSON, fromAppkey, secretKey, fromCode string) (string, error) { + // 正式环境 + //ztoUrl := "https://cloudinter-linkgateway.sto.cn/gateway/link.do" + // 测试环境 + stoUrl := "http://cloudinter-linkgatewaytest.sto.cn/gateway/link.do" + + signature := stoGenerateSign(requestJSON, secretKey) + + // 准备请求参数 + params := map[string]string{ + "api_name": "OMS_EXPRESS_ORDER_CREATE", + "content": requestJSON, + "from_appkey": fromAppkey, + "from_code": fromCode, + "to_appkey": "sto_oms", + "to_code": "sto_oms", + "data_digest": signature, + } + + // 创建gorequest实例 + req := gorequest.New() + + // 设置超时和重试策略 + req = req.Timeout(30*time.Second). + Retry(3, 5*time.Second, http.StatusInternalServerError, http.StatusBadGateway) + + // 使用gorequest发送POST请求 + req.Post(stoUrl). + Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") + + // 添加表单参数 + for key, value := range params { + req.Send(fmt.Sprintf("%s=%s", key, value)) + } + resp, body, errs := req.End() + + // 处理错误 + if len(errs) > 0 { + errStr := "" + for _, e := range errs { + errStr += e.Error() + "; " + } + return "", fmt.Errorf("发送HTTP请求失败: %s", errStr) + } + + // 检查HTTP状态码 + if resp.StatusCode >= 400 { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + // 解析响应体 + 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 stoGenerateSign(content, secretKey string) string { + text := content + secretKey + // 计算 MD5 + hash := md5.Sum([]byte(text)) + // Base64 编码 + return base64.StdEncoding.EncodeToString(hash[:]) +} + +// 韵达快递--电子面单下单 +// 参数:requestJSON:业务参数JSON字符串 appid:合作商appid(等同app-key) appSecret:签名 partnerID +func ydCreateBmOrder(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + //baseURL := "https://openapi.yundax.com/openapi-api/v1/accountOrder/createBmOrder" + // 测试环境 + baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/createBmOrder" + + // 3. 构建 Headers(根据文档) + timestamp := time.Now().UnixMilli() + headers := map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + "app-key": appKey, + "req-time": fmt.Sprintf("%d", timestamp), + "sign": ydGenerateSign(requestJSON, appSecret), // 需要实现签名方法 + } + + // 4. 发送请求 + request := gorequest.New() + resp, body, errs := request.Post(baseURL). + Set("Content-Type", headers["Content-Type"]). + Set("app-key", headers["app-key"]). + Set("req-time", headers["req-time"]). + Set("sign", headers["sign"]). + Send(requestJSON). + End() + if len(errs) > 0 { + errStr := "" + for _, e := range errs { + errStr += e.Error() + "; " + } + return "", fmt.Errorf("发送HTTP请求失败: %s", errStr) + } + + // 检查HTTP状态码 + if resp.StatusCode >= 400 { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + // 解析响应体 + 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 ydGenerateSign(content, secretKey string) string { + text := content + "_" + secretKey + // 计算 MD5 + hash := md5.Sum([]byte(text)) + // Base64 编码 + return base64.StdEncoding.EncodeToString(hash[:]) +} + +// 韵达快递--电子面单打印 +func ydBmGetPdfInfo(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + //baseURL := "https://openapi.yundax.com/openapi-api/v1/accountOrder/createBmOrder" + // 测试环境 + baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/createBmOrder" + + // 3. 构建 Headers(根据文档) + timestamp := time.Now().UnixMilli() + headers := map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + "app-key": appKey, + "req-time": fmt.Sprintf("%d", timestamp), + "sign": ydGenerateSign(requestJSON, appSecret), // 需要实现签名方法 + } + + // 4. 发送请求 + request := gorequest.New() + resp, body, errs := request.Post(baseURL). + Set("Content-Type", headers["Content-Type"]). + Set("app-key", headers["app-key"]). + Set("req-time", headers["req-time"]). + Set("sign", headers["sign"]). + Send(requestJSON). + End() + if len(errs) > 0 { + errStr := "" + for _, e := range errs { + errStr += e.Error() + "; " + } + return "", fmt.Errorf("发送HTTP请求失败: %s", errStr) + } + + // 检查HTTP状态码 + if resp.StatusCode >= 400 { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + // 解析响应体 + 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 +} + +const ( + ZTO_ORDER_TYPE = "ZTO" + JT_ORDER_TYPE = "JT" + EMS_ORDER_TYPE = "EMS" + STO_ORDER_TYPE = "STO" + YD_ORDER_TYPE = "YD" +) + +/* + 整合所有快递--订单接口 + 请求参数: + orderType 快递类型 + requestJSON 创建订单参数JSON字符串 +*/ +func integrationOrderCreate(orderType, requestJSON, key, secret, fromCode string) (string, error) { + switch orderType { + // 中通 + case ZTO_ORDER_TYPE: + return ztoOpenCreateOrder(requestJSON, key, secret) + // 极兔 + case JT_ORDER_TYPE: + return jtOrderAddOrder(requestJSON, key, secret) + // 邮政 + case EMS_ORDER_TYPE: + return emsAmpApiOpen(requestJSON, key) + // 申通 + case STO_ORDER_TYPE: + return stoOmsExpressOrderCreate(requestJSON, key, secret, fromCode) + // 韵达 + case YD_ORDER_TYPE: + return ydCreateBmOrder(requestJSON, key, secret) + } + return "", fmt.Errorf("快递类型不匹配: %s", orderType) +} + +// ========================== C 导入函数 =================== + +// ZtoOpenCreateOrder 中通快递--创建订单接口 +// +//export ZtoOpenCreateOrder +func ZtoOpenCreateOrder(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoOpenCreateOrder(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtOrderAddOrder 极兔快递--创建订单接口 +// +//export JtOrderAddOrder +func JtOrderAddOrder(requestJSON, apiAccount, privateKey *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtOrderAddOrder(requestJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// EmsAmpApiOpen 邮政快递--订单接入接口 +// +//export EmsAmpApiOpen +func EmsAmpApiOpen(requestJSON, secretKey *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + secretKeyStr := C.GoString(secretKey) + info, err := emsAmpApiOpen(requestJSONStr, secretKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// StoOmsExpressOrderCreate 申通快递--订单接入接口 +// +//export StoOmsExpressOrderCreate +func StoOmsExpressOrderCreate(requestJSON, fromAppkey, secretKey, fromCode *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + fromAppkeyStr := C.GoString(fromAppkey) + secretKeyStr := C.GoString(secretKey) + fromCodeStr := C.GoString(fromCode) + + info, err := stoOmsExpressOrderCreate(requestJSONStr, fromAppkeyStr, secretKeyStr, fromCodeStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// YdCreateBmOrder 韵达快递--电子面单下单 +// +//export YdCreateBmOrder +func YdCreateBmOrder(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ydCreateBmOrder(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// YdBmGetPdfInfo 韵达快递--电子面单打印 +// +//export YdBmGetPdfInfo +func YdBmGetPdfInfo(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ydBmGetPdfInfo(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// IntegrationOrderCreate 整合所有快递--订单接口 +// +//export IntegrationOrderCreate +func IntegrationOrderCreate(orderType, requestJSON, key, secret, fromCode *C.char) *C.char { + orderTypeStr := C.GoString(orderType) + requestJSONStr := C.GoString(requestJSON) + keyStr := C.GoString(key) + secretStr := C.GoString(secret) + fromCodeStr := C.GoString(fromCode) + info, err := integrationOrderCreate(orderTypeStr, requestJSONStr, keyStr, secretStr, fromCodeStr) + 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)) +} + +func getRequestParamter(orderType string) { + switch orderType { + case ZTO_ORDER_TYPE: + + case JT_ORDER_TYPE: + + case EMS_ORDER_TYPE: + + case STO_ORDER_TYPE: + var stoOrder StoOrder + stoOrder.OrderNo = "OrderNo string 订单号(客户系统自己生成,唯一)" + } + +} + +// StoOrder 订单信息 +type StoOrder struct { + // OrderNo 订单号(客户系统自己生成,唯一) + OrderNo string `json:"orderNo"` + // OrderSource 订单来源(订阅服务时填写的来源编码) + OrderSource string `json:"orderSource"` + // BillType 获取面单的类型(00-普通、03-国际、01-代收、02-到付、04-生鲜),默认普通业务,如果有其他业务先与业务方沟通清楚 + BillType string `json:"billType"` + // OrderType 订单类型(01-普通订单、02-调度订单)默认01-普通订单,如果有散单业务需先业务方沟通清楚 + OrderType string `json:"orderType"` + // Sender 寄件人信息 + Sender StoSenderInfo `json:"sender"` + // Receiver 收件人信息 + Receiver ReceiverInfo `json:"receiver"` + // Cargo 包裹信息 + Cargo CargoInfo `json:"cargo"` + // Customer 客户信息,在线下单取运单号必填,代单号下单不需要填写,测试账号传值如下,生产账号联系合作业务方提供 + Customer CustomerInfo `json:"customer"` + // InternationalAnnex 国际订单附属信息(国际业务订单必填,其他业务不要填写) + InternationalAnnex InternationalInfo `json:"internationalAnnex"` + // WaybillNo 运单号(下单前已获取运单号时必传,否则不传或传NULL) + WaybillNo string `json:"waybillNo"` + // AssignAnnex 指定网点揽收(调度散单业务订单需要传)其他业务不需要 + AssignAnnex AssignInfo `json:"assignAnnex"` + // CodValue 代收货款金额,单位:元(代收货款业务时必填) + CodValue string `json:"codValue"` + // FreightCollectValue 到付运费金额,单位:元(到付业务时必填) + FreightCollectValue string `json:"freightCollectValue"` + // TimelessType 时效类型(01-普通) + TimelessType string `json:"timelessType"` + // ProductType 产品类型(01-普通、02-冷链、03-生鲜) + ProductType string `json:"productType"` + // ServiceTypeList 增值服务(TRACE_PUSH-轨迹回传;PRIVACY_SURFACE_SINGLE-隐私面单标;STO_MARKET_SDD-申咚咚按需派送,STO_HOME_DELIVERY_OP 送货上门开城判断) + ServiceTypeList []string `json:"serviceTypeList"` + // ExtendFieldMap 拓展字段 注意事项:属性值有逗号等于号需过滤掉 + ExtendFieldMap map[string]string `json:"extendFieldMap"` + // Remark 备注 + Remark string `json:"remark"` + // ExpressDirection 快递流向(01-正向订单)默认01 + ExpressDirection string `json:"expressDirection"` + // CreateChannel 创建原因(01-客户创建)默认01 + CreateChannel string `json:"createChannel"` + // RegionType 区域类型(01-国内)默认01 + RegionType string `json:"regionType"` + // InsuredAnnex 保价模型(保价服务必填) + InsuredAnnex InsuredInfo `json:"insuredAnnex"` + // ExpectValue 预估费用(散单业务使用) + ExpectValue string `json:"expectValue"` + // PayModel 支付方式(1-现付;2-到付;3-月结) + PayModel string `json:"payModel"` +} + +// StoSenderInfo 寄件人信息 +type StoSenderInfo struct { + // Name 寄件人名称 + Name string `json:"name"` + // Tel 寄件人固定电话 + Tel string `json:"tel"` + // Mobile 寄件人手机号码 + Mobile string `json:"mobile"` + // PostCode 邮编 + PostCode string `json:"postCode"` + // Country 国家 + Country string `json:"country"` + // Province 省 + Province string `json:"province"` + // City 市 + City string `json:"city"` + // Area 区 + Area string `json:"area"` + // Town 镇 + Town string `json:"town"` + // Address 详细地址 + Address string `json:"address"` +} + +// ReceiverInfo 收件人信息 +type ReceiverInfo struct { + // Name 收件人名称 + Name string `json:"name"` + // Tel 收件人固定电话 + Tel string `json:"tel"` + // Mobile 收件人手机号码 + Mobile string `json:"mobile"` + // PostCode 邮编 + PostCode string `json:"postCode"` + // Country 国家 + Country string `json:"country"` + // Province 省 + Province string `json:"province"` + // City 市 + City string `json:"city"` + // Area 区 + Area string `json:"area"` + // Town 镇 + Town string `json:"town"` + // Address 详细地址 + Address string `json:"address"` + // SafeNo 安全号码 + SafeNo string `json:"safeNo"` +} + +// CargoInfo 包裹信息 +type CargoInfo struct { + // Battery 带电标识(10/未知 20/带电 30/不带电) + Battery string `json:"battery"` + // GoodsType 物品类型(大件、小件、扁平件\文件) + GoodsType string `json:"goodsType"` + // GoodsName 物品名称 + GoodsName string `json:"goodsName"` + // GoodsCount 物品数量 + GoodsCount int `json:"goodsCount"` + // SpaceX 长(cm) + SpaceX float64 `json:"spaceX"` + // SpaceY 宽(cm) + SpaceY float64 `json:"spaceY"` + // SpaceZ 高(cm) + SpaceZ float64 `json:"spaceZ"` + // Weight 重量(kg) + Weight float64 `json:"weight"` + // GoodsAmount 商品金额 + GoodsAmount string `json:"goodsAmount"` + // CargoItemList 小包信息(国际业务专用,其他业务不需要填写) + CargoItemList []CargoItem `json:"cargoItemList"` +} + +// CargoItem 小包信息 +type CargoItem struct { + // SerialNumber 小包号 + SerialNumber string `json:"serialNumber"` + // ReferenceNumber 关联单号 + ReferenceNumber string `json:"referenceNumber"` + // ProductId 商品ID + ProductId string `json:"productId"` + // Name 名称 + Name string `json:"name"` + // Qty 数量 + Qty int `json:"qty"` + // UnitPrice 单价 + UnitPrice float64 `json:"unitPrice"` + // Amount 总价 + Amount float64 `json:"amount"` + // Currency 币种 + Currency string `json:"currency"` + // Weight 重量(kg) + Weight float64 `json:"weight"` + // Remark 备注 + Remark string `json:"remark"` +} + +// CustomerInfo 客户信息 +type CustomerInfo struct { + // SiteCode 网点编码必传,测试传值"siteCode":"666666" + SiteCode string `json:"siteCode"` + // CustomerName 客户编码,测试传值"customerName":"666666000001" + CustomerName string `json:"customerName"` + // SitePwd 电子面单密码,测试传值"sitePwd":"abc123" + SitePwd string `json:"sitePwd"` + // MonthCustomerCode 月结客户编码(不传单号需调度才传月结编号)如果填写一般和客户编号值相同 + MonthCustomerCode string `json:"monthCustomerCode"` +} + +// InternationalInfo 国际订单附属信息 +type InternationalInfo struct { + // InternationalProductType 国际业务类型(01-国际进口,02-国际保税,03-国际直邮) + InternationalProductType string `json:"internationalProductType"` + // CustomsDeclaration 是否报关,默认为否 + CustomsDeclaration bool `json:"customsDeclaration"` + // SenderCountry 发件人所在国家,国际件为必填字段 + SenderCountry string `json:"senderCountry"` + // ReceiverCountry 收件人所在国家,国际件为必填字段 + ReceiverCountry string `json:"receiverCountry"` +} + +// AssignInfo 指定网点揽收信息 +type AssignInfo struct { + // TakeCompanyCode 指定取件的网点编号 + TakeCompanyCode string `json:"takeCompanyCode"` + // TakeUserCode 指定取件的业务员编号(指定业务员时takeCompanyCode可传可不传,若传必须传正确,举例:寄件地址是上海,只能是指定上海业务员取件) + TakeUserCode string `json:"takeUserCode"` +} + +// InsuredInfo 保价信息 +type InsuredInfo struct { + // InsuredValue 保价金额,单位:元(保价服务时必填,精确到小数点后两位) + InsuredValue string `json:"insuredValue"` + // GoodsValue 物品价值,单位:元(保价服务时必填,精确到小数点后两位) + GoodsValue string `json:"goodsValue"` +} + +// 极兔快递配置结构体 +type JituConfig struct { + APIAccount string // 接入方在平台的api账户标识 + APIKey string // 用于生成digest的密钥 + BaseURL string + CustomerCode string +} + +// 请求头结构体 +type JituHeader struct { + APIAccount string `json:"apiAccount"` + Digest string `json:"digest"` + Timestamp string `json:"timestamp"` +} + +// 地址结构体 +type Address struct { + Name string `json:"name"` + Company string `json:"company,omitempty"` + PostCode string `json:"postCode,omitempty"` + MailBox string `json:"mailBox,omitempty"` + Mobile string `json:"mobile"` + Phone string `json:"phone,omitempty"` + CountryCode string `json:"countryCode"` + Prov string `json:"prov"` + City string `json:"city"` + Area string `json:"area"` + Town string `json:"town,omitempty"` + Street string `json:"street,omitempty"` + Address string `json:"address"` +} + +// 商品项结构体 +type Item struct { + ItemType string `json:"itemType,omitempty"` + ItemName string `json:"itemName"` + ChineseName string `json:"chineseName,omitempty"` + EnglishName string `json:"englishName,omitempty"` + Number int `json:"number"` + ItemValue string `json:"itemValue,omitempty"` + PriceCurrency string `json:"priceCurrency,omitempty"` + Desc string `json:"desc,omitempty"` + ItemURL string `json:"itemUrl,omitempty"` +} + +// 扩展信息结构体 +type ExtendInfo struct { + IsFourLevelAddress string `json:"isFourLevelAddress,omitempty"` +} + +// 业务内容结构体 +type BizContent struct { + CustomerCode string `json:"customerCode"` + Digest string `json:"digest"` + Network *string `json:"network,omitempty"` + TxlogisticId string `json:"txlogisticId"` + ExpressType string `json:"expressType"` + OrderType string `json:"orderType"` + ServiceType string `json:"serviceType"` + DeliveryType string `json:"deliveryType"` + PayType string `json:"payType"` + Sender Address `json:"sender"` + Receiver Address `json:"receiver"` + SendStartTime string `json:"sendStartTime"` + SendEndTime string `json:"sendEndTime"` + GoodsType string `json:"goodsType"` + Length float64 `json:"length"` + Width float64 `json:"width"` + Height float64 `json:"height"` + Weight string `json:"weight"` + TotalQuantity int `json:"totalQuantity"` + ItemsValue *string `json:"itemsValue,omitempty"` + PriceCurrency *string `json:"priceCurrency,omitempty"` + OfferFee *string `json:"offerFee,omitempty"` + Remark *string `json:"remark,omitempty"` + Items []Item `json:"items"` + CustomsInfo *string `json:"customsInfo,omitempty"` + PostSiteCode *string `json:"postSiteCode,omitempty"` + PostSiteName *string `json:"postSiteName,omitempty"` + PostSiteAddress *string `json:"postSiteAddress,omitempty"` + RealName *string `json:"realName,omitempty"` + ExtendInfo ExtendInfo `json:"extendInfo,omitempty"` +} + +// 生成digest +func generateDigest(data string) string { + hash := md5.Sum([]byte(data)) + return base64.StdEncoding.EncodeToString(hash[:]) +} + +// 生成请求头 +func generateHeaders(config *JituConfig) JituHeader { + timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond)) + + // 根据极兔API文档,header中的digest通常是apiAccount + apiKey + timestamp的MD5 + headerData := config.APIAccount + config.APIKey + timestamp + headerDigest := generateDigest(headerData) + + return JituHeader{ + APIAccount: config.APIAccount, + Digest: headerDigest, + Timestamp: timestamp, + } +} + +// 创建订单请求 +func CreateJituOrder(config *JituConfig, bizContent *BizContent) (map[string]interface{}, error) { + // 设置请求URL--测试地址 + jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + // 设置请求URL--正式地址 + //jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder" + + // 生成请求头 + headers := generateHeaders(config) + + // 设置业务内容中的customerCode + bizContent.CustomerCode = config.CustomerCode + + // 生成业务内容的digest(这里需要根据极兔API的实际要求生成) + // 通常是对bizContent的某些字段进行签名 + bizContentDigest := generateDigest(bizContent.TxlogisticId + config.APIKey) + bizContent.Digest = bizContentDigest + + // 将bizContent转换为JSON字符串 + bizContentJSON, err := json.Marshal(bizContent) + if err != nil { + return nil, fmt.Errorf("序列化bizContent失败: %v", err) + } + + // 构建请求体 + requestBody := map[string]interface{}{ + "bizContent": string(bizContentJSON), + } + + // 使用gorequest发送请求 + request := gorequest.New() + + // 设置请求头 + request.Set("apiAccount", headers.APIAccount) + request.Set("digest", headers.Digest) + request.Set("timestamp", headers.Timestamp) + request.Set("Content-Type", "application/x-www-form-urlencoded") + + // 发送POST请求 + resp, body, errs := request.Post(jtUrl). + Type("form"). + Send(requestBody). + End() + + if len(errs) > 0 { + return nil, fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("请求返回非200状态码: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return nil, fmt.Errorf("解析响应失败: %v, 原始响应: %s", err, body) + } + + return response, nil +} + +// 主函数 +func main() { +} diff --git a/expressDeliveryOrder/expressDeliveryOrderTest.go b/expressDeliveryOrder/expressDeliveryOrderTest.go new file mode 100644 index 0000000..2a6ad8b --- /dev/null +++ b/expressDeliveryOrder/expressDeliveryOrderTest.go @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + + //ztoJSON := `{"partnerType":"2","orderType":"1","partnerOrderCode":"商家自主定义","accountInfo":{"accountId":"test","accountPassword":"","type":1,"customerId":"GPG1576724269"},"billCode":"","senderInfo":{"senderId":"","senderName":"张三","senderPhone":"010-22226789","senderMobile":"13900000000","senderProvince":"上海","senderCity":"上海市","senderDistrict":"青浦区","senderAddress":"华志路"},"receiveInfo":{"receiverName":"Jone Star","receiverPhone":"021-87654321","receiverMobile":"13500000000","receiverProvince":"上海","receiverCity":"上海市","receiverDistrict":"闵行区","receiverAddress":"申贵路1500号"},"orderVasList":[{"vasType":"COD","vasAmount":100000,"vasPrice":0,"vasDetail":"","accountNo":""}],"hallCode":"S2044","siteCode":"02100","siteName":"上海","summaryInfo":{"size":"","quantity":3,"price":30,"freight":20,"premium":10,"startTime":"2020-12-10 12:00:00","endTime":"2020-12-10 12:00:00"},"remark":"小吉下单","orderItems":[{"name":"","category":"","material":"","size":"","weight":0,"unitprice":0,"quantity":0,"remark":""}],"cabinet":{"address":"","specification":0,"code":""}}` + //order, err := ztoOpenCreateOrder(ztoJSON, "2e2858ad0c0006150011b", "5f935e459b9fc50db8220714adcb2c2e") + //if err != nil { + // fmt.Printf(err.Error()) + //} + //fmt.Println(order) + + jtJSON := `{"customerCode":"J0086474299","digest":"qonqb4O1eNr6VCWS07Ieeg==","network":null,"txlogisticId":"TEST20220704210006","expressType":"EZ","orderType":"1","serviceType":"01","deliveryType":"06","payType":"PP_PM","sender":{"name":"小九","company":null,"postCode":null,"mailBox":null,"mobile":"15546168286","phone":"","countryCode":"CHN","prov":"上海","city":"上海市","area":"青浦区","town":null,"street":null,"address":"庆丰三路28号"},"receiver":{"name":"田丽","company":null,"postCode":null,"mailBox":null,"mobile":"13766245825","phone":"","countryCode":"CHN","prov":"上海","city":"上海市","area":"嘉定区","town":null,"street":null,"address":"站前西路永利酒店斜对面童装店"},"sendStartTime":"2022-07-04 09:00:00","sendEndTime":"2022-07-04 18:00:00","goodsType":"bm000006","length":0,"width":0,"height":0,"weight":"0.02","totalQuantity":0,"itemsValue":null,"priceCurrency":null,"offerFee":null,"remark":null,"items":[{"itemType":null,"itemName":"衣帽鞋服","chineseName":null,"englishName":null,"number":1,"itemValue":null,"priceCurrency":"RMB","desc":null,"itemUrl":null}],"customsInfo":null,"postSiteCode":null,"postSiteName":null,"postSiteAddress":null,"realName":null, "extendInfo": { "isFourLevelAddress": "1" }}` + order, err := jtOrderAddOrder(jtJSON, "795641885325495168", "d8c262832bff4b8a9dd6e06147024861") + if err != nil { + fmt.Printf(err.Error()) + } + fmt.Println(order) +} diff --git a/gin/gin.go b/gin/gin.go index ae757be..3ca1b94 100644 --- a/gin/gin.go +++ b/gin/gin.go @@ -1,4 +1,4 @@ -package main +package gin import ( "github.com/gin-gonic/gin" diff --git a/go.mod b/go.mod index 1c4ae62..d6938a6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module kfzgw-info go 1.25 require ( + gioui.org v0.9.0 github.com/PuerkitoBio/goquery v1.10.3 github.com/boombuler/barcode v1.1.0 github.com/disintegration/imaging v1.6.2 @@ -14,21 +15,24 @@ require ( github.com/makiuchi-d/gozxing v0.1.1 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/parnurzeal/gorequest v0.3.0 - github.com/redis/go-redis/v9 v9.17.2 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/tjfoc/gmsm v1.4.1 github.com/xuri/excelize/v2 v2.10.0 - golang.org/x/image v0.25.0 + golang.org/x/image v0.26.0 golang.org/x/sys v0.37.0 + gopkg.in/ini.v1 v1.67.1 + gopkg.in/yaml.v3 v3.0.1 + modernc.org/sqlite v1.44.3 ) require ( filippo.io/edwards25519 v1.1.0 // indirect + gioui.org/shader v1.0.8 // 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/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect github.com/elazarl/goproxy v1.7.2 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect @@ -38,19 +42,24 @@ require ( 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/go-text/typesetting v0.3.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/kr/pretty v0.3.1 // 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/ncruces/go-strftime v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect @@ -65,11 +74,17 @@ require ( 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/mod v0.28.0 // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect + golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/mod v0.29.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/tools v0.37.0 // indirect + golang.org/x/tools v0.38.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + modernc.org/libc v1.67.6 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 934fc07..ff9cdb7 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,46 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= +eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.9.0 h1:4u7XZwnb5kzQW91Nz/vR0wKD6LdW9CaVF96r3rfy4kc= +gioui.org v0.9.0/go.mod h1:CjNig0wAhLt9WZxOPAusgFD8x8IRvqt26LdDBa3Jvao= +gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= +gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= +gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= 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/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo= github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elastic/elastic-transport-go/v8 v8.7.0 h1:OgTneVuXP2uip4BA658Xi6Hfw+PeIOod2rY3GVMGoVE= github.com/elastic/elastic-transport-go/v8 v8.7.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= github.com/elastic/go-elasticsearch/v8 v8.19.0 h1:VmfBLNRORY7RZL+9hTxBD97ehl9H8Nxf2QigDh6HuMU= github.com/elastic/go-elasticsearch/v8 v8.19.0/go.mod h1:F3j9e+BubmKvzvLjNui/1++nJuJxbkhHefbaT0kFKGY= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= @@ -54,24 +64,53 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO 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/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= +github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= 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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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/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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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= @@ -84,27 +123,33 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G 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/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI= 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= -github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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= @@ -114,14 +159,18 @@ github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9 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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4= github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= 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= @@ -146,6 +195,8 @@ 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -153,17 +204,31 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80= +golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -174,6 +239,9 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -183,7 +251,10 @@ 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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -218,19 +289,72 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= +gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= +modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY= +modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..b360676 Binary files /dev/null and b/icon.ico differ diff --git a/image/123.png b/image/123.png new file mode 100644 index 0000000..7e029c6 Binary files /dev/null and b/image/123.png differ diff --git a/kfztp.go b/kfztp.go deleted file mode 100644 index b2a4fee..0000000 --- a/kfztp.go +++ /dev/null @@ -1,866 +0,0 @@ -package main - -//import ( -// "crypto/md5" -// "encoding/json" -// "fmt" -// "log" -// "math/rand" -// "net/http" -// "net/url" -// "regexp" -// "strconv" -// "strings" -// "sync" -// "time" -// -// "github.com/parnurzeal/gorequest" -//) -// -//// 获取商品模版 -//func outGetGoodsTplMsgLinux(token, itemId, proxy string) (map[string]interface{}, error) { -// if token == "" { -// return nil, fmt.Errorf("请先登录获取Token") -// } -// -// url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d", -// itemId, time.Now().Unix()) -// -// log.Printf("请求URL: %s", url) -// -// // 创建HTTP客户端 -// request := gorequest.New() -// -// // 设置代理(如果有提供代理URL) -// if proxy != "" { -// request.Proxy(proxy) -// log.Printf("使用代理: %s", proxy) -// } -// -// resp, body, errs := request.Get(url). -// Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). -// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). -// Set("Accept", "application/json, text/plain, */*"). -// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). -// Set("Referer", "https://seller.kongfz.com/"). -// Set("Origin", "https://seller.kongfz.com"). -// Timeout(30 * time.Second). -// End() -// -// if len(errs) > 0 { -// return nil, fmt.Errorf("请求失败: %v", errs) -// } -// -// // 检查HTTP状态码 -// if resp.StatusCode != http.StatusOK { -// return nil, fmt.Errorf("HTTP错误: %d - %s", resp.StatusCode, resp.Status) -// } -// -// var data map[string]interface{} -// if err := json.Unmarshal([]byte(body), &data); err != nil { -// return nil, fmt.Errorf("解析JSON失败: %w", err) -// } -// -// // 检查API响应状态 -// if val, ok := data["status"].(float64); ok && val == 1 { -// return data, nil -// } -// -// return nil, fmt.Errorf("API返回错误: %+v", data) -//} -// -//func main() { -// fmt.Println("=== 孔夫子商品模板API服务 ===") -// -// http.HandleFunc("/api/goodsTemplate", getGoodsTemplateHandler) -// http.HandleFunc("/api/outGetImageByIsbn", getOutGetImageByIsbnHandler) -// -// port := "8989" -// server := &http.Server{ -// Addr: ":" + port, -// Handler: nil, -// } -// // 启动服务器 -// if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { -// fmt.Printf("服务器启动失败: %v\n", err) -// } -//} -// -//// 获取商品模板接口 -//func getGoodsTemplateHandler(w http.ResponseWriter, r *http.Request) { -// // 设置响应头 -// w.Header().Set("Content-Type", "application/json") -// -// // 只处理GET请求 -// if r.Method != http.MethodGet { -// w.WriteHeader(http.StatusMethodNotAllowed) -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": false, -// "error": "只支持GET请求", -// }) -// return -// } -// -// // 获取查询参数 -// token := r.URL.Query().Get("token") -// itemId := r.URL.Query().Get("itemId") -// proxy := r.URL.Query().Get("proxy") -// -// // 验证必需参数 -// if token == "" || itemId == "" { -// w.WriteHeader(http.StatusBadRequest) -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": false, -// "error": "缺少必需参数: token 和 itemid", -// "usage": map[string]string{ -// "token": "登录Token (PHPSESSID)", -// "itemid": "商品ID", -// "proxy": "代理地址 (可选)", -// }, -// "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080", -// }) -// return -// } -// -// log.Printf("收到请求 - 商品ID: %s", itemId) -// -// // 调用函数获取商品模板 -// result, err := outGetGoodsTplMsgLinux(token, itemId, proxy) -// if err != nil { -// log.Printf("获取商品模板失败: %v", err) -// w.WriteHeader(http.StatusInternalServerError) -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": false, -// "error": err.Error(), -// }) -// return -// } -// -// // 返回成功响应 -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": true, -// "data": result, -// }) -//} -// -//func getOutGetImageByIsbnHandler(w http.ResponseWriter, r *http.Request) { -// // 设置响应头 -// w.Header().Set("Content-Type", "application/json") -// -// // 只处理GET请求 -// if r.Method != http.MethodGet { -// w.WriteHeader(http.StatusMethodNotAllowed) -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": false, -// "error": "只支持GET请求", -// }) -// return -// } -// -// // 获取查询参数 -// token := r.URL.Query().Get("token") -// isbn := r.URL.Query().Get("isbn") -// proxyType := r.URL.Query().Get("proxyType") -// username := r.URL.Query().Get("username") -// password := r.URL.Query().Get("password") -// machineCode := r.URL.Query().Get("machineCode") -// //proxy := r.URL.Query().Get("proxy") -// -// // 验证必需参数 -// if token == "" || isbn == "" { -// w.WriteHeader(http.StatusBadRequest) -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": false, -// "error": "缺少必需参数: token 和 isbn", -// "usage": map[string]string{ -// "token": "登录Token (PHPSESSID)", -// "isbn": "isbn", -// "proxy": "代理地址 (可选)", -// }, -// "example": "GET /api/goods-template?token=abc123&itemid=123456&proxy=http://proxy:8080", -// }) -// return -// } -// -// manager, err2 := proxyTypeManager(proxyType, username, password, machineCode) -// if err2 != nil { -// return -// } -// log.Printf("收到请求 - 商品ID: %s", isbn) -// -// // 调用函数获取商品模板 -// result, err := OutGetImageByIsbns(token, isbn, manager, 0, 0) -// if err != nil { -// log.Printf("获取商品模板失败: %v", err) -// w.WriteHeader(http.StatusInternalServerError) -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": false, -// "error": err.Error(), -// }) -// return -// } -// // 返回成功响应 -// json.NewEncoder(w).Encode(map[string]interface{}{ -// "success": true, -// "data": result, -// }) -//} -// -//// 条目详情结构体 -//type BookInfo struct { -// BookName string `json:"book_name"` // 书名 -// Author string `json:"author"` // 作者 -// Publisher string `json:"publisher"` // 出版社 -// ISBN string `json:"isbn"` // ISBN -// PublicationTime int64 `json:"publication_time"` // 出版时间 -// Edition string `json:"edition"` // 版次 -// PrintTime string `json:"print_time"` // 印刷时间 -// FixPrice string `json:"fix_price"` // 定价 -// BindingLayout string `json:"binding_layout"` // 装帧 -// Format string `json:"format"` // 开本 -// Paper string `json:"paper"` // 纸张 -// Pages string `json:"pages"` // 页数 -// Wordage string `json:"wordage"` // 字数 -// Languages string `json:"languages"` // 语种 -// Era string `json:"era"` // 年代 -// EngravingMethod string `json:"engraving_method"` // 刻印方式 -// Dimensions string `json:"dimensions"` // 尺寸 -// VolumeNumber string `json:"volume_number"` // 册数 -// BookPic string `json:"book_pic"` // 图书封面图(官图) -// BookPicS string `json:"book_pic_s"` // 图书封面图(实拍图) -// SellingPrice string `json:"selling_price"` // 售价 -// Condition string `json:"condition"` // 品相 -// ExpressDeliveryFee string `json:"express_delivery_fee"` // 快递费 -// Editor string `json:"editor"` // 编辑 -// Category string `json:"category"` // 分类 -// BuyCount string `json:"buy_count"` // 买过 -// SellCount string `json:"sell_count"` // 在卖 -// Content string `json:"content"` // 内容 -// Mid int64 `json:"mid"` // 商家id -// ItemId int64 `json:"item_id"` // 商品id -// ShopId int64 `json:"shop_id"` // 店铺id -// DetailUrl string `json:"detail_url"` // 商品详情url -//} -// -//// 获取图片URL(官图和拍图) -//func OutGetImageByIsbns(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (*BookInfo, error) { -// fmt.Println("[DEBUG] 使用的ISBN: ", isbn) -// // isLiveImage 1实拍图 0官图 ,isReturnMsg 0商品信息 -// bookInfo := &BookInfo{} -// //if isLiveImage == 0 { -// // 孔网官图请求 -// gtUrl := fmt.Sprintf("%s?keyword=%s", "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", isbn) -// // 创建HTTP客户端 -// requestGt := gorequest.New() -// // 设置代理(如果有提供代理URL) -// if proxy != "" { -// requestGt.Proxy(proxy) -// } -// // 发送请求 -// respGt, bodyGt, errsGt := requestGt.Get(gtUrl). -// Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). -// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). -// Set("Accept", "application/json, text/plain, */*"). -// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). -// Set("Referer", "https://item.kongfz.com/"). -// Timeout(30 * time.Second). -// End() -// if len(errsGt) > 0 { -// // 检查是否是代理相关错误 -// var isProxyError bool -// var errorDetails []string -// for _, e := range errsGt { -// errorStr := e.Error() -// errorDetails = append(errorDetails, errorStr) -// if strings.Contains(errorStr, "Proxy Authentication Required") || -// strings.Contains(errorStr, "connectex: A connection attempt failed") || -// strings.Contains(errorStr, "connectex: No connection could be made") || -// strings.Contains(errorStr, "proxyconnect tcp") || -// strings.Contains(errorStr, "timeout") || -// strings.Contains(errorStr, "connection refused") { -// isProxyError = true -// } -// } -// log.Printf("[ERROR] 请求错误详情: %v", errorDetails) -// if isProxyError { -// // 处理代理失败 -// return nil, fmt.Errorf("代理连接失败") -// } -// return nil, fmt.Errorf("查询请求失败: %v", errsGt) -// } -// //检查HTTP状态码 -// if respGt.StatusCode != http.StatusOK { -// return nil, fmt.Errorf("HTTP错误: %s", respGt.Status) -// } -// // 解析响应 -// var apiGtResp struct { -// Status int `json:"status"` -// ErrType string `json:"errType"` -// Message string `json:"message"` -// SystemTime int64 `json:"systemTime"` -// Data struct { -// ItemResponse struct { -// Total int `json:"total"` -// List []struct { -// BookName string `json:"bookName"` -// Mid int64 `json:"mid"` -// ImgUrlEntity struct { -// BigImgUrl string `json:"bigImgUrl"` -// } `json:"imgUrlEntity"` -// Isbn string `json:"isbn"` -// BookShowInfo []string `json:"bookShowInfo"` -// } `json:"list"` -// } `json:"itemResponse"` -// } `json:"data"` -// } -// if err := json.Unmarshal([]byte(bodyGt), &apiGtResp); err != nil { -// return nil, fmt.Errorf("解析JSON失败: %w", err) -// } -// if apiGtResp.ErrType == "102" { -// return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiGtResp.Message, apiGtResp.ErrType) -// } -// // 如果找到条目,返回图片URL -// if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 { -// list := apiGtResp.Data.ItemResponse.List[0] -// bookShowInfo := list.BookShowInfo -// bookInfo.BookName = list.BookName -// bookInfo.BookPic = list.ImgUrlEntity.BigImgUrl -// bookInfo.ISBN = list.Isbn -// // 根据长度安全填充字段 -// if isReturnMsg == 0 { -// if len(bookShowInfo) > 0 { -// bookInfo.Author = bookShowInfo[0] -// } -// if len(bookShowInfo) > 1 { -// bookInfo.Publisher = bookShowInfo[1] -// } -// if len(bookShowInfo) > 2 { -// bookInfo.PublicationTime = validateDateFormat(bookShowInfo[2]) -// } -// if len(bookShowInfo) > 3 { -// bookInfo.BindingLayout = bookShowInfo[3] -// } -// if len(bookShowInfo) > 4 { -// bookInfo.FixPrice = bookShowInfo[4] -// } else { -// log.Printf("[WARN] BookShowInfo 长度不足 (仅 %d 项): %v", len(bookShowInfo), bookShowInfo) -// } -// } -// } -// //return bookInfo, nil -// -// //} -// //if isLiveImage == 1 { -// -// size := 10 -// // 实拍图 -// sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000", -// "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, size) -// //创建HTTP客户端 -// requestSpt := gorequest.New() -// //设置代理(如果有提供代理URL) -// if proxy != "" { -// requestSpt.Proxy(proxy) -// } -// // 发送请求 -// respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl). -// Proxy(proxy). -// Set("Cookie", token). -// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). -// Set("Accept", "application/json, text/plain, */*"). -// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). -// Set("Referer", "https://item.kongfz.com/"). -// Timeout(30 * time.Second). -// End() -// // 错误处理 -// if len(errsSpt) > 0 { -// return nil, fmt.Errorf("请求失败: %v", errsSpt) -// } -// // 检查HTTP状态码 -// if respSpt.StatusCode != http.StatusOK { -// return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status) -// } -// // 解析响应 -// var apiSptResp struct { -// Status int `json:"status"` -// ErrType string `json:"errType"` -// Message string `json:"message"` -// SystemTime int64 `json:"systemTime"` -// Data struct { -// ItemResponse struct { -// Total int `json:"total"` -// List []struct { -// ItemId int64 `json:"itemId"` -// Title string `json:"title"` -// ImgUrl string `json:"imgUrl"` -// ImgBigUrl string `json:"imgBigUrl"` -// Author string `json:"author"` -// PubDateText string `json:"pubDateText"` -// Isbn string `json:"isbn"` -// Press string `json:"press"` -// ShopId int64 `json:"shopId"` -// TplRecords []struct { -// Key string `json:"key"` -// Value string `json:"value"` -// } `json:"tplRecords"` -// } `json:"list"` -// } `json:"itemResponse"` -// } `json:"data"` -// } -// // 解析JSON -// if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil { -// return nil, fmt.Errorf("解析JSON失败: %w", err) -// } -// if apiSptResp.ErrType == "102" { -// return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiSptResp.Message, apiSptResp.ErrType) -// } -// if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 { -// // 确定其实索引 -// var startIndex int -// if size >= apiSptResp.Data.ItemResponse.Total { -// startIndex = apiSptResp.Data.ItemResponse.Total - 1 -// } else { -// startIndex = size - 1 -// } -// for attempt := 0; attempt < 3; attempt++ { -// currentIndex := startIndex - attempt -// // 检查索引是否有效 -// if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) { -// log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex) -// continue -// } -// randomNum := rand.Intn(currentIndex + 1) -// item := apiSptResp.Data.ItemResponse.List[randomNum] -// // 检查图片URL是否存在 -// if item.ImgBigUrl == "" { -// log.Printf("[DEBUG] 索引 %d 的图片URL为空,跳过", randomNum) -// continue -// } -// // 响应信息 -// bookInfo.BookPicS = item.ImgUrl -// bookInfo.ISBN = item.Isbn -// if bookInfo.BookName == "" { -// bookInfo.BookName = item.Title -// if isReturnMsg == 0 { -// // 安全地获取TplRecords中的值 -// if len(item.TplRecords) > 0 { -// bookInfo.Author = item.TplRecords[0].Value -// } -// if len(item.TplRecords) > 1 { -// bookInfo.Publisher = item.TplRecords[1].Value -// } -// if len(item.TplRecords) > 2 { -// bookInfo.PublicationTime = validateDateFormat(item.TplRecords[2].Value) -// } -// if len(item.TplRecords) > 3 { -// bookInfo.BindingLayout = item.TplRecords[3].Value -// } -// } -// } -// return bookInfo, nil -// } -// //} -// } -// return nil, fmt.Errorf("查询失败,没有数据!") -//} -// -//// 验证日期格式 -//func validateDateFormat(dateStr string) int64 { -// // 去除前后空格 -// dateStr = strings.TrimSpace(dateStr) -// -// // 替换各种分隔符为统一的分隔符"-" -// dateStr = regexp.MustCompile(`[/_\\.,\s]+`).ReplaceAllString(dateStr, "-") -// -// // 处理纯年份格式 (4位数字) -// if regexp.MustCompile(`^\d{4}$`).MatchString(dateStr) { -// dateStr += "-01-01" -// } -// -// // 处理年月格式 (4位数字-1或2位数字) -// if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})$`).FindStringSubmatch(dateStr); len(matches) == 3 { -// year := matches[1] -// month := matches[2] -// if len(month) == 1 { -// month = "0" + month -// } -// if monthNum, _ := strconv.Atoi(month); monthNum >= 1 && monthNum <= 12 { -// dateStr = year + "-" + month + "-01" -// } else { -// return 0 -// } -// } -// -// // 处理年月日格式 (4位数字-1或2位数字-1或2位数字) -// if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})-(\d{1,2})`).FindStringSubmatch(dateStr); len(matches) >= 4 { -// year := matches[1] -// month := matches[2] -// day := matches[3] -// -// // 标准化月份和日期为两位数 -// if len(month) == 1 { -// month = "0" + month -// } -// if len(day) == 1 { -// day = "0" + day -// } -// -// dateStr = year + "-" + month + "-" + day -// } -// -// // 加载上海时区 -// loc, err := time.LoadLocation("Asia/Shanghai") -// if err != nil { -// // 如果加载时区失败,使用UTC+8作为后备方案 -// loc = time.FixedZone("CST", 8*60*60) -// } -// -// // 尝试解析为标准日期格式,并指定上海时区 -// parsedTime, err := time.ParseInLocation("2006-01-02", dateStr, loc) -// if err != nil { -// return 0 -// } -// -// // 返回秒级时间戳(UTC时间,但解析时已经考虑了时区偏移) -// return parsedTime.Unix() -//} -// -//// 代理类型常量 -//const ( -// CalfElephantProxyType = "CALF_ELEPHANT_PROXY" -// TailProxyType = "TAIL_PROXY" -//) -// -//// 代理服务器列表 -//var ( -// servers = []string{ -// "http-dynamic.xiaoxiangdaili.com", -// "http-dynamic-S02.xiaoxiangdaili.com", -// "http-dynamic-S03.xiaoxiangdaili.com", -// "http-dynamic-S04.xiaoxiangdaili.com", -// } -// randMutex sync.Mutex -// globalRand *rand.Rand -// -// // 代理健康状态管理 -// proxyHealthMaps = make(map[string]*ProxyHealth) -// proxyHealthMutex sync.RWMutex -//) -// -//// ProxyManager 代理管理器结构体 -//type ProxyManager struct { -// servers []string `json:"servers"` // 代理服务器列表 -// username string `json:"username"` // 代理账号 -// password string `json:"password"` // 代理密码 -// tailCardSecret string `json:"tail_card_secret"` // 尾巴代理卡密 -// proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY -//} -// -//// 代理健康状态 -//type ProxyHealth struct { -// SuccessCount int // 成功次数 -// FailCount int // 失败次数 -// LastCheck time.Time // 最后检查时间 -// ResponseTime time.Duration // 响应时间 -// IsHealthy bool // 是否健康 -//} -// -//func init() { -// // 创建全局的随机数生成器 -// globalRand = rand.New(rand.NewSource(time.Now().UnixNano())) -//} -// -//// 获取代理URL -//func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) { -// switch proxyType { -// case CalfElephantProxyType: -// return buildCalfElephantProxyURL(username, password) -// case TailProxyType: -// return buildTailProxyURL(machineCode) -// default: -// return "", fmt.Errorf("不支持的代理类型: %s", proxyType) -// } -//} -// -//// 构建小象代理URL -//func buildCalfElephantProxyURL(username, password string) (string, error) { -// server := randomServer() -// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d", -// url.QueryEscape(username), -// url.QueryEscape(password), -// server, -// 10030) -// -// // 检测代理可用性 -// if err := checkProxyHealth(proxyURL); err != nil { -// log.Printf("[WARN] 代理 %s 检测失败: %v", server, err) -// // 尝试下一个代理服务器 -// return tryNextCalfElephantProxy(username, password, server) -// } -// -// log.Printf("[INFO] 使用小象代理: %s", server) -// return proxyURL, nil -//} -// -//// 尝试下一个小象代理服务器 -//func tryNextCalfElephantProxy(username, password, failedServer string) (string, error) { -// // 创建服务器副本并排除失败的服务器 -// availableServers := make([]string, 0) -// for _, server := range servers { -// if server != failedServer { -// availableServers = append(availableServers, server) -// } -// } -// -// if len(availableServers) == 0 { -// return "", fmt.Errorf("所有小象代理服务器都不可用") -// } -// -// // 随机尝试可用服务器 -// for _, server := range shuffleServers(availableServers) { -// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d", -// url.QueryEscape(username), -// url.QueryEscape(password), -// server, -// 10030) -// -// if err := checkProxyHealth(proxyURL); err == nil { -// log.Printf("[INFO] 切换到可用代理: %s", server) -// return proxyURL, nil -// } -// log.Printf("[WARN] 代理 %s 检测失败", server) -// } -// -// return "", fmt.Errorf("所有可用的小象代理服务器都检测失败") -//} -// -//// 构建内置代理URL -//func buildTailProxyURL(machineCode string) (string, error) { -// proxies, err := getProxies(machineCode) -// if err != nil { -// return "", err -// } -// if len(proxies) == 0 { -// return "", fmt.Errorf("未获取到有效代理") -// } -// -// // 过滤并选择健康的代理 -// healthyProxies := filterHealthyProxies(proxies) -// if len(healthyProxies) > 0 { -// proxyURL := "http://" + randomElement(healthyProxies) -// log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL) -// return proxyURL, nil -// } -// -// // 如果没有健康代理,检测所有代理 -// log.Printf("[INFO] 未找到健康代理,开始检测代理可用性...") -// return findWorkingTailProxy(proxies) -//} -// -//// 获取代理服务器列表 -//func getProxies(machineCode string) ([]string, error) { -// log.Printf("[INFO] 开始获取代理列表: %s", machineCode) -// // 生成签名 -// sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode)))) -// -// GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999", -// machineCode, sign) -// -// req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second) -// _, body, errs := req.End() -// if len(errs) > 0 { -// return nil, fmt.Errorf("获取代理失败: %v", errs) -// } -// -// // 检查是否为JSON错误响应 -// if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") { -// // 尝试解析为JSON错误响应 -// var errorResp struct { -// Code int `json:"code"` -// Message string `json:"message"` -// } -// if err := json.Unmarshal([]byte(body), &errorResp); err == nil { -// return nil, fmt.Errorf("获取代理失败: %s (错误码: %d)", errorResp.Message, errorResp.Code) -// } -// } -// -// // 解析响应 -// lines := strings.Split(strings.TrimSpace(body), "\n") -// var proxies []string -// for _, line := range lines { -// line = strings.TrimSpace(line) -// if line != "" && !strings.HasPrefix(line, "{") { -// proxies = append(proxies, line) -// } -// } -// if len(proxies) == 0 { -// return nil, fmt.Errorf("未获取到有效代理") -// } -// -// log.Printf("[INFO] 获取到 %d 个代理", len(proxies)) -// return proxies, nil -//} -// -//// 过滤健康代理 -//func filterHealthyProxies(proxies []string) []string { -// proxyHealthMutex.RLock() -// defer proxyHealthMutex.RUnlock() -// -// var healthy []string -// for _, proxy := range proxies { -// if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy { -// // 检查是否在最近检查过(5分钟内) -// if time.Since(health.LastCheck) < 5*time.Minute { -// healthy = append(healthy, proxy) -// } -// } -// } -// return healthy -//} -// -//// 查找可用的尾巴代理 -//func findWorkingTailProxy(proxies []string) (string, error) { -// // 打乱代理顺序 -// shuffledProxies := shuffleServers(proxies) -// -// // 并发检测代理 -// type proxyResult struct { -// proxy string -// err error -// } -// -// ch := make(chan proxyResult, len(shuffledProxies)) -// var wg sync.WaitGroup -// -// // 限制并发数 -// sem := make(chan struct{}, 5) -// -// for _, proxy := range shuffledProxies { -// wg.Add(1) -// go func(p string) { -// defer wg.Done() -// sem <- struct{}{} -// defer func() { <-sem }() -// -// proxyURL := "http://" + p -// err := checkProxyHealth(proxyURL) -// ch <- proxyResult{proxy: p, err: err} -// }(proxy) -// } -// -// wg.Wait() -// close(ch) -// -// // 收集结果 -// var workingProxies []string -// for result := range ch { -// if result.err == nil { -// workingProxies = append(workingProxies, result.proxy) -// // 更新健康状态 -// updateProxyHealth(result.proxy, true, 0) -// } else { -// updateProxyHealth(result.proxy, false, 0) -// } -// } -// -// if len(workingProxies) > 0 { -// selected := randomElement(workingProxies) -// log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies)) -// return "http://" + selected, nil -// } -// -// return "", fmt.Errorf("所有尾巴代理都不可用") -//} -// -//// 检测代理健康状态 -//func checkProxyHealth(proxyURL string) error { -// start := time.Now() -// -// req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second) -// resp, _, errs := req.Get("https://shop.kongfz.com/").End() -// -// responseTime := time.Since(start) -// -// if len(errs) > 0 { -// updateProxyHealth(proxyURL, false, responseTime) -// return fmt.Errorf("代理连接失败: %v", errs) -// } -// -// if resp.StatusCode != 200 { -// updateProxyHealth(proxyURL, false, responseTime) -// return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode) -// } -// -// updateProxyHealth(proxyURL, true, responseTime) -// log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime) -// return nil -//} -// -//// 更新代理健康状态 -//func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) { -// proxyHealthMutex.Lock() -// defer proxyHealthMutex.Unlock() -// -// host := getProxyHost(proxyURL) -// if _, exists := proxyHealthMaps[host]; !exists { -// proxyHealthMaps[host] = &ProxyHealth{} -// } -// -// health := proxyHealthMaps[host] -// health.LastCheck = time.Now() -// -// if success { -// health.SuccessCount++ -// health.FailCount = 0 -// health.ResponseTime = responseTime -// health.IsHealthy = true -// } else { -// health.FailCount++ -// health.SuccessCount = 0 -// // 连续失败3次标记为不健康 -// if health.FailCount >= 3 { -// health.IsHealthy = false -// } -// } -//} -// -//// 获取代理主机名 -//func getProxyHost(proxyURL string) string { -// if strings.HasPrefix(proxyURL, "http://") { -// proxyURL = proxyURL[7:] -// } -// // 去除认证信息 -// if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 { -// proxyURL = proxyURL[atIndex+1:] -// } -// // 去除端口 -// if colonIndex := strings.Index(proxyURL, ":"); colonIndex != -1 { -// proxyURL = proxyURL[:colonIndex] -// } -// return proxyURL -//} -// -//// 随机代理服务器 -//func randomServer() string { -// return randomElement(servers) -//} -// -//// 线程安全的随机元素选择 -//func randomElement(slice []string) string { -// randMutex.Lock() -// defer randMutex.Unlock() -// return slice[globalRand.Intn(len(slice))] -//} -// -//// 打乱服务器顺序 -//func shuffleServers(servers []string) []string { -// randMutex.Lock() -// defer randMutex.Unlock() -// -// shuffled := make([]string, len(servers)) -// copy(shuffled, servers) -// globalRand.Shuffle(len(shuffled), func(i, j int) { -// shuffled[i], shuffled[j] = shuffled[j], shuffled[i] -// }) -// return shuffled -//} diff --git a/kongfz/dll/kongfz.dll b/kongfz/dll/kongfz.dll index 2457433..24b6ef2 100644 Binary files a/kongfz/dll/kongfz.dll and b/kongfz/dll/kongfz.dll differ diff --git a/kongfz/dll/kongfz.h b/kongfz/dll/kongfz.h index 6e14cda..8459e0e 100644 --- a/kongfz/dll/kongfz.h +++ b/kongfz/dll/kongfz.h @@ -172,6 +172,10 @@ extern __declspec(dllexport) char* Initialize(char* configJSON); // extern __declspec(dllexport) void FreeCString(char* str); +// 获取版本信息 +// +extern __declspec(dllexport) char* GetVersion(void); + #ifdef __cplusplus } #endif diff --git a/dll/xkongfz.dll~ b/kongfz/kongfz.dll similarity index 69% rename from dll/xkongfz.dll~ rename to kongfz/kongfz.dll index 67d8165..1eedaba 100644 Binary files a/dll/xkongfz.dll~ and b/kongfz/kongfz.dll differ diff --git a/kongfz/kongfz.go b/kongfz/kongfz.go index 4693e6c..ac03efd 100644 --- a/kongfz/kongfz.go +++ b/kongfz/kongfz.go @@ -84,7 +84,9 @@ type BookInfo struct { Category string `json:"category"` // 分类 BuyCount string `json:"buy_count"` // 买过 SellCount string `json:"sell_count"` // 在卖 - Content string `json:"content"` // 内容 + Content string `json:"content"` // 内容简介 + AuthorIntroduction string `json:"author_introduction"` // 作者简介 + Catalogue string `json:"catalogue"` // 目录 Mid int64 `json:"mid"` // 商家id ItemId int64 `json:"item_id"` // 商品id ShopId int64 `json:"shop_id"` // 店铺id @@ -1285,6 +1287,7 @@ func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, bookInfo.BookName = list.BookName bookInfo.BookPic = list.ImgUrlEntity.BigImgUrl bookInfo.ISBN = list.Isbn + bookInfo.Mid = list.Mid // 根据长度安全填充字段 if isReturnMsg == 0 { if len(bookShowInfo) > 0 { @@ -1322,8 +1325,7 @@ func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, } // 发送请求 respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl). - Proxy(proxy). - Set("Cookie", token). + Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). @@ -1425,6 +1427,265 @@ func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, return nil, fmt.Errorf("查询失败,没有数据!") } +// 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息 +func outGetMidDetail(token string, mid string) (*BookInfo, error) { + detailUrl := fmt.Sprintf("https://item.kongfz.com/book/%s.html", mid) + + // 创建HTTP客户端,设置超时时间 + client := &http.Client{ + Timeout: 120 * time.Second, + } + + // 创建HTTP请求 + req, err := http.NewRequest("GET", detailUrl, nil) + if err != nil { + return nil, fmt.Errorf("创建请求失败: %w", err) + } + + // 设置请求头,模拟浏览器访问 + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") + req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") + req.Header.Set("Connection", "keep-alive") + req.Header.Set("Cache-Control", "max-age=0") + req.Header.Set("Upgrade-Insecure-Requests", "1") + req.Header.Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)) + + // 发送请求 + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("请求失败: %w", err) + } + defer resp.Body.Close() + + // 读取响应体 + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %w", err) + } + var bookInfo BookInfo + // 创建goquery文档 + doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) + if err != nil { + return nil, fmt.Errorf("解析HTML失败: %w", err) + } + + // 1. 提取书名 + bookInfo.BookName = strings.TrimSpace(doc.Find("title").Text()) + + // 2. 提取右侧详细信息区域 + rightDiv := doc.Find("div.detail-con-right") + if rightDiv.Length() == 0 { + return nil, fmt.Errorf("未找到详细信息区域") + } + + // 左侧信息框 + leftInfoBox := rightDiv.Find("div.info-con-box-left") + + // 提取作者 + leftInfoBox.Find(".zuozhe").Each(func(i int, s *goquery.Selection) { + authorText := s.Find(".text-value").Text() + // 清理作者信息 + authorText = strings.ReplaceAll(authorText, "\n", "") + authorText = strings.ReplaceAll(authorText, " ", "") + authorText = strings.TrimSpace(authorText) + bookInfo.Author = authorText + }) + + // 提取出版社 + leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "出版社") { + bookInfo.Publisher = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取出版时间(转换为时间戳) + leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "出版时间") { + timeStr := strings.TrimSpace(s.Find("span.text-value").Text()) + // 尝试解析时间 + t, err := time.Parse("2006-01", timeStr) + if err != nil { + // 如果月份解析失败,尝试只解析年份 + t, err = time.Parse("2006", timeStr) + if err == nil { + bookInfo.PublicationTime = t.Unix() + } + } else { + bookInfo.PublicationTime = t.Unix() + } + } + }) + + // 提取版次 + leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "版次") { + bookInfo.Edition = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取ISBN + leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "ISBN") { + bookInfo.ISBN = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取定价 + leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "定价") { + bookInfo.FixPrice = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取装帧 + leftInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "装帧") { + bookInfo.BindingLayout = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 右侧信息框 + rightInfoBox := rightDiv.Find("div.info-con-box").Last() + + // 提取开本 + rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "开本") { + bookInfo.Format = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取纸张 + rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "纸张") { + bookInfo.Paper = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取页数 + rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "页数") { + bookInfo.Pages = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取语种 + rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "正文语种") { + bookInfo.Languages = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取分类 + rightInfoBox.Find(".item").Each(func(i int, s *goquery.Selection) { + key := s.Find(".text-key").Text() + if strings.Contains(key, "分类") { + bookInfo.Category = strings.TrimSpace(s.Find("span.text-value").Text()) + } + }) + + // 提取买过人数 + rightInfoBox.Find(".sale-qty").Each(func(i int, s *goquery.Selection) { + text := s.Text() + // 使用正则提取数字 + re := regexp.MustCompile(`(\d+)`) + matches := re.FindStringSubmatch(text) + if len(matches) > 1 { + bookInfo.BuyCount = matches[1] + } + }) + + // 3. 提取底部详细信息(内容简介、作者简介、目录) + bottomDiv := rightDiv.Find("div.detail-con-right-bottom") + + // 提取内容简介 + bottomDiv.Find(".jianjie").Each(func(i int, s *goquery.Selection) { + h5 := s.Find("h5").Text() + if strings.Contains(h5, "内容简介") { + content := s.Contents().FilterFunction(func(i int, s *goquery.Selection) bool { + return goquery.NodeName(s) == "#text" + }).Text() + // 清理文本 + content = strings.ReplaceAll(content, "\n", " ") + content = strings.ReplaceAll(content, " ", " ") + content = strings.TrimSpace(content) + bookInfo.Content = content + } + }) + + // 提取作者简介 + bottomDiv.Find(".jianjie").Each(func(i int, s *goquery.Selection) { + h5 := s.Find("h5").Text() + if strings.Contains(h5, "作者简介") { + intro := s.Contents().FilterFunction(func(i int, s *goquery.Selection) bool { + return goquery.NodeName(s) == "#text" + }).Text() + intro = strings.ReplaceAll(intro, "\n", " ") + intro = strings.ReplaceAll(intro, " ", " ") + intro = strings.TrimSpace(intro) + bookInfo.AuthorIntroduction = intro + } + }) + + // 提取目录 + bottomDiv.Find(".jianjie").Each(func(i int, s *goquery.Selection) { + h5 := s.Find("h5").Text() + if strings.Contains(h5, "目录") { + catalog := s.Contents().FilterFunction(func(i int, s *goquery.Selection) bool { + return goquery.NodeName(s) == "#text" + }).Text() + catalog = strings.ReplaceAll(catalog, "\n", " ") + catalog = strings.ReplaceAll(catalog, " ", " ") + catalog = strings.TrimSpace(catalog) + bookInfo.Catalogue = catalog + } + }) + + // 4. 提取图书封面图 + doc.Find("div.detail-con-left").Find("img").Each(func(i int, s *goquery.Selection) { + if src, exists := s.Attr("src"); exists { + if strings.Contains(src, "kongfz") { + bookInfo.BookPic = src + } + } + }) + + // 5. 提取商品信息(售价、品相等) + doc.Find("div.store-list-item").First().Each(func(i int, s *goquery.Selection) { + // 提取售价 + priceText := s.Find(".item-price").Text() + re := regexp.MustCompile(`¥(\d+\.?\d*)`) + matches := re.FindStringSubmatch(priceText) + if len(matches) > 1 { + bookInfo.SellingPrice = matches[1] + } + + // 提取品相 + qualityText := s.Find(".item-quality").Text() + if qualityText != "" { + bookInfo.Condition = strings.TrimSpace(qualityText) + } + }) + + // 6. 尝试提取店铺ID、商品ID等 + // 从URL中提取mid + if midInt, err := strconv.ParseInt(mid, 10, 64); err == nil { + bookInfo.Mid = midInt + } + + return &bookInfo, nil +} + /* * 获取商品列表通过店铺ID * param shopId[int] 店铺ID @@ -1440,7 +1701,7 @@ func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, * Error 无效的排序类型: %s,可选值: sort, putDate, newItem, price * Error 无效的排序类型: %s,可选值: desc, asc */ -func outGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage int, sortType string, +func outGetGoodsListMsgByShopId(token string, shopId int, proxy string, retPrice int, isImage int, sortType string, sort string, priceMin float32, priceMax float32, pageNum, returnNum int) (books []BookInfo, goodsNum string, pNum string, err error) { // 判断店铺ID @@ -1488,7 +1749,7 @@ func outGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: desc, asc", sort) } } - var url string + var kfzUrl string var pMin int pMin = 0 var pMax int @@ -1496,35 +1757,111 @@ func outGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage // 判断价格下限,设置默认值0 if priceMin == 0 && priceMax == 0 { // 调用的url - url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%d", + kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%d", shopId, isImageStr, returnNum, pageNum, sortType, sort, pMin, pMax) } else if priceMin == 0 { - url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%.2f", + kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%.2f", shopId, isImageStr, returnNum, pageNum, sortType, sort, pMin, priceMax) } else if priceMax == 0 { - url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%d", + kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%d", shopId, isImageStr, returnNum, pageNum, sortType, sort, priceMin, pMax) } else { - url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%.2f", + kfzUrl = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%.2f", shopId, isImageStr, returnNum, pageNum, sortType, sort, priceMin, priceMax) } - // 发送请求 - response, err := fetchResponse(url, proxy) - if err != nil { - return nil, "", "", err + + // 发送请求(移除了重试机制) + var response *http.Response + var errors []error + + // 根据是否有代理发送请求 + if proxy != "" { + response, _, errors = gorequest.New(). + Get(kfzUrl). + Proxy(proxy). + Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). + Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Timeout(120 * time.Second). + End() + } else { + fmt.Println("没使用代理") + response, _, errors = gorequest.New(). + Get(kfzUrl). + Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). + Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Timeout(120 * time.Second). + End() } + + // 检测请求是否错误 + if len(errors) > 0 { + var proxyAuthFailed bool + var timeoutError bool + var connectionError bool + // 分析错误类型 + for _, e := range errors { + errStr := e.Error() + if strings.Contains(errStr, "Proxy Authentication Required") { + proxyAuthFailed = true + } + if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "i/o timeout") { + timeoutError = true + } + if strings.Contains(errStr, "connection") || strings.Contains(errStr, "connect") { + connectionError = true + } + } + // 根据错误类型返回相应的错误信息 + if proxyAuthFailed { + fmt.Println("代理认证失败") + return nil, "", "", fmt.Errorf("代理认证失败") + } + if timeoutError { + fmt.Println("请求超时,超时网址") + return nil, "", "", fmt.Errorf("请求超时,超时网址:%s", kfzUrl) + } + if connectionError { + fmt.Println("网络连接错误,错误网址:") + return nil, "", "", fmt.Errorf("网络连接错误,错误网址:%s", kfzUrl) + } + fmt.Println("查询请求失败:", errors) + return nil, "", "", fmt.Errorf("查询请求失败: %v,失败网址:%s", errors, kfzUrl) + } + + // 检查响应是否为空 + if response == nil { + fmt.Println("响应为空") + return nil, "", "", fmt.Errorf("响应为空") + } + + // 检查HTTP状态码 + if response.StatusCode != http.StatusOK { + fmt.Println("HTTP错误") + return nil, "", "", fmt.Errorf("HTTP错误: %s", response.Status) + } + // 读取响应体 body, err := io.ReadAll(response.Body) if err != nil { return nil, "", "", err } + // 解析HTML文档 doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) + if err != nil { + return nil, "", "", err + } + // 全部商品数量 num := doc.Find("div.crumbs-nav-main.clearfix").Find("span") if match := regexp.MustCompile(`\d+`).FindString(num.Text()); match != "" { goodsNum = match } + // 商品页数 pg := doc.Find("li.pull-right.page_num").Find("span") _, split, found := strings.Cut(strings.TrimSpace(pg.Text()), "/") @@ -1533,6 +1870,7 @@ func outGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage } else { log.Printf("未找到页数!") } + // 提取商品信息 infoDiv := doc.Find("div.list-content") var params ParamsInfo @@ -1568,6 +1906,7 @@ func outGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage params.Area = "13003000000" dataItem, err := getGoodsListShippingFee(params, proxy) if err != nil { + fmt.Println("查询快递费失败") return nil, "", "", err } // 将快递费信息填充到图书信息中 @@ -3132,10 +3471,45 @@ func OutGetImageByIsbn(token, proxy, isbn *C.char, isLiveImage C.int, isReturnMs return C.CString(string(jsonData)) } +// OutGetMidDetail 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息 +// +//export OutGetMidDetail +func OutGetMidDetail(token, mid *C.char) *C.char { + goToken := C.GoString(token) + gomid := C.GoString(mid) + bookInfo, err := outGetMidDetail(goToken, gomid) + // 构建API响应 + var apiResponse APIResponse + if err != nil { + apiResponse = APIResponse{ + Success: false, + Message: err.Error(), + } + } else { + apiResponse = APIResponse{ + Success: true, + Data: bookInfo, + } + } + // 转换为JSON字符串 + jsonData, marshalErr := json.Marshal(apiResponse) + if marshalErr != nil { + // 如果JSON序列化失败,返回错误信息 + apiResponse = APIResponse{ + Success: false, + Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), + } + errorJson, _ := json.Marshal(apiResponse) + return C.CString(string(errorJson)) + } + return C.CString(string(jsonData)) +} + // OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID // //export OutGetGoodsListMsgByShopId -func OutGetGoodsListMsgByShopId(shopId C.int, proxy *C.char, retPrice C.int, isImage C.int, sortType *C.char, sort *C.char, priceMin C.float, priceMax C.float, pageNum, returnNum C.int) *C.char { +func OutGetGoodsListMsgByShopId(token *C.char, shopId C.int, proxy *C.char, retPrice C.int, isImage C.int, sortType *C.char, sort *C.char, priceMin C.float, priceMax C.float, pageNum, returnNum C.int) *C.char { + goToken := C.GoString(token) goShopId := int(shopId) goProxy := C.GoString(proxy) goRetPrice := int(retPrice) @@ -3146,7 +3520,7 @@ func OutGetGoodsListMsgByShopId(shopId C.int, proxy *C.char, retPrice C.int, isI goPriceMax := float32(priceMax) goPageNum := int(pageNum) goReturnNum := int(returnNum) - books, num, pNum, err := outGetGoodsListMsgByShopId(goShopId, goProxy, goRetPrice, goIsImage, + books, num, pNum, err := outGetGoodsListMsgByShopId(goToken, goShopId, goProxy, goRetPrice, goIsImage, goSortType, goSort, goPriceMin, goPriceMax, goPageNum, goReturnNum) // 构建统一格式的响应 bookInfo := struct { @@ -3397,6 +3771,18 @@ func FreeCString(str *C.char) { C.free(unsafe.Pointer(str)) } +// CSV_VERSION 版本号 +const ( + CSV_VERSION = "v3" +) + +// 获取版本信息 +// +//export GetVersion +func GetVersion() *C.char { + return C.CString(CSV_VERSION) +} + // 空main函数,编译DLL时需要 func main() { } diff --git a/kongfz/kongfz.h b/kongfz/kongfz.h new file mode 100644 index 0000000..20a7521 --- /dev/null +++ b/kongfz/kongfz.h @@ -0,0 +1,185 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +extern size_t _GoStringLen(_GoString_ s); +extern const char *_GoStringPtr(_GoString_ s); +#endif + +#endif + +/* Start of preamble from import "C" comments. */ + + +#line 3 "kongfz.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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 + + +// OutLogin 孔网登录 +// +extern __declspec(dllexport) char* OutLogin(char* username, char* password); + +// OutGetUserMsg 获取孔网用户信息 +// +extern __declspec(dllexport) char* OutGetUserMsg(char* token); + +// OutGetGoodsTplMsg 获取商品模版--已登的店铺 +// +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); + +// OutAddGoods 新增商品-已登的店铺 +// +extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData); + +// OutAddGoodsAndFile 整合添加商品和获取孔网图片 +// +extern __declspec(dllexport) char* OutAddGoodsAndFile(char* token, char* proxy, char* filePath, char* formData); + +// OutDelGoodsFromSelfShop 删除商品-已登的店铺 +// +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); + +// OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图) +// +extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg); + +// OutGetMidDetail 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息 +// +extern __declspec(dllexport) char* OutGetMidDetail(char* token, char* mid); + +// OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID +// +extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(char* token, 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); + +// OutGetTopGoodsListMsg 获取销量榜商品列表 +// +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); + +// KongfzImageUpload 上传图片接口 +// +extern __declspec(dllexport) char* KongfzImageUpload(int appId, char* appSecret, char* accessToken, char* filePath, char* savePath); + +// KongfzShopItemAdd 添加店铺商品 +// +extern __declspec(dllexport) char* KongfzShopItemAdd(int appId, char* appSecret, char* accessToken, char* shopItemAddJson); + +// KongfzOrderList 查询订单列表 +// +extern __declspec(dllexport) char* KongfzOrderList(int appId, char* appSecret, char* accessToken, char* orderListJson); + +// KongfzOrderGet 查询单个订单 +// +extern __declspec(dllexport) char* KongfzOrderGet(int appId, char* appSecret, char* accessToken, char* userType, int orderId); + +// Initialize 初始化配置 +// +extern __declspec(dllexport) char* Initialize(char* configJSON); + +// FreeCString 释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +// 获取版本信息 +// +extern __declspec(dllexport) char* GetVersion(void); + +#ifdef __cplusplus +} +#endif diff --git a/kongfz/kongfzDll.go b/kongfz/kongfzDll.go index 0e085df..1fabfb3 100644 --- a/kongfz/kongfzDll.go +++ b/kongfz/kongfzDll.go @@ -1,109 +1,107 @@ package main -import "fmt" - -func main() { - //upload, err := kongfzImageUpload(576, "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8", - // "306849d7541661989453102bc0e1a4f000c4469ab084beddc7b10a90ca55f6bb", - // "D:/work/image/9787511220660.jpg", - // "D:\\work\\project\\kfzgw-info\\kongfz") - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(upload) - - //var q OrderQueryParams - //q.UserType = "buyer" - //marshal, err := json.Marshal(q) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(string(marshal)) - // - //list, err := kongfzOrderList(576, "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8", - // "306849d7541661989453102bc0e1a4f000c4469ab084beddc7b10a90ca55f6bb", string(marshal)) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(list) - // - //key, err := outKfzimgKey("de810e9e702de07c50601ef27bb51c93878c920a", "") - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(key) - // - //upload, err := outKfzimgUpload("8b7f613a6f9de5dbaff8f6c419e1c1de8495a993", "", - // "D:/work/image/9787115460622.jpg") - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(upload) - - //uploadd, err := outKfzimgUploadd("de810e9e702de07c50601ef27bb51c93878c920a", "", - // "D:/work/image/9787511220660.jpg") - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(uploadd) - - appId := 576 - appSecret := "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8" - accessToken := "7c5ef2e492f82457898dd9a1bc2eb725df3a67b282721c9c52bfa24e460c4d92" - shippingComName := "韵达快递" - orderId := 275723461 - shippingId := "" - shippingCom := "" - shipmentNum := "312944151258647" - userDefined := "" - moreShipmentNum := "" - - synchronization, err := kongfzOrderSynchronization(appId, appSecret, accessToken, shippingComName, orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum) - if err != nil { - fmt.Println(err) - } - fmt.Println(synchronization) - - //data := ItemInfo{ - // ItemName: "至关重要的关系", - // ISBN: "9787550213869", - // Author: "[美]里德·霍夫曼、本·卡斯诺瓦 著;钱峰 译", - // Press: "北京联合出版公司", - // YearsGroup: "1", - // PubDate: "2013-04", - // PubDateYear: "2013", - // PubDateMonth: "4", - // Edition: "1", - // Binding: "2", // 2可能表示平装 - // Quality: "95", - // Price: "1500", - // Number: "2", - // ItemSn: "a1", - // MouldId: "909963", - // DeliverTime: "48h", - // IsDeliverTimeDefault: "48h", - // CatId: "43000000000000000", - // PageType: "add", - // OldCatId: "0000000000000000000", - // Tpl: "13", - // BearShipping: "buyer", - // WeightPiece: "1", - // IsOnSale: "1", - // Weight: "0.5", - // IsUseMould: "1", - // DeliverTimeGroup: "1", - //} - //marshal, err := json.Marshal(data) - //if err != nil { - // fmt.Println(err) - //} - // - //file, err := outAddGoodsAndFile("7feb7d192911b5440a4c83d81decd568e3cb4833", "", "D:/work/image/9787115460622.jpg", string(marshal)) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(string(file)) -} +//func main() { +// //upload, err := kongfzImageUpload(576, "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8", +// // "306849d7541661989453102bc0e1a4f000c4469ab084beddc7b10a90ca55f6bb", +// // "D:/work/image/9787511220660.jpg", +// // "D:\\work\\project\\kfzgw-info\\kongfz") +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(upload) +// +// //var q OrderQueryParams +// //q.UserType = "buyer" +// //marshal, err := json.Marshal(q) +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(string(marshal)) +// // +// //list, err := kongfzOrderList(576, "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8", +// // "306849d7541661989453102bc0e1a4f000c4469ab084beddc7b10a90ca55f6bb", string(marshal)) +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(list) +// // +// //key, err := outKfzimgKey("de810e9e702de07c50601ef27bb51c93878c920a", "") +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(key) +// // +// //upload, err := outKfzimgUpload("8b7f613a6f9de5dbaff8f6c419e1c1de8495a993", "", +// // "D:/work/image/9787115460622.jpg") +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(upload) +// +// //uploadd, err := outKfzimgUploadd("de810e9e702de07c50601ef27bb51c93878c920a", "", +// // "D:/work/image/9787511220660.jpg") +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(uploadd) +// +// appId := 576 +// appSecret := "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8" +// accessToken := "7c5ef2e492f82457898dd9a1bc2eb725df3a67b282721c9c52bfa24e460c4d92" +// shippingComName := "韵达快递" +// orderId := 275723461 +// shippingId := "" +// shippingCom := "" +// shipmentNum := "312944151258647" +// userDefined := "" +// moreShipmentNum := "" +// +// synchronization, err := kongfzOrderSynchronization(appId, appSecret, accessToken, shippingComName, orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum) +// if err != nil { +// fmt.Println(err) +// } +// fmt.Println(synchronization) +// +// //data := ItemInfo{ +// // ItemName: "至关重要的关系", +// // ISBN: "9787550213869", +// // Author: "[美]里德·霍夫曼、本·卡斯诺瓦 著;钱峰 译", +// // Press: "北京联合出版公司", +// // YearsGroup: "1", +// // PubDate: "2013-04", +// // PubDateYear: "2013", +// // PubDateMonth: "4", +// // Edition: "1", +// // Binding: "2", // 2可能表示平装 +// // Quality: "95", +// // Price: "1500", +// // Number: "2", +// // ItemSn: "a1", +// // MouldId: "909963", +// // DeliverTime: "48h", +// // IsDeliverTimeDefault: "48h", +// // CatId: "43000000000000000", +// // PageType: "add", +// // OldCatId: "0000000000000000000", +// // Tpl: "13", +// // BearShipping: "buyer", +// // WeightPiece: "1", +// // IsOnSale: "1", +// // Weight: "0.5", +// // IsUseMould: "1", +// // DeliverTimeGroup: "1", +// //} +// //marshal, err := json.Marshal(data) +// //if err != nil { +// // fmt.Println(err) +// //} +// // +// //file, err := outAddGoodsAndFile("7feb7d192911b5440a4c83d81decd568e3cb4833", "", "D:/work/image/9787115460622.jpg", string(marshal)) +// //if err != nil { +// // fmt.Println(err) +// //} +// //fmt.Println(string(file)) +//} type ItemInfo struct { ItemName string `json:"itemName"` diff --git a/listener/listener.go b/listener/listener.go new file mode 100644 index 0000000..7ea564d --- /dev/null +++ b/listener/listener.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "time" +) + +func main() { + // 定义路由 + http.HandleFunc("/", homeHandler) + http.HandleFunc("/api/data", dataHandler) + http.HandleFunc("/ws", websocketHandler) + + // 中间件示例 + wrappedHandler := loggingMiddleware(http.DefaultServeMux) + + // 配置服务器 + server := &http.Server{ + Addr: ":8080", + Handler: wrappedHandler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 60 * time.Second, + } + + fmt.Println("HTTP Server listening on :8080") + + // 启动服务器 + if err := server.ListenAndServe(); err != nil { + log.Fatal("Server error:", err) + } +} + +func homeHandler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + fmt.Fprintf(w, "Welcome to HTTP Server!\n") +} + +func dataHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + fmt.Fprintf(w, `{"status": "ok", "message": "GET request received"}`) + case "POST": + fmt.Fprintf(w, `{"status": "ok", "message": "POST request received"}`) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func websocketHandler(w http.ResponseWriter, r *http.Request) { + // WebSocket处理逻辑 + fmt.Fprintf(w, "WebSocket endpoint") +} + +// 中间件:记录请求日志 +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // 调用下一个处理器 + next.ServeHTTP(w, r) + + // 记录日志 + log.Printf("%s %s %s %v", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start)) + }) +} diff --git a/logger/dll/logger.dll b/logger/dll/logger.dll new file mode 100644 index 0000000..52e722b Binary files /dev/null and b/logger/dll/logger.dll differ diff --git a/logger/dll/logger.h b/logger/dll/logger.h new file mode 100644 index 0000000..67ba5ee --- /dev/null +++ b/logger/dll/logger.h @@ -0,0 +1,107 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#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 "logger.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex GoComplex128; +#endif +#else +typedef float _Complex GoComplex64; +typedef double _Complex GoComplex128; +#endif + +/* + static assertion to make sure the file is being used on architecture + at least with matching size of GoInt. +*/ +typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef _GoString_ GoString; +#endif +typedef void *GoMap; +typedef void *GoChan; +typedef struct { void *t; void *v; } GoInterface; +typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; + +#endif + +/* End of boilerplate cgo prologue. */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern __declspec(dllexport) char* CreateLogger(char* configJSON); +extern __declspec(dllexport) char* CreateContextWithTaskType(char* loggerHandle, char* taskType); +extern __declspec(dllexport) void LogInfo(char* ctxHandle, char* message); +extern __declspec(dllexport) void LogError(char* ctxHandle, char* message); +extern __declspec(dllexport) void LogWarning(char* ctxHandle, char* message); +extern __declspec(dllexport) void LogSuccess(char* ctxHandle, char* message); +extern __declspec(dllexport) void FreeString(char* str); +extern __declspec(dllexport) char* CloseAllLoggers(void); +extern __declspec(dllexport) char* GetLogs(char* loggerHandle, char* configJSON); +extern __declspec(dllexport) char* GetLogFiles(char* loggerHandle); + +// 获取版本信息 +// +extern __declspec(dllexport) char* GetVersion(void); + +#ifdef __cplusplus +} +#endif diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..f30adfd --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,1053 @@ +package main + +/* +#include +*/ +import "C" +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" + "strings" + "sync" + "time" + "unsafe" +) + +// LogLevel 日志级别类型 +type LogLevel int + +const ( + LevelSuccess LogLevel = iota + LevelInfo + LevelWarning + LevelError +) + +// SplitType 日志分片方式 +type SplitType int + +const ( + SplitByMonth SplitType = iota + SplitByDay + SplitByHour + SplitByMinute + SplitBySecond +) + +// RotateType 日志分割方式 +type RotateType int + +const ( + RotateBySize RotateType = iota + RotateByCount +) + +// 上下文键类型 +type contextKey string + +const ( + loggerKey contextKey = "logger" + taskTypeKey contextKey = "task_type" +) + +// LevelFileLogger 每个级别的文件日志器 +type LevelFileLogger struct { + mu sync.RWMutex // 读写锁,保证并发安全 + level LogLevel // 日志级别 + logDir string // 日志目录 + splitType SplitType // 分片方式 + rotateType RotateType // 轮转方式 + maxSize int64 // 最大文件大小 + maxCount int // 最大文件数量 + currentFile *os.File // 当前日志文件 + currentDate string // 当前日期 + writer io.Writer // 写入器 + taskType string // 任务类型 +} + +// Logger 日志结构体 +type Logger struct { + mu sync.RWMutex + baseLogDir string // 基础日志目录 + splitType SplitType // 分片方式 + rotateType RotateType // 轮转方式 + maxSize int64 // 最大文件大小 + maxCount int // 最大文件数量 + level LogLevel // 日志级别 + enableCaller bool // 是否启用调用者信息 + defaultTaskType string // 默认任务类型 + levelLoggers map[string]*LevelFileLogger // key: "level-taskType" 级别日志器映射 + lastUsed time.Time // 最后使用时间 +} + +// Config 日志配置 +type Config struct { + LogDir string + SplitType SplitType + RotateType RotateType + MaxSize int64 + MaxCount int + Level LogLevel + EnableCaller bool + DefaultTaskType string // 默认任务类型 +} + +// DLLConfig 用于JSON解析的配置结构体 +type DLLConfig struct { + LogDir string `json:"log_dir"` + SplitType SplitType `json:"split_type"` + RotateType RotateType `json:"rotate_type"` + MaxSize int64 `json:"max_size"` + MaxCount int `json:"max_count"` + Level LogLevel `json:"level"` + EnableCaller bool `json:"enable_caller"` + DefaultTaskType string `json:"default_task_type"` +} + +// 日志级别字符串映射 +var levelStrings = map[LogLevel]string{ + LevelSuccess: "SUCCESS", + LevelInfo: "INFO", + LevelWarning: "WARNING", + LevelError: "ERROR", +} + +// 级别目录名映射 +var levelDirs = map[LogLevel]string{ + LevelSuccess: "success", + LevelInfo: "info", + LevelWarning: "warning", + LevelError: "error", +} + +// 分片格式映射 +var splitFormats = map[SplitType]string{ + SplitByMonth: "2006-01", + SplitByDay: "2006-01-02", + SplitByHour: "2006-01-02-15", + SplitByMinute: "2006-01-02-15-04", + SplitBySecond: "2006-01-02-15-04-05", +} + +// 全局变量 +var ( + loggers = make(map[string]*Logger) + contexts = make(map[string]context.Context) + globalMu sync.Mutex + lastCleanup time.Time +) + +// 初始化自动清理 +func init() { + go autoCleanupRoutine() +} + +func autoCleanupRoutine() { + ticker := time.NewTicker(30 * time.Minute) // 每30分钟清理一次 + defer ticker.Stop() + + for range ticker.C { + cleanupExpiredResources() // 清理2小时未使用的资源 + } +} + +func cleanupExpiredResources() { + globalMu.Lock() + defer globalMu.Unlock() + + now := time.Now() + cutoff := now.Add(-2 * time.Hour) // 2小时未使用的资源 + + for handle, logger := range loggers { + if logger == nil { + delete(loggers, handle) + delete(contexts, handle) + continue + } + + // 使用cutoff:清理2小时未使用的logger + logger.mu.RLock() + lastUsed := logger.lastUsed + logger.mu.RUnlock() + + if lastUsed.Before(cutoff) { + logger.Close() // 关闭文件资源 + delete(loggers, handle) + delete(contexts, handle) + } + } + + lastCleanup = now +} + +// NewLogger 创建新的日志实例 +func NewLogger(config Config) (*Logger, error) { + // 确保基础日志目录存在 + if err := os.MkdirAll(config.LogDir, 0755); err != nil { + return nil, fmt.Errorf("创建日志目录失败: %v", err) + } + + // 设置默认任务类型 + if config.DefaultTaskType == "" { + config.DefaultTaskType = "main" + } + + logger := &Logger{ + baseLogDir: config.LogDir, + splitType: config.SplitType, + rotateType: config.RotateType, + maxSize: config.MaxSize, + maxCount: config.MaxCount, + level: config.Level, + enableCaller: config.EnableCaller, + defaultTaskType: config.DefaultTaskType, + levelLoggers: make(map[string]*LevelFileLogger), + } + + return logger, nil +} + +// WithContext 将logger存入context +func (l *Logger) WithContext(ctx context.Context) context.Context { + return context.WithValue(ctx, loggerKey, l) +} + +// WithTaskType 将任务类型存入context +func (l *Logger) WithTaskType(ctx context.Context, taskType string) context.Context { + return context.WithValue(ctx, taskTypeKey, taskType) +} + +// FromContext 从context获取logger +func FromContext(ctx context.Context) *Logger { + if logger, ok := ctx.Value(loggerKey).(*Logger); ok { + return logger + } + return nil +} + +// getTaskTypeFromContext 从context获取任务类型,如果没有则返回默认值 +func (l *Logger) getTaskTypeFromContext(ctx context.Context) string { + if taskType, ok := ctx.Value(taskTypeKey).(string); ok && taskType != "" { + return taskType + } + return l.defaultTaskType +} + +// getCurrentDate 获取当前日期字符串 +func (lfl *LevelFileLogger) getCurrentDate() string { + format := splitFormats[lfl.splitType] + return time.Now().Format(format) +} + +// getLogFileName 获取日志文件名 +func (lfl *LevelFileLogger) getLogFileName(date string) string { + fileName := fmt.Sprintf("%s-%s-%s.log", levelStrings[lfl.level], lfl.taskType, date) + return filepath.Join(lfl.logDir, fileName) +} + +// getLevelLoggerKey 生成级别日志器的key +func getLevelLoggerKey(level LogLevel, taskType string) string { + return fmt.Sprintf("%d-%s", level, taskType) +} + +// getLevelLogger 获取或创建指定任务类型的级别日志器 +func (l *Logger) getLevelLogger(level LogLevel, taskType string) (*LevelFileLogger, error) { + key := getLevelLoggerKey(level, taskType) + + // 第一次检查(读锁) + l.mu.RLock() + if levelLogger, exists := l.levelLoggers[key]; exists { + l.mu.RUnlock() + return levelLogger, nil + } + l.mu.RUnlock() + + // 获取写锁创建新的日志器 + l.mu.Lock() + defer l.mu.Unlock() + + // 第二次检查(写锁内),防止并发创建 + if levelLogger, exists := l.levelLoggers[key]; exists { + return levelLogger, nil + } + + // 创建级别目录 + levelDir := filepath.Join(l.baseLogDir, levelDirs[level]) + if err := os.MkdirAll(levelDir, 0755); err != nil { + return nil, fmt.Errorf("创建级别目录失败: %v", err) + } + + levelLogger := &LevelFileLogger{ + level: level, + logDir: levelDir, + splitType: l.splitType, + rotateType: l.rotateType, + maxSize: l.maxSize, + maxCount: l.maxCount, + taskType: taskType, + } + + if err := levelLogger.rotateFile(); err != nil { + return nil, err + } + + // 使用复合key存储 + l.levelLoggers[key] = levelLogger + return levelLogger, nil +} + +// rotateFile 轮转日志文件 +func (lfl *LevelFileLogger) rotateFile() error { + lfl.mu.Lock() + defer lfl.mu.Unlock() + + currentDate := lfl.getCurrentDate() + + // 如果日期没变且不需要按大小分割,则不需要轮转 + if lfl.currentDate == currentDate && lfl.currentFile != nil { + if lfl.rotateType == RotateBySize { + info, err := lfl.currentFile.Stat() + if err == nil && info.Size() < lfl.maxSize { + return nil + } + } else { + return nil + } + } + + // 关闭当前文件 + if lfl.currentFile != nil { + err := lfl.currentFile.Close() + if err != nil { + return err + } + } + + // 创建新文件 + fileName := lfl.getLogFileName(currentDate) + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("创建日志文件失败: %v", err) + } + + lfl.currentFile = file + lfl.currentDate = currentDate + lfl.writer = file + + // 如果启用了控制台输出,同时输出到控制台 + if os.Getenv("LOG_CONSOLE") == "true" { + lfl.writer = io.MultiWriter(file, os.Stdout) + } + + // 处理文件数量限制 + if lfl.rotateType == RotateByCount { + lfl.cleanupOldFiles() + } + + return nil +} + +// cleanupOldFiles 清理旧日志文件 +func (lfl *LevelFileLogger) cleanupOldFiles() { + pattern := filepath.Join(lfl.logDir, fmt.Sprintf("%s-%s-*.log", levelStrings[lfl.level], lfl.taskType)) + files, err := filepath.Glob(pattern) + if err != nil { + return + } + + if len(files) <= lfl.maxCount { + return + } + + // 按修改时间排序,删除最旧的文件 + for i := 0; i < len(files)-lfl.maxCount; i++ { + err := os.Remove(files[i]) + if err != nil { + return + } + + } +} + +// writeLog 写入日志 +func (lfl *LevelFileLogger) writeLog(message string) error { + // 检查是否需要轮转文件 + if err := lfl.rotateFile(); err != nil { + return err + } + + lfl.mu.RLock() + defer lfl.mu.RUnlock() + + if lfl.writer != nil { + _, err := lfl.writer.Write([]byte(message)) + return err + } + return nil +} + +// getCallerInfo 获取调用者信息 +func (l *Logger) getCallerInfo() string { + if !l.enableCaller { + return "" + } + + _, file, line, ok := runtime.Caller(3) // 跳过3层调用栈 + if !ok { + return "" + } + + return fmt.Sprintf("%s:%d", filepath.Base(file), line) +} + +// log 基础日志方法 +func (l *Logger) log(ctx context.Context, level LogLevel, format string, args ...interface{}) { + l.mu.Lock() + l.lastUsed = time.Now() + l.mu.Unlock() + + // 级别过滤 + if level < l.level { + return + } + + // 获取任务类型 + taskType := l.getTaskTypeFromContext(ctx) + + // 获取或创建级别日志器 + levelLogger, err := l.getLevelLogger(level, taskType) + if err != nil { + return + } + + // 格式化日志条目 + timestamp := time.Now().Format("2006-01-02 15:04:05.000") + levelStr := levelStrings[level] + message := fmt.Sprintf(format, args...) + callerInfo := l.getCallerInfo() + + var logEntry string + if callerInfo != "" { + logEntry = fmt.Sprintf("[%s] [%s] [%s] [%s] %s\n", timestamp, levelStr, taskType, callerInfo, message) + } else { + logEntry = fmt.Sprintf("[%s] [%s] [%s] %s\n", timestamp, levelStr, taskType, message) + } + + // 写入对应级别的日志文件 + err = levelLogger.writeLog(logEntry) +} + +// Success 记录成功日志 +func (l *Logger) Success(ctx context.Context, format string, args ...interface{}) { + l.log(ctx, LevelSuccess, format, args...) +} + +// Info 记录信息日志 +func (l *Logger) Info(ctx context.Context, format string, args ...interface{}) { + l.log(ctx, LevelInfo, format, args...) +} + +// Warning 记录警告日志 +func (l *Logger) Warning(ctx context.Context, format string, args ...interface{}) { + l.log(ctx, LevelWarning, format, args...) +} + +// Error 记录错误日志 +func (l *Logger) Error(ctx context.Context, format string, args ...interface{}) { + l.log(ctx, LevelError, format, args...) +} + +// TimeCost 耗时记录方法 +func (l *Logger) TimeCost(ctx context.Context, operation string) func() { + start := time.Now() + return func() { + cost := time.Since(start) + l.Info(ctx, "%s 耗时: %s", operation, cost.String()) + } +} + +// TimeCostMs 返回毫秒级耗时(直接返回耗时值) +func (l *Logger) TimeCostMs(ctx context.Context, operation string) func() float64 { + start := time.Now() + return func() float64 { + cost := time.Since(start) + ms := float64(cost.Nanoseconds()) / 1e6 + l.Info(ctx, "%s 耗时: %.3fms", operation, ms) + return ms + } +} + +// Close 关闭所有日志文件 +func (l *Logger) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + + var lastErr error + for _, levelLogger := range l.levelLoggers { + levelLogger.mu.Lock() + if levelLogger.currentFile != nil { + if err := levelLogger.currentFile.Close(); err != nil { + lastErr = err + } + } + levelLogger.mu.Unlock() + } + + // 清空映射 + l.levelLoggers = make(map[string]*LevelFileLogger) + return lastErr +} + +// ============================ DLL 导出函数 ============================ + +//export CreateLogger +func CreateLogger(configJSON *C.char) *C.char { + jsonStr := C.GoString(configJSON) + + var config DLLConfig + if err := json.Unmarshal([]byte(jsonStr), &config); err != nil { + return C.CString("错误: " + err.Error()) + } + + loggerConfig := Config{ + LogDir: config.LogDir, + SplitType: config.SplitType, + RotateType: config.RotateType, + MaxSize: config.MaxSize, + MaxCount: config.MaxCount, + Level: config.Level, + EnableCaller: config.EnableCaller, + DefaultTaskType: config.DefaultTaskType, + } + + logger, err := NewLogger(loggerConfig) + if err != nil { + return C.CString("错误: " + err.Error()) + } + + globalMu.Lock() + defer globalMu.Unlock() + + handle := fmt.Sprintf("logger_%d", time.Now().UnixNano()) + loggers[handle] = logger + + // 创建初始上下文 + ctx := context.Background() + ctx = logger.WithContext(ctx) + contexts[handle] = ctx + + return C.CString(handle) +} + +//export CreateContextWithTaskType +func CreateContextWithTaskType(loggerHandle *C.char, taskType *C.char) *C.char { + handle := C.GoString(loggerHandle) + taskTypeStr := C.GoString(taskType) + + globalMu.Lock() + defer globalMu.Unlock() + + logger, exists := loggers[handle] + if !exists { + return C.CString("错误: 无效的logger句柄") + } + + baseCtx, exists := contexts[handle] + if !exists { + return C.CString("错误: 无效的上下文") + } + + // 创建带任务类型的新上下文 + ctx := logger.WithTaskType(baseCtx, taskTypeStr) + + ctxHandle := fmt.Sprintf("ctx_%d", time.Now().UnixNano()) + contexts[ctxHandle] = ctx + + return C.CString(ctxHandle) +} + +//export LogInfo +func LogInfo(ctxHandle *C.char, message *C.char) { + handle := C.GoString(ctxHandle) + msg := C.GoString(message) + + globalMu.Lock() + ctx, exists := contexts[handle] + globalMu.Unlock() + + if !exists { + return + } + + if logger := FromContext(ctx); logger != nil { + logger.Info(ctx, "%s", msg) + } +} + +//export LogError +func LogError(ctxHandle *C.char, message *C.char) { + handle := C.GoString(ctxHandle) + msg := C.GoString(message) + + globalMu.Lock() + ctx, exists := contexts[handle] + globalMu.Unlock() + + if !exists { + return + } + + if logger := FromContext(ctx); logger != nil { + logger.Error(ctx, "%s", msg) + } +} + +//export LogWarning +func LogWarning(ctxHandle *C.char, message *C.char) { + handle := C.GoString(ctxHandle) + msg := C.GoString(message) + + globalMu.Lock() + ctx, exists := contexts[handle] + globalMu.Unlock() + + if !exists { + return + } + + if logger := FromContext(ctx); logger != nil { + logger.Warning(ctx, "%s", msg) + } +} + +//export LogSuccess +func LogSuccess(ctxHandle *C.char, message *C.char) { + handle := C.GoString(ctxHandle) + msg := C.GoString(message) + + globalMu.Lock() + ctx, exists := contexts[handle] + globalMu.Unlock() + + if !exists { + return + } + + if logger := FromContext(ctx); logger != nil { + logger.Success(ctx, "%s", msg) + } +} + +//export FreeString +func FreeString(str *C.char) { + // 这是专门用于释放C.CString返回的内存 + C.free(unsafe.Pointer(str)) +} + +//export CloseAllLoggers +func CloseAllLoggers() *C.char { + globalMu.Lock() + defer globalMu.Unlock() + + var errorCount int + var lastError string + + // 关闭所有日志器 + for handle, logger := range loggers { + if err := logger.Close(); err != nil { + errorCount++ + lastError = err.Error() + } + delete(loggers, handle) + delete(contexts, handle) + } + + if errorCount > 0 { + return C.CString(fmt.Sprintf("关闭了%d个logger,其中%d个出错,最后错误: %s", + len(loggers), errorCount, lastError)) + } + + return C.CString("成功关闭所有logger") +} + +/* +// 便捷函数 - 通过上下文使用 +func Success(ctx context.Context, format string, args ...interface{}) { + if logger := FromContext(ctx); logger != nil { + logger.Success(ctx, format, args...) + } +} + +func Info(ctx context.Context, format string, args ...interface{}) { + if logger := FromContext(ctx); logger != nil { + logger.Info(ctx, format, args...) + } +} + +func Warning(ctx context.Context, format string, args ...interface{}) { + if logger := FromContext(ctx); logger != nil { + logger.Warning(ctx, format, args...) + } +} + +func Error(ctx context.Context, format string, args ...interface{}) { + if logger := FromContext(ctx); logger != nil { + logger.Error(ctx, format, args...) + } +} + +func TimeCost(ctx context.Context, operation string) func() { + if logger := FromContext(ctx); logger != nil { + return logger.TimeCost(ctx, operation) + } + return func() {} +} + +func TimeCostMs(ctx context.Context, operation string) func() float64 { + if logger := FromContext(ctx); logger != nil { + return logger.TimeCostMs(ctx, operation) + } + return func() float64 { return 0 } +} + +// WithTaskType 便捷函数 - 将任务类型存入context +func WithTaskType(ctx context.Context, taskType string) context.Context { + if logger := FromContext(ctx); logger != nil { + return logger.WithTaskType(ctx, taskType) + } + return ctx +} +*/ + +// ============================ 日志读取功能 ============================ + +// LogEntry 表示一个日志条目 +type LogEntry struct { + Timestamp string `json:"timestamp"` + Level string `json:"level"` + TaskType string `json:"task_type"` + Caller string `json:"caller,omitempty"` + Message string `json:"message"` + Time time.Time `json:"-"` +} + +// ReadLogsConfig 读取日志的配置 +type ReadLogsConfig struct { + Level LogLevel `json:"level"` + TaskType string `json:"task_type"` + StartTime string `json:"start_time"` // 格式: 2006-01-02 15:04:05 + EndTime string `json:"end_time"` // 格式: 2006-01-02 15:04:05 + MaxEntries int `json:"max_entries"` // 最大返回条目数 +} + +// parseLogLine 解析单行日志 +func parseLogLine(line string) (*LogEntry, error) { + // 正则表达式:匹配四个 [...] 块,后面跟任意消息 + re := regexp.MustCompile(`^\[(.*?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$`) + matches := re.FindStringSubmatch(line) + + if len(matches) != 6 { + return nil, fmt.Errorf("log line does not match expected format") + } + + timestampStr := matches[1] + levelStr := matches[2] + taskType := matches[3] + var caller, message string + if len(matches) == 6 { + // 有调用者信息 [caller] + caller = matches[4] + message = matches[5] + } else { + // 无调用者信息(只有三个 []) + caller = "" + message = matches[4] + } + + // 解析时间戳(支持带毫秒格式:2006-01-02 15:04:05.000) + var logTime time.Time + var err error + if strings.Contains(timestampStr, ".") { + logTime, err = time.Parse("2006-01-02 15:04:05.000", timestampStr) + } else { + logTime, err = time.Parse("2006-01-02 15:04:05", timestampStr) + } + if err != nil { + // 如果解析失败,保留字符串但 Time 为零值(或可跳过) + logTime = time.Time{} + } + + return &LogEntry{ + Timestamp: timestampStr, + Level: levelStr, + TaskType: taskType, + Caller: caller, + Message: message, + Time: logTime, + }, nil +} + +// readLogFile 读取单个日志文件 +func (l *Logger) readLogFile(filePath string, config ReadLogsConfig) ([]LogEntry, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + // 预解析时间范围 + var startTime, endTime time.Time + var hasStartTime, hasEndTime bool + + if config.StartTime != "" { + if t, err := time.Parse("2006-01-02 15:04:05", config.StartTime); err == nil { + startTime = t + hasStartTime = true + } + } + if config.EndTime != "" { + if t, err := time.Parse("2006-01-02 15:04:05", config.EndTime); err == nil { + endTime = t + hasEndTime = true + } + } + + var entries []LogEntry + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + entry, err := parseLogLine(line) + if err != nil { + continue // 跳过无法解析的行 + } + + // 过滤条件 + if config.Level != -1 && levelStrings[config.Level] != entry.Level { + continue + } + + if config.TaskType != "" && config.TaskType != entry.TaskType { + continue + } + + // 时间过滤(仅当 Time 有效时才比较) + if !entry.Time.IsZero() { + if hasStartTime && entry.Time.Before(startTime) { + continue + } + if hasEndTime && entry.Time.After(endTime) { + continue + } + } + + entries = append(entries, *entry) + + // 检查最大条目数 + if config.MaxEntries > 0 && len(entries) >= config.MaxEntries { + break + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return entries, nil +} + +// GetLogs 获取日志条目 +func (l *Logger) GetLogs(config ReadLogsConfig) ([]LogEntry, error) { + var allEntries []LogEntry + + // 确定要搜索的级别目录 + levelsToSearch := []LogLevel{} + if config.Level == -1 { + // 搜索所有级别 + for level := range levelDirs { + levelsToSearch = append(levelsToSearch, level) + } + } else { + levelsToSearch = append(levelsToSearch, config.Level) + } + + for _, level := range levelsToSearch { + levelDir := filepath.Join(l.baseLogDir, levelDirs[level]) + + // 构建文件匹配模式 + pattern := filepath.Join(levelDir, "*.log") + if config.TaskType != "" { + pattern = filepath.Join(levelDir, fmt.Sprintf("%s-%s-*.log", levelStrings[level], config.TaskType)) + } + + files, err := filepath.Glob(pattern) + if err != nil { + continue + } + + // 按文件名排序(通常是时间顺序) + sort.Strings(files) + + for _, file := range files { + entries, err := l.readLogFile(file, config) + if err != nil { + continue + } + allEntries = append(allEntries, entries...) + + // 检查最大条目数 + if config.MaxEntries > 0 && len(allEntries) >= config.MaxEntries { + break + } + } + + if config.MaxEntries > 0 && len(allEntries) >= config.MaxEntries { + break + } + } + + // 按时间排序 + sort.Slice(allEntries, func(i, j int) bool { + return allEntries[i].Time.Before(allEntries[j].Time) + }) + + // 限制返回数量 + if config.MaxEntries > 0 && len(allEntries) > config.MaxEntries { + allEntries = allEntries[:config.MaxEntries] + } + + return allEntries, nil +} + +// ============================ DLL 导出函数 ============================ + +//export GetLogs +func GetLogs(loggerHandle *C.char, configJSON *C.char) *C.char { + handle := C.GoString(loggerHandle) + jsonStr := C.GoString(configJSON) + + globalMu.Lock() + logger, exists := loggers[handle] + globalMu.Unlock() + + if !exists { + return C.CString(`{"error": "无效的logger句柄"}`) + } + + var config ReadLogsConfig + if err := json.Unmarshal([]byte(jsonStr), &config); err != nil { + return C.CString(`{"error": "配置解析错误: ` + err.Error() + `"}`) + } + + // 设置默认值 + if config.MaxEntries == 0 { + config.MaxEntries = 1000 // 默认最大1000条 + } + + entries, err := logger.GetLogs(config) + if err != nil { + return C.CString(`{"error": "读取日志失败: ` + err.Error() + `"}`) + } + + result := map[string]interface{}{ + "count": len(entries), + "entries": entries, + } + + jsonData, err := json.Marshal(result) + if err != nil { + return C.CString(`{"error": "JSON序列化失败: ` + err.Error() + `"}`) + } + + return C.CString(string(jsonData)) +} + +//export GetLogFiles +func GetLogFiles(loggerHandle *C.char) *C.char { + handle := C.GoString(loggerHandle) + + globalMu.Lock() + logger, exists := loggers[handle] + globalMu.Unlock() + + if !exists { + return C.CString(`{"error": "无效的logger句柄"}`) + } + + type LogFileInfo struct { + Level string `json:"level"` + TaskType string `json:"task_type"` + FileName string `json:"file_name"` + FileSize int64 `json:"file_size"` + ModTime string `json:"mod_time"` + } + + var logFiles []LogFileInfo + + for _, levelDirName := range levelDirs { + levelDir := filepath.Join(logger.baseLogDir, levelDirName) + + pattern := filepath.Join(levelDir, "*.log") + files, err := filepath.Glob(pattern) + if err != nil { + continue + } + + for _, file := range files { + info, err := os.Stat(file) + if err != nil { + continue + } + + // 从文件名解析级别和任务类型 + baseName := filepath.Base(file) + parts := strings.Split(strings.TrimSuffix(baseName, ".log"), "-") + if len(parts) >= 3 { + logFile := LogFileInfo{ + Level: parts[0], + TaskType: parts[1], + FileName: baseName, + FileSize: info.Size(), + ModTime: info.ModTime().Format("2006-01-02 15:04:05"), + } + logFiles = append(logFiles, logFile) + } + } + } + + result := map[string]interface{}{ + "count": len(logFiles), + "files": logFiles, + } + + jsonData, err := json.Marshal(result) + if err != nil { + return C.CString(`{"error": "JSON序列化失败: ` + err.Error() + `"}`) + } + + return C.CString(string(jsonData)) +} + +// LOGGER_VERSION 版本号 +const ( + LOGGER_VERSION = "v1" +) + +// 获取版本信息 +// +//export GetVersion +func GetVersion() *C.char { + return C.CString(LOGGER_VERSION) +} + +// main 函数 - DLL必须的入口 +func main() { +} diff --git a/logger/loggerDll.go b/logger/loggerDll.go new file mode 100644 index 0000000..907f5d5 --- /dev/null +++ b/logger/loggerDll.go @@ -0,0 +1,840 @@ +package main + +import "C" +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "runtime" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +// LoggerWrapper 封装DLL调用 +type LoggerWrapper struct { + dllHandle windows.Handle + createLogger uintptr + createContext uintptr + logInfo uintptr + logError uintptr + logWarning uintptr + logSuccess uintptr + freeString uintptr + closeAllLoggers uintptr + // cui + getLogs uintptr + getLogFiles uintptr +} + +// 全局变量来跟踪创建的logger数量 +var ( + createdLoggers []string + loggerCount int +) + +func NewLoggerWrapper(dllPath string) (*LoggerWrapper, error) { + if _, err := os.Stat(dllPath); os.IsNotExist(err) { + return nil, fmt.Errorf("DLL文件不存在: %s", dllPath) + } + + dllHandle, err := windows.LoadLibrary(dllPath) + if err != nil { + return nil, fmt.Errorf("加载DLL失败: %v", err) + } + + wrapper := &LoggerWrapper{ + dllHandle: dllHandle, + } + + if err := wrapper.loadFunctions(); err != nil { + windows.FreeLibrary(dllHandle) + return nil, err + } + return wrapper, nil +} + +func (lw *LoggerWrapper) loadFunctions() error { + var err error + + lw.createLogger, err = windows.GetProcAddress(lw.dllHandle, "CreateLogger") + if err != nil { + return err + } + + lw.createContext, err = windows.GetProcAddress(lw.dllHandle, "CreateContextWithTaskType") + if err != nil { + return err + } + + lw.logInfo, err = windows.GetProcAddress(lw.dllHandle, "LogInfo") + if err != nil { + return err + } + + lw.logError, err = windows.GetProcAddress(lw.dllHandle, "LogError") + if err != nil { + return err + } + + lw.logWarning, err = windows.GetProcAddress(lw.dllHandle, "LogWarning") + if err != nil { + return err + } + + lw.logSuccess, err = windows.GetProcAddress(lw.dllHandle, "LogSuccess") + if err != nil { + return err + } + + lw.freeString, err = windows.GetProcAddress(lw.dllHandle, "FreeString") + if err != nil { + return err + } + + lw.closeAllLoggers, err = windows.GetProcAddress(lw.dllHandle, "CloseAllLoggers") + if err != nil { + return err + } + + // cui + lw.getLogs, err = windows.GetProcAddress(lw.dllHandle, "GetLogs") + if err != nil { + return err + } + + // cui + lw.getLogFiles, err = windows.GetProcAddress(lw.dllHandle, "GetLogFiles") + if err != nil { + return err + } + + return nil +} + +func (lw *LoggerWrapper) CreateLogger(configJSON string) (string, error) { + configCStr, err := syscall.BytePtrFromString(configJSON) + if err != nil { + return "", err + } + + resultPtr, _, err := syscall.SyscallN(lw.createLogger, uintptr(unsafe.Pointer(configCStr))) + + if err != syscall.Errno(0) { + return "", fmt.Errorf("CreateLogger调用失败: %v", err) + } + + if resultPtr == 0 { + return "", fmt.Errorf("CreateLogger返回空指针") + } + + result := readNullTerminatedString(resultPtr) + lw.FreeStringByPtr(resultPtr) + + // 记录创建的logger + createdLoggers = append(createdLoggers, result) + loggerCount++ + + return result, nil +} + +func (lw *LoggerWrapper) CreateContextWithTaskType(loggerHandle, taskType string) (string, error) { + loggerHandleCStr, err := syscall.BytePtrFromString(loggerHandle) + if err != nil { + return "", err + } + + taskTypeCStr, err := syscall.BytePtrFromString(taskType) + if err != nil { + return "", err + } + + resultPtr, _, err := syscall.SyscallN( + lw.createContext, + uintptr(unsafe.Pointer(loggerHandleCStr)), + uintptr(unsafe.Pointer(taskTypeCStr)), + ) + + if err != syscall.Errno(0) { + return "", fmt.Errorf("CreateContextWithTaskType调用失败: %v", err) + } + + if resultPtr == 0 { + return "", fmt.Errorf("CreateContextWithTaskType返回空指针") + } + + result := readNullTerminatedString(resultPtr) + lw.FreeStringByPtr(resultPtr) + return result, nil +} + +func (lw *LoggerWrapper) LogInfo(ctxHandle, message string) error { + return lw.logMessage(ctxHandle, message, lw.logInfo) +} + +func (lw *LoggerWrapper) LogError(ctxHandle, message string) error { + return lw.logMessage(ctxHandle, message, lw.logError) +} + +func (lw *LoggerWrapper) LogWarning(ctxHandle, message string) error { + return lw.logMessage(ctxHandle, message, lw.logWarning) +} + +func (lw *LoggerWrapper) LogSuccess(ctxHandle, message string) error { + return lw.logMessage(ctxHandle, message, lw.logSuccess) +} + +func (lw *LoggerWrapper) logMessage(ctxHandle, message string, logFunc uintptr) error { + ctxHandleCStr, err := syscall.BytePtrFromString(ctxHandle) + if err != nil { + return err + } + + messageCStr, err := syscall.BytePtrFromString(message) + if err != nil { + return err + } + + _, _, err = syscall.SyscallN( + logFunc, + uintptr(unsafe.Pointer(ctxHandleCStr)), + uintptr(unsafe.Pointer(messageCStr)), + ) + + if err != syscall.Errno(0) { + return fmt.Errorf("日志记录失败: %v", err) + } + + return nil +} + +// cui +func (lw *LoggerWrapper) GetLogs(loggerHandle, configJSON string) (string, error) { + loggerHandleCStr, err := syscall.BytePtrFromString(loggerHandle) + if err != nil { + return "", err + } + + configCStr, err := syscall.BytePtrFromString(configJSON) + if err != nil { + return "", err + } + + resultPtr, _, err := syscall.SyscallN( + lw.getLogs, + uintptr(unsafe.Pointer(loggerHandleCStr)), + uintptr(unsafe.Pointer(configCStr)), + ) + + if err != syscall.Errno(0) { + return "", fmt.Errorf("GetLogs调用失败: %v", err) + } + + if resultPtr == 0 { + return "", fmt.Errorf("GetLogs返回空指针") + } + + result := ptrToString(resultPtr) + lw.FreeStringByPtr(resultPtr) + return result, nil +} + +// cui +func (lw *LoggerWrapper) GetLogFiles(loggerHandle string) (string, error) { + loggerHandleCStr, err := syscall.BytePtrFromString(loggerHandle) + if err != nil { + return "", err + } + + resultPtr, _, err := syscall.SyscallN( + lw.getLogFiles, + uintptr(unsafe.Pointer(loggerHandleCStr)), + ) + + if err != syscall.Errno(0) { + return "", fmt.Errorf("GetLogFiles调用失败: %v", err) + } + + if resultPtr == 0 { + return "", fmt.Errorf("GetLogFiles返回空指针") + } + + result := ptrToString(resultPtr) + lw.FreeStringByPtr(resultPtr) + return result, nil +} + +func ptrToString(ptr uintptr) string { + if ptr == 0 { + return "" + } + // 先找到字符串长度 + var length int + for { + b := *(*byte)(unsafe.Pointer(ptr + uintptr(length))) + if b == 0 { + break + } + length++ + if length > 64*1024 { // 64KB限制 + break + } + } + if length == 0 { + return "" + } + // 创建适当大小的切片 + data := make([]byte, length) + for i := 0; i < length; i++ { + data[i] = *(*byte)(unsafe.Pointer(ptr + uintptr(i))) + } + return string(data) +} + +func (lw *LoggerWrapper) FreeStringByPtr(ptr uintptr) { + if ptr != 0 { + syscall.SyscallN(lw.freeString, ptr) + } +} + +func (lw *LoggerWrapper) CloseAllLoggers() (string, error) { + resultPtr, _, err := syscall.SyscallN(lw.closeAllLoggers) + if err != syscall.Errno(0) { + return "", fmt.Errorf("CloseAllLoggers调用失败: %v", err) + } + + if resultPtr == 0 { + return "", fmt.Errorf("CloseAllLoggers返回空指针") + } + + result := readNullTerminatedString(resultPtr) + lw.FreeStringByPtr(resultPtr) + return result, nil +} + +func readNullTerminatedString(ptr uintptr) string { + if ptr == 0 { + return "" + } + + var bytes []byte + for i := 0; i < 256; i++ { + b := *(*byte)(unsafe.Pointer(ptr + uintptr(i))) + if b == 0 { + break + } + bytes = append(bytes, b) + } + return string(bytes) +} + +func (lw *LoggerWrapper) Close() error { + return windows.FreeLibrary(lw.dllHandle) +} + +func checkError(result string) error { + if len(result) >= 6 && result[:6] == "错误:" { + return fmt.Errorf(result) + } + return nil +} + +// 监控内存 +func monitorResources() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("📊 内存使用: Alloc=%.2fMB, TotalAlloc=%.2fMB, Sys=%.2fMB, NumGC=%d, Goroutines=%d\n", + float64(m.Alloc)/1024/1024, + float64(m.TotalAlloc)/1024/1024, + float64(m.Sys)/1024/1024, + m.NumGC, + runtime.NumGoroutine()) +} + +// 测试日志读取功能 cui +func testLogReadingFunctions(wrapper *LoggerWrapper) { + fmt.Println("\n📖 ========== 开始日志读取功能测试 ==========") + + // 创建测试logger + config := `{ + "log_dir": "./test_logs_reading", + "level": 1, + "split_type": 1, + "max_size": 1048576, + "max_count": 5, + "enable_caller": true, + "default_task_type": "reading_test" + }` + + loggerHandle, err := wrapper.CreateLogger(config) + if err != nil { + log.Printf("创建测试logger失败: %v", err) + return + } + fmt.Printf("✅ 创建测试logger: %s\n", loggerHandle) + + // 创建不同任务类型的上下文 + taskTypes := []string{"task_a", "task_b", "task_c"} + ctxHandles := make(map[string]string) + + for _, taskType := range taskTypes { + ctxHandle, err := wrapper.CreateContextWithTaskType(loggerHandle, taskType) + if err != nil { + log.Printf("创建上下文失败: %v", err) + continue + } + ctxHandles[taskType] = ctxHandle + fmt.Printf("✅ 创建上下文: %s -> %s\n", taskType, ctxHandle) + } + + // 记录测试日志 + fmt.Println("\n📝 记录测试日志数据...") + messages := []struct { + level string + message string + task string + }{ + {"INFO", "这是INFO级别的测试消息", "task_a"}, + {"ERROR", "这是ERROR级别的错误消息", "task_b"}, + {"WARNING", "这是WARNING级别的警告消息", "task_c"}, + {"SUCCESS", "这是SUCCESS级别的成功消息", "task_a"}, + {"INFO", "另一条INFO消息", "task_b"}, + } + + for i, msg := range messages { + var err error + switch msg.level { + case "INFO": + err = wrapper.LogInfo(ctxHandles[msg.task], fmt.Sprintf("%s - 序号: %d", msg.message, i)) + case "ERROR": + err = wrapper.LogError(ctxHandles[msg.task], fmt.Sprintf("%s - 序号: %d", msg.message, i)) + case "WARNING": + err = wrapper.LogWarning(ctxHandles[msg.task], fmt.Sprintf("%s - 序号: %d", msg.message, i)) + case "SUCCESS": + err = wrapper.LogSuccess(ctxHandles[msg.task], fmt.Sprintf("%s - 序号: %d", msg.message, i)) + } + if err != nil { + log.Printf("记录日志失败: %v", err) + } else { + fmt.Printf("✅ 记录%s日志: %s\n", msg.level, msg.message) + } + } + + // 等待一下确保日志写入完成 + time.Sleep(100 * time.Millisecond) + + // 测试1: 获取日志文件列表 + fmt.Println("\n📁 测试1: 获取日志文件列表") + filesResult, err := wrapper.GetLogFiles(loggerHandle) + if err != nil { + log.Printf("获取日志文件列表失败: %v", err) + } else { + fmt.Printf("✅ 获取日志文件列表成功:\n%s\n", formatJSON(filesResult)) + } + + // 测试2: 获取所有日志 + fmt.Println("\n📄 测试2: 获取所有日志") + allLogsConfig := `{ + "level": -1, + "max_entries": 50 + }` + allLogsResult, err := wrapper.GetLogs(loggerHandle, allLogsConfig) + if err != nil { + log.Printf("获取所有日志失败: %v", err) + } else { + fmt.Printf("✅ 获取所有日志成功:\n%s\n", formatJSON(allLogsResult)) + } + + // 测试3: 按级别过滤日志 + fmt.Println("\n🔍 测试3: 按级别过滤日志(INFO)") + infoLogsConfig := `{ + "level": 1, + "max_entries": 10 + }` + infoLogsResult, err := wrapper.GetLogs(loggerHandle, infoLogsConfig) + if err != nil { + log.Printf("获取INFO日志失败: %v", err) + } else { + fmt.Printf("✅ 获取INFO日志成功:\n%s\n", formatJSON(infoLogsResult)) + } + + // 测试4: 按任务类型过滤日志 + fmt.Println("\n🔍 测试4: 按任务类型过滤日志(task_a)") + taskALogsConfig := `{ + "level": -1, + "task_type": "task_a", + "max_entries": 10 + }` + taskALogsResult, err := wrapper.GetLogs(loggerHandle, taskALogsConfig) + if err != nil { + log.Printf("获取task_a日志失败: %v", err) + } else { + fmt.Printf("✅ 获取task_a日志成功:\n%s\n", formatJSON(taskALogsResult)) + } + + // 测试5: 限制返回条目数 + fmt.Println("\n🔍 测试5: 限制返回条目数(2条)") + limitedLogsConfig := `{ + "level": -1, + "max_entries": 2 + }` + limitedLogsResult, err := wrapper.GetLogs(loggerHandle, limitedLogsConfig) + if err != nil { + log.Printf("获取限制条目日志失败: %v", err) + } else { + fmt.Printf("✅ 获取限制条目日志成功:\n%s\n", formatJSON(limitedLogsResult)) + } + + // 测试6: 组合条件查询 + fmt.Println("\n🔍 测试6: 组合条件查询(ERROR级别 + task_b)") + combinedConfig := `{ + "level": 3, + "task_type": "task_b", + "max_entries": 10 + }` + combinedResult, err := wrapper.GetLogs(loggerHandle, combinedConfig) + if err != nil { + log.Printf("组合条件查询失败: %v", err) + } else { + fmt.Printf("✅ 组合条件查询成功:\n%s\n", formatJSON(combinedResult)) + } + + // 测试7: 组合条件查询 + fmt.Println("\n🔍 测试7: 组合条件查询(task_b)") + combinedTimeConfig := `{ + "level": -1, + "task_type": "task_b", + "start_time": "2025-11-25 15:03:31", + "end_time": "2025-11-25 15:04:31", + "max_entries": 10 + }` + combinedTimeResult, err := wrapper.GetLogs(loggerHandle, combinedTimeConfig) + if err != nil { + log.Printf("组合条件查询失败: %v", err) + } else { + fmt.Printf("✅ 组合条件查询成功:\n%s\n", formatJSON(combinedTimeResult)) + } + + // 解析并分析结果 + fmt.Println("\n📊 日志读取功能测试分析:") + analyzeLogReadingResults(allLogsResult) + + fmt.Println("✅ 日志读取功能测试完成") +} + +// 分析日志读取结果 cui +func analyzeLogReadingResults(logsResult string) { + var result map[string]interface{} + if err := json.Unmarshal([]byte(logsResult), &result); err != nil { + log.Printf("解析日志结果失败: %v", err) + return + } + + if errorMsg, exists := result["error"]; exists { + fmt.Printf("❌ 错误: %v\n", errorMsg) + return + } + + count, _ := result["count"].(float64) + entries, _ := result["entries"].([]interface{}) + + fmt.Printf(" 总日志条目数: %.0f\n", count) + + // 统计各级别日志数量 + levelCount := make(map[string]int) + taskCount := make(map[string]int) + + for _, entry := range entries { + if entryMap, ok := entry.(map[string]interface{}); ok { + level, _ := entryMap["level"].(string) + taskType, _ := entryMap["task_type"].(string) + levelCount[level]++ + taskCount[taskType]++ + } + } + + fmt.Println(" 日志级别分布:") + for level, count := range levelCount { + fmt.Printf(" - %s: %d条\n", level, count) + } + + fmt.Println(" 任务类型分布:") + for taskType, count := range taskCount { + fmt.Printf(" - %s: %d条\n", taskType, count) + } +} + +// 格式化JSON输出 cui +func formatJSON(jsonStr string) string { + var result map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &result); err != nil { + return jsonStr // 返回原始字符串如果解析失败 + } + + formatted, err := json.MarshalIndent(result, " ", " ") + if err != nil { + return jsonStr + } + return string(formatted) +} + +// 测试资源回收的核心函数 +func testResourceCleanup(wrapper *LoggerWrapper) { + fmt.Println("🧪 开始资源回收验证测试") + + // 测试配置 + config := `{ + "log_dir": "./resource_cleanup_test", + "level": 1, + "split_type": 1, + "max_size": 1048576, + "max_count": 3, + "enable_caller": false, + "default_task_type": "cleanup_test" + }` + + // 阶段1: 创建多个logger并记录初始状态 + fmt.Println("\n📝 阶段1: 创建测试logger") + activeLoggers := make([]string, 0) + idleLoggers := make([]string, 0) + + // 创建6个logger,3个活跃,3个闲置 + for i := 0; i < 6; i++ { + loggerHandle, err := wrapper.CreateLogger(config) + if err != nil { + log.Printf("创建logger %d 失败: %v", i, err) + continue + } + + if i < 3 { + activeLoggers = append(activeLoggers, loggerHandle) + fmt.Printf("✅ 创建活跃logger %d: %s\n", i, loggerHandle) + } else { + idleLoggers = append(idleLoggers, loggerHandle) + fmt.Printf("✅ 创建闲置logger %d: %s\n", i, loggerHandle) + } + } + + fmt.Printf("\n📊 创建统计: 活跃logger=%d, 闲置logger=%d, 总计=%d\n", + len(activeLoggers), len(idleLoggers), len(activeLoggers)+len(idleLoggers)) + + // 阶段2: 使用活跃logger,让闲置logger保持不使用状态 + fmt.Println("\n📝 阶段2: 使用活跃logger") + for i, loggerHandle := range activeLoggers { + ctxHandle, err := wrapper.CreateContextWithTaskType(loggerHandle, fmt.Sprintf("active_task_%d", i)) + if err != nil { + log.Printf("创建上下文失败: %v", err) + continue + } + + // 记录多条日志确保活跃状态 + for j := 0; j < 5; j++ { + message := fmt.Sprintf("活跃logger %d - 消息 %d - 时间: %s", i, j, time.Now().Format("15:04:05")) + err = wrapper.LogInfo(ctxHandle, message) + if err != nil { + log.Printf("记录日志失败: %v", err) + } + } + fmt.Printf("✅ 活跃logger %d 已使用: %s\n", i, loggerHandle) + } + + // 记录当前资源状态 + fmt.Println("\n📊 资源使用前状态:") + monitorResources() + + // 阶段3: 模拟时间流逝并验证资源回收 + fmt.Println("\n⏰ 阶段3: 模拟时间流逝等待资源回收") + fmt.Println(" 自动清理机制: 每30分钟运行一次,清理2小时未使用的资源") + fmt.Println(" 由于测试时间限制,我们将观察资源使用模式...") + + // 多次采样资源使用情况来观察趋势 + fmt.Println("\n📈 资源使用趋势观察:") + initialMemory := getMemoryUsage() + + for i := 0; i < 3; i++ { + fmt.Printf("\n 采样 %d:\n", i+1) + monitorResources() + + // 继续使用活跃logger以保持其活跃状态 + for _, loggerHandle := range activeLoggers { + ctxHandle, err := wrapper.CreateContextWithTaskType(loggerHandle, "keep_alive") + if err == nil { + wrapper.LogInfo(ctxHandle, fmt.Sprintf("保持活跃 - 采样 %d", i+1)) + } + } + + time.Sleep(2 * time.Second) + } + + finalMemory := getMemoryUsage() + memoryChange := finalMemory - initialMemory + + fmt.Printf("\n📊 内存变化: %.2fMB\n", memoryChange) + + // 阶段4: 验证闲置logger的状态 + fmt.Println("\n🔍 阶段4: 验证闲置logger状态") + stillWorkingCount := 0 + for i, loggerHandle := range idleLoggers { + ctxHandle, err := wrapper.CreateContextWithTaskType(loggerHandle, "test_after_idle") + if err != nil { + fmt.Printf("❌ 闲置logger %d 可能已被回收: %s -> %v\n", i, loggerHandle, err) + } else { + // 尝试记录日志 + err = wrapper.LogInfo(ctxHandle, "测试闲置logger是否仍然工作") + if err != nil { + fmt.Printf("❌ 闲置logger %d 记录日志失败: %s -> %v\n", i, loggerHandle, err) + } else { + fmt.Printf("✅ 闲置logger %d 仍然工作: %s\n", i, loggerHandle) + stillWorkingCount++ + } + } + } + + // 阶段5: 验证活跃logger的状态 + fmt.Println("\n🔍 阶段5: 验证活跃logger状态") + activeWorkingCount := 0 + for i, loggerHandle := range activeLoggers { + ctxHandle, err := wrapper.CreateContextWithTaskType(loggerHandle, "test_active") + if err != nil { + fmt.Printf("❌ 活跃logger %d 异常: %s -> %v\n", i, loggerHandle, err) + } else { + err = wrapper.LogInfo(ctxHandle, "测试活跃logger是否正常工作") + if err != nil { + fmt.Printf("❌ 活跃logger %d 记录日志失败: %s -> %v\n", i, loggerHandle, err) + } else { + fmt.Printf("✅ 活跃logger %d 正常工作: %s\n", i, loggerHandle) + activeWorkingCount++ + } + } + } + + // 阶段6: 分析测试结果 + fmt.Println("\n📋 阶段6: 资源回收测试结果分析") + fmt.Printf(" 初始创建: %d 个logger\n", len(activeLoggers)+len(idleLoggers)) + fmt.Printf(" 活跃logger正常: %d/%d\n", activeWorkingCount, len(activeLoggers)) + fmt.Printf(" 闲置logger仍然工作: %d/%d\n", stillWorkingCount, len(idleLoggers)) + fmt.Printf(" 内存变化: %.2fMB\n", memoryChange) + + // 资源回收效果评估 + if stillWorkingCount < len(idleLoggers) { + fmt.Println("🎯 资源回收效果: ✅ 检测到可能的资源回收") + fmt.Println(" 说明: 部分闲置logger无法使用,可能已被自动清理机制回收") + } else { + fmt.Println("🎯 资源回收效果: ⚠️ 未检测到明显的资源回收") + fmt.Println(" 说明: 所有logger仍然可用,可能因为:") + fmt.Println(" - 测试时间不足以触发自动清理") + fmt.Println(" - 自动清理机制配置时间较长") + fmt.Println(" - 所有logger仍被认定为活跃状态") + } + + // 阶段7: 显式清理 + fmt.Println("\n🧹 阶段7: 显式清理所有资源") + result, err := wrapper.CloseAllLoggers() + if err != nil { + log.Printf("关闭所有logger失败: %v", err) + } else { + fmt.Printf("✅ %s\n", result) + } + + // 最终资源状态 + fmt.Println("\n📊 最终资源状态:") + monitorResources() + + fmt.Println("🧪 资源回收验证测试完成") +} + +func getMemoryUsage() float64 { + var m runtime.MemStats + runtime.ReadMemStats(&m) + return float64(m.Alloc) / 1024 / 1024 +} + +// 文件系统资源检查 +func checkFileSystemResources() { + fmt.Println("\n📁 文件系统资源检查:") + + // 检查日志目录 + dirs := []string{"./resource_cleanup_test", "./test_logs"} + for _, dir := range dirs { + if _, err := os.Stat(dir); !os.IsNotExist(err) { + files, _ := filepath.Glob(filepath.Join(dir, "*", "*.log")) + fmt.Printf(" 目录 %s: %d 个日志文件\n", dir, len(files)) + + // 显示文件详情 + for _, file := range files { + info, err := os.Stat(file) + if err == nil { + fmt.Printf(" - %s (大小: %.2fKB, 修改时间: %s)\n", + filepath.Base(file), + float64(info.Size())/1024, + info.ModTime().Format("15:04:05")) + } + } + } else { + fmt.Printf(" 目录 %s: 不存在\n", dir) + } + } +} + +func main() { + config := `{ + "log_dir": "./test_logs_reading", + "level": 1, + "split_type": 1, + "max_size": 1048576, + "max_count": 5, + "enable_caller": true, + "default_task_type": "reading_test" + }` + var configs Config + err2 := json.Unmarshal([]byte(config), &configs) + if err2 != nil { + fmt.Println(err2) + } + logger, err := NewLogger(configs) + if err != nil { + fmt.Println(err) + } + fmt.Println(logger) + + fmt.Println("🚀 开始资源回收验证测试") + + // 确定DLL路径 + dllPath := "logger.dll" + if _, err := os.Stat(dllPath); os.IsNotExist(err) { + log.Fatalf("DLL文件不存在: %s", dllPath) + } + + // 创建包装器 + wrapper, err := NewLoggerWrapper(dllPath) + if err != nil { + log.Fatalf("创建日志包装器失败: %v", err) + } + defer wrapper.Close() + + fmt.Println("✅ DLL加载成功") + + // 初始资源状态 + fmt.Println("\n📈 初始资源状态:") + monitorResources() + + // 运行日志读取功能测试 + testLogReadingFunctions(wrapper) + + // 运行资源回收测试 + testResourceCleanup(wrapper) + + // 检查文件系统资源 + checkFileSystemResources() + + fmt.Println("\n🎉 资源回收验证完成!") + fmt.Println("\n💡 测试说明:") + fmt.Println(" - 本测试创建了活跃和闲置的logger来模拟真实使用场景") + fmt.Println(" - 通过观察闲置logger的可用性来验证自动回收机制") + fmt.Println(" - 内存使用趋势可以帮助识别资源泄漏") + fmt.Println(" - 实际自动清理需要较长时间(2小时未使用)") +} diff --git a/main.go b/main.go index b606fc3..1bee21a 100644 --- a/main.go +++ b/main.go @@ -1,153 +1,1714 @@ package main -import ( - "errors" - "fmt" - "testing" - "time" -) - -// 回调函数的类型定义 -type Callback func(int) int - -// 接受回调函数作为参数的函数 -func process(nums []int, callback Callback) []int { - result := make([]int, len(nums)) - for i, v := range nums { - result[i] = callback(v) // 调用回调函数 - } - return result -} - -// 模拟异步操作 -func fetchData(callback func(string)) { - go func() { - time.Sleep(2 * time.Second) - data := "从服务器获取的数据" - callback(data) // 操作完成后调用回调 - }() -} - -// 事件处理器 -type EventHandler struct { - callbacks map[string][]func(interface{}) -} - -func NewEventHandler() *EventHandler { - return &EventHandler{ - callbacks: make(map[string][]func(interface{})), - } -} - -// 注册事件回调 -func (h *EventHandler) On(event string, callback func(interface{})) { - h.callbacks[event] = append(h.callbacks[event], callback) -} - -// 触发事件 -func (h *EventHandler) Emit(event string, data interface{}) { - if callbacks, exists := h.callbacks[event]; exists { - for _, callback := range callbacks { - callback(data) // 执行所有注册的回调 - } - } -} - -func main() { - //// 定义回调函数 - //square := func(x int) int { - // return x * x - //} - //nums := []int{1, 2, 3, 4, 5} - //result := process(nums, square) - //fmt.Println(result) // [1 4 9 16 25] - - //fmt.Println("开始请求数据...") - // - //fetchData(func(data string) { - // fmt.Println("收到数据:", data) - //}) - // - //time.Sleep(3 * time.Second) // 等待goroutine完成 - - //handler := NewEventHandler() - // - //// 注册点击事件回调 - //handler.On("click", func(data interface{}) { - // fmt.Println("点击事件1:", data) - //}) - // - //handler.On("click", func(data interface{}) { - // fmt.Println("点击事件2:", data) - //}) - // - //// 触发事件 - //handler.Emit("click", "按钮被点击") - - // 协程返回值 - //resultChan := make(chan int, 3) - // - //// 启动多个协程 - //for i := 1; i <= 3; i++ { - // go worker(i, resultChan) - //} - // - //// 收集结果 - //for i := 1; i <= 3; i++ { - // result := <-resultChan - // fmt.Printf("Received result: %d\n", result) - //} - - // 带错误处理的返回值 - values := []int{1, -2, 3, 4} - resultChan := make(chan Result, len(values)) - - for _, v := range values { - go asyncCalculate(v, resultChan) - } - - for range values { - result := <-resultChan - if result.Err != nil { - fmt.Printf("Error: %v\n", result.Err) - } else { - fmt.Printf("Result: %d\n", result.Value) - } - } -} - -type Result struct { - Value int - Err error -} - -func calculate(x int) (int, error) { - if x < 0 { - return 0, errors.New("negative value not allowed") - } - return x * 2, nil -} - -func asyncCalculate(x int, resultChan chan<- Result) { - value, err := calculate(x) - resultChan <- Result{Value: value, Err: err} -} - -func worker(id int, resultChan chan<- int) { - fmt.Printf("Worker %d starting\n", id) - time.Sleep(time.Second) - fmt.Printf("Worker %d done\n", id) - resultChan <- id * 10 // 发送结果到通道 -} - -func TestAdd(t *testing.T) { - result := Add(2, 3) - expected := 5 - if result != expected { - t.Errorf("Add(2, 3) = %d; want %d", result, expected) - } -} - -func Add(a, b int) int { - return a + b -} +//import ( +// "bufio" +// "context" +// "encoding/json" +// "fmt" +// "gopkg.in/yaml.v3" +// "io" +// "io/ioutil" +// "net" +// "net/http" +// "os" +// "os/exec" +// "path/filepath" +// "strings" +// "syscall" +// "time" +// "unsafe" +//) +// +//// 颜色代码常量 +//const ( +// ColorReset = "\033[0m" +// ColorRed = "\033[31m" +// ColorGreen = "\033[32m" +// ColorYellow = "\033[33m" +// ColorBlue = "\033[34m" +// ColorMagenta = "\033[35m" +// ColorCyan = "\033[36m" +// ColorWhite = "\033[37m" +// ColorGray = "\033[90m" +// +// // 加粗 +// ColorBoldRed = "\033[1;31m" +// ColorBoldGreen = "\033[1;32m" +// ColorBoldYellow = "\033[1;33m" +// ColorBoldBlue = "\033[1;34m" +// +// // 背景色 +// ColorBgRed = "\033[41m" +// ColorBgGreen = "\033[42m" +//) +// +//// 定义结构体来映射 YAML 数据 +//type VersionConfig struct { +// CsvVersion string `json:"csv"` +// KongfzVersion string `json:"kongfz"` +// LoggerVersion string `json:"logger"` +// ModuleErpVersion string `json:"module-erp"` +// ModuleKongfzVersion string `json:"module-kongfz"` +// ModuleTaskPoolVersion string `json:"module-taskPool"` +// ModuleVerifyPriceVersion string `json:"module-verifyPrice"` +// ModuleCenterBookVersion string `json:"module-centerBook"` +// ModuleLoginVersion string `json:"module-login"` +// PicToolVersion string `json:"picTool"` +// ProxyVersion string `json:"proxy"` +//} +// +//type VerifyPriceYAML struct { +// VerifyPriceLatestVersion string `yaml:"verifyPriceLatestVersion"` +//} +// +//// 自定义日志函数,添加颜色 +//func printInfo(format string, v ...interface{}) { +// fmt.Printf(ColorCyan+"[信息] "+format+ColorReset+"\n", v...) +//} +// +//func printSuccess(format string, v ...interface{}) { +// fmt.Printf(ColorGreen+"[成功] "+format+ColorReset+"\n", v...) +//} +// +//func printWarning(format string, v ...interface{}) { +// fmt.Printf(ColorYellow+"[警告] "+format+ColorReset+"\n", v...) +//} +// +//func printError(format string, v ...interface{}) { +// fmt.Printf(ColorRed+"[错误] "+format+ColorReset+"\n", v...) +//} +// +//func printDebug(format string, v ...interface{}) { +// fmt.Printf(ColorGray+"[调试] "+format+ColorReset+"\n", v...) +//} +// +//func printBanner(format string, v ...interface{}) { +// fmt.Printf(ColorBoldBlue+"== "+format+" ==\n"+ColorReset, v...) +//} +// +//func printDownload(format string, v ...interface{}) { +// fmt.Printf(ColorBoldGreen+"[下载] "+format+ColorReset+"\n", v...) +//} +// +//func printVersionInfo(label, version string) { +// fmt.Printf(ColorBoldYellow+"%-30s: "+ColorCyan+"%s"+ColorReset+"\n", label, version) +//} +// +//func main() { +// // 打印彩色横幅 +// fmt.Printf(ColorBoldBlue + "================================================\n") +// fmt.Printf(" 核价软件更新器\n") +// fmt.Printf("================================================\n" + ColorReset) +// printWarning("更新时请勿操作!!!") +// // 获取当前工作目录 +// currentDir, err := os.Getwd() +// if err != nil { +// printError("获取当前目录失败: %v", err) +// } +// printInfo("当前目录: %v", currentDir) +// +// // 获取选品中心中verify_price_version_test.json +// printInfo("正在检查核价软件版本...") +// verifyPriceVersionJSON, err := getVerifyPriceVersionJSON() +// if err != nil { +// printError("读取核价软件版本信息失败: %v", err) +// } +// +// // 直接读取yaml文件 +// yamlPath := filepath.Join(currentDir, "version.yaml") +// // 读取 YAML 文件 +// data, err := ioutil.ReadFile(yamlPath) +// if err != nil { +// printError("读取YAML配置文件失败: %v", err) +// } +// +// // 解析 YAML +// var config VerifyPriceYAML +// err = yaml.Unmarshal(data, &config) +// if err != nil { +// printError("解析 YAML 配置失败: %v", err) +// } +// printVersionInfo("当前核价软件版本号", config.VerifyPriceLatestVersion) +// +// // 检查并下载核价软件主程序 +// printInfo("检查核价软件主程序...") +// if _, err := os.Stat(filepath.Join(currentDir, "VerifyPriceApp.exe")); os.IsNotExist(err) { +// printDownload("发现核价软件缺失,开始下载...") +// url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", verifyPriceVersionJSON.VerifyPriceLatestVersion) +// err = downloadEXE(url, currentDir, "VerifyPriceApp.exe") +// if err != nil { +// if err.Error() == "The process cannot access the file because it is being used by another process." { +// printWarning("文件被占用,请重新启动与书同行.exe软件") +// } else { +// printError("下载核价软件失败: %v", err) +// } +// } else { +// printSuccess("核价软件下载完成") +// } +// //修改yaml文件 +// config.VerifyPriceLatestVersion = verifyPriceVersionJSON.VerifyPriceLatestVersion +// err = writeYAML(yamlPath, config) +// if err != nil { +// printError("更新YAML配置文件失败: %v", err) +// } +// } else { +// if verifyPriceVersionJSON.VerifyPriceLatestVersion != config.VerifyPriceLatestVersion || config.VerifyPriceLatestVersion == "" { +// printDownload("发现新版本核价软件,开始更新...") +// url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", verifyPriceVersionJSON.VerifyPriceLatestVersion) +// err = downloadEXE(url, currentDir, "VerifyPriceApp.exe") +// if err != nil { +// if err.Error() == "The process cannot access the file because it is being used by another process." { +// printWarning("文件被占用,请重新启动与书同行.exe软件") +// } else { +// printError("下载核价软件失败: %v", err) +// } +// } else { +// printSuccess("核价软件更新完成") +// } +// //修改yaml文件 +// config.VerifyPriceLatestVersion = verifyPriceVersionJSON.VerifyPriceLatestVersion +// err = writeYAML(yamlPath, config) +// if err != nil { +// printError("更新YAML配置文件失败: %v", err) +// } +// } else { +// printSuccess("核价软件已是最新版本") +// } +// } +// +// // 获取ES2中version.json +// printInfo("正在检查DLL组件版本...") +// versionsJSON, err := getVersionsJSON() +// if err != nil { +// printError("读取DLL版本信息失败: %v", err) +// } +// +// // 打印版本信息标题 +// printBanner("版本信息") +// +// // 打印所有版本信息 +// printVersionInfo("最新核价软件版本", verifyPriceVersionJSON.VerifyPriceLatestVersion) +// printVersionInfo("csv.dll版本号:", versionsJSON.CsvVersion) +// printVersionInfo("kongfz.dll版本号:", versionsJSON.KongfzVersion) +// printVersionInfo("logger.dll版本号:", versionsJSON.LoggerVersion) +// printVersionInfo("module-centerBook.dll版本号:", versionsJSON.ModuleCenterBookVersion) +// printVersionInfo("module-login.dll版本号:", versionsJSON.ModuleLoginVersion) +// printVersionInfo("module-erp.dll版本号:", versionsJSON.ModuleErpVersion) +// printVersionInfo("module-kongfz.dll版本号:", versionsJSON.ModuleKongfzVersion) +// printVersionInfo("module-taskPool.dll版本号:", versionsJSON.ModuleTaskPoolVersion) +// printVersionInfo("module-verifyPrice.dll版本号:", versionsJSON.ModuleVerifyPriceVersion) +// printVersionInfo("picTool.dll版本号:", versionsJSON.PicToolVersion) +// printVersionInfo("proxy.dll版本号:", versionsJSON.ProxyVersion) +// +// // 验证并更新DLL文件 +// printBanner("组件检查与更新") +// +// // 验证csv.dll文件版本 +// printInfo("检查csv.dll...") +// csvVersion, err := csvDllVersion(currentDir) +// if err != nil { +// printWarning("获取csv.dll版本失败: %v", err) +// } +// if versionsJSON.CsvVersion != csvVersion && csvVersion != "" { +// printDownload("csv.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "csv.dll"), "csv.dll", "") +// if err != nil { +// printError("下载csv.dll失败: %v", err) +// } else { +// printSuccess("csv.dll更新完成") +// } +// } else { +// printSuccess("csv.dll已是最新版本") +// } +// +// // 验证kongfz.dll文件版本 +// printInfo("检查kongfz.dll...") +// kongfzVersion, err := kongfzDllVersion(currentDir) +// if err != nil { +// printWarning("获取kongfz.dll版本失败: %v", err) +// } +// if versionsJSON.KongfzVersion != kongfzVersion && kongfzVersion != "" { +// printDownload("kongfz.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "kongfz.dll"), "kongfz.dll", "") +// if err != nil { +// printError("下载kongfz.dll失败: %v", err) +// } else { +// printSuccess("kongfz.dll更新完成") +// } +// } else { +// printSuccess("kongfz.dll已是最新版本") +// } +// +// // 验证logger.dll文件版本 +// printInfo("检查logger.dll...") +// loggerVersion, err := loggerDllVersion(currentDir) +// if err != nil { +// printWarning("获取logger.dll版本失败: %v", err) +// } +// if versionsJSON.LoggerVersion != loggerVersion && loggerVersion != "" { +// printDownload("logger.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "logger.dll"), "logger.dll", "") +// if err != nil { +// printError("下载logger.dll失败: %v", err) +// } else { +// printSuccess("logger.dll更新完成") +// } +// } else { +// printSuccess("logger.dll已是最新版本") +// } +// +// // 验证module-centerBook.dll文件版本 +// printInfo("检查module-centerBook.dll...") +// moduleCenterBookVersion, err := moduleCenterBookDllVersion(currentDir) +// if err != nil { +// printWarning("获取module-centerBook.dll版本失败: %v", err) +// } +// if versionsJSON.ModuleCenterBookVersion != moduleCenterBookVersion && moduleCenterBookVersion != "" { +// printDownload("module-centerBook.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-centerBook.dll"), "module-centerBook.dll", "") +// if err != nil { +// printError("下载module-centerBook.dll失败: %v", err) +// } else { +// printSuccess("module-centerBook.dll更新完成") +// } +// } else { +// printSuccess("module-centerBook.dll已是最新版本") +// } +// +// // 验证module-login.dll文件版本 +// printInfo("检查module-login.dll...") +// moduleLoginVersion, err := moduleLoginDllVersion(currentDir) +// if err != nil { +// printWarning("获取module-login.dll版本失败: %v", err) +// } +// if versionsJSON.ModuleLoginVersion != moduleLoginVersion && moduleLoginVersion != "" { +// printDownload("module-login.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-login.dll"), "module-login.dll", "") +// if err != nil { +// printError("下载module-login.dll失败: %v", err) +// } else { +// printSuccess("module-login.dll更新完成") +// } +// } else { +// printSuccess("module-login.dll已是最新版本") +// } +// +// // 验证module-erp.dll文件版本 +// printInfo("检查module-erp.dll...") +// moduleErpVersion, err := moduleErpDllVersion(currentDir) +// if err != nil { +// printWarning("获取module-erp.dll版本失败: %v", err) +// } +// if versionsJSON.ModuleErpVersion != moduleErpVersion && moduleErpVersion != "" { +// printDownload("module-erp.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-erp.dll"), "module-erp.dll", "") +// if err != nil { +// printError("下载module-erp.dll失败: %v", err) +// } else { +// printSuccess("module-erp.dll更新完成") +// } +// } else { +// printSuccess("module-erp.dll已是最新版本") +// } +// +// // 验证module-kongfz.dll文件版本 +// printInfo("检查module-kongfz.dll...") +// moduleKongfzVersion, err := moduleKongfzDllVersion(currentDir) +// if err != nil { +// printWarning("获取module-kongfz.dll版本失败: %v", err) +// } +// if versionsJSON.ModuleKongfzVersion != moduleKongfzVersion && moduleKongfzVersion != "" { +// printDownload("module-kongfz.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-kongfz.dll"), "module-kongfz.dll", "") +// if err != nil { +// printError("下载module-kongfz.dll失败: %v", err) +// } else { +// printSuccess("module-kongfz.dll更新完成") +// } +// } else { +// printSuccess("module-kongfz.dll已是最新版本") +// } +// +// // 验证module-taskPool.dll文件版本 +// printInfo("检查module-taskPool.dll...") +// moduleTaskPoolVersion, err := moduleTaskPoolDllVersion(currentDir) +// if err != nil { +// printWarning("获取module-taskPool.dll版本失败: %v", err) +// } +// if versionsJSON.ModuleTaskPoolVersion != moduleTaskPoolVersion && moduleTaskPoolVersion != "" { +// printDownload("module-taskPool.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-taskPool.dll"), "module-taskPool.dll", "") +// if err != nil { +// printError("下载module-taskPool.dll失败: %v", err) +// } else { +// printSuccess("module-taskPool.dll更新完成") +// } +// } else { +// printSuccess("module-taskPool.dll已是最新版本") +// } +// +// // 验证module-verifyPrice.dll文件版本 +// printInfo("检查module-verifyPrice.dll...") +// moduleVerifyPriceVersion, err := moduleVerifyPriceDllVersion(currentDir) +// if err != nil { +// printWarning("获取module-verifyPrice.dll版本失败: %v", err) +// } +// if versionsJSON.ModuleVerifyPriceVersion != moduleVerifyPriceVersion && moduleVerifyPriceVersion != "" { +// printDownload("module-verifyPrice.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-verifyPrice.dll"), "module-verifyPrice.dll", "") +// if err != nil { +// printError("下载module-verifyPrice.dll失败: %v", err) +// } else { +// printSuccess("module-verifyPrice.dll更新完成") +// } +// } else { +// printSuccess("module-verifyPrice.dll已是最新版本") +// } +// +// // 验证picTool.dll文件版本 +// printInfo("检查picTool.dll...") +// picToolVersion, err := picToolDllVersion(currentDir) +// if err != nil { +// printWarning("获取picTool.dll版本失败: %v", err) +// } +// if versionsJSON.PicToolVersion != picToolVersion && picToolVersion != "" { +// printDownload("picTool.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "picTool.dll"), "picTool.dll", "") +// if err != nil { +// printError("下载picTool.dll失败: %v", err) +// } else { +// printSuccess("picTool.dll更新完成") +// } +// } else { +// printSuccess("picTool.dll已是最新版本") +// } +// +// // 验证proxy.dll文件版本 +// printInfo("检查proxy.dll...") +// proxyVersion, err := proxyDllVersion(currentDir) +// if err != nil { +// printWarning("获取proxy.dll版本失败: %v", err) +// } +// if versionsJSON.ProxyVersion != proxyVersion && proxyVersion != "" { +// printDownload("proxy.dll需要更新") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "proxy.dll"), "proxy.dll", "") +// if err != nil { +// printError("下载proxy.dll失败: %v", err) +// } else { +// printSuccess("proxy.dll更新完成") +// } +// } else { +// printSuccess("proxy.dll已是最新版本") +// } +// +// printWarning("更新时请勿操作!!!出现:立即按回车键打开核价软件,可以点击 回车键") +// +// // 启动提示 +// printBanner("启动核价软件") +// fmt.Printf("\n" + ColorBoldGreen + "立即按回车键打开核价软件..." + ColorReset) +// fmt.Printf(ColorGray + "(或30秒后自动打开)\n" + ColorReset) +// +// // 创建带超时的输入读取器 +// inputCh := make(chan bool, 1) +// +// // 启动一个goroutine等待用户输入 +// go func() { +// bufio.NewReader(os.Stdin).ReadBytes('\n') +// inputCh <- true +// }() +// // 设置30秒超时 +// timeout := 30 * time.Second +// +// select { +// case <-inputCh: +// printSuccess("用户按下了回车键,立即打开核价软件") +// case <-time.After(timeout): +// printWarning("等待超时(30秒),自动打开核价软件") +// } +// +// // 尝试打开 VerifyPriceApp.exe +// verifyPriceAppPath := filepath.Join(currentDir, "VerifyPriceApp.exe") +// printInfo("正在启动核价软件: %s", verifyPriceAppPath) +// +// // 检查文件是否存在 --- TODO 存在问题,有可能打不开 VerifyPriceApp.exe +// if _, err := os.Stat(verifyPriceAppPath); os.IsNotExist(err) { +// printError("核价软件主程序不存在: %s", verifyPriceAppPath) +// } else { +// // 启动 VerifyPriceApp.exe +// cmd := exec.Command(verifyPriceAppPath) +// +// // 设置工作目录为当前目录 +// cmd.Dir = currentDir +// +// // 启动进程(不等待它完成) +// err := cmd.Start() +// if err != nil { +// printError("启动核价软件失败: %v", err) +// } else { +// printSuccess("核价软件已启动 (PID: %d)", cmd.Process.Pid) +// +// // 给新进程一点时间启动(可选) +// time.Sleep(100 * time.Millisecond) +// } +// } +// +// printBanner("程序执行完毕") +//} +// +//// 将结构体写回YAML文件 +//func writeYAML(filePath string, config VerifyPriceYAML) error { +// // 将结构体序列化为YAML +// yamlData, err := yaml.Marshal(config) +// if err != nil { +// return fmt.Errorf("序列化YAML失败: %v", err) +// } +// +// // 创建备份文件(可选) +// backupFilePath := filePath + ".bak" +// err = backupFile(filePath, backupFilePath) +// if err != nil { +// printDebug("创建备份文件失败: %v", err) +// } +// +// // 写回文件 +// err = ioutil.WriteFile(filePath, yamlData, 0755) +// if err != nil { +// return fmt.Errorf("写入文件失败: %v", err) +// } +// +// return nil +//} +// +//// 备份原文件 +//func backupFile(originalPath, backupPath string) error { +// // 读取原文件 +// data, err := ioutil.ReadFile(originalPath) +// if err != nil { +// return err +// } +// +// // 写入备份文件 +// return ioutil.WriteFile(backupPath, data, 0755) +//} +// +//func downloadEXE(url, folderPath, filename string) error { +// // 确保文件夹存在 +// if err := os.MkdirAll(folderPath, 0755); err != nil { +// return fmt.Errorf("创建文件夹失败: %v", err) +// } +// +// // 拼接完整文件路径 +// filePath := filepath.Join(folderPath, filename) +// +// printDebug("下载到: %s", filePath) +// +// // 发送HTTP请求 +// resp, err := http.Get(url) +// if err != nil { +// return fmt.Errorf("请求失败: %v", err) +// } +// defer resp.Body.Close() +// +// // 检查响应状态 +// if resp.StatusCode != http.StatusOK { +// return fmt.Errorf("下载失败,状态码: %d", resp.StatusCode) +// } +// +// // 创建文件 +// file, err := os.Create(filePath) +// if err != nil { +// return fmt.Errorf("创建文件失败: %v", err) +// } +// defer file.Close() +// +// // 下载文件 +// _, err = io.Copy(file, resp.Body) +// if err != nil { +// return fmt.Errorf("写入文件失败: %v", err) +// } +// +// return nil +//} +// +//// VersionInfo 定义 JSON 结构体 +//type VersionInfo struct { +// LatestVersion string `json:"latestVersion"` +// HistoricalVersions []string `json:"historicalVersions"` +// VerifyPriceLatestVersion string `json:"verifyPriceLatestVersion"` +//} +// +//// 读取选品中心中verify_price_version_test.json文件 +//func getVerifyPriceVersionJSON() (*VersionInfo, error) { +// // 目标 URL +// url := "https://newverifyprice.buzhiyushu.cn/verify_price_version.json" +// +// // 创建 HTTP 客户端,设置超时时间 +// client := &http.Client{ +// Timeout: 30 * time.Second, +// } +// +// // 发送 GET 请求 +// resp, err := client.Get(url) +// if err != nil { +// return nil, fmt.Errorf("请求失败: %v", err) +// } +// defer resp.Body.Close() +// +// // 检查响应状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP 请求失败,状态码: %d", resp.StatusCode) +// } +// +// // 读取响应体 +// body, err := io.ReadAll(resp.Body) +// if err != nil { +// return nil, fmt.Errorf("读取响应失败: %v", err) +// } +// +// // 解析 JSON 数据 +// var versionInfo VersionInfo +// err = json.Unmarshal(body, &versionInfo) +// if err != nil { +// return nil, fmt.Errorf("解析 JSON 失败: %v", err) +// } +// return &versionInfo, nil +//} +// +//// 读取ES2中versions.json文件 +//func getVersionsJSON() (*VersionConfig, error) { +// // 目标 URL +// url := "http://36.212.20.113:53300/version.json" +// +// // 创建 HTTP 客户端,设置超时时间 +// client := &http.Client{ +// Timeout: 30 * time.Second, +// } +// +// // 发送 GET 请求 +// resp, err := client.Get(url) +// if err != nil { +// return nil, fmt.Errorf("请求失败: %v", err) +// } +// defer resp.Body.Close() +// +// // 检查响应状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP 请求失败,状态码: %d", resp.StatusCode) +// } +// +// // 读取响应体 +// body, err := io.ReadAll(resp.Body) +// if err != nil { +// return nil, fmt.Errorf("读取响应失败: %v", err) +// } +// +// // 解析 JSON 数据 +// var versionConfig VersionConfig +// err = json.Unmarshal(body, &versionConfig) +// if err != nil { +// return nil, fmt.Errorf("解析 JSON 失败: %v", err) +// } +// return &versionConfig, nil +//} +// +//// cStr 将 C 字符串指针转换为 Go 字符串 +//func cStr(ptr uintptr) string { +// if ptr == 0 { +// return "" +// } +// var b []byte +// for { +// c := *(*byte)(unsafe.Pointer(ptr)) +// if c == 0 { +// break +// } +// b = append(b, c) +// ptr++ +// } +// return string(b) +//} +// +//// 查询csv.dll版本号 +//func csvDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "csv.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("csv.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "csv.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 csv.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载csv.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找csv.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用csv.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询kongfz.dll版本号 +//func kongfzDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "kongfz.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("kongfz.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "kongfz.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 kongfz.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载kongfz.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找kongfz.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用kongfz.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询logger.dll版本号 +//func loggerDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "logger.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("logger.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "logger.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 logger.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载logger.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找logger.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用logger.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询module_centerBook.dll版本号 +//func moduleCenterBookDllVersion(currentDir string) (string, error) { +// // 方案1:使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "module-centerBook.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("module-centerBook.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-centerBook.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 module-centerBook.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载module-centerBook.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找module-centerBook.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用module-centerBook.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询module_login.dll版本号 +//func moduleLoginDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "module-login.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("module-login.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-login.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 module-login.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载module-login.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找module-login.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用module-login.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询module-erp.dll版本号 +//func moduleErpDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "module-erp.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("module-erp.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-erp.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 module-erp.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载module-erp.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找module-erp.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用module-erp.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询module-kongfz.dll版本号 +//func moduleKongfzDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "module-kongfz.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("module-kongfz.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-kongfz.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 module-kongfz.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载module-kongfz.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找module-kongfz.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用module-kongfz.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询module-taskPool.dll版本号 +//func moduleTaskPoolDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "module-taskPool.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("module-taskPool.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-taskPool.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 module-taskPool.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载module-taskPool.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找module-taskPool.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用module-taskPool.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询module-verifyPrice.dll版本号 +//func moduleVerifyPriceDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "module-verifyPrice.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("module-verifyPrice.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-verifyPrice.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 module-verifyPrice.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载module-verifyPrice.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找module-verifyPrice.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用module-verifyPrice.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询picTool.dll版本号 +//func picToolDllVersion(currentDir string) (string, error) { +// // 使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "picTool.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("picTool.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "picTool.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 picTool.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载picTool.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" // 你的函数名 +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找picTool.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用picTool.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +//// 查询proxy.dll版本号 +//func proxyDllVersion(currentDir string) (string, error) { +// // 方案1:使用绝对路径 +// dllPath := filepath.Join(currentDir, "dll", "proxy.dll") +// +// // 判断dll文件是否存在,不存在下载新版本 +// _, err := os.Stat(dllPath) +// if err != nil { +// if os.IsNotExist(err) { +// printDownload("proxy.dll文件缺失,开始下载...") +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "proxy.dll", "") +// if err != nil { +// return "", fmt.Errorf("下载 proxy.dll 文件失败: %v", err) +// } +// return "", nil +// } +// } +// +// // 加载DLL +// dll, err := syscall.LoadDLL(dllPath) +// if err != nil { +// return "", fmt.Errorf("加载proxy.dll文件失败: %v", err) +// } +// +// // 查找函数 +// funcName := "GetVersion" // 你的函数名 +// proc, err := dll.FindProc(funcName) +// if err != nil { +// return "", fmt.Errorf("查找proxy.dll GetVersion 函数失败: %v", err) +// } +// +// // 调用函数(无参数版本) +// ret, _, err := proc.Call() +// if err != nil && err.Error() != "The operation completed successfully." { +// return "", fmt.Errorf("调用proxy.dll GetVersion 函数失败: %v", err) +// } +// str := cStr(ret) +// // 释放C字符串 +// if proc, err = dll.FindProc("FreeCString"); err == nil { +// defer proc.Call(ret) +// } +// return str, nil +//} +// +////func downloadDLL(url, outputPath, filename, version string) error { +//// // 1. 确保目录存在 +//// dir := filepath.Dir(outputPath) +//// if err := os.MkdirAll(dir, 0755); err != nil { +//// return fmt.Errorf("创建目录失败: %v", err) +//// } +//// +//// // 2. 创建临时文件路径(避免直接操作目标文件) +//// tempPath := outputPath + ".tmp" +//// +//// // 清理可能存在的旧临时文件 +//// if _, err := os.Stat(tempPath); err == nil { +//// os.Remove(tempPath) +//// } +//// +//// // 3. 检查目标文件是否存在 +//// if _, err := os.Stat(outputPath); err == nil { +//// // 尝试重命名原文件(而不是直接删除) +//// backupPath := outputPath + ".bak" +//// if err := os.Rename(outputPath, backupPath); err != nil { +//// // 重命名失败,尝试直接删除 +//// for i := 0; i < 3; i++ { // 重试3次 +//// if err := os.Remove(outputPath); err == nil { +//// break +//// } +//// if i == 2 { // 最后一次尝试 +//// if strings.Contains(err.Error(), "Access is denied") { +//// return fmt.Errorf("文件被其他程序占用,请关闭可能使用此文件的程序后再试: %s", outputPath) +//// } +//// return fmt.Errorf("无法删除已存在的文件: %v", err) +//// } +//// time.Sleep(1 * time.Second) +//// } +//// } else { +//// // 异步清理备份文件 +//// go func() { +//// time.Sleep(30 * time.Second) // 30秒后清理 +//// if _, err := os.Stat(backupPath); err == nil { +//// os.Remove(backupPath) +//// } +//// }() +//// } +//// } +//// +//// // 4. 创建HTTP客户端 +//// client := &http.Client{ +//// Timeout: 30 * time.Minute, +//// } +//// +//// // 5. 发送GET请求下载文件 +//// // 注意:这里先打印下载开始信息,这行会在主函数打印版本号信息之后立即执行 +//// if version == "" { +//// printDownload("正在下载: %s (版本: %s)", filename, "最新版本") +//// } else { +//// printDownload("正在下载: %s (版本: %s)", filename, version) +//// } +//// +//// // 添加查询参数 +//// req, err := http.NewRequest("GET", url, nil) +//// if err != nil { +//// return fmt.Errorf("创建请求失败: %v", err) +//// } +//// +//// q := req.URL.Query() +//// q.Add("filename", filename) +//// if version != "" { +//// q.Add("version", version) +//// } +//// req.URL.RawQuery = q.Encode() +//// +//// resp, err := client.Do(req) +//// if err != nil { +//// return fmt.Errorf("请求失败: %v", err) +//// } +//// defer resp.Body.Close() +//// +//// if resp.StatusCode != http.StatusOK { +//// return fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode) +//// } +//// +//// // 6. 获取文件大小 +//// totalSize := resp.ContentLength +//// if totalSize > 0 { +//// fmt.Printf(ColorGray+"文件大小: %.2f MB\n"+ColorReset, float64(totalSize)/1024/1024) +//// } +//// +//// // 7. 创建临时文件 +//// out, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) +//// if err != nil { +//// return fmt.Errorf("创建临时文件失败: %v", err) +//// } +//// defer out.Close() +//// +//// // 8. 确保下载失败时清理临时文件 +//// downloadSuccess := false +//// defer func() { +//// if !downloadSuccess { +//// os.Remove(tempPath) +//// } +//// }() +//// +//// // 9. 手动进度显示 +//// startTime := time.Now() +//// var downloaded int64 = 0 +//// var lastUpdate time.Time +//// lastUpdate = time.Now() +//// +//// // 10. 创建缓冲区读取 +//// buf := make([]byte, 32*1024) +//// for { +//// n, err := resp.Body.Read(buf) +//// if n > 0 { +//// // 写入临时文件 +//// if _, writeErr := out.Write(buf[:n]); writeErr != nil { +//// return fmt.Errorf("写入文件失败: %v", writeErr) +//// } +//// +//// // 更新下载量 +//// downloaded += int64(n) +//// +//// // 每500ms更新一次进度 +//// if time.Since(lastUpdate) > 500*time.Millisecond || err != nil { +//// if totalSize > 0 { +//// percent := float64(downloaded) / float64(totalSize) * 100 +//// fmt.Printf("\r"+ColorCyan+"下载进度: %.2f%% (%.2f/%.2f MB)"+ColorReset, +//// percent, +//// float64(downloaded)/1024/1024, +//// float64(totalSize)/1024/1024) +//// } else { +//// fmt.Printf("\r"+ColorCyan+"已下载: %.2f MB"+ColorReset, float64(downloaded)/1024/1024) +//// } +//// lastUpdate = time.Now() +//// } +//// } +//// +//// if err != nil { +//// if err != io.EOF { +//// return fmt.Errorf("读取失败: %v", err) +//// } +//// break +//// } +//// } +//// +//// // 下载完成,确保进度显示100% +//// if totalSize > 0 && downloaded < totalSize { +//// downloaded = totalSize +//// } +//// fmt.Printf("\r"+ColorGreen+"下载进度: 100.00%% (%.2f/%.2f MB)\n"+ColorReset, +//// float64(downloaded)/1024/1024, +//// float64(totalSize)/1024/1024) +//// +//// // 11. 关闭临时文件 +//// out.Close() +//// +//// // 12. 验证下载的文件大小 +//// if totalSize > 0 { +//// if fi, err := os.Stat(tempPath); err == nil { +//// if fi.Size() != totalSize { +//// return fmt.Errorf("文件大小不匹配: 期望 %d, 实际 %d", totalSize, fi.Size()) +//// } +//// } +//// } +//// +//// // 13. 将临时文件重命名为目标文件 +//// for i := 0; i < 3; i++ { +//// if err := os.Rename(tempPath, outputPath); err == nil { +//// downloadSuccess = true +//// break +//// } +//// +//// if i == 2 { +//// // 尝试直接复制 +//// if err := copyFile(tempPath, outputPath); err != nil { +//// return fmt.Errorf("保存文件失败: %v", err) +//// } +//// downloadSuccess = true +//// } +//// +//// time.Sleep(500 * time.Millisecond) +//// } +//// +//// elapsed := time.Since(startTime) +//// printSuccess("下载完成: %s (耗时: %v)", filepath.Base(outputPath), elapsed.Round(time.Second)) +//// +//// // 14. 验证最终文件 +//// if _, err := os.Stat(outputPath); err != nil { +//// return fmt.Errorf("最终文件验证失败: %v", err) +//// } +//// +//// return nil +////} +// +//// 主下载函数 +//func downloadDLL(url, outputPath, filename, version string) error { +// // 1. 确保目录存在 +// dir := filepath.Dir(outputPath) +// if err := os.MkdirAll(dir, 0755); err != nil { +// return fmt.Errorf("创建目录失败: %v", err) +// } +// +// // 2. 创建临时文件路径 +// tempPath := outputPath + ".tmp" +// +// // 3. 清理可能存在的旧临时文件 +// if _, err := os.Stat(tempPath); err == nil { +// os.Remove(tempPath) +// } +// +// // 4. 检查目标文件是否存在 +// if _, err := os.Stat(outputPath); err == nil { +// backupPath := outputPath + ".bak" +// if err := os.Rename(outputPath, backupPath); err != nil { +// // 重命名失败,尝试直接删除 +// for i := 0; i < 3; i++ { +// if err := os.Remove(outputPath); err == nil { +// break +// } +// if i == 2 { +// if strings.Contains(err.Error(), "Access is denied") { +// return fmt.Errorf("文件被其他程序占用,请关闭可能使用此文件的程序后再试: %s", outputPath) +// } +// return fmt.Errorf("无法删除已存在的文件: %v", err) +// } +// time.Sleep(1 * time.Second) +// } +// } else { +// // 异步清理备份文件 +// go func() { +// time.Sleep(30 * time.Second) +// if _, err := os.Stat(backupPath); err == nil { +// os.Remove(backupPath) +// } +// }() +// } +// } +// +// // 5. 打印下载信息 +// if version == "" { +// printDownload("正在下载: %s (版本: 最新版本)", filename) +// } else { +// printDownload("正在下载: %s (版本: %s)", filename, version) +// } +// +// // 6. 首先尝试获取文件大小信息(HEAD请求) +// headClient := &http.Client{ +// Timeout: 10 * time.Second, +// } +// +// headReq, err := http.NewRequest("HEAD", url, nil) +// if err != nil { +// printError("创建HEAD请求失败,使用默认超时设置: %v", err) +// } else { +// // 添加查询参数 +// q := headReq.URL.Query() +// q.Add("filename", filename) +// if version != "" { +// q.Add("version", version) +// } +// headReq.URL.RawQuery = q.Encode() +// headReq.Header.Set("User-Agent", "DLL-Downloader/1.0") +// +// resp, err := headClient.Do(headReq) +// if err == nil && resp.StatusCode == http.StatusOK { +// if contentLength := resp.ContentLength; contentLength > 0 { +// fmt.Printf(ColorGray+"获取到文件大小: %.2f MB\n"+ColorReset, +// float64(contentLength)/1024/1024) +// } +// resp.Body.Close() +// } +// } +// +// // 7. 创建长时间下载的HTTP客户端 +// // 使用自定义Transport,不设置Client.Timeout,改用context控制 +// transport := &http.Transport{ +// Proxy: http.ProxyFromEnvironment, +// DialContext: (&net.Dialer{ +// Timeout: 30 * time.Second, +// KeepAlive: 30 * time.Second, +// DualStack: true, +// }).DialContext, +// ForceAttemptHTTP2: true, +// MaxIdleConns: 100, +// MaxIdleConnsPerHost: 10, +// IdleConnTimeout: 90 * time.Second, +// TLSHandshakeTimeout: 15 * time.Second, +// ExpectContinueTimeout: 5 * time.Second, +// ResponseHeaderTimeout: 30 * time.Second, +// ReadBufferSize: 128 * 1024, // 128KB +// WriteBufferSize: 128 * 1024, +// // 禁用读写超时,通过活动检测控制 +// } +// +// client := &http.Client{ +// Transport: transport, +// // 重要:不设置Timeout,使用context控制 +// } +// +// // 8. 创建下载请求 +// // 使用3小时的context超时,但通过活动检测防止死连接 +// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Hour) +// defer cancel() +// +// req, err := http.NewRequestWithContext(ctx, "GET", url, nil) +// if err != nil { +// return fmt.Errorf("创建请求失败: %v", err) +// } +// +// // 添加查询参数 +// q := req.URL.Query() +// q.Add("filename", filename) +// if version != "" { +// q.Add("version", version) +// } +// req.URL.RawQuery = q.Encode() +// +// // 添加请求头 +// req.Header.Set("User-Agent", "DLL-Downloader/1.0") +// req.Header.Set("Accept", "*/*") +// req.Header.Set("Accept-Encoding", "gzip, deflate") +// req.Header.Set("Connection", "keep-alive") +// +// // 9. 发送请求 +// resp, err := client.Do(req) +// if err != nil { +// if strings.Contains(err.Error(), "context deadline exceeded") { +// return fmt.Errorf("连接超时,请检查网络连接: %v", err) +// } +// return fmt.Errorf("请求失败: %v", err) +// } +// defer resp.Body.Close() +// +// if resp.StatusCode != http.StatusOK { +// body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) +// return fmt.Errorf("服务器返回错误: %d - %s", resp.StatusCode, string(body)) +// } +// +// // 10. 获取文件大小 +// totalSize := resp.ContentLength +// if totalSize > 0 { +// fmt.Printf(ColorGray+"文件大小: %.2f MB\n"+ColorReset, float64(totalSize)/1024/1024) +// +// // 根据文件大小提供预估时间 +// // 假设最低速度 50KB/s +// minSpeed := 50.0 * 1024 +// estimatedTime := time.Duration(float64(totalSize)/minSpeed) * time.Second +// if estimatedTime > 30*time.Second { +// fmt.Printf(ColorGray+"预估下载时间: %v (基于50KB/s最低速度)\n"+ColorReset, +// estimatedTime.Round(time.Second)) +// } +// } else { +// fmt.Println(ColorGray + "文件大小: 未知" + ColorReset) +// } +// +// // 11. 创建临时文件 +// out, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) +// if err != nil { +// return fmt.Errorf("创建临时文件失败: %v", err) +// } +// defer out.Close() +// +// // 12. 确保下载失败时清理临时文件 +// downloadSuccess := false +// defer func() { +// if !downloadSuccess { +// os.Remove(tempPath) +// } +// }() +// +// // 13. 活动检测机制 +// activityCh := make(chan time.Time, 100) // 缓冲通道,避免阻塞 +// var lastActivity time.Time +// lastActivity = time.Now() +// +// // 创建活动检测的reader +// activityReader := &activityReader{ +// reader: resp.Body, +// activityCh: activityCh, +// activityPtr: &lastActivity, +// } +// +// // 启动活动检测goroutine +// activityCtx, activityCancel := context.WithCancel(context.Background()) +// defer activityCancel() +// +// go func() { +// ticker := time.NewTicker(30 * time.Second) // 每30秒检查一次活动 +// defer ticker.Stop() +// +// for { +// select { +// case <-activityCtx.Done(): +// return +// case activityTime := <-activityCh: +// lastActivity = activityTime +// case <-ticker.C: +// // 检查最近60秒内是否有活动 +// if time.Since(lastActivity) > 60*time.Second { +// printError("下载活动超时,60秒内没有收到数据") +// cancel() // 取消主context +// return +// } +// } +// } +// }() +// +// // 14. 下载进度显示 +// startTime := time.Now() +// var downloaded int64 = 0 +// var lastUpdate time.Time +// lastUpdate = time.Now() +// +// // 15. 创建缓冲区 +// buf := make([]byte, 128*1024) // 128KB缓冲区 +// +// // 显示初始进度 +// fmt.Print(ColorCyan + "下载进度: 0.00% (0.00/0.00 MB)" + ColorReset) +// +// for { +// // 检查context是否被取消 +// select { +// case <-ctx.Done(): +// return fmt.Errorf("下载被取消: %v", ctx.Err()) +// default: +// // 继续下载 +// } +// +// n, err := activityReader.Read(buf) +// if n > 0 { +// // 写入临时文件 +// if _, writeErr := out.Write(buf[:n]); writeErr != nil { +// return fmt.Errorf("写入文件失败: %v", writeErr) +// } +// +// // 更新下载量 +// downloaded += int64(n) +// +// // 更新进度显示(每秒最多更新一次) +// if time.Since(lastUpdate) > 1*time.Second || err != nil { +// if totalSize > 0 { +// percent := float64(downloaded) / float64(totalSize) * 100 +// elapsed := time.Since(startTime).Seconds() +// speed := 0.0 +// if elapsed > 0 { +// speed = float64(downloaded) / elapsed / 1024 / 1024 +// } +// +// // 计算剩余时间 +// remaining := time.Duration(0) +// if speed > 0 && downloaded > 0 { +// remainingSecs := float64(totalSize-downloaded) / (float64(downloaded) / elapsed) +// remaining = time.Duration(remainingSecs) * time.Second +// } +// +// // 清除当前行并显示进度 +// fmt.Printf("\r\033[K"+ColorCyan+"下载进度: %.2f%% (%.2f/%.2f MB) 速度: %.2f MB/s 剩余: %v"+ColorReset, +// percent, +// float64(downloaded)/1024/1024, +// float64(totalSize)/1024/1024, +// speed, +// remaining.Round(time.Second)) +// } else { +// // 未知文件大小的情况 +// elapsed := time.Since(startTime).Seconds() +// speed := 0.0 +// if elapsed > 0 { +// speed = float64(downloaded) / elapsed / 1024 / 1024 +// } +// fmt.Printf("\r\033[K"+ColorCyan+"已下载: %.2f MB 速度: %.2f MB/s"+ColorReset, +// float64(downloaded)/1024/1024, +// speed) +// } +// lastUpdate = time.Now() +// } +// } +// +// if err != nil { +// if err == io.EOF { +// break +// } +// // 检查是否是context取消的错误 +// if ctx.Err() != nil { +// return fmt.Errorf("下载中断: %v", ctx.Err()) +// } +// return fmt.Errorf("读取数据失败: %v", err) +// } +// } +// +// // 16. 下载完成,显示最终进度 +// if totalSize > 0 && downloaded < totalSize { +// downloaded = totalSize // 确保显示100% +// } +// +// if totalSize > 0 { +// fmt.Printf("\r\033[K"+ColorGreen+"下载完成: 100.00%% (%.2f/%.2f MB)\n"+ColorReset, +// float64(downloaded)/1024/1024, +// float64(totalSize)/1024/1024) +// } else { +// fmt.Printf("\r\033[K"+ColorGreen+"下载完成: %.2f MB\n"+ColorReset, +// float64(downloaded)/1024/1024) +// } +// +// // 停止活动检测 +// activityCancel() +// +// // 17. 验证下载的文件大小 +// if totalSize > 0 { +// fi, err := os.Stat(tempPath) +// if err != nil { +// return fmt.Errorf("无法验证下载文件: %v", err) +// } +// if fi.Size() != totalSize { +// return fmt.Errorf("文件大小不匹配: 期望 %d 字节, 实际 %d 字节", totalSize, fi.Size()) +// } +// } +// +// // 18. 将临时文件重命名为目标文件 +// maxRetries := 5 +// for i := 0; i < maxRetries; i++ { +// if err := os.Rename(tempPath, outputPath); err == nil { +// downloadSuccess = true +// break +// } +// +// if i == maxRetries-1 { +// // 最后一次尝试:使用复制方式 +// if err := copyFile(tempPath, outputPath); err != nil { +// return fmt.Errorf("保存文件失败: %v", err) +// } +// downloadSuccess = true +// } else { +// // 等待后重试 +// time.Sleep(500 * time.Millisecond * time.Duration(i+1)) +// } +// } +// +// // 19. 验证最终文件 +// fi, err := os.Stat(outputPath) +// if err != nil { +// return fmt.Errorf("最终文件验证失败: %v", err) +// } +// +// elapsed := time.Since(startTime) +// speed := float64(fi.Size()) / elapsed.Seconds() / 1024 / 1024 +// printSuccess("下载完成: %s (大小: %.2f MB, 耗时: %v, 平均速度: %.2f MB/s)", +// filepath.Base(outputPath), +// float64(fi.Size())/1024/1024, +// elapsed.Round(time.Second), +// speed) +// +// return nil +//} +// +//// 活动检测读取器 +//type activityReader struct { +// reader io.Reader +// activityCh chan<- time.Time +// activityPtr *time.Time +//} +// +//func (r *activityReader) Read(p []byte) (n int, err error) { +// n, err = r.reader.Read(p) +// if n > 0 { +// now := time.Now() +// r.activityCh <- now +// if r.activityPtr != nil { +// *r.activityPtr = now +// } +// } +// return +//} +// +//// 辅助函数:复制文件 +//func copyFile(src, dst string) error { +// source, err := os.Open(src) +// if err != nil { +// return err +// } +// defer source.Close() +// +// destination, err := os.Create(dst) +// if err != nil { +// return err +// } +// defer destination.Close() +// +// _, err = io.Copy(destination, source) +// return err +//} +// +//// 辅助函数:检查文件是否被占用 +//func isFileLocked(filepath string) bool { +// file, err := os.OpenFile(filepath, os.O_RDWR, 0666) +// if err != nil { +// return strings.Contains(err.Error(), "The process cannot access the file") +// } +// file.Close() +// return false +//} diff --git a/md/config.md b/md/config.md new file mode 100644 index 0000000..3807f5a --- /dev/null +++ b/md/config.md @@ -0,0 +1,156 @@ +# config.dll 使用教程 +## 1.创建DLL工具实例 +### 加载DLL文件 +```gotemplate +// ConfigDLL 配置文件读取DLL结构 +type ConfigDLL struct { + dll *syscall.DLL + readConfigFile *syscall.Proc // 读取配置文件 + getVersion *syscall.Proc // 获取版本信息 + freeCString *syscall.Proc // 释放C字符串 +} + +// 初始化ConfigDLL +func InitConfigDLL() (*ConfigDLL, error) { + dllPath := filepath.Join("dll", "config.dll") + if _, err := os.Stat(dllPath); os.IsNotExist(err) { + return nil, fmt.Errorf("config DLL 不存在: %s", dllPath) + } + if dll, err := syscall.LoadDLL(dllPath); err != nil { + return nil, fmt.Errorf("加载config DLL 失败: %s", err) + } else { + return &ConfigDLL{ + dll: dll, + readConfigFile: dll.MustFindProc("ReadConfigFile"), + getVersion: dll.MustFindProc("GetVersion"), + freeCString: dll.MustFindProc("FreeCString"), + }, nil + } +} + +dll, err := InitConfigDLL() +``` + +#### 获取C字符串 +```gotemplate +// cStr 获取C字符串 +func (m *ConfigDLL) 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 *ConfigDLL) ReadConfigFile(filePath, fileName string) (string, error) { + proc, err := m.dll.FindProc("ReadConfigFile") + if err != nil { + return "", fmt.Errorf("找不到函数 ReadConfigFile: %v", err) + } + + filePathPtr, _ := syscall.BytePtrFromString(filePath) + fileNamePtr, _ := syscall.BytePtrFromString(fileName) + + resultPtr, _, _ := proc.Call( + uintptr(unsafe.Pointer(filePathPtr)), + uintptr(unsafe.Pointer(fileNamePtr)), + ) + + result := m.cStr(resultPtr) + return result, nil +} +``` + +# 接口详情 +## 读取配置文件接口--ReadConfigFile +### 请求信息 +```gotemplate +dll.ReadConfigFile(filePath, fileName) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| filePath | string | 是 | 文件路径(可以是本地路径或HTTP/HTTPS URL) | +| fileName | string | 是 | 配置文件名 | +#### 功能说明 +```text +此接口用于读取多种格式的配置文件,支持以下特性: +1.支持的文件格式: +JSON (.json) +YAML (.yaml, .yml) +INI (.ini, .conf) +2.支持的数据源: +本地文件路径 +HTTP/HTTPS URL +3.配置解析: +JSON/YAML文件解析为Map结构 +INI文件支持多节(section)解析 +DEFAULT节的内容会合并到顶层 +``` +### 响应示例 +```text +JSON字符串 +{ + "database": { + "host": "localhost", + "port": 3306, + "username": "admin" + }, + "server": { + "port": 8080, + "timeout": 30 + } +} +``` +### 错误响应示例 +```text +"error": "JSON解析错误: invalid character '}' looking for beginning of object key string" +``` + +## 释放C字符串内存--FreeCString +### 请求信息 +```gotemplate +dll.FreeCString(str) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|------------| +| str | string | 是 | 需要释放的C字符串 | +### 响应示例 +```text +无 +``` + +## 获取版本信息接口--GetVersion +### 请求信息 +```gotemplate +dll.GetVersion() +``` +### 请求参数 +无 +### 响应示例 +```text +v3 +``` +### 错误响应示例 +```text +{ + "error": "找不到函数 GetVersion" +} +``` + diff --git a/md/expressDeliveryOrder.md b/md/expressDeliveryOrder.md new file mode 100644 index 0000000..a613c71 --- /dev/null +++ b/md/expressDeliveryOrder.md @@ -0,0 +1,739 @@ +# expressDeliveryOrder.dll 使用教程 +## 创建DLL工具实例 +### 加载DLL文件 +```gotemplate +// ExpressDeliveryOrderDLL 快递订单DLL结构 +type ExpressDeliveryOrderDLL struct { + dll *syscall.DLL + ztoOpenCreateOrder *syscall.Proc // 中通创建订单 + jtOrderAddOrder *syscall.Proc // 极兔创建订单 + emsAmpApiOpen *syscall.Proc // 邮政订单接入 + stoOmsExpressOrderCreate *syscall.Proc // 申通订单创建 + integrationOrderCreate *syscall.Proc // 整合快递订单创建 + freeCString *syscall.Proc // 释放C字符串 +} + +// 初始化ExpressDeliveryOrderDLL +func InitExpressDeliveryOrderDLL() (*ExpressDeliveryOrderDLL, error) { + dllPath := filepath.Join("dll", "expressDeliveryOrder.dll") + if _, err := os.Stat(dllPath); os.IsNotExist(err) { + return nil, fmt.Errorf("expressDeliveryOrder DLL 不存在: %s", dllPath) + } + if dll, err := syscall.LoadDLL(dllPath); err != nil { + return nil, fmt.Errorf("加载expressDeliveryOrder DLL 失败: %s", err) + } else { + return &ExpressDeliveryOrderDLL{ + dll: dll, + ztoOpenCreateOrder: dll.MustFindProc("ZtoOpenCreateOrder"), + jtOrderAddOrder: dll.MustFindProc("JtOrderAddOrder"), + emsAmpApiOpen: dll.MustFindProc("EmsAmpApiOpen"), + stoOmsExpressOrderCreate: dll.MustFindProc("StoOmsExpressOrderCreate"), + integrationOrderCreate: dll.MustFindProc("IntegrationOrderCreate"), + freeCString: dll.MustFindProc("FreeCString"), + }, nil + } +} + +dll, err := InitExpressDeliveryOrderDLL() +``` + +### 获取C字符串 +```gotemplate +// cStr 获取C字符串 +func (m *ExpressDeliveryOrderDLL) 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 +} +``` + +### 使用DLL函数示例 +```gotemplate +// 调用中通创建订单接口 +func (m *ExpressDeliveryOrderDLL) ZtoOpenCreateOrder(requestJSON, appKey, appSecret string) (string, error) { + proc, err := m.dll.FindProc("ZtoOpenCreateOrder") + if err != nil { + return "", fmt.Errorf("找不到函数 ZtoOpenCreateOrder: %v", err) + } + + requestJSONPtr, _ := syscall.BytePtrFromString(requestJSON) + appKeyPtr, _ := syscall.BytePtrFromString(appKey) + appSecretPtr, _ := syscall.BytePtrFromString(appSecret) + + resultPtr, _, _ := proc.Call( + uintptr(unsafe.Pointer(requestJSONPtr)), + uintptr(unsafe.Pointer(appKeyPtr)), + uintptr(unsafe.Pointer(appSecretPtr)), + ) + + result := m.cStr(resultPtr) + return result, nil +} +``` + +# 接口详情 +## 中通快递创建订单接口--ZtoOpenCreateOrder +### 请求信息 +```gotemplate +dll.ZtoOpenCreateOrder(requestJSON, appKey, appSecret) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| appKey | string | 是 | 中通APIkey | +| appSecret | string | 是 | 中通API应用密钥 | +#### requestJSON json字符串 +API地址: https://open.zto.com/#/interfaces?resourceGroup=20&apiName=zto.open.createOrder +```json +{ + "partnerType": "2", + "orderType": "1", + "partnerOrderCode": "商家自主定义", + "accountInfo": { + "accountId": "test", + "accountPassword": "", + "type": 1, + "customerId": "GPG1576724269" + }, + "billCode": "", + "senderInfo": { + "senderId": "", + "senderName": "张三", + "senderPhone": "010-22226789", + "senderMobile": "13900000000", + "senderProvince": "上海", + "senderCity": "上海市", + "senderDistrict": "青浦区", + "senderAddress": "华志路" + }, + "receiveInfo": { + "receiverName": "Jone Star", + "receiverPhone": "021-87654321", + "receiverMobile": "13500000000", + "receiverProvince": "上海", + "receiverCity": "上海市", + "receiverDistrict": "闵行区", + "receiverAddress": "申贵路1500号" + }, + "orderVasList": [ + { + "vasType": "COD", + "vasAmount": 100000, + "vasPrice": 0, + "vasDetail": "", + "accountNo": "" + } + ], + "hallCode": "S2044", + "siteCode": "02100", + "siteName": "上海", + "summaryInfo": { + "size": "", + "quantity": 3, + "price": "30", + "freight": "20", + "premium": "10", + "startTime": "2020-12-10 12:00:00", + "endTime": "2020-12-10 12:00:00" + }, + "remark": "小吉下单", + "orderItems": [ + { + "name": "", + "category": "", + "material": "", + "size": "", + "weight": 0, + "unitprice": 0, + "quantity": 0, + "remark": "" + } + ], + "cabinet": { + "address": "", + "specification": 0, + "code": "" + } +} +``` +### 响应数据 +```json +{"result":{"bigMarkInfo":{"bagAddr":"合肥","mark":"460- 38"},"siteCode":"02100","siteName":"上海","signBillInfo":{},"orderCode":"12123412434","billCode":"130005102254","partnerOrderCode":"43423424"},"message":"请求成功","status":true,"statusCode":"SYS000"} +``` +### 异常示例 +```json +{"result":null,"message":"电子面单账号或者集团客户编码不能为空","status":false,"statusCode":"DEF001"} +``` + +## 极兔快递创建订单接口--JtOrderAddOrder +### 请求信息 +```gotemplate +dll.JtOrderAddOrder(requestJSON, apiAccount, privateKey) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| apiAccount | string | 是 | 极兔API账户标识 | +| privateKey | string | 是 | 极兔API私钥 | +#### requestJSON JSON字符串 +API地址: https://open.bxexpress.com.cn/#/apiDoc/orderserve/create +```json +{ + "customerCode": "J0086474299", + "digest": "Base64(Md5(客户编号+密文+privateKey))", + "network": "合作网点编码", + "txlogisticId": "客户订单号", + "expressType": "EZ", + "orderType": "2", + "serviceType": "01", + "deliveryType": "03", + "payType": "PP_PM", + "sender": { + "name": "寄件人姓名", + "company": "寄件公司", + "postCode": "寄件邮编", + "mailBox": "寄件邮箱", + "mobile": "寄件手机", + "phone": "寄件电话", + "countryCode": "CHN", + "prov": "寄件省份", + "city": "寄件城市", + "area": "寄件区域", + "town": "寄件乡镇", + "street": "寄件街道", + "address": "寄件详细地址" + }, + "receiver": { + "name": "收件人姓名", + "company": "收件公司", + "postCode": "收件邮编", + "mailBox": "收件邮箱", + "mobile": "收件手机", + "phone": "收件电话", + "countryCode": "CHN", + "prov": "收件省份", + "city": "收件城市", + "area": "收件区域", + "town": "收件乡镇", + "street": "收件街道", + "address": "收件详细地址" + }, + "sendStartTime": "yyyy-MM-dd HH:mm:ss", + "sendEndTime": "yyyy-MM-dd HH:mm:ss", + "goodsType": "bm000001", + "isRealName": true, + "isCustomsDeclaration": false, + "length": 10, + "width": 10, + "height": 10, + "weight": "0.02", + "totalQuantity": 1, + "itemsValue": "100.00", + "priceCurrency": "RMB", + "offerFee": "100.00", + "remark": "备注信息", + "items": [ + { + "itemType": "bm000001", + "itemName": "物品名称", + "chineseName": "物品中文名称", + "englishName": "English Name", + "number": 1, + "itemValue": "100.00", + "priceCurrency": "RMB", + "desc": "物品描述", + "itemUrl": "https://example.com/item" + } + ], + "customsInfo": { + "count": 1, + "unit": "个", + "sourceArea": "CHN", + "productRecordNo": "产品国检备案编号", + "goodPrepardNo": "商品海关备案号", + "taxNo": "商品行邮税号", + "hsCode": "海关编码", + "goodsCode": "商品编号", + "brand": "货物品牌", + "specifications": "规格型号", + "manufacturer": "生产厂家", + "cargoDeclaredValue": 100.00000, + "declaredValueDeclaredCurrency": "RMB", + "customerFreight": "50.00", + "iePort": "口岸代码", + "enterpriseCustomsCode": "海关注册编号" + }, + "postSiteCode": "驿站编码", + "postSiteName": "驿站名称", + "postSiteAddress": "驿站地址", + "pickupExpressCodeCheckWay": 0, + "pickUpCode": "取件码", + "extendInfo": { + "isFourLevelAddress": "1" + } +} +``` +### 响应参数 +```json +{ + "code":"1", + "msg":"success", + "data":{ + "txlogisticId":"TEST20220704210006", + "billCode":"UT0000498364212", + "sortingCode":"382 300-64 010", + "sumFreight":"5.00", + "createOrderTime":"2022-07-04 12:00:53", + "lastCenterName":"华东转运中心B1" + } +} +``` + +## 邮政快递订单接入接口--EmsAmpApiOpen +### 请求信息 +```gotemplate +dll.EmsAmpApiOpen(requestJSON, secretKey) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| secretKey | string | 是 | 邮政API密钥 | +#### requestJSON JSON字符串 +API地址:https://api.ems.com.cn/#/gnapijj 需要下载API文档,里面的 订单接入 接口 +```json +[ + { + "ecommerceUserId": "12313165", + "deliveryPasswordFlag": "1", + "deliveryPassword": "", + "logisticsOrderNo": "12384565600", + "batchNo": "", + "waybillNo": "", + "oneBillFlag": "", + "submailNo": "", + "oneBillNum": "", + "oneBillFeeType": "", + "contentsAttribute": "1", + "bizProductNo": "1", + "bizProductId": "", + "teanIncrementFlag": "", + "weight": 0, + "volume": 0, + "length": 0, + "width": 0, + "height": 0, + "postageTotal": 12, + "remarks": "", + "insuranceFlag": "", + "insuranceAmount": "0", + "deliverType": "1", + "deliverPreDate": "", + "paymentMode": "", + "codFlag": "", + "codAmount": "0", + "valuableFlag": "1", + "receiverAgentIdType": "1", + "receiverIdNo": "", + "projectId": "", + "reservationReturnFlag": "1", + "reservationReturnTime": "1", + "returnRelationNo": "", + "receiptFlag": "", + "receiptWaybillNo": "", + "localCollectionFee": "1", + "sender": { + "name": "张三", + "postCode": "", + "phone": "", + "mobile": "15232805086", + "prov": "北京市", + "city": "北京市", + "county": "西城区", + "address": "永安路174号" + }, + "receiver": { + "name": "张三", + "postCode": "", + "phone": "", + "mobile": "15232805086", + "prov": "北京市", + "city": "北京市", + "county": "西城区", + "address": "永安路174号" + }, + "cargos": [ + { + "cargoName": "测试商品", + "cargoCategory": "", + "cargoQuantity": "1", + "cargoValue": "1", + "cargoWeight": "1" + } + ] + } +] +``` +### 响应参数 +```json +{ + "logisticsOrderNo": "客户内部订单号", + "waybillNo": "物流运单号1,物流运单号2", + "routeCode": "四段码/分拣码", + "packageCode": "集包地编码", + "packageCodeName": "集包地名称", + "markDestinationCode": "大头笔编码", + "markDestinationName": "大头笔" +} +``` + +## 申通快递订单创建接口--StoOmsExpressOrderCreate +### 请求信息 +```gotemplate +dll.StoOmsExpressOrderCreate(requestJSON, fromAppkey, secretKey, fromCode) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-------------| +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| fromAppkey | string | 是 | 请求发起方应用密钥 | +| secretKey | string | 是 | 签名密钥 | +| fromCode | string | 是 | 请求发起方应用资源编码| +#### requestJSON JSON字符串 +API地址:https://open.sto.cn/#/apiDocument +```json +{ + "orderNo": "8885452262", + "orderSource": "****", + "billType": "00", + "orderType": "01", + "sender": { + "name": "测试名称", + "tel": "0558-45778586", + "mobile": "18775487548", + "postCode": "100001", + "country": "中国", + "province": "安徽", + "city": "合肥", + "area": "泸州", + "town": "测试镇", + "address": "XX街道XX小区XX楼888" + }, + "receiver": { + "name": "测试名称", + "tel": "0556-45778586", + "mobile": "15575487548", + "postCode": "100001", + "country": "中国", + "province": "河北", + "city": "湖州", + "area": "江汉", + "town": "收件镇", + "address": "XX街道XX小区XX楼666", + "safeNo": "13466666632-0011" + }, + "cargo": { + "battery": "10", + "goodsType": "大件", + "goodsName": "XX物", + "goodsCount": 10, + "spaceX": 10, + "spaceY": 10, + "spaceZ": 10, + "weight": 10, + "goodsAmount": "100", + "cargoItemList": [ + { + "serialNumber": "8451234", + "referenceNumber": "88838783634", + "productId": "001", + "name": "小商品", + "qty": 10, + "unitPrice": 1, + "amount": 10, + "currency": "美元", + "weight": 10, + "remark": "无" + } + ] + }, + "customer": { + "siteCode": "666666", + "customerName": "666666000001", + "sitePwd": "***", + "monthCustomerCode": "9000000" + }, + "internationalAnnex": { + "internationalProductType": "01", + "customsDeclaration": false, + "senderCountry": "中国", + "receiverCountry": "俄罗斯" + }, + "waybillNo": "59635456632", + "assignAnnex": { + "takeCompanyCode": "862456565466", + "takeUserCode": "9000000007" + }, + "codValue": "2000", + "freightCollectValue": "20", + "timelessType": "01", + "productType": "01", + "serviceTypeList": [ + "***" + ], + "extendFieldMap": { + "mapValue": "***" + }, + "remark": "无备注", + "expressDirection": "01", + "createChannel": "01", + "regionType": "01", + "insuredAnnex": { + "insuredValue": "6.66", + "goodsValue": "6.66" + }, + "expectValue": "10", + "payModel": "1" +} +``` +### 响应数据 +```json +{ + "success": true, + "errorCode": "PARAM_INVALID/QUERY_ROOKIE_EXCEPTION/AddOrderFailed/SendMobileError/SendPhoneError/SERVER_EXCEPTION/TIME_OUT/PrintCodeRepeat", + "errorMsg": "waybillNo invalid:运单号不符合申通单号规则;ROUTING_INFO_QUERY_NO_REACHABLE: 物流服务不支持派送;该账号剩余面单库存不足,请联系合作网点充值;", + "data": { + "orderNo": "88875485332", + "waybillNo": "47893154", + "bigWord": "877-342-213", + "packagePlace": "上海青浦测试集包地", + "sourceOrderId": "88875485332", + "safeNo": "95013740109424", + "newBlockCode": "新四段码" + } +} +``` + +## 韵达快递--电子面单下单接口--YdCreateBmOrder +### 请求信息 +```gotemplate +dll.YdCreateBmOrder(requestJSON, appKey, appSecret) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-------------| +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| appKey | string | 是 | 请求发起方应用密钥 | +| appSecret | string | 是 | 签名密钥 | +#### requestJSON JSON字符串 +API地址:https://open.yundaex.com/api/apiDoc?apiCode=apiauth_doc 电子面单下单接口 +```json +{ + "appid": "999999", + "partner_id": "201700101001", + "secret": "123456789", + "orders": [ + { + "collection_value": 126.5, + "cus_area1": "", + "cus_area2": "", + "isProtectPrivacy": "", + "items": [ + { + "name": "衣服", + "number": 1, + "remark": "袜子" + } + ], + "khddh": 2012121715001, + "node_id": "350", + "order_serial_no": 2012121715001, + "order_type": "common", + "platform_source": "", + "receiver": { + "address": "上海市,青浦区,盈港东路 6679 号", + "city": "上海市", + "company": "", + "county": "青浦区", + "mobile": "17601206977", + "name": "李四", + "province": "上海市" + }, + "remark": "", + "sender": { + "address": "上海市,青浦区,盈港东路 7766 号", + "city": "上海市", + "company": "", + "county": "青浦区", + "mobile": "17601206977", + "name": "张三", + "province": "上海市" + }, + "size": "0.12,0.23,0.11", + "special": 0, + "value": 126.5, + "weight": 0, + "multi_pack": { + "mulpck": "", + "total": 0, + "endmark": 0 + }, + "markingInfos": [ + { + "type": "INSURED", + "markingValue": { + "value": 2100 + } + }, + { + "type": "DF", + "markingValue": { + "value": 15 + } + }, + { + "type": "COD", + "markingValue": { + "value": 15 + } + }, + { + "type": "RETURN", + "markingValue": { + "value": "1,2" + } + }, + { + "type": "YXZ" + }, + { + "type": "MUL", + "markingValue": { + "value": 10 + } + }, + { + "type": "CONTACT" + } + ] + } + ] +} +``` +### 响应参数 +```json +{ + "code": "0000", + "message": "请求成功", + "result": true, + "data":[{ + "order_serial_no": "0000019876576897", + "pdf_info": "[[{"order_id":"5160533654","order_serial_no":"test_191021002","partner_id":"2017001068","partner_orderid":"test_191021002","order_type":"common","mailno":"4060005469862","customer_id":"","sender_name":"\u738b\u5c0f\u864e","sender_company":"\u51ef\u5229","sender_area_ids":"","sender_area_names":"\u6c5f\u82cf\u7701\uff0c\u5f90\u5dde\u5e02\uff0c\u65b0\u6c82\u5e02","sender_address":"\u6e56\u4e1c\u8def999\u53f7","sender_postcode":"221435","sender_phone":"021-85926525","sender_mobile":"13761960078","sender_branch":"201700","receiver_name":"\u9646\u5927\u6709","receiver_company":"\u5343\u5343","receiver_area_ids":"310118","receiver_area_names":"\u4e0a\u6d77\u5e02,\u4e0a\u6d77\u5e02,\u9752\u6d66\u533a","receiver_address":"\u4e0a\u6d77\u5e02\u9752\u6d66\u533a\u76c8\u6e2f\u4e1c\u8def6633\u53f7","receiver_postcode":"201700","receiver_phone":"020-57720341","receiver_mobile":"13761960075","receiver_branch":"200230","weight":"11.00","remark":"","status":"rs10","time":"2019-10-21 11:16:26","position_no":"G096-00 20","position_zz":"0","options":"","send_num":"0","nb_ckh":"2001123","cus_area1":"\u8ba2\u5355\u53f7:test_191021002\n\u8ba2\u5355\u53f7\uff1a123 \n\u6279\u6b21\u53f7\uff1a456212","cus_area2":"","position":"300","receiver_flag":"1","package_wd":"J200000","callback_id":"","wave_no":"","node_id":"","ems_flag":"","cus_area3":"","trade_code":"","shi1":null,"sheng1":null,"shi2":"310100","sheng2":"310000","collection_value":"100.00","value":"20.00","zffs":"0","innerProvinceName":"\u7701\u5185\u4ef6","package_wdjc":"\u96c6\u5305\u5730\uff1a\u4e0a\u6d77\u5206\u62e8\u5305 ","sender_branch_jc":"\u9752\u6d66\u533aYD","bigpen_code":"G096-00","lattice_mouth_no":"20","mailno_barcode":"406000546986204240","tname":"mailtmp_s12","dispatch_code":"20","qrcode":"4060005469862\/300 G096-00 20","privacy_receiver_name":"\u9646**","privacy_receiver_phone":"020-****0341","privacy_receiver_mobile":"137****0075"},["0424",0]]]", + "mail_no": "5300010219368", + "status": "1", + "remark": null, + "msg": "订单创建成功", + "orderId":"9876576897" + }] +} +``` +### 错误响应参数 +```json +{ + "result": false, + "code": "7777", + "message": "内部接口服务失败", + "sub_code":"s10", + "sub_msg": "参数校验不合法" +} +``` + +## 韵达快递--电子面单打印接口--YdBmGetPdfInfo +### 请求信息 +```gotemplate +dll.YdBmGetPdfInfo(requestJSON, appKey, appSecret) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-------------| +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| appKey | string | 是 | 请求发起方应用密钥 | +| appSecret | string | 是 | 签名密钥 | +#### requestJSON JSON字符串 +API地址:https://open.yundaex.com/api/apiDoc?apiCode=apiauth_doc 电子面单打印接口 +```json +{ + "appid": "999999", + "partner_id": "201700101001", + "secret": "123456789", + "orders": [ + { + "mailno": "312356465656666" + } + ] +} +``` +### 响应参数 +```json +{ + "code": "0000", + "data": [ + { + "mailno": "312356465656666", + "pdfInfo": "base64info" + } + ], + "message": "请求成功", + "result": true, + "sub_code": null, + "sub_msg": null +} + +``` + +## 整合快递订单创建接口--IntegrationOrderCreate +### 请求信息 +```gotemplate +dll.StoOmsExpressOrderCreate(requestJSON, fromAppkey, secretKey, fromCode) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|-------------|--|--|---------------------------------------------------------------------| +| orderType | string | 是 | 快递类型(ZTO 中通/JT 极兔/EMS 邮政/STO 申通) | +| requestJSON | string | 是 | 创建订单参数JSON字符串 | +| key | string | 是 | key 对应ZTO中appKey,JT中apiAccount,STO中fromAppkey,EMS类型的时候可以不填,其他类型必填 | +| secret | string | 是 | 签名密钥 对应ZTO中appSecret,JT中privateKey,EMS中secretKey,STO中secretKey | +| fromCode | string | 是 | 请求发起方应用资源编码,快递类型:EMS类型时必填 | +### 请求参数 +```text +可以看上面(ZTO 中通/JT 极兔/EMS 邮政/STO 申通) 的订单接口请求参数 +``` +### 响应参数 +```text +可以看上面(ZTO 中通/JT 极兔/EMS 邮政/STO 申通) 的订单接口响应参数 +``` + +## 释放C字符串内存--FreeCString +### 请求信息 +```gotemplate +dll.FreeCString(str) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| str | string | 是 | 需要释放的字符串 | \ No newline at end of file diff --git a/md/logger.md b/md/logger.md new file mode 100644 index 0000000..103d285 --- /dev/null +++ b/md/logger.md @@ -0,0 +1,322 @@ +# logger.dll 使用教程 +## 1. 创建DLL工具实例 +### 加载DLL文件 +```gotemplate +// LoggerDLL 日志工具DLL结构 +type loggerDLL struct { + dll *syscall.DLL + createLogger *syscall.Proc // 创建日志器 + createContextWithTaskType *syscall.Proc // 创建带任务类型的上下文 + logInfo *syscall.Proc // 记录信息日志 + logError *syscall.Proc // 记录错误日志 + logWarning *syscall.Proc // 记录警告日志 + logSuccess *syscall.Proc // 记录成功日志 + getLogs *syscall.Proc // 获取日志条目 + getLogFiles *syscall.Proc // 获取日志文件列表 + getVersion *syscall.Proc // 获取版本信息 + closeAllLoggers *syscall.Proc // 关闭所有日志器 + freeString *syscall.Proc // 释放C字符串 +} + +// 初始化loggerDLL +func InitLoggerDLL() (*loggerDLL, error) { + dllPath := filepath.Join("dll", "logger.dll") + if _, err := os.Stat(dllPath); os.IsNotExist(err) { + return nil, fmt.Errorf("logger DLL 不存在: %s", dllPath) + } + if dll, err := syscall.LoadDLL(dllPath); err != nil { + return nil, fmt.Errorf("加载logger DLL 失败: %s", err) + } else { + return &loggerDLL{ + dll: dll, + createLogger: dll.MustFindProc("CreateLogger"), + createContextWithTaskType: dll.MustFindProc("CreateContextWithTaskType"), + logInfo: dll.MustFindProc("LogInfo"), + logError: dll.MustFindProc("LogError"), + logWarning: dll.MustFindProc("LogWarning"), + logSuccess: dll.MustFindProc("LogSuccess"), + getLogs: dll.MustFindProc("GetLogs"), + getLogFiles: dll.MustFindProc("GetLogFiles"), + getVersion: dll.MustFindProc("GetVersion"), + closeAllLoggers: dll.MustFindProc("CloseAllLoggers"), + freeString: dll.MustFindProc("FreeString"), + }, nil + } +} + +dll, err := InitLoggerDLL() +``` + +### 获取C字符串 +```gotemplate +// cStr 获取C字符串 +func (m *loggerDLL) 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.freeString != nil { + m.freeString.Call(p) + } + return s +} +``` + +# 接口详情 +## 创建日志器--CreateLogger +### 请求信息 +```gotemplate +dll.CreateLogger(configJSON) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| configJSON | string | 是 | 配置信息JSON字符串 | +#### 配置JSON结构 +```json +{ + "log_dir": "/path/to/logs", + "split_type": 0, + "rotate_type": 0, + "max_size": 104857600, + "max_count": 30, + "level": 1, + "enable_caller": true, + "default_task_type": "main" +} +``` +#### 参数说明: +```text +log_dir: 日志目录路径 +split_type: 分片方式(0=按月,1=按天,2=按小时,3=按分钟,4=按秒) +rotate_type: 轮转方式(0=按大小,1=按数量) +max_size: 最大文件大小(字节),仅在rotate_type=0时有效 +max_count: 最大文件数量,仅在rotate_type=1时有效 +level: 日志级别(0=SUCCESS,1=INFO,2=WARNING,3=ERROR) +enable_caller: 是否启用调用者信息 +default_task_type: 默认任务类型 +``` +### 响应示例 +```json +"错误: 创建日志目录失败: permission denied" +``` + +## 创建带任务类型的上下文--CreateContextWithTaskType +### 请求信息 +```gotemplate +dll.CreateContextWithTaskType(loggerHandle, taskType) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| loggerHandle | string | 是 | 日志器句柄 | +| taskType | string | 是 | 任务类型 | +### 响应示例 +```json +"ctx_1645497600000000000" +``` +#### 错误响应示例 +```json +"错误: 无效的logger句柄" +``` + +## 记录信息日志--LogInfo +### 请求信息 +```gotemplate +dll.LogInfo(ctxHandle, message) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| ctxHandle | string | 是 | 上下文句柄 | +| message | string | 是 | 日志消息 | +### 响应示例 +```text +无返回值,日志将写入到对应的日志文件中。 +``` + +## 记录错误日志--LogError +### 请求信息 +```gotemplate +dll.LogError(ctxHandle, message) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| ctxHandle | string | 是 | 上下文句柄 | +| message | string | 是 | 日志消息 | +### 响应示例 +```text +无返回值,日志将写入到对应的日志文件中。 +``` + +## 记录警告日志--LogWarning +### 请求信息 +```gotemplate +dll.LogWarning(ctxHandle, message) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| ctxHandle | string | 是 | 上下文句柄 | +| message | string | 是 | 日志消息 | +### 响应示例 +```text +无返回值,日志将写入到对应的日志文件中。 +``` + +## 记录成功日志--LogSuccess +### 请求信息 +```gotemplate +dll.LogSuccess(ctxHandle, message) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|--------------| +| ctxHandle | string | 是 | 上下文句柄 | +| message | string | 是 | 日志消息 | +### 响应示例 +```text +无返回值,日志将写入到对应的日志文件中。 +``` + +## 获取日志条目--GetLogs +### 请求信息 +```gotemplate +dll.GetLogs(loggerHandle, configJSON) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-----------| +| loggerHandle | string | 是 | 日志器句柄 | +| configJSON | string | 是 | 查询配置JSON | +#### 查询配置JSON结构 +```json +{ + "level": 1, + "task_type": "main", + "start_time": "2024-01-01 00:00:00", + "end_time": "2024-01-31 23:59:59", + "max_entries": 1000 +} +``` +#### 参数说明: +```text +level: 日志级别(-1表示所有级别) +task_type: 任务类型(空字符串表示所有任务类型) +start_time: 开始时间(格式: 2006-01-02 15:04:05) +end_time: 结束时间(格式: 2006-01-02 15:04:05) +max_entries: 最大返回条目数(0表示使用默认值1000) +``` +### 响应示例 +```json +{ + "count": 125, + "entries": [ + { + "timestamp": "2024-01-15 10:30:45.123", + "level": "INFO", + "task_type": "main", + "caller": "logger.go:256", + "message": "系统启动完成" + }, + { + "timestamp": "2024-01-15 10:31:15.456", + "level": "ERROR", + "task_type": "backup", + "caller": "backup.go:89", + "message": "备份文件失败: 磁盘空间不足" + } + ] +} +``` +### 错误响应示例 +```json +{"error": "无效的logger句柄"} +``` + +## 获取日志文件列表--GetLogFiles +### 请求信息 +```gotemplate +dll.GetLogFiles(loggerHandle) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-----------| +| loggerHandle | string | 是 | 日志器句柄 | +### 响应示例 +```json +{ + "count": 8, + "files": [ + { + "level": "INFO", + "task_type": "main", + "file_name": "INFO-main-2024-01.log", + "file_size": 1048576, + "mod_time": "2024-01-15 10:30:45" + }, + { + "level": "ERROR", + "task_type": "backup", + "file_name": "ERROR-backup-2024-01.log", + "file_size": 51200, + "mod_time": "2024-01-15 10:31:15" + } + ] +} +``` +### 错误响应示例 +```json +{"error": "无效的logger句柄"} +``` + +## 获取版本信息--GetVersion +### 请求信息 +```gotemplate +dll.GetVersion() +``` +### 请求参数 +```text +无参数 +``` +### 响应示例 +```json +"v1" +``` + +## 关闭所有日志器--CloseAllLoggers +### 请求信息 +```gotemplate +dll.CloseAllLoggers() +``` +### 请求参数 +```text +无参数 +``` +### 响应示例 +```json +"成功关闭所有logger" +``` +### 错误响应示例 +```json +"关闭了5个logger,其中1个出错,最后错误: close file error" +``` + +## 释放C字符串内存--FreeString +### 请求信息 +```gotemplate +dll.FreeString(str) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-----------| +| str | string | 是 | 需要释放的字符串 | \ No newline at end of file diff --git a/md/pdd.md b/md/pdd.md index 8810905..9f502d3 100644 --- a/md/pdd.md +++ b/md/pdd.md @@ -490,6 +490,368 @@ dll.PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson) } ``` +## 生成商家自定义的规格--PddGoodsSpecIdGet +### 请求信息 +```gotemplate +dll.PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| parentSpecId | string | 是 | 拼多多标准规格ID | +| specName | string | 是 | 商家编辑的规格值,如颜色规格下设置白色属性 | +### 响应参数 +```json +{ + "goods_spec_id_get_response": { + "parent_spec_id": 0, + "spec_id": 0, + "spec_name": "str" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 修改商品SKU价格--PddGoodsSkuPriceUpdate +### 请求信息 +```gotemplate +dll.PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, request) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-----------------------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| request | string | 是 | 价格更新请求JSON字符串 | +#### 请求JSON结构 +```json +{ + "goods_id": "必填,商品id,类型为LONG", + "ignore_edit_warn": "非必填,是否获取商品发布警告信息,默认为忽略,类型为BOOLEAN", + "market_price": "非必填,参考价(单位分),类型为LONG", + "market_price_in_yuan": "非必填,参考价(单位元),类型为STRING", + "sku_price_list": [ + { + "group_price": "非必填,拼团购买价格(单位分),类型为LONG", + "is_onsale": "非必填,sku上架状态,0-已下架,1-上架中,类型为INTEGER", + "single_price": "非必填,单独购买价格(单位分),类型为LONG", + "sku_id": "必填,sku标识,类型为LONG" + } + ], + "sync_goods_operate": "非必填,提交后上架状态,0:上架,1:保持原样,类型为INTEGER", + "two_pieces_discount": "非必填,满2件折扣,可选范围0-100,0表示取消,95表示95折,设置需先查询规则接口获取实际可填范围,类型为INTEGER" +} +``` +### 响应参数 +```json +{ + "goods_update_sku_price_response": { + "goods_commit_id": 0, + "is_success": true + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 商品库存更新接口--PddGoodsQuantityUpdate +### 请求信息 +```gotemplate +dll.PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, request) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|---------------------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| request | string | 是 | 库存更新请求JSON字符串 | +#### 请求JSON结构 request 字符串 +```json +{ + "force_update": "非必填,是否强制更新,仅update_type=1(全量更新)时有效,默认值false;force_update=false时,quantity不能小于预扣库存;force_update=true时,代表强制更新,当quantity<预扣库存时,不报错,直接将quantity清0,类型为BOOLEAN", + "goods_id": "必填,商品id,类型为LONG", + "outer_id": "非必填,sku商家编码,类型为STRING", + "quantity": "必填,库存修改值。当全量更新库存时,quantity必须为大于等于0的正整数;当增量更新库存时,quantity为整数,可小于等于0。若增量更新时传入的库存为负数,则负数与实际库存之和不能小于0。比如当前实际库存为1,传入增量更新quantity=-1,库存改为0,类型为LONG", + "sku_id": "非必填,sku_id和outer_id必填一个,类型为LONG", + "update_type": "非必填,库存更新方式,可选。1为全量更新,2为增量更新。如果不填,默认为全量更新,类型为INTEGER" +} +``` +### 响应参数 +```json +{ + "goods_quantity_update_response": { + "is_success": false + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 获取商品信息接口 -- OutPddAuthGetCommitDetailt +### 请求信息 +```gotemplate +dll.OutPddAuthGetCommitDetailt(goodsCommitId, goodsId, accessToken) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| goodsCommitId | string | 是 | 商品提交ID | +| goodsId | string | 是 | 商品ID | +| accessToken | string | 是 | 授权令牌 | +### 响应参数 +```json + +``` + + +## 获取商品详情信息接口 -- OutPddAuthGetGoodsDetail +### 请求信息 +```gotemplate +dll.OutPddAuthGetGoodsDetail(goodsId, accessToken) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| goodsId | string | 是 | 商品ID | +| accessToken | string | 是 | 授权令牌 | +### 响应参数 +```json +{ + "bad_fruit_claim": 0, + "buy_limit": 999999, + "carousel_gallery_list": [ + "https://img.pddpic.com/open-gw/2025-06-30/59c30d4c-193f-40a3-a639-7af59a381ec5.jpeg", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2023-09-07/02a5c39a-7a90-4530-a338-3e87095a21a9.png", + "https://img.pddpic.com/open-gw/2025-06-30/4539f740-331b-4687-aa00-5c96855de6cd.jpeg", + "https://img.pddpic.com/open-gw/2025-06-30/b0e89e39-c97b-475d-9be2-f1909e30acb5.jpeg" + ], + "cat_id": 15678, + "cost_template_id": 655688447565777, + "country_id": 0, + "customer_num": 2, + "customs": "", + "detail_gallery_list": [ + "https://img.pddpic.com/open-gw/2025-06-30/b691c104-baf8-42b2-97e2-b7258113114b.jpeg", + "https://img.pddpic.com/open-gw/2023-09-07/53e6f7ff-d15e-4e8f-8625-e293717ca1e4.jpeg", + "https://img.pddpic.com/open-gw/2023-09-07/ecff591d-32a6-42c9-ba5a-6a42829092a8.jpeg", + "https://img.pddpic.com/open-gw/2023-10-16/7034f8a0-5d88-49f8-a96f-608abb8cac80.jpeg", + "https://img.pddpic.com/open-gw/2023-10-16/e10c2b6c-d4de-4fdd-8d48-f0a334735e9a.jpeg", + "https://img.pddpic.com/open-gw/2023-10-16/c19358fb-0a4d-49ad-bcc8-b2980e938064.jpeg", + "https://img.pddpic.com/open-gw/2025-06-30/1deeb9c0-7212-432b-a309-f774db6e1adb.jpeg" + ], + "goods_desc": "书名:金属工艺学 下 第6版,作者:'邓文英,宋力宏主编',ISBN:9787040456295,出版社:高等教育出版社", + "goods_id": 770621582375, + "goods_name": "金属工艺学 下 第6版 邓文英,宋力宏主编 高等教育出版社 978", + "goods_property_list": [ + { + "punit": "", + "ref_pid": 425, + "template_pid": 401030, + "vid": 0, + "vvalue": "9787040456295" + }, + { + "punit": "", + "ref_pid": 876, + "template_pid": 401029, + "vid": 0, + "vvalue": "金属工艺学 下 第6版" + }, + { + "punit": "页", + "ref_pid": 692, + "template_pid": 401032, + "vid": 0, + "vvalue": "157" + }, + { + "punit": "元", + "ref_pid": 879, + "template_pid": 401034, + "vid": 0, + "vvalue": "24.70" + }, + { + "punit": "", + "ref_pid": 882, + "template_pid": 401037, + "vid": 0, + "vvalue": "邓文英,宋力宏主编" + }, + { + "punit": "", + "ref_pid": 880, + "template_pid": 401035, + "vid": 483761, + "vvalue": "高等教育出版社" + }, + { + "punit": "", + "ref_pid": 888, + "template_pid": 401043, + "vid": 0, + "vvalue": "平装" + } + ], + "goods_type": 1, + "image_url": "", + "invoice_status": 0, + "is_customs": 0, + "is_folt": 0, + "is_group_pre_sale": 0, + "is_pre_sale": 0, + "is_refundable": 1, + "is_sku_pre_sale": 0, + "market_price": 5948, + "order_limit": 999999, + "outer_goods_id": "9787040456295", + "oversea_type": 0, + "pre_sale_time": 0, + "privacy_delivery": 0, + "quan_guo_lian_bao": 0, + "second_hand": 1, + "shipment_limit_second": 172800, + "sku_list": [ + { + "is_onsale": 1, + "limit_quantity": 999999, + "multi_price": 1487, + "out_sku_sn": "9787040456295", + "price": 1587, + "quantity": 0, + "reserve_quantity": 0, + "sku_id": 1753931570290, + "sku_pre_sale_time": 0, + "spec": [ + { + "parent_id": 1216, + "parent_name": "尺寸", + "spec_id": 27632894279, + "spec_name": "单本 无附赠 超七天不退换" + } + ], + "thumb_url": "https://img.pddpic.com/open-gw/2025-06-30/59c30d4c-193f-40a3-a639-7af59a381ec5.jpeg", + "weight": 500 + } + ], + "status": 4, + "tiny_name": "金属工艺学 下 第6", + "two_pieces_discount": 96, + "video_gallery": [], + "warehouse": "", + "warm_tips": "", + "zhi_huan_bu_xiu": 0 +} +``` + +## 生成自定义规格接口 -- OutPddAuthSetSpec +### 请求信息 +```gotemplate +dll.OutPddAuthSetSpec(specTypeId, specName, accessToken) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| specTypeId | int | 是 | 规格类型ID | +| specName | string | 是 | 规格名称 | +| accessToken | string | 是 | 授权令牌 | +### 响应参数 +```json +{ + "parentSpecId": 3820, + "specName": "全新", + "specId": 1080396526 +} +``` + +## 修改价格接口 -- OutPddAuthUpdatePrice +### 请求信息 +```gotemplate +dll.OutPddAuthUpdatePrice(jsonData) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-----------------| +| jsonData | int | 是 | 价格修改信息JSON字符串 | +### 响应参数 +```json +[ + { + "success": true, + "msg": "操作成功" + }, + { + "success": false, + "msg": "操作失败" + } +] +``` + +## 修改库存接口 -- OutPddAuthUpdateStock +### 请求信息 +```gotemplate +dll.OutPddAuthUpdateStock(jsonData) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|-----------------| +| jsonData | int | 是 | 价格修改信息JSON字符串 | +### 响应参数 +```json +[ + { + "success": true, + "msg": "操作成功" + }, + { + "success": false, + "msg": "操作失败" + } +] +``` + ## 12.释放C字符串内存--FreeCString ### 请求信息 ```gotemplate diff --git a/pdd/dll/pdd.dll b/pdd/dll/pdd.dll index cb352d9..2f5a3f3 100644 Binary files a/pdd/dll/pdd.dll and b/pdd/dll/pdd.dll differ diff --git a/pdd/dll/pdd.h b/pdd/dll/pdd.h index 95f58f6..b28ecd5 100644 --- a/pdd/dll/pdd.h +++ b/pdd/dll/pdd.h @@ -120,6 +120,46 @@ extern __declspec(dllexport) char* SelfPddGoodsAdd(char* clientId, char* clientS // extern __declspec(dllexport) char* PddOpenDecryptMaskBatch(char* clientId, char* clientSecret, char* accessToken, char* reqJson); +// PddGoodsSpecIdGet 生成商家自定义的规格 +// +extern __declspec(dllexport) char* PddGoodsSpecIdGet(char* clientId, char* clientSecret, char* accessToken, char* parentSpecId, char* specName); + +// PddGoodsSkuPriceUpdate 修改商品sku价格 +// +extern __declspec(dllexport) char* PddGoodsSkuPriceUpdate(char* clientId, char* clientSecret, char* accessToken, char* request); + +// PddGoodsQuantityUpdate 商品库存更新接口 +// +extern __declspec(dllexport) char* PddGoodsQuantityUpdate(char* clientId, char* clientSecret, char* accessToken, char* request); + +// PddGoodsImageUpload 商品图片上传接口 +// +extern __declspec(dllexport) char* PddGoodsImageUpload(char* clientId, char* clientSecret, char* accessToken, char* fileBase); + +// OutPddAuthGetCommitDetailt 获取商品信息 +// +extern __declspec(dllexport) char* OutPddAuthGetCommitDetailt(char* goodsCommitId, char* goodsId, char* accessToken); + +// OutPddAuthGetGoodsDetail 获取商品信息 +// +extern __declspec(dllexport) char* OutPddAuthGetGoodsDetail(char* goodsId, char* accessToken); + +// OutPddAuthSetSpec 生成自定义规格 +// +extern __declspec(dllexport) char* OutPddAuthSetSpec(int specTypeId, char* specName, char* accessToken); + +// OutPddAuthGetCats 获取商品信息 +// +extern __declspec(dllexport) char* OutPddAuthGetCats(void); + +// OutPddAuthUpdatePrice 修改价格 +// +extern __declspec(dllexport) char* OutPddAuthUpdatePrice(char* jsonData); + +// OutPddAuthUpdateStock 修改库存 +// +extern __declspec(dllexport) char* OutPddAuthUpdateStock(char* jsonData); + // FreeCString 释放C字符串内存 // extern __declspec(dllexport) void FreeCString(char* str); diff --git a/pdd/pdd.dll b/pdd/pdd.dll new file mode 100644 index 0000000..b0577f6 Binary files /dev/null and b/pdd/pdd.dll differ diff --git a/pdd/pdd.go b/pdd/pdd.go index a2ea928..72e60cf 100644 --- a/pdd/pdd.go +++ b/pdd/pdd.go @@ -13,9 +13,12 @@ import ( "github.com/parnurzeal/gorequest" "io" "mime/multipart" + "net/http" + "net/url" "os" "path/filepath" "sort" + "strconv" "strings" "time" "unsafe" @@ -314,6 +317,7 @@ func pddLogisticsOnlineSend(clientId, clientSecret, accessToken string, logistic "access_token": accessToken, "timestamp": timestamp, } + // 将JSON参数合并到params中 toParams, err := addStructToParams(logisticsOnlineSendJson, params) if err != nil { @@ -697,14 +701,13 @@ type SkuProperty struct { * return 商品新增接口响应结构体,错误信息 */ func pddGoodsAdd(clientId, clientSecret, accessToken string, goodsAddJson string) (string, error) { - url := fmt.Sprint("http://gw-api.pinduoduo.com/api/router") + pddUrl := 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, @@ -713,6 +716,7 @@ func pddGoodsAdd(clientId, clientSecret, accessToken string, goodsAddJson string if goodsAddJson == "" { return "", fmt.Errorf("goodsAddJson 参数为空!") } + // 将JSON参数合并到params中 toParams, err := addStructToParams(goodsAddJson, params) if err != nil { @@ -726,13 +730,35 @@ func pddGoodsAdd(clientId, clientSecret, accessToken string, goodsAddJson string } params["sign"] = sign + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + // 将interface{}类型的值转换为字符串 + switch v := value.(type) { + case string: + formData.Set(key, v) + case int, int64, float64: + formData.Set(key, fmt.Sprintf("%v", v)) + case bool: + formData.Set(key, fmt.Sprintf("%t", v)) + default: + // 如果是复杂类型,尝试转换为JSON字符串 + if jsonStr, err := json.Marshal(v); err == nil { + formData.Set(key, string(jsonStr)) + } else { + formData.Set(key, fmt.Sprintf("%v", v)) + } + } + } + request := gorequest.New() - resp, body, errs := request.Get(url). + resp, body, errs := request.Post(pddUrl). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). Timeout(30 * time.Second). - Send(params). + Send(formData.Encode()). End() if len(errs) > 0 { return "", fmt.Errorf("请求失败: %v", errs) @@ -868,6 +894,781 @@ func pddOpenDecryptMaskBatch(clientId, clientSecret, accessToken string, reqJson return string(responseJSON), nil } +// 生成商家自定义的规格 +func pddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName string) (string, error) { + pddUrl := fmt.Sprint("http://gw-api.pinduoduo.com/api/router") + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + // 生成签名参数 + params := map[string]string{ + "type": "pdd.goods.spec.id.get", // API类型:批量解密脱敏 + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "parent_spec_id": parentSpecId, + "spec_name": specName, + "timestamp": timestamp, + } + + sign := generateSign(map[string]interface{}{ + "type": params["type"], + "data_type": params["data_type"], + "client_id": params["client_id"], + "access_token": params["access_token"], + "parent_spec_id": params["parent_spec_id"], + "spec_name": params["spec_name"], + "timestamp": params["timestamp"], + }, clientSecret) + + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + formData.Set(key, value) + } + + request := gorequest.New() + resp, body, errs := request.Post(pddUrl). + 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("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). + Timeout(30 * time.Second). + Send(formData.Encode()). // 发送编码后的表单数据 + 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 +} + +// PddGoodsSkuPriceUpdateRequest 修改价格请求结构体 +type PddGoodsSkuPriceUpdateRequest struct { + GoodsId int64 `json:"goods_id"` + IgnoreEditWarn bool `json:"ignore_edit_warn,omitempty"` + MarketPrice int64 `json:"market_price,omitempty"` + MarketPriceInYuan string `json:"market_price_in_yuan,omitempty"` + SkuPriceList []SkuPriceListItem `json:"sku_price_list"` + SyncGoodsOperate int `json:"sync_goods_operate,omitempty"` + TwoPiecesDiscount int `json:"two_pieces_discount,omitempty"` +} + +// SkuPriceListItem SKU价格列表项 +type SkuPriceListItem struct { + GroupPrice int64 `json:"group_price"` + IsOnsale int `json:"is_onsale"` + SinglePrice int64 `json:"single_price"` + SkuId int64 `json:"sku_id"` +} + +// 修改商品sku价格 +func pddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken string, request PddGoodsSkuPriceUpdateRequest) (string, error) { + pddUrl := fmt.Sprint("http://gw-api.pinduoduo.com/api/router") + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 将 sku_price_list 转换为 JSON 字符串 + skuPriceListJSON, err := json.Marshal(request.SkuPriceList) + if err != nil { + return "", fmt.Errorf("序列化 sku_price_list 失败: %v", err) + } + + // 生成签名参数 + params := map[string]string{ + "type": "pdd.goods.sku.price.update", // API类型:批量解密脱敏 + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + // 添加非空参数 + if request.GoodsId > 0 { + params["goods_id"] = strconv.FormatInt(request.GoodsId, 10) + } + + if request.MarketPrice > 0 { + params["market_price"] = strconv.FormatInt(request.MarketPrice, 10) + } + + if request.MarketPriceInYuan != "" { + params["market_price_in_yuan"] = request.MarketPriceInYuan + } + + if len(request.SkuPriceList) > 0 { + params["sku_price_list"] = string(skuPriceListJSON) + } + + if request.SyncGoodsOperate != 0 { + params["sync_goods_operate"] = strconv.Itoa(request.SyncGoodsOperate) + } + + if request.TwoPiecesDiscount != 0 { + params["two_pieces_discount"] = strconv.Itoa(request.TwoPiecesDiscount) + } + + if request.IgnoreEditWarn { + params["ignore_edit_warn"] = "true" + } + + // 复制参数用于签名(保持顺序) + signParams := make(map[string]interface{}) + for k, v := range params { + signParams[k] = v + } + + sign := generateSign(signParams, clientSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + formData.Set(key, value) + } + + req := gorequest.New() + resp, body, errs := req.Post(pddUrl). + 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("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). + Timeout(30 * time.Second). + Send(formData.Encode()). // 发送编码后的表单数据 + 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 +} + +// PddGoodsQuantityUpdateRequest 商品库存更新接口请求参数 +type PddGoodsQuantityUpdateRequest struct { + // 是否强制更新,仅update_type=1(全量更新)时有效 + // 默认值false;force_update=false时,quantity不能小于预扣库存; + // force_update=true时,代表强制更新,当quantity<预扣库存时,不报错,直接将quantity清0 + ForceUpdate *bool `json:"force_update,omitempty"` + // 商品id + GoodsId int64 `json:"goods_id" validate:"required"` + // sku商家编码 + OuterId *string `json:"outer_id,omitempty"` + // 库存修改值 + // 当全量更新库存时,quantity必须为大于等于0的正整数; + // 当增量更新库存时,quantity为整数,可小于等于0。 + // 若增量更新时传入的库存为负数,则负数与实际库存之和不能小于0。 + // 比如当前实际库存为1,传入增量更新quantity=-1,库存改为0 + Quantity int64 `json:"quantity" validate:"required"` + // sku_id和outer_id必填一个 + SkuId *int64 `json:"sku_id,omitempty"` + // 库存更新方式,可选。1为全量更新,2为增量更新。 + // 如果不填,默认为全量更新 + UpdateType *int32 `json:"update_type,omitempty"` +} + +// 商品库存更新接口 +func pddGoodsQuantityUpdate(clientId, clientSecret, accessToken string, request PddGoodsQuantityUpdateRequest) (string, error) { + pddUrl := fmt.Sprint("http://gw-api.pinduoduo.com/api/router") + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 验证必需参数 + if request.GoodsId == 0 { + return "", fmt.Errorf("goods_id 不能为空") + } + + // 验证sku_id和outer_id至少有一个 + if request.SkuId == nil && request.OuterId == nil { + return "", fmt.Errorf("sku_id 和 outer_id 不能同时为空") + } + + // 生成签名参数 + params := map[string]string{ + "type": "pdd.goods.quantity.update", // API类型:批量解密脱敏 + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + "goods_id": strconv.FormatInt(request.GoodsId, 10), + } + + // 设置可选参数 + if request.UpdateType != nil { + params["update_type"] = strconv.FormatInt(int64(*request.UpdateType), 10) + } + + if request.SkuId != nil { + params["sku_id"] = strconv.FormatInt(*request.SkuId, 10) + } + + if request.OuterId != nil { + params["outer_id"] = *request.OuterId + } + + params["quantity"] = strconv.FormatInt(request.Quantity, 10) + + if request.ForceUpdate != nil { + params["force_update"] = strconv.FormatBool(*request.ForceUpdate) + } + + // 复制参数用于签名(保持顺序) + signParams := make(map[string]interface{}) + for k, v := range params { + signParams[k] = v + } + + sign := generateSign(signParams, clientSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + formData.Set(key, value) + } + + req := gorequest.New() + resp, body, errs := req.Post(pddUrl). + 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("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). + Timeout(30 * time.Second). + Send(formData.Encode()). // 发送编码后的表单数据 + 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 pddGoodsImageUpload(clientId, clientSecret, accessToken string, fileBase string) (string, error) { + pddUrl := "http://gw-api.pinduoduo.com/api/router" + // 准备参数 + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + params := map[string]string{ + "type": "pdd.goods.image.upload", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + "image": fileBase, + } + + sign := map[string]interface{}{ + "type": "pdd.goods.image.upload", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + "image": fileBase, + } + + // 生成签名 + params["sign"] = generateSign(sign, clientSecret) + + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + formData.Set(key, value) + } + + request := gorequest.New() + resp, body, errs := request.Post(pddUrl). + 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("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). + Timeout(30 * time.Second). + Send(formData.Encode()). // 发送编码后的表单数据 + 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 pddOrderBasicListGet(clientId, clientSecret, accessToken string, orderBasicListGetJSONStr string) (string, error) { + pddUrl := fmt.Sprint("http://gw-api.pinduoduo.com/api/router") + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + // 生成签名参数 + params := map[string]interface{}{ + "type": "pdd.order.basic.list.get", // API类型:商品新增接口 + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if orderBasicListGetJSONStr == "" { + return "", fmt.Errorf("goodsAddJson 参数为空!") + } + // 将JSON参数合并到params中 + toParams, err := addStructToParams(orderBasicListGetJSONStr, params) + if err != nil { + return "", err + } + + sign := generateSign(toParams, clientSecret) + + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + // 将interface{}类型的值转换为字符串 + switch v := value.(type) { + case string: + formData.Set(key, v) + case int, int64, float64: + formData.Set(key, fmt.Sprintf("%v", v)) + case bool: + formData.Set(key, fmt.Sprintf("%t", v)) + default: + // 如果是复杂类型,尝试转换为JSON字符串 + if jsonStr, err := json.Marshal(v); err == nil { + formData.Set(key, string(jsonStr)) + } else { + formData.Set(key, fmt.Sprintf("%v", v)) + } + } + } + + request := gorequest.New() + resp, body, errs := request.Post(pddUrl). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). + Timeout(30 * time.Second). + Send(formData.Encode()). + 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 pddGoodsCommitDetailGet(clientId, clientSecret, accessToken string, goodsCommitId, goodsId string) (string, error) { + pddUrl := fmt.Sprint("http://gw-api.pinduoduo.com/api/router") + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + // 生成签名参数 + params := map[string]interface{}{ + "type": "pdd.goods.commit.detail.get", // API类型:商品新增接口 + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + "goods_commit_id": goodsCommitId, + "goods_id": goodsId, + } + + sign := generateSign(params, clientSecret) + + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + // 将参数编码为 application/x-www-form-urlencoded 格式 + formData := url.Values{} + for key, value := range params { + // 将interface{}类型的值转换为字符串 + switch v := value.(type) { + case string: + formData.Set(key, v) + case int, int64, float64: + formData.Set(key, fmt.Sprintf("%v", v)) + case bool: + formData.Set(key, fmt.Sprintf("%t", v)) + default: + // 如果是复杂类型,尝试转换为JSON字符串 + if jsonStr, err := json.Marshal(v); err == nil { + formData.Set(key, string(jsonStr)) + } else { + formData.Set(key, fmt.Sprintf("%v", v)) + } + } + } + + fmt.Println(formData.Encode()) + + request := gorequest.New() + resp, body, errs := request.Post(pddUrl). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"). + Timeout(30 * time.Second). + Send(formData.Encode()). + 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 outPddAuthGetCommitDetailt(goodsCommitId, goodsId, accessToken string) { + url := "http://127.0.0.1:4523/m1/6145055-5836942-default/api/pdd/auth/getCommitDetail" + + // 构建查询参数 + params := map[string]string{ + "goodsCommitId": goodsCommitId, + "goodsId": goodsId, + "accessToken": accessToken, + } + + // 创建请求对象 + request := gorequest.New() + + // 发送GET请求 + resp, body, errs := request.Get(url). + Query(params). // 添加查询参数 + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + End() // 发送请求 + + // 检查错误 + if len(errs) > 0 { + for _, err := range errs { + fmt.Println("请求错误:", err) + } + return + } + + // 检查响应状态码 + if resp != nil && resp.StatusCode != http.StatusOK { + fmt.Printf("HTTP错误: %s\n", resp.Status) + return + } + + // 输出响应体 + fmt.Println(body) +} + +/* +获取商品详情信息 +参数:goodsId:商品ID accessToken:店铺token +*/ +func outPddAuthGetGoodsDetail(goodsId, accessToken string) (string, error) { + // 请求路径 + outPddUrl := "http://pdd.buzhiyushu.cn/api/pdd/auth/getGoodsDetail" + + // 创建 multipart 表单数据 + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // 添加表单字段 + _ = writer.WriteField("goodsId", goodsId) + _ = writer.WriteField("accessToken", accessToken) + + contentType := writer.FormDataContentType() + writer.Close() + + // 发送 GET 请求(实际上是带有表单数据的 GET 请求) + resp, bodyBytes, errs := gorequest.New(). + Get(outPddUrl). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Set("Connection", "keep-alive"). + Set("Content-Type", contentType). + Send(body.String()). + End() + + // 检查错误 + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + // 检查响应状态码 + if resp != nil && resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP状态码异常:%d,响应内容:%s", resp.StatusCode, bodyBytes) + } + + return bodyBytes, nil +} + +/* +生成自定义规格 +参数:specTypeId:规格类型ID specName:规格名称 accessToken:店铺token +*/ +func outPddAuthSetSpec(specTypeId int, specName, accessToken string) (string, error) { + outPddUrl := fmt.Sprintf("http://pdd.buzhiyushu.cn/api/pdd/auth/setSpec?specTypeId=%d&specName=%s&accessToken=%s", specTypeId, specName, accessToken) + + // 创建请求对象 + request := gorequest.New() + + // 发送GET请求 + resp, body, errs := request.Get(outPddUrl). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Set("Content-Type", "application/json"). + End() // 发送请求 + // 检查错误 + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + // 检查响应状态码 + if resp != nil && resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP状态码异常:%d,响应内容:%s", resp.StatusCode, body) + } + + // 输出响应体 + return body, nil +} + +// 获取商品信息 +func outPddAuthGetCats() (string, error) { + outPddUrl := "http://pdd.buzhiyushu.cn/api/pdd/auth/getCats" + // 创建请求对象 + request := gorequest.New() + + // 发送GET请求 + resp, body, errs := request.Get(outPddUrl). + Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Set("Content-Type", "application/json"). + End() // 发送请求 + // 检查错误 + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + // 检查响应状态码 + if resp != nil && resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP状态码异常:%d,响应内容:%s", resp.StatusCode, body) + } + + // 输出响应体 + return body, nil +} + +// 修改价格 +func outPddAuthUpdatePrice(jsonData string) (string, error) { + // 请求url + outPddUrl := "http://pdd.buzhiyushu.cn/api/pdd/auth/updatePrice" + + // 创建gorequest代理 + request := gorequest.New() + + // 发送POST请求,使用form-data格式 + // 注意:gorequest的Send方法会根据传入的类型自动处理 + resp, body, errs := request.Post(outPddUrl). + Type("multipart"). + Send("jsonData=" + jsonData). + End() + + // 处理错误 + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + // 检查响应状态码 + if resp.StatusCode != 200 { + return "", fmt.Errorf("请求返回非200状态码: %d", resp.StatusCode) + } + return body, nil +} + +// 修改库存 +func outPddAuthUpdateStock(jsonData string) (string, error) { + // 请求url + outPddUrl := "http://pdd.buzhiyushu.cn/api/pdd/auth/updateStock" + + // 创建gorequest代理 + request := gorequest.New() + + // 发送POST请求,使用form-data格式 + // 注意:gorequest的Send方法会根据传入的类型自动处理 + resp, body, errs := request.Post(outPddUrl). + Type("multipart"). + Send("jsonData=" + jsonData). + End() + + // 处理错误 + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + // 检查响应状态码 + if resp.StatusCode != 200 { + return "", fmt.Errorf("请求返回非200状态码: %d", resp.StatusCode) + } + return body, nil +} + // =========================== 辅助函数 ============================ // 将结构体的字段添加到 params 映射中 func addStructToParams(req string, params map[string]interface{}) (map[string]interface{}, error) { @@ -876,11 +1677,43 @@ func addStructToParams(req string, params map[string]interface{}) (map[string]in if err := json.Unmarshal([]byte(req), &tempMap); err != nil { return nil, fmt.Errorf("解析 req json失败:%v ", err) } - // 合并到params + + //// 合并到params + //for k, v := range tempMap { + // vStr, _ := json.Marshal(v) + // // 只添加非nil的值 + // if v != nil { + // params[k] = string(vStr) + // } + //} + //合并到params for k, v := range tempMap { - // 只添加非nil的值 if v != nil { - params[k] = v + // 类型断言,直接处理各种类型 + switch val := v.(type) { + case string: + params[k] = val + case bool: + params[k] = val + case nil: + // 跳过nil + case []interface{}: // 数组 + vBytes, _ := json.Marshal(val) + params[k] = string(vBytes) + case map[string]interface{}: // 对象 + vBytes, _ := json.Marshal(val) + params[k] = string(vBytes) + default: + // 其他类型转换为字符串 + vBytes, _ := json.Marshal(val) + strValue := string(vBytes) + // 检查并去掉字符串类型的引号 + if len(strValue) >= 2 && strValue[0] == '"' && strValue[len(strValue)-1] == '"' { + params[k] = strValue[1 : len(strValue)-1] + } else { + params[k] = strValue + } + } } } return params, nil @@ -1017,6 +1850,179 @@ func PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson *C.cha return C.CString(info) } +// PddGoodsSpecIdGet 生成商家自定义的规格 +// +//export PddGoodsSpecIdGet +func PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specName *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + parentSpecIdStr := C.GoString(parentSpecId) + specNameStr := C.GoString(specName) + info, err := pddGoodsSpecIdGet(clientIdStr, clientSecretStr, accessTokenStr, parentSpecIdStr, specNameStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddGoodsSkuPriceUpdate 修改商品sku价格 +// +//export PddGoodsSkuPriceUpdate +func PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, request *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestStr := C.GoString(request) + var pddGoodsSkuPriceUpdateRequest PddGoodsSkuPriceUpdateRequest + if err := json.Unmarshal([]byte(requestStr), &pddGoodsSkuPriceUpdateRequest); err != nil { + return C.CString(fmt.Sprintf("解析JSON失败: %s", err)) + } + info, err := pddGoodsSkuPriceUpdate(clientIdStr, clientSecretStr, accessTokenStr, pddGoodsSkuPriceUpdateRequest) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddGoodsQuantityUpdate 商品库存更新接口 +// +//export PddGoodsQuantityUpdate +func PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, request *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestStr := C.GoString(request) + var pddGoodsQuantityUpdateRequest PddGoodsQuantityUpdateRequest + if err := json.Unmarshal([]byte(requestStr), &pddGoodsQuantityUpdateRequest); err != nil { + return C.CString(fmt.Sprintf("解析JSON失败: %s", err)) + } + info, err := pddGoodsQuantityUpdate(clientIdStr, clientSecretStr, accessTokenStr, pddGoodsQuantityUpdateRequest) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddGoodsImageUpload 商品图片上传接口 +// +//export PddGoodsImageUpload +func PddGoodsImageUpload(clientId, clientSecret, accessToken, fileBase *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + fileBaseStr := C.GoString(fileBase) + info, err := pddGoodsImageUpload(clientIdStr, clientSecretStr, accessTokenStr, fileBaseStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddOrderBasicListGet 商品图片上传接口 +// +//export PddOrderBasicListGet +func PddOrderBasicListGet(clientId, clientSecret, accessToken, orderBasicListGetJSON *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + orderBasicListGetJSONStr := C.GoString(orderBasicListGetJSON) + info, err := pddOrderBasicListGet(clientIdStr, clientSecretStr, accessTokenStr, orderBasicListGetJSONStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddGoodsCommitDetailGet 获取商品提交的商品详情 +// +//export PddGoodsCommitDetailGet +func PddGoodsCommitDetailGet(clientId, clientSecret, accessToken, goodsCommitId, goodsId *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + goodsCommitIdStr := C.GoString(goodsCommitId) + goodsIdStr := C.GoString(goodsId) + info, err := pddGoodsCommitDetailGet(clientIdStr, clientSecretStr, accessTokenStr, goodsCommitIdStr, goodsIdStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// OutPddAuthGetCommitDetailt 获取商品信息 +// +//export OutPddAuthGetCommitDetailt +func OutPddAuthGetCommitDetailt(goodsCommitId, goodsId, accessToken *C.char) *C.char { + goodsCommitIdStr := C.GoString(goodsCommitId) + goodsIdStr := C.GoString(goodsId) + accessTokenStr := C.GoString(accessToken) + outPddAuthGetCommitDetailt(goodsCommitIdStr, goodsIdStr, accessTokenStr) + return C.CString("") +} + +// OutPddAuthGetGoodsDetail 获取商品信息 +// +//export OutPddAuthGetGoodsDetail +func OutPddAuthGetGoodsDetail(goodsId, accessToken *C.char) *C.char { + goodsIdStr := C.GoString(goodsId) + accessTokenStr := C.GoString(accessToken) + info, err := outPddAuthGetGoodsDetail(goodsIdStr, accessTokenStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// OutPddAuthSetSpec 生成自定义规格 +// +//export OutPddAuthSetSpec +func OutPddAuthSetSpec(specTypeId C.int, specName, accessToken *C.char) *C.char { + specTypeIdInt := int(specTypeId) + specNameStr := C.GoString(specName) + accessTokenStr := C.GoString(accessToken) + info, err := outPddAuthSetSpec(specTypeIdInt, specNameStr, accessTokenStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// OutPddAuthGetCats 获取商品信息 +// +//export OutPddAuthGetCats +func OutPddAuthGetCats() *C.char { + info, err := outPddAuthGetCats() + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// OutPddAuthUpdatePrice 修改价格 +// +//export OutPddAuthUpdatePrice +func OutPddAuthUpdatePrice(jsonData *C.char) *C.char { + jsonDataStr := C.GoString(jsonData) + info, err := outPddAuthUpdatePrice(jsonDataStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// OutPddAuthUpdateStock 修改库存 +// +//export OutPddAuthUpdateStock +func OutPddAuthUpdateStock(jsonData *C.char) *C.char { + jsonDataStr := C.GoString(jsonData) + info, err := outPddAuthUpdateStock(jsonDataStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + // FreeCString 释放C字符串内存 // //export FreeCString @@ -1025,5 +2031,5 @@ func FreeCString(str *C.char) { } // main函数 -//func main() { -//} +func main() { +} diff --git a/pdd/pdd.h b/pdd/pdd.h new file mode 100644 index 0000000..10f0888 --- /dev/null +++ b/pdd/pdd.h @@ -0,0 +1,177 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#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 + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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* logisticsOnlineSendJson); + +// 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); + +// PddOpenDecryptMaskBatch 批量数据解密脱敏接口 +// +extern __declspec(dllexport) char* PddOpenDecryptMaskBatch(char* clientId, char* clientSecret, char* accessToken, char* reqJson); + +// PddGoodsSpecIdGet 生成商家自定义的规格 +// +extern __declspec(dllexport) char* PddGoodsSpecIdGet(char* clientId, char* clientSecret, char* accessToken, char* parentSpecId, char* specName); + +// PddGoodsSkuPriceUpdate 修改商品sku价格 +// +extern __declspec(dllexport) char* PddGoodsSkuPriceUpdate(char* clientId, char* clientSecret, char* accessToken, char* request); + +// PddGoodsQuantityUpdate 商品库存更新接口 +// +extern __declspec(dllexport) char* PddGoodsQuantityUpdate(char* clientId, char* clientSecret, char* accessToken, char* request); + +// PddGoodsImageUpload 商品图片上传接口 +// +extern __declspec(dllexport) char* PddGoodsImageUpload(char* clientId, char* clientSecret, char* accessToken, char* fileBase); + +// PddOrderBasicListGet 商品图片上传接口 +// +extern __declspec(dllexport) char* PddOrderBasicListGet(char* clientId, char* clientSecret, char* accessToken, char* orderBasicListGetJSON); + +// PddGoodsCommitDetailGet 获取商品提交的商品详情 +// +extern __declspec(dllexport) char* PddGoodsCommitDetailGet(char* clientId, char* clientSecret, char* accessToken, char* goodsCommitId, char* goodsId); + +// OutPddAuthGetCommitDetailt 获取商品信息 +// +extern __declspec(dllexport) char* OutPddAuthGetCommitDetailt(char* goodsCommitId, char* goodsId, char* accessToken); + +// OutPddAuthGetGoodsDetail 获取商品信息 +// +extern __declspec(dllexport) char* OutPddAuthGetGoodsDetail(char* goodsId, char* accessToken); + +// OutPddAuthSetSpec 生成自定义规格 +// +extern __declspec(dllexport) char* OutPddAuthSetSpec(int specTypeId, char* specName, char* accessToken); + +// OutPddAuthGetCats 获取商品信息 +// +extern __declspec(dllexport) char* OutPddAuthGetCats(void); + +// OutPddAuthUpdatePrice 修改价格 +// +extern __declspec(dllexport) char* OutPddAuthUpdatePrice(char* jsonData); + +// OutPddAuthUpdateStock 修改库存 +// +extern __declspec(dllexport) char* OutPddAuthUpdateStock(char* jsonData); + +// FreeCString 释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +#ifdef __cplusplus +} +#endif diff --git a/pdd/pddDll.go b/pdd/pddDll.go index d08a340..86167cf 100644 --- a/pdd/pddDll.go +++ b/pdd/pddDll.go @@ -134,112 +134,144 @@ func (m *pddDLL) PddErpOrderSync(clientId, clientSecret, accessToken, logisticsI return m.cStr(info), nil } -func main() { - //clientId := "203c5a7ba8bd4b8488d5e26f93052642" - //clientSecret := "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" - //accessToken := "bd96218bb2a146779701506dc1e5e5c478692539" - //outerCatId := "15543" - //outerCatName := "书籍/杂志/报纸" - //outerGoodsName := "书籍医家金鉴 妇产科学卷" - //logisticsId := 0 - //orderSn := "" - //orderState := "" - //waybillNo := "" - //logisticsCompany := "德邦" +//func main() { +//// 获取商品类目 +//info, err := outPddAuthGetCats() +//if err != nil { +// fmt.Printf(err.Error()) +//} +//fmt.Println(info) - // 初始化 - //dll, err := InitPddDLL() - //if err != nil { - // fmt.Println(err) - //} +//// 生成自定义规格 +//info, err := outPddAuthSetSpec(3820, "全新", "e7e8a719b9b74378980914c9ca9e4a3e57a6a0c8") +//if err != nil { +// fmt.Printf(err.Error()) +//} +//fmt.Println(info) - // 类目预测 - //info, err := dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken, outerCatId, outerCatName, outerGoodsName) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(info) +//// 获取商品详情信息 +//info, err := outPddAuthGetGoodsDetail("770621582375", "e7e8a719b9b74378980914c9ca9e4a3e57a6a0c8") +//if err != nil { +// fmt.Printf(err.Error()) +//} +//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) +//clientId := "203c5a7ba8bd4b8488d5e26f93052642" +//clientSecret := "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" +//accessToken := "bd96218bb2a146779701506dc1e5e5c478692539" +//outerCatId := "15543" +//outerCatName := "书籍/杂志/报纸" +//outerGoodsName := "书籍医家金鉴 妇产科学卷" +//logisticsId := 0 +//orderSn := "" +//orderState := "" +//waybillNo := "" +//logisticsCompany := "德邦" - //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) +// 初始化 +//dll, err := InitPddDLL() +//if err != nil { +// fmt.Println(err) +//} - //get, err := pddLogisticsCompaniesGet(clientId, clientSecret) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(get) +// 类目预测 +//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) - //jsonStr := `[{"data_tag":"251229-272441044622514","encrypted_data":"~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"}]` +//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) - //var records []DataList - //err := json.Unmarshal([]byte(jsonStr), &records) - //if err != nil { - // log.Fatal("解析JSON失败:", err) - //} +//get, err := pddLogisticsCompaniesGet(clientId, clientSecret) +//if err != nil { +// fmt.Println(err) +//} +//fmt.Println(get) - //batch, err := pddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, jsonStr) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(batch) - //clientId := "203c5a7ba8bd4b8488d5e26f93052642" - //clientSecret := "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" - //accessToken := "5b1e9506827049a7a9335302e917d2b896a3d6c7" - //logisticsCompany := "韵达快递" - //logisticsId := "" - //orderSn := "260107-652497405582514" - //orderState := "1" - //waybillNo := "312944253800986" - // - //synchronization, err := pddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId, orderSn, orderState, waybillNo) - //if err != nil { - // fmt.Println(err) - //} - //fmt.Println(string(synchronization)) -} +// 脱敏 + +//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) +//clientId := "203c5a7ba8bd4b8488d5e26f93052642" +//clientSecret := "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" +//accessToken := "5b1e9506827049a7a9335302e917d2b896a3d6c7" +//logisticsCompany := "韵达快递" +//logisticsId := "" +//orderSn := "260107-652497405582514" +//orderState := "1" +//waybillNo := "312944253800986" +// +//synchronization, err := pddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId, orderSn, orderState, waybillNo) +//if err != nil { +// fmt.Println(err) +//} +//fmt.Println(string(synchronization)) +//} type DataList struct { DataTag string `json:"data_tag"` EncryptedData string `json:"encrypted_data"` } + +//func main() { +// jsonStr := `{"goods_name":"未厌居习作未厌居习作9787543421523叶圣陶(1894.10~1988.2)","carousel_gallery":["https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg"],"cat_id":0,"goods_type":1,"market_price":515,"detail_gallery":[],"out_goods_id":"9787543421523","sku_list":[{"is_onsale":0,"limit_quantity":999,"multi_price":415,"price":515,"sku_properties":[{"punit":"","ref_pid":0,"value":"","vid":0,"spec_id_list":"1","thumb_url":"https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","weight":250}],"quantity":999}],"is_folt":false,"is_pre_sale":false,"is_refundable":false,"second_hand":true,"cost_template_id":0,"country_id":0,"shipment_limit_second":172800}` +// add, err := pddGoodsAdd("203c5a7ba8bd4b8488d5e26f93052642", +// "892ffaa86e12b7a3d8d2942b669d9aa520ad8179", +// "1177d0c36419417eba692a3fea88f611d42f0665", jsonStr) +// if err != nil { +// fmt.Println(err.Error()) +// } +// fmt.Println(add) +//} diff --git a/pddTask/common/pddDll.go b/pddTask/common/pddDll.go new file mode 100644 index 0000000..49a59b9 --- /dev/null +++ b/pddTask/common/pddDll.go @@ -0,0 +1,128 @@ +package common + +import "C" +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "syscall" + "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"` // 错误响应 +} + +// GoodsImageUploadResp 商品图片上传响应结构体 +type GoodsImageUploadResp struct { + GoodsImageUploadResponse struct { + ImageURL string `json:"image_url"` // 上传后的图片URL + RequestID string `json:"request_id"` // 请求ID + } `json:"goods_image_upload_response"` +} + +// PddDLL 拼多多工具DLL结构 +type pddDLL struct { + dll *syscall.DLL + pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测接口 + pddGoodsAdd *syscall.Proc // 商品新增接口 + pddGoodsAd *syscall.Proc // 商品新增接口 + pddGoodsImageUpload *syscall.Proc // 商品图片上传接口 + freeCString *syscall.Proc // 释放C字符串 +} + +// InitPddDLL 初始化pddDLL +func InitPddDLL() (*pddDLL, error) { + dllPath := filepath.Join("pddTask", "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"), + pddGoodsAdd: dll.MustFindProc("PddGoodsAdd"), + pddGoodsAd: dll.MustFindProc("PddGoodsAd"), + pddGoodsImageUpload: dll.MustFindProc("PddGoodsImageUpload"), + 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 +} + +// PddGoodsAdd 商品新增接口 +func PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson string) (string, error) { + dll, err := InitPddDLL() + if err != nil { + return "", err + } + + clientIdPtr, _ := syscall.BytePtrFromString(clientId) + clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret) + accessTokenPtr, _ := syscall.BytePtrFromString(accessToken) + goodsAddJsonPtr, _ := syscall.BytePtrFromString(goodsAddJson) + info, _, _ := dll.pddGoodsAdd.Call( + uintptr(unsafe.Pointer(clientIdPtr)), + uintptr(unsafe.Pointer(clientSecretPtr)), + uintptr(unsafe.Pointer(accessTokenPtr)), + uintptr(unsafe.Pointer(goodsAddJsonPtr)), + ) + result := dll.cStr(info) + return result, err +} + +// PddGoodsImageUpload 商品图片上传接口 +func (m *pddDLL) PddGoodsImageUpload(clientId, clientSecret, accessToken, fileBase string) (string, error) { + proc := m.pddGoodsImageUpload + + clientIdPtr, _ := syscall.BytePtrFromString(clientId) + clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret) + accessTokenPtr, _ := syscall.BytePtrFromString(accessToken) + fileBasePtr, _ := syscall.BytePtrFromString(fileBase) + + info, _, _ := proc.Call( + uintptr(unsafe.Pointer(clientIdPtr)), + uintptr(unsafe.Pointer(clientSecretPtr)), + uintptr(unsafe.Pointer(accessTokenPtr)), + uintptr(unsafe.Pointer(fileBasePtr)), + ) + result := m.cStr(info) + // 解析 + var goodsImageUploadResp GoodsImageUploadResp + if err := json.Unmarshal([]byte(result), &goodsImageUploadResp); err != nil { + return "", fmt.Errorf("解析 goodsImageUploadResp JSON失败: %s,响应信息: %s", err, result) + } + return goodsImageUploadResp.GoodsImageUploadResponse.ImageURL, nil +} diff --git a/pddTask/dll/pdd.dll b/pddTask/dll/pdd.dll new file mode 100644 index 0000000..04ab592 Binary files /dev/null and b/pddTask/dll/pdd.dll differ diff --git a/pddTask/pdd-task.go b/pddTask/pdd-task.go new file mode 100644 index 0000000..dbc833b --- /dev/null +++ b/pddTask/pdd-task.go @@ -0,0 +1,489 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/disintegration/imaging" + "github.com/fogleman/gg" + "image" + "image/jpeg" + "io" + "kfzgw-info/pddTask/common" + "net/http" + "strings" + "time" +) + +// TaskBody 任务主体结构 +type TaskBody struct { + BookInfo BookInfo `json:"book_info"` + Detail TaskDetail `json:"detail"` +} + +// BookInfo 书籍信息结构 +type BookInfo struct { + Isbn string `json:"isbn"` // ISBN + BookName string `json:"book_name"` // 书名 + Author string `json:"author"` // 作者 + Publishing string `json:"publishing"` // 出版社 + PublicationDate string `json:"publication_date"` // 出版时间 + Binding string `json:"binding"` // 装帧 + PageNumber int64 `json:"page_number"` // 页数 + Format int64 `json:"format"` // 开本 + ImageObject ImageObject `json:"image_object"` // 图片 + Price int64 `json:"price"` // 售价 +} + +// ImageObject 图片对象结构 +type ImageObject struct { + CarouselUrlArray []string `json:"carousel_url_array"` // 轮播图 + WhiteBackgroundUrl string `json:"white_background_url"` // 白底图 + DetailUrlObject DetailImageObject `json:"detail_url_object"` // 详情对象 +} + +// DetailImageObject 详情图片对象结构 +type DetailImageObject struct { + IntroductionUrl []string `json:"introduction_url"` // 简介图 + CatalogueUrl []string `json:"catalogue_url"` // 目录图 + LiveShootingUrl []string `json:"live_shooting_url"` // 实拍图 + OtherUrl []string `json:"other_url"` // 其他图 +} + +// TaskDetail 详情结构 +type TaskDetail struct { + Condition int64 `json:"condition"` // 品相 + Price int64 `json:"price"` // 价格 + Stock int64 `json:"stock"` // 库存 + Status int64 `json:"status"` // 状态 + GoodsId int64 `json:"goods_id"` // 商品 ID + ReturnId int64 `json:"return_id"` // 拼多多返回 ID +} + +// ShopMsg 店铺信息结构体 +type ShopMsg struct { + ID int64 `json:"id"` // ID + ShopAliasName string `json:"shop_alias_name"` // 店铺别名 + ShopName string `json:"shop_name"` // 店铺名 + SkuSpec string `json:"sku_spec"` // 库存规格 + Token string `json:"token"` // token + CoverWatermarkImageUrl string `json:"cover_watermark_image_url"` // 封面水印图 + ProductDetailsWatermarkImageUrl string `json:"product_details_watermark_image_url"` // 商详水印图 + WatermarkPosition int64 `json:"watermark_position"` // 水印位置 +} + +type CatIdObject struct { + PinDuoDuoCatId int64 `json:"pin_duo_duo_cat_id"` // 拼多多分类 ID + KongFuZiCatId int64 `json:"kong_fu_zi_cat_id"` // 孔夫子分类 ID + XianYuCatId int64 `json:"xian_yu_cat_id"` // 闲鱼分类 ID +} + +// 常量 +const ( + CLIENT_ID = "203c5a7ba8bd4b8488d5e26f93052642" + CLIENT_SECRET = "892ffaa86e12b7a3d8d2942b669d9aa520ad8179" +) + +// GoodsAddOrUpdateRequest 商品添加/更新请求 +type GoodsAddOrUpdateRequest struct { + AutoFillSpuProperty *bool `json:"auto_fill_spu_property,omitempty"` + BadFruitClaim *int64 `json:"bad_fruit_claim,omitempty"` + BuyLimit *int64 `json:"buy_limit,omitempty"` + CarouselGallery []string `json:"carousel_gallery"` // 必填 + CarouselVideo []*CarouselVideoItem `json:"carousel_video,omitempty"` + CarouselVideoUrl *string `json:"carousel_video_url,omitempty"` + CatID int64 `json:"cat_id"` // 必填 + CostTemplateID int64 `json:"cost_template_id"` // 必填 + CountryID int64 `json:"country_id"` // 必填 + CustomerNum *int64 `json:"customer_num,omitempty"` + Customs *string `json:"customs,omitempty"` + DeliveryOneDay *int64 `json:"delivery_one_day,omitempty"` + DeliveryType *int64 `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 int64 `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 *int64 `json:"is_group_pre_sale,omitempty"` + IsPreSale bool `json:"is_pre_sale"` // 必填 + IsRefundable bool `json:"is_refundable"` // 必填 + IsSkuPreSale *int64 `json:"is_sku_pre_sale,omitempty"` + LackOfWeightClaim *int64 `json:"lack_of_weight_claim,omitempty"` + LocalServiceIDList []int64 `json:"local_service_id_list,omitempty"` + MaiJiaZiTi *string `json:"mai_jia_zi_ti,omitempty"` + MarketPrice int64 `json:"market_price"` // 必填 + OrderLimit *int64 `json:"order_limit,omitempty"` + OriginCountryID *int64 `json:"origin_country_id,omitempty"` + OutGoodsID *string `json:"out_goods_id,omitempty"` + OutSourceGoodsID *string `json:"out_source_goods_id,omitempty"` + OutSourceType *int64 `json:"out_source_type,omitempty"` + OverseaGoods *OverseaGoods `json:"oversea_goods,omitempty"` + OverseaType *int64 `json:"oversea_type,omitempty"` + PreSaleTime *int64 `json:"pre_sale_time,omitempty"` + PrivacyDelivery *int64 `json:"privacy_delivery,omitempty"` + QuanGuoLianBao *int64 `json:"quan_guo_lian_bao,omitempty"` + SecondHand bool `json:"second_hand"` // 必填 + ShangMenAnZhuang *string `json:"shang_men_an_zhuang,omitempty"` + ShipmentLimitSecond int64 `json:"shipment_limit_second"` // 必填 + ShopGroupID *int64 `json:"shop_group_id,omitempty"` + SizeSpecID *int64 `json:"size_spec_id,omitempty"` + SkuList []*SkuItem `json:"sku_list"` // 必填 + SkuType *int64 `json:"sku_type,omitempty"` + SongHuoAnZhuang *string `json:"song_huo_an_zhuang,omitempty"` + SongHuoRuHu *string `json:"song_huo_ru_hu,omitempty"` + TinyName *string `json:"tiny_name,omitempty"` + TwoPiecesDiscount *int64 `json:"two_pieces_discount,omitempty"` + Warehouse *string `json:"warehouse,omitempty"` + WarmTips *string `json:"warm_tips,omitempty"` + ZhiHuanBuXiu *int64 `json:"zhi_huan_bu_xiu,omitempty"` + TableInfo *TableInfo `json:"table_info,omitempty"` +} + +// CarouselVideoItem 轮播视频项 +type CarouselVideoItem struct { + FileID *int64 `json:"file_id,omitempty"` + VideoURL *string `json:"video_url,omitempty"` +} + +// ElecGoodsAttributes 卡券类商品属性 +type ElecGoodsAttributes struct { + BeginTime *int64 `json:"begin_time,omitempty"` + DaysTime *int64 `json:"days_time,omitempty"` + EndTime *int64 `json:"end_time,omitempty"` + TimeType *int64 `json:"time_type,omitempty"` +} + +// GoodsProperty 商品属性 +type GoodsProperty struct { + GroupID *int64 `json:"group_id,omitempty"` + ImgURL *string `json:"img_url,omitempty"` + Note *string `json:"note,omitempty"` + ParentSpecID *int64 `json:"parent_spec_id,omitempty"` + RefPID *int64 `json:"ref_pid,omitempty"` + SpecID *int64 `json:"spec_id,omitempty"` + TemplatePID *int64 `json:"template_pid,omitempty"` + Value *string `json:"value,omitempty"` + ValueUnit *string `json:"value_unit,omitempty"` + VID *int64 `json:"vid,omitempty"` +} + +// TableInfo 成分表表单信息 +type TableInfo struct { + TableValueList []*TableValue `json:"table_value_list,omitempty"` +} + +// TableValue 表单内容 +type TableValue struct { + ColumnType *int64 `json:"column_type,omitempty"` + Unit *string `json:"unit,omitempty"` + Value *string `json:"value,omitempty"` +} + +// GoodsTradeAttr 日历商品交易相关信息 +type GoodsTradeAttr struct { + AdvancesDays *int64 `json:"advances_days,omitempty"` + BookingNotes *BookingNote `json:"booking_notes,omitempty"` + LifeSpan *int64 `json:"life_span,omitempty"` +} + +// BookingNote 预订须知 +type BookingNote struct { + URL *string `json:"url,omitempty"` +} + +// GoodsTravelAttr 商品出行信息 +type GoodsTravelAttr struct { + NeedTourist *bool `json:"need_tourist,omitempty"` + Type *int64 `json:"type,omitempty"` +} + +// OverseaGoods 海外商品信息 +type OverseaGoods struct { + BondedWarehouseKey string `json:"bonded_warehouse_key"` // 必填 + ConsumptionTaxRate *int64 `json:"consumption_tax_rate,omitempty"` + CustomsBroker *string `json:"customs_broker,omitempty"` + HsCode *string `json:"hs_code,omitempty"` + ValueAddedTaxRate *int64 `json:"value_added_tax_rate,omitempty"` +} + +// SkuItem SKU项 +type SkuItem struct { + IsOnsale int64 `json:"is_onsale"` // 必填 + Length *int64 `json:"length,omitempty"` + LimitQuantity int64 `json:"limit_quantity"` // 必填 + MultiPrice int64 `json:"multi_price"` // 必填 + OutSkuSn *string `json:"out_sku_sn,omitempty"` + OutSourceSkuID *string `json:"out_source_sku_id,omitempty"` + OverseaSku *OverseaSku `json:"oversea_sku,omitempty"` + Price int64 `json:"price"` // 必填 + Quantity int64 `json:"quantity"` // 必填 + SkuPreSaleTime *int64 `json:"sku_pre_sale_time,omitempty"` + SkuProperties []*SkuProperty `json:"sku_properties"` // 必填 + SpecIDList string `json:"spec_id_list"` // 必填 + ThumbURL string `json:"thumb_url"` // 必填 + Weight int64 `json:"weight"` // 必填 +} + +// OverseaSku 海外SKU信息 +type OverseaSku struct { + MeasurementCode string `json:"measurement_code"` // 必填 + Specifications string `json:"specifications"` // 必填 + Taxation int64 `json:"taxation"` // 必填 +} + +// SkuProperty SKU属性 +type SkuProperty struct { + Punit string `json:"punit"` // 必填 + RefPID int64 `json:"ref_pid"` // 必填 + Value string `json:"value"` // 必填 + VID int64 `json:"vid"` // 必填 +} + +// 新增商品 +func addGoods(taskBodyStr string, shopMsgStr string) (string, error) { + // 任务主体结构 + var taskBody TaskBody + if err := json.Unmarshal([]byte(taskBodyStr), &taskBody); err != nil { + return "", fmt.Errorf("解析 taskBodyStr JSON字符串失败: %v", err) + } + // 店铺信息结构体 + var shopMsg ShopMsg + if err := json.Unmarshal([]byte(shopMsgStr), &shopMsg); err != nil { + return "", fmt.Errorf("解析 shopMsgStr JSON字符串失败: %v", err) + } + + dll, err := common.InitPddDLL() + if err != nil { + return "", err + } + + // 校验token是否存在 + if shopMsg.Token == "" { + return "", fmt.Errorf("token不能为空或为空字符串!") + } + + // 新的轮播图 + var newCarouselUrlArray []string + + // pdd实际轮播图 + for i, carouselUrlArray := range taskBody.BookInfo.ImageObject.CarouselUrlArray { + if (i == 0 || shopMsg.WatermarkPosition == 0) && shopMsg.CoverWatermarkImageUrl != "" { + toBase64, err := MergeImagesToBase64(carouselUrlArray, shopMsg.CoverWatermarkImageUrl) + if err != nil { + return "", fmt.Errorf("合成水印图片失败: %v", err) + } + // 调用pdd.dll中商品图片上传接口 + upload, err := dll.PddGoodsImageUpload(CLIENT_ID, CLIENT_SECRET, shopMsg.Token, toBase64) + newCarouselUrlArray = append(newCarouselUrlArray, upload) + } else if strings.Contains(carouselUrlArray, "www0.kfzimg.com") { + // TODO 本地图片需要下载 + } else { + newCarouselUrlArray = append(newCarouselUrlArray, carouselUrlArray) + } + if len(carouselUrlArray) == 1 && shopMsg.WatermarkPosition == 1 { + if strings.Contains(carouselUrlArray, "www0.kfzimg.com") { + // TODO 本地图片需要下载 + } else { + newCarouselUrlArray = append(newCarouselUrlArray, carouselUrlArray) + } + } + } + + // 商品详情图 + + common.PddGoodsAdd(CLIENT_ID, CLIENT_SECRET, shopMsg.Token, "") + + return "", nil +} + +// ================================ 辅助函数 ============================= +const ( + MaxRetries = 5 + RetryDelay = 100 * time.Millisecond +) + +// MergeImagesToBase64 合并主图和水印图,返回Base64字符串 +func MergeImagesToBase64(mainImageURL, watermarkImageURL string) (string, error) { + // 1. 读取主图和水印图(带重试机制) + mainImg, err := readImageWithRetry(mainImageURL) + if err != nil { + return "", fmt.Errorf("读取主图失败: %v", err) + } + + watermarkImg, err := readImageWithRetry(watermarkImageURL) + if err != nil { + return "", fmt.Errorf("读取水印图失败: %v", err) + } + + // 2. 将水印图缩放到与主图相同的尺寸 + watermarkImg = imaging.Resize(watermarkImg, mainImg.Bounds().Dx(), mainImg.Bounds().Dy(), imaging.Lanczos) + + // 3. 创建绘图上下文 + dc := gg.NewContextForImage(mainImg) + + // 4. 计算水印位置(居中) + x := float64(mainImg.Bounds().Dx()-watermarkImg.Bounds().Dx()) / 2 + y := float64(mainImg.Bounds().Dy()-watermarkImg.Bounds().Dy()) / 2 + + // 5. 绘制水印(不透明) + dc.DrawImage(watermarkImg, int(x), int(y)) + + // 6. 将结果保存为JPEG + var buf bytes.Buffer + err = jpeg.Encode(&buf, dc.Image(), &jpeg.Options{Quality: 80}) + if err != nil { + return "", fmt.Errorf("JPEG编码失败: %v", err) + } + + // 7. 转换为Base64 + base64Str := base64.StdEncoding.EncodeToString(buf.Bytes()) + return "data:image/jpeg;base64," + base64Str, nil +} + +// readImageWithRetry 带重试机制的图片读取 +func readImageWithRetry(url string) (image.Image, error) { + var img image.Image + var err error + + for i := 0; i < MaxRetries; i++ { + img, err = readImageFromURL(url) + if err == nil && img != nil { + return img, nil + } + + if i < MaxRetries-1 { + fmt.Printf("第 %d 次读取图片失败,准备重试...\n", i+1) + time.Sleep(RetryDelay) + } + } + + return nil, fmt.Errorf("经过 %d 次重试后仍然失败: %v", MaxRetries, err) +} + +// readImageFromURL 从URL读取图片 +func readImageFromURL(url string) (image.Image, error) { + // 创建HTTP客户端,设置超时 + client := &http.Client{ + Timeout: 30 * time.Second, + } + + resp, err := client.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP请求失败: %s", resp.Status) + } + + // 读取图片数据 + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // 解码图片 + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + + return img, nil +} + +func main() { + //// 创建 TaskBody 实例并赋值 + //taskBody := TaskBody{ + // BookInfo: BookInfo{ + // Isbn: "978-7-111-59948-9", + // BookName: "Go语言编程实战", + // Author: "张三", + // Publishing: "机械工业出版社", + // PublicationDate: "2023-05-01", + // Binding: "平装", + // PageNumber: 320, + // Format: 16, + // ImageObject: ImageObject{ + // CarouselUrlArray: []string{ + // "https://img.pddpic.com/open-gw/2025-11-29/d095b11b-301c-45ec-aec3-a90b21c5465b.jpeg", + // "https://img.pddpic.com/open-gw/2026-01-30/f990c681-ba21-45ae-b6be-52e8d0cd25b4.jpeg", + // "https://img.pddpic.com/open-gw/2025-12-08/81377bc18e8837b477f6a61b7b1e6527.jpeg", + // }, + // WhiteBackgroundUrl: "https://example.com/white-bg.jpg", + // DetailUrlObject: DetailImageObject{ + // IntroductionUrl: []string{ + // "https://example.com/intro1.jpg", + // "https://example.com/intro2.jpg", + // }, + // CatalogueUrl: []string{ + // "https://example.com/catalog1.jpg", + // "https://example.com/catalog2.jpg", + // }, + // LiveShootingUrl: []string{ + // "https://example.com/live1.jpg", + // "https://example.com/live2.jpg", + // }, + // OtherUrl: []string{ + // "https://example.com/other1.jpg", + // }, + // }, + // }, + // Price: 6800, // 单位:分(68元) + // }, + // Detail: TaskDetail{ + // Condition: 95, // 品相95% + // Price: 6800, // 价格68元 + // Stock: 100, // 库存100件 + // Status: 1, // 状态:上架 + // GoodsId: 123456789, + // ReturnId: 987654321, + // }, + //} + // + //taskBodyStr, _ := json.Marshal(taskBody) + //fmt.Println(string(taskBodyStr)) + // + //shopMsg := ShopMsg{ + // ID: 1, + // ShopAliasName: "", + // ShopName: "", + // SkuSpec: "", + // Token: "1177d0c36419417eba692a3fea88f611d42f0665", + // CoverWatermarkImageUrl: "https://img.pddpic.com/open-gw/2025-12-01/64bc8d20ade2bef879ab3e413b40de44.png", + // ProductDetailsWatermarkImageUrl: "", + // WatermarkPosition: 0, + //} + // + //shopMsgStr, _ := json.Marshal(shopMsg) + //fmt.Println(string(shopMsgStr)) + // + //goods, err := addGoods(string(taskBodyStr), string(shopMsgStr)) + //if err != nil { + // fmt.Println(err) + //} + //fmt.Println(goods) + + //toBase64 := "data:image/jpeg;base64,/9j/2wCEAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSgBBwcHCggKEwoKEygaFhooKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKP/AABEIAlgCWAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APqmiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKK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" + +} + +// info, err := dll.PddGoodsImageUpload("203c5a7ba8bd4b8488d5e26f93052642", "892ffaa86e12b7a3d8d2942b669d9aa520ad8179", +// "1177d0c36419417eba692a3fea88f611d42f0665", toBase64) +// +// //upload, err := pddGoodsImageUpload("203c5a7ba8bd4b8488d5e26f93052642", "892ffaa86e12b7a3d8d2942b669d9aa520ad8179", +// // "1177d0c36419417eba692a3fea88f611d42f0665", toBase64) +// if err != nil { +// fmt.Println(err.Error()) +// } +// fmt.Println("info", info) +//} diff --git a/proxy/dll/proxy.dll b/proxy/dll/proxy.dll index 0a317ec..c30b648 100644 Binary files a/proxy/dll/proxy.dll and b/proxy/dll/proxy.dll differ diff --git a/proxy/dll/proxy.h b/proxy/dll/proxy.h index 45dcab0..2eec6b7 100644 --- a/proxy/dll/proxy.h +++ b/proxy/dll/proxy.h @@ -120,6 +120,10 @@ extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* use // extern __declspec(dllexport) void FreeCString(char* str); +// GetVersion 获取版本 +// +extern __declspec(dllexport) char* GetVersion(void); + #ifdef __cplusplus } #endif diff --git a/proxy/proxy.dll b/proxy/proxy.dll new file mode 100644 index 0000000..ca1a1c5 Binary files /dev/null and b/proxy/proxy.dll differ diff --git a/proxy/proxy.go b/proxy/proxy.go index d0fe0aa..2786561 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -87,6 +87,23 @@ func proxyTypeManager(proxyType, username, password, machineCode string) (string } } +func proxyTypeManagerNew(proxyType, username, password, machineCode string) (string, error) { + // 获取代理列表 + proxies, err := getProxies(machineCode) + if err != nil { + return "", err + } + + // 检查是否获取到有效代理 + if len(proxies) == 0 { + return "", fmt.Errorf("未获取到有效代理") + } + // 获取代理 + proxy := randomElement(proxies) + proxy = fmt.Sprintf("http://%s", proxy) + return proxy, nil +} + /* * 构建小象代理URL * param username[string] 代理用户名 @@ -607,12 +624,30 @@ func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.cha return C.CString(proxyURL) } +// ProxyTypeManagerNew 导出函数:代理类型管理器 +// +//export ProxyTypeManagerNew +func ProxyTypeManagerNew(proxyType, username, password, machineCode *C.char) *C.char { + // C字符串转换为Go字符串 + goProxyType := C.GoString(proxyType) + goUsername := C.GoString(username) + goPassword := C.GoString(password) + goMachineCode := C.GoString(machineCode) + + // 调用代理类型管理器 + proxyURL, err := proxyTypeManagerNew(goProxyType, goUsername, goPassword, goMachineCode) + if err != nil { + errorMsg := fmt.Sprintf("ERROR: %v", err) + return C.CString(errorMsg) + } + return C.CString(proxyURL) +} + // GetMachineCode 导出函数:查询机器码 // //export GetMachineCode func GetMachineCode(tailCardSecret *C.char) *C.char { goTailCardSecret := C.GoString(tailCardSecret) - log.Printf("[DEBUG] 查询机器码调用: card=%s", goTailCardSecret) resp, err := getMachineCode(goTailCardSecret) if err != nil { @@ -629,7 +664,6 @@ func GetMachineCode(tailCardSecret *C.char) *C.char { return C.CString(errorMsg) } - log.Printf("[DEBUG] 查询机器码成功: code=%d, machine_code=%s", resp.Code, resp.Data.MachineCode) return C.CString(string(jsonData)) } @@ -703,7 +737,6 @@ func GetProxies(machineCode *C.char) *C.char { //export CheckTailCardSecretExpired func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char { goTailCardSecret := C.GoString(tailCardSecret) - log.Printf("[DEBUG] 检查卡密是否过期调用: card=%s", goTailCardSecret) isValid, err := checkTailCardSecretExpired(goTailCardSecret) if err != nil { @@ -732,8 +765,6 @@ func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char { errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) return C.CString(errorMsg) } - - log.Printf("[DEBUG] 检查卡密过期结果: is_valid=%v", isValid) return C.CString(string(jsonData)) } @@ -795,6 +826,18 @@ func FreeCString(str *C.char) { C.free(unsafe.Pointer(str)) } +// PROXY_VERSION 版本号 +const ( + PROXY_VERSION = "v2" +) + +// GetVersion 获取版本 +// +//export GetVersion +func GetVersion() *C.char { + return C.CString(PROXY_VERSION) +} + // 空main函数,编译DLL时需要 func main() { } diff --git a/proxy/proxy.h b/proxy/proxy.h new file mode 100644 index 0000000..5e896fe --- /dev/null +++ b/proxy/proxy.h @@ -0,0 +1,133 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +extern size_t _GoStringLen(_GoString_ s); +extern const char *_GoStringPtr(_GoString_ s); +#endif + +#endif + +/* Start of preamble from import "C" comments. */ + + +#line 3 "proxy.go" + +#include + +#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 +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +#include +typedef std::complex GoComplex64; +typedef std::complex 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 + + +// GetProxyHealth 导出函数:获取代理健康状态(用于调试) +// +extern __declspec(dllexport) char* GetProxyHealth(void); + +// ProxyTypeManager 导出函数:代理类型管理器 +// +extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode); + +// ProxyTypeManagerNew 导出函数:代理类型管理器 +// +extern __declspec(dllexport) char* ProxyTypeManagerNew(char* proxyType, char* username, char* password, char* machineCode); + +// GetMachineCode 导出函数:查询机器码 +// +extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret); + +// RechargeCard 导出函数:充值卡密 +// +extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode); + +// GetProxies 导出函数:获取代理服务器列表 +// +extern __declspec(dllexport) char* GetProxies(char* machineCode); + +// CheckTailCardSecretExpired 导出函数:检查卡密是否过期 +// +extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret); + +// InitProxyManager 导出函数:初始化代理管理器 +// +extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType); + +// FreeCString 导出函数:释放C字符串内存 +// +extern __declspec(dllexport) void FreeCString(char* str); + +// GetVersion 获取版本 +// +extern __declspec(dllexport) char* GetVersion(void); + +#ifdef __cplusplus +} +#endif diff --git a/proxy/proxy_so.go b/proxy/proxy_so.go index 2688756..ce00944 100644 --- a/proxy/proxy_so.go +++ b/proxy/proxy_so.go @@ -1,5 +1,7 @@ package main +import "fmt" + ///* //#cgo LDFLAGS: -ldl // @@ -697,3 +699,11 @@ package main // //func main() { //} + +func main() { + managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c") + if err != nil { + fmt.Println(err) + } + fmt.Println(managerNew) +} diff --git a/sqlite/sqLite.go b/sqlite/sqLite.go new file mode 100644 index 0000000..448528e --- /dev/null +++ b/sqlite/sqLite.go @@ -0,0 +1,342 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "time" + + _ "modernc.org/sqlite" +) + +// 用户模型 +type User struct { + ID int + Name string + Email string + Age int + CreatedAt time.Time +} + +// 数据库连接 +var db *sql.DB + +func main() { + var err error + + // 1. 初始化数据库连接 + db, err = initDatabase("test.db") + if err != nil { + log.Fatal("初始化数据库失败:", err) + } + defer db.Close() + + fmt.Println("SQLite数据库CRUD示例") + fmt.Println("====================") + + //// 2. 创建用户表 + //err = createTable() + //if err != nil { + // log.Fatal("创建表失败:", err) + //} + //fmt.Println("✓ 用户表创建成功") + // + //// 3. 创建用户 + //user1 := User{ + // Name: "张三", + // Email: "zhangsan@example.com", + // Age: 25, + //} + // + //id, err := createUser(user1) + //if err != nil { + // log.Fatal("创建用户失败:", err) + //} + //fmt.Printf("✓ 创建用户成功,ID: %d\n", id) + // + user2 := User{ + Name: "李四", + Email: "lisi@example.com", + Age: 30, + } + id2, _ := createUser(user2) + fmt.Printf("✓ 创建用户成功,ID: %d\n", id2) + + //// 4. 查询单个用户 + //fmt.Println("\n查询用户ID=1:") + //user, err := getUserByID(1) + //if err != nil { + // log.Fatal("查询用户失败:", err) + //} + //printUser(user) + + // 5. 查询所有用户 + fmt.Println("\n查询所有用户:") + users, err := getAllUsers() + if err != nil { + log.Fatal("查询所有用户失败:", err) + } + for _, u := range users { + printUser(u) + } + + //// 6. 更新用户 + //fmt.Println("\n更新用户ID=1的信息:") + //updateData := map[string]interface{}{ + // "name": "张三丰", + // "email": "zhangsanfeng@example.com", + // "age": 28, + //} + //rowsAffected, err := updateUser(1, updateData) + //if err != nil { + // log.Fatal("更新用户失败:", err) + //} + //fmt.Printf("✓ 更新成功,影响行数: %d\n", rowsAffected) + // + //// 7. 再次查询验证更新 + //updatedUser, _ := getUserByID(1) + //printUser(updatedUser) + // + //// 8. 删除用户 + //fmt.Println("\n删除用户ID=2:") + //rowsAffected, err = deleteUser(2) + //if err != nil { + // log.Fatal("删除用户失败:", err) + //} + //fmt.Printf("✓ 删除成功,影响行数: %d\n", rowsAffected) + // + //// 9. 查询删除后的用户列表 + //fmt.Println("\n删除后的所有用户:") + //remainingUsers, _ := getAllUsers() + //if len(remainingUsers) == 0 { + // fmt.Println("没有用户数据") + //} + //for _, u := range remainingUsers { + // printUser(u) + //} + // + //// 10. 批量插入示例 + //fmt.Println("\n批量插入用户:") + //err = batchCreateUsers() + //if err != nil { + // log.Fatal("批量插入失败:", err) + //} + // + //// 11. 分页查询示例 + //fmt.Println("\n分页查询(每页2条):") + //pageSize := 2 + //fmt.Println("第1页:") + //page1Users, _ := getUsersByPage(1, pageSize) + //for _, u := range page1Users { + // printUser(u) + //} + // + //fmt.Println("\n第2页:") + //page2Users, _ := getUsersByPage(2, pageSize) + //for _, u := range page2Users { + // printUser(u) + //} + // + //// 12. 统计用户数量 + //count, _ := countUsers() + //fmt.Printf("\n用户总数: %d\n", count) +} + +// 初始化数据库连接 +func initDatabase(dbPath string) (*sql.DB, error) { + db, err := sql.Open("sqlite", dbPath) + if err != nil { + return nil, err + } + + // 设置连接池参数 + db.SetMaxOpenConns(10) + db.SetMaxIdleConns(5) + db.SetConnMaxLifetime(5 * time.Minute) + + // 验证连接 + err = db.Ping() + if err != nil { + return nil, err + } + + return db, nil +} + +// 创建用户表 +func createTable() error { + query := ` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL, + age INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); + CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at); + ` + + _, err := db.Exec(query) + return err +} + +// 创建用户(返回生成的ID) +func createUser(user User) (int64, error) { + query := `INSERT INTO users (name, email, age) VALUES (?, ?, ?)` + result, err := db.Exec(query, user.Name, user.Email, user.Age) + if err != nil { + return 0, err + } + + id, err := result.LastInsertId() + if err != nil { + return 0, err + } + + return id, nil +} + +// 根据ID查询用户 +func getUserByID(id int) (User, error) { + var user User + query := `SELECT id, name, email, age, created_at FROM users WHERE id = ?` + row := db.QueryRow(query, id) + + err := row.Scan(&user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt) + if err != nil { + return User{}, err + } + + return user, nil +} + +// 查询所有用户 +func getAllUsers() ([]User, error) { + query := `SELECT id, name, email, age, created_at FROM users ORDER BY id` + rows, err := db.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + + var users []User + for rows.Next() { + var user User + err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt) + if err != nil { + return nil, err + } + users = append(users, user) + } + + return users, nil +} + +// 更新用户 +func updateUser(id int, updates map[string]interface{}) (int64, error) { + // 构建SQL语句 + query := "UPDATE users SET " + args := []interface{}{} + i := 0 + + for key, value := range updates { + if i > 0 { + query += ", " + } + query += key + " = ?" + args = append(args, value) + i++ + } + + query += " WHERE id = ?" + args = append(args, id) + + result, err := db.Exec(query, args...) + if err != nil { + return 0, err + } + + return result.RowsAffected() +} + +// 删除用户 +func deleteUser(id int) (int64, error) { + query := `DELETE FROM users WHERE id = ?` + result, err := db.Exec(query, id) + if err != nil { + return 0, err + } + + return result.RowsAffected() +} + +// 批量创建用户 +func batchCreateUsers() error { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + stmt, err := tx.Prepare("INSERT INTO users (name, email, age) VALUES (?, ?, ?)") + if err != nil { + return err + } + defer stmt.Close() + + users := []User{ + {Name: "王五", Email: "wangwu@example.com", Age: 22}, + {Name: "赵六", Email: "zhaoliu@example.com", Age: 35}, + {Name: "孙七", Email: "sunqi@example.com", Age: 28}, + } + + for _, user := range users { + _, err := stmt.Exec(user.Name, user.Email, user.Age) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// 分页查询用户 +func getUsersByPage(page, pageSize int) ([]User, error) { + offset := (page - 1) * pageSize + query := `SELECT id, name, email, age, created_at FROM users + ORDER BY id LIMIT ? OFFSET ?` + + rows, err := db.Query(query, pageSize, offset) + if err != nil { + return nil, err + } + defer rows.Close() + + var users []User + for rows.Next() { + var user User + err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt) + if err != nil { + return nil, err + } + users = append(users, user) + } + + return users, nil +} + +// 统计用户数量 +func countUsers() (int, error) { + var count int + query := `SELECT COUNT(*) FROM users` + err := db.QueryRow(query).Scan(&count) + return count, err +} + +// 打印用户信息 +func printUser(user User) { + fmt.Printf("ID: %d, 姓名: %s, 邮箱: %s, 年龄: %d, 创建时间: %s\n", + user.ID, user.Name, user.Email, user.Age, user.CreatedAt.Format("2006-01-02 15:04:05")) +} diff --git a/test.db b/test.db new file mode 100644 index 0000000..1961c86 Binary files /dev/null and b/test.db differ diff --git a/timer/timer.go b/timer/timer.go new file mode 100644 index 0000000..9bfccef --- /dev/null +++ b/timer/timer.go @@ -0,0 +1,252 @@ +package main + +import ( + "container/heap" + "fmt" + "sync" + "time" +) + +//func main() { +// // 创建一个每秒触发一次的定时器 +// ticker := time.NewTicker(1 * time.Second) +// defer ticker.Stop() // 确保资源被释放 +// +// fmt.Println("定时器启动,每秒执行一次...") +// +// for i := 1; i <= 5; i++ { +// <-ticker.C // 等待定时器触发 +// fmt.Printf("执行第 %d 次,当前时间: %v\n", i, time.Now().Format("15:04:05")) +// } +// +// fmt.Println("定时器执行完成") +//} + +//func main() { +// ticker := time.NewTicker(500 * time.Millisecond) +// done := make(chan bool) +// +// // 5秒后停止定时器 +// go func() { +// time.Sleep(5 * time.Second) +// ticker.Stop() +// done <- true +// fmt.Println("定时器已停止") +// }() +// +// fmt.Println("定时器启动,每500毫秒执行一次...") +// +// for { +// select { +// case t := <-ticker.C: +// fmt.Printf("定时任务执行,时间: %v\n", t.Format("15:04:05.000")) +// case <-done: +// return +// } +// } +//} + +//func main() { +// fmt.Println("程序启动:", time.Now().Format("15:04:05")) +// +// // 创建一个3秒后触发的单次定时器 +// timer := time.NewTimer(3 * time.Second) +// +// fmt.Println("等待3秒...") +// +// // 阻塞直到定时器触发 +// <-timer.C +// fmt.Println("3秒时间到!", time.Now().Format("15:04:05")) +// +// // 定时器重置 +// fmt.Println("\n重置定时器为2秒...") +// timer.Reset(2 * time.Second) +// <-timer.C +// fmt.Println("2秒时间到!", time.Now().Format("15:04:05")) +// +// // 停止定时器示例 +// timer2 := time.NewTimer(5 * time.Second) +// go func() { +// time.Sleep(2 * time.Second) +// if timer2.Stop() { +// fmt.Println("定时器在触发前被停止了") +// } +// }() +// +// select { +// case <-timer2.C: +// fmt.Println("定时器正常触发") +// case <-time.After(3 * time.Second): +// fmt.Println("检查完成") +// } +//} + +// 延迟任务 +type DelayedTask struct { + ExecuteAt time.Time + Task func() + Index int // heap需要的索引 +} + +// 延迟队列 +type DelayQueue struct { + tasks []*DelayedTask + mu sync.Mutex + cond *sync.Cond + done chan struct{} +} + +func NewDelayQueue() *DelayQueue { + dq := &DelayQueue{ + tasks: make([]*DelayedTask, 0), + done: make(chan struct{}), + } + dq.cond = sync.NewCond(&dq.mu) + return dq +} + +// heap接口实现 +func (dq *DelayQueue) Len() int { return len(dq.tasks) } +func (dq *DelayQueue) Less(i, j int) bool { + return dq.tasks[i].ExecuteAt.Before(dq.tasks[j].ExecuteAt) +} +func (dq *DelayQueue) Swap(i, j int) { + dq.tasks[i], dq.tasks[j] = dq.tasks[j], dq.tasks[i] + dq.tasks[i].Index = i + dq.tasks[j].Index = j +} + +func (dq *DelayQueue) Push(x interface{}) { + task := x.(*DelayedTask) + task.Index = len(dq.tasks) + dq.tasks = append(dq.tasks, task) +} + +func (dq *DelayQueue) Pop() interface{} { + n := len(dq.tasks) + task := dq.tasks[n-1] + dq.tasks[n-1] = nil // 避免内存泄漏 + dq.tasks = dq.tasks[:n-1] + return task +} + +// 添加延迟任务 +func (dq *DelayQueue) AddTask(delay time.Duration, task func()) { + dq.mu.Lock() + defer dq.mu.Unlock() + + heap.Push(dq, &DelayedTask{ + ExecuteAt: time.Now().Add(delay), + Task: task, + }) + + // 通知等待的 goroutine 有新的任务 + dq.cond.Signal() +} + +// 停止队列处理器 +func (dq *DelayQueue) Stop() { + close(dq.done) + dq.cond.Broadcast() +} + +// 启动队列处理器 +func (dq *DelayQueue) Start() { + go func() { + for { + select { + case <-dq.done: + return + default: + dq.processTasks() + } + } + }() +} + +func (dq *DelayQueue) processTasks() { + dq.mu.Lock() + + // 等待直到有任务 + for len(dq.tasks) == 0 { + dq.cond.Wait() + + // 检查是否停止 + select { + case <-dq.done: + dq.mu.Unlock() + return + default: + } + } + + now := time.Now() + task := dq.tasks[0] // 查看堆顶元素 + + if now.Before(task.ExecuteAt) { + // 还未到执行时间,等待 + waitDuration := task.ExecuteAt.Sub(now) + + // 使用定时器等待 + timer := time.NewTimer(waitDuration) + + // 释放锁并等待 + dq.mu.Unlock() + + select { + case <-timer.C: + // 时间到了,重新获取锁处理任务 + dq.mu.Lock() + // 再次检查任务是否还在(可能被其他操作移除) + if len(dq.tasks) > 0 && dq.tasks[0] == task { + heap.Pop(dq) + dq.mu.Unlock() + task.Task() + } else { + dq.mu.Unlock() + } + return + case <-dq.done: + timer.Stop() + return + } + } + + // 立即执行任务 + heap.Pop(dq) + dq.mu.Unlock() + + // 执行任务 + task.Task() +} + +func main() { + queue := NewDelayQueue() + queue.Start() + + fmt.Println("延迟队列启动,当前时间:", time.Now().Format("15:04:05")) + + // 添加延迟任务 + queue.AddTask(2*time.Second, func() { + fmt.Printf("[延迟2秒] 执行时间: %v\n", time.Now().Format("15:04:05")) + }) + + queue.AddTask(5*time.Second, func() { + fmt.Printf("[延迟5秒] 执行时间: %v\n", time.Now().Format("15:04:05")) + }) + + queue.AddTask(1*time.Second, func() { + fmt.Printf("[延迟1秒] 执行时间: %v\n", time.Now().Format("15:04:05")) + }) + + queue.AddTask(3*time.Second, func() { + fmt.Printf("[延迟3秒] 执行时间: %v\n", time.Now().Format("15:04:05")) + }) + + // 等待所有任务执行 + time.Sleep(10 * time.Second) + + // 停止队列 + queue.Stop() + fmt.Println("程序结束") +} diff --git a/walkingWithBooks.go b/walkingWithBooks.go new file mode 100644 index 0000000..e4f7664 --- /dev/null +++ b/walkingWithBooks.go @@ -0,0 +1,2210 @@ +package main + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "gopkg.in/yaml.v3" + "image" + "image/color" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + "unsafe" + + "gioui.org/app" + "gioui.org/io/key" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" +) + +// ============================ 常量定义 ============================ +const ( + ColorReset = "\033[0m" + ColorRed = "\033[31m" + ColorGreen = "\033[32m" + ColorYellow = "\033[33m" + ColorBlue = "\033[34m" + ColorMagenta = "\033[35m" + ColorCyan = "\033[36m" + ColorWhite = "\033[37m" + ColorGray = "\033[90m" + + ColorBoldRed = "\033[1;31m" + ColorBoldGreen = "\033[1;32m" + ColorBoldYellow = "\033[1;33m" + ColorBoldBlue = "\033[1;34m" + + ColorBgRed = "\033[41m" + ColorBgGreen = "\033[42m" +) + +// ============================ 结构体定义 ============================ +type VersionConfig struct { + CsvVersion string `json:"csv"` + KongfzVersion string `json:"kongfz"` + LoggerVersion string `json:"logger"` + ModuleErpVersion string `json:"module-erp"` + ModuleKongfzVersion string `json:"module-kongfz"` + ModuleTaskPoolVersion string `json:"module-taskPool"` + ModuleVerifyPriceVersion string `json:"module-verifyPrice"` + ModuleCenterBookVersion string `json:"module-centerBook"` + ModuleLoginVersion string `json:"module-login"` + PicToolVersion string `json:"picTool"` + ProxyVersion string `json:"proxy"` +} + +type VerifyPriceYAML struct { + VerifyPriceLatestVersion string `yaml:"verifyPriceLatestVersion"` +} + +type VersionInfo struct { + LatestVersion string `json:"latestVersion"` + HistoricalVersions []string `json:"historicalVersions"` + VerifyPriceLatestVersion string `json:"verifyPriceLatestVersion"` +} + +// ============================ Gio GUI 结构体 ============================ +type UpdaterApp struct { + window *app.Window + theme *material.Theme + + // 小部件 + progress float32 + statusText string + actionBtn widget.Clickable + showConsole bool + consoleText string + logEntries []string + + isPaused bool + isUpdating bool + isComplete bool + isChecking bool + isDownloading bool + + totalSize int64 + downloaded int64 + currentSpeed float64 + startTime time.Time + + // 更新状态 + currentFile string + totalFiles int + completedFiles int + + // 数据 + versionsJSON *VersionConfig + verifyPriceVersion *VersionInfo + localConfig VerifyPriceYAML + currentDir string + yamlPath string + + // 布局 + ops op.Ops + scroll widget.List +} + +// ============================ 控制台输出函数 ============================ +func printInfo(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorCyan + "[信息] " + message + ColorReset + "\n") + if globalApp != nil { + globalApp.addLog("[信息] " + message) + } +} + +func printSuccess(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorGreen + "[成功] " + message + ColorReset + "\n") + if globalApp != nil { + globalApp.addLog("[成功] " + message) + } +} + +func printWarning(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorYellow + "[警告] " + message + ColorReset + "\n") + if globalApp != nil { + globalApp.addLog("[警告] " + message) + } +} + +func printError(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorRed + "[错误] " + message + ColorReset + "\n") + if globalApp != nil { + globalApp.addLog("[错误] " + message) + } +} + +func printDebug(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorGray + "[调试] " + message + ColorReset + "\n") + if globalApp != nil { + globalApp.addLog("[调试] " + message) + } +} + +func printBanner(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorBoldBlue + "== " + message + " ==\n" + ColorReset) + if globalApp != nil { + globalApp.addLog("== " + message + " ==") + } +} + +func printDownload(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + fmt.Printf(ColorBoldGreen + "[下载] " + message + ColorReset + "\n") + if globalApp != nil { + globalApp.addLog("[下载] " + message) + } +} + +func printVersionInfo(label, version string) { + message := fmt.Sprintf("%-30s: %s", label, version) + fmt.Printf(ColorBoldYellow+"%-30s: "+ColorCyan+"%s"+ColorReset+"\n", label, version) + if globalApp != nil { + globalApp.addLog(message) + } +} + +// ============================ 全局变量 ============================ +var globalApp *UpdaterApp + +// ============================ Gio GUI 方法 ============================ +// NewUpdaterApp 创建新的更新器应用 +func NewUpdaterApp() *UpdaterApp { + // 使用新的 Window 创建方式 + window := new(app.Window) + + // 设置窗口选项 + window.Option( + app.Title("核价软件更新器"), + app.Size(unit.Dp(800), unit.Dp(600)), + app.MinSize(unit.Dp(600), unit.Dp(400)), + ) + + // 创建主题 + theme := material.NewTheme() + + appNew := &UpdaterApp{ + window: window, + theme: theme, + progress: 0.0, + statusText: "初始化更新器...", + isPaused: false, + isUpdating: true, + startTime: time.Now(), + showConsole: true, + totalFiles: 12, // DLL文件数量 + 主程序 + scroll: widget.List{ + List: layout.List{ + Axis: layout.Vertical, + }, + }, + } + + globalApp = appNew + return appNew +} + +// addLog 添加日志条目 +func (u *UpdaterApp) addLog(message string) { + u.logEntries = append(u.logEntries, message) + // 保持最后100条日志 + if len(u.logEntries) > 100 { + u.logEntries = u.logEntries[len(u.logEntries)-100:] + } + u.window.Invalidate() +} + +// updateStatus 更新状态文本 +func (u *UpdaterApp) updateStatus(status string) { + u.statusText = status + u.addLog(status) + u.window.Invalidate() +} + +// updateProgress 更新进度 +func (u *UpdaterApp) updateProgress(progress float32) { + u.progress = progress + u.window.Invalidate() +} + +// updateFileProgress 更新文件进度 +func (u *UpdaterApp) updateFileProgress(currentFile string, downloaded, totalSize int64) { + u.currentFile = currentFile + u.downloaded = downloaded + u.totalSize = totalSize + + if totalSize > 0 { + u.progress = float32(downloaded) / float32(totalSize) + } + + // 计算速度 + elapsed := time.Since(u.startTime).Seconds() + if elapsed > 0 && downloaded > 0 { + u.currentSpeed = float64(downloaded) / elapsed / 1024 / 1024 + } + + u.window.Invalidate() +} + +// updateFileCount 更新文件计数 +func (u *UpdaterApp) updateFileCount(completed int) { + u.completedFiles = completed + + // 计算总体进度(文件计数占30%,文件内进度占70%) + fileProgress := float32(completed) / float32(u.totalFiles) + overallProgress := 0.3*fileProgress + 0.7*u.progress + u.updateProgress(overallProgress) + + u.window.Invalidate() +} + +// Run 运行GUI主循环 +func (u *UpdaterApp) Run() error { + // 启动更新过程 + go u.startUpdateProcess() + + var ops op.Ops + for { + ev := u.window.Event() + switch e := ev.(type) { + case app.DestroyEvent: + return e.Err + + case app.FrameEvent: + gtx := app.NewContext(&ops, e) + u.handleEvents(gtx) + u.drawUI(gtx) + e.Frame(gtx.Ops) + + case key.Event: + if e.Name == "C" && e.Modifiers.Contain(key.ModCtrl) { + u.showConsole = !u.showConsole + u.window.Invalidate() + } + } + } +} + +// handleEvents 处理用户事件 +func (u *UpdaterApp) handleEvents(gtx layout.Context) { + // 处理按钮点击 + if u.actionBtn.Clicked(gtx) { + if u.isComplete { + // 启动核价软件 + u.startVerifyPriceApp() + } else if u.isPaused { + // 继续更新 + u.isPaused = false + u.updateStatus("继续更新...") + } else { + // 暂停更新 + u.isPaused = true + u.updateStatus("更新已暂停") + } + } +} + +// drawUI 绘制用户界面 +func (u *UpdaterApp) drawUI(gtx layout.Context) layout.Dimensions { + // 背景色 + paint.Fill(gtx.Ops, color.NRGBA{R: 245, G: 245, B: 245, A: 255}) + + return layout.Flex{ + Axis: layout.Vertical, + Spacing: layout.SpaceBetween, + }.Layout(gtx, + // 标题栏 + layout.Rigid(u.drawHeader), + + // 主内容区域 + layout.Flexed(1, u.drawMainContent), + + // 底部控制栏 + layout.Rigid(u.drawFooter), + ) +} + +// drawHeader 绘制标题栏 +func (u *UpdaterApp) drawHeader(gtx layout.Context) layout.Dimensions { + return layout.Inset{ + Top: unit.Dp(10), + Bottom: unit.Dp(10), + Left: unit.Dp(20), + Right: unit.Dp(20), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Horizontal, + Spacing: layout.SpaceBetween, + Alignment: layout.Middle, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + title := material.H4(u.theme, "核价软件更新器") + title.Color = color.NRGBA{R: 0, G: 100, B: 200, A: 255} + return title.Layout(gtx) + }), + + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + // 状态文本和颜色的逻辑保持不变 + statusText := "就绪" + if u.isChecking { + statusText = "检查中" + } else if u.isDownloading { + statusText = "下载中" + } else if u.isPaused { + statusText = "已暂停" + } else if u.isComplete { + statusText = "已完成" + } + + // 根据状态设置文本颜色 + statusLabel := material.Body2(u.theme, statusText) + if u.isPaused { + statusLabel.Color = color.NRGBA{R: 241, G: 196, B: 15, A: 255} // 黄色 + } else if u.isDownloading { + statusLabel.Color = color.NRGBA{R: 52, G: 152, B: 219, A: 255} // 蓝色 + } else if u.isChecking { + statusLabel.Color = color.NRGBA{R: 155, G: 89, B: 182, A: 255} // 紫色 + } else if u.isComplete { + statusLabel.Color = color.NRGBA{R: 46, G: 204, B: 113, A: 255} // 绿色 + } else { + statusLabel.Color = color.NRGBA{R: 120, G: 120, B: 120, A: 255} // 灰色 + } + + return material.Body2(u.theme, statusText).Layout(gtx) + }), + ) + }) +} + +// drawMainContent 绘制主内容区域 +func (u *UpdaterApp) drawMainContent(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Vertical, + Spacing: layout.SpaceSides, + }.Layout(gtx, + // 状态信息区域 + layout.Rigid(u.drawStatusInfo), + + // 进度条区域 + layout.Rigid(u.drawProgressBar), + + // 文件信息区域 + layout.Rigid(u.drawFileInfo), + + // 日志区域(可折叠) + layout.Flexed(1, u.drawLogArea), + ) +} + +// drawStatusInfo 绘制状态信息 +func (u *UpdaterApp) drawStatusInfo(gtx layout.Context) layout.Dimensions { + return layout.Inset{ + Left: unit.Dp(20), + Right: unit.Dp(20), + Top: unit.Dp(10), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Vertical, + Spacing: layout.SpaceEnd, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + status := material.H6(u.theme, u.statusText) + status.Color = color.NRGBA{R: 60, G: 60, B: 60, A: 255} + return status.Layout(gtx) + }), + + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if u.currentFile != "" { + fileInfo := material.Caption(u.theme, "当前文件: "+u.currentFile) + return fileInfo.Layout(gtx) + } + return layout.Dimensions{} + }), + ) + }) +} + +// drawProgressBar 绘制进度条 +func (u *UpdaterApp) drawProgressBar(gtx layout.Context) layout.Dimensions { + return layout.Inset{ + Left: unit.Dp(20), + Right: unit.Dp(20), + Top: unit.Dp(15), + Bottom: unit.Dp(15), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + // 进度条背景 + barHeight := gtx.Dp(unit.Dp(30)) + barWidth := gtx.Constraints.Max.X + + // 圆角矩形背景 + rrect := clip.UniformRRect( + image.Rect(0, 0, barWidth, barHeight), + gtx.Dp(unit.Dp(6)), + ) + paint.FillShape(gtx.Ops, color.NRGBA{R: 230, G: 230, B: 230, A: 255}, rrect.Op(gtx.Ops)) + + // 进度条前景 + progressWidth := int(float32(barWidth) * u.progress) + if progressWidth > 0 { + progressRrect := clip.UniformRRect( + image.Rect(0, 0, progressWidth, barHeight), + gtx.Dp(unit.Dp(6)), + ) + + // 根据进度选择颜色 + var barColor color.NRGBA + if u.isComplete { + barColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255} // 绿色 + } else if u.progress < 0.3 { + barColor = color.NRGBA{R: 231, G: 76, B: 60, A: 255} // 红色 + } else if u.progress < 0.7 { + barColor = color.NRGBA{R: 241, G: 196, B: 15, A: 255} // 黄色 + } else { + barColor = color.NRGBA{R: 52, G: 152, B: 219, A: 255} // 蓝色 + } + + paint.FillShape(gtx.Ops, barColor, progressRrect.Op(gtx.Ops)) + } + + // 使用 Flex 布局来居中文本 + return layout.Flex{ + Axis: layout.Vertical, + Spacing: layout.SpaceSides, + Alignment: layout.Middle, // 水平居中 + }.Layout(gtx, + // 占位空间,让文本垂直居中 + layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + return layout.Dimensions{} + }), + // 文本行 + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Horizontal, + Spacing: layout.SpaceSides, + Alignment: layout.Middle, // 水平居中 + }.Layout(gtx, + // 占位空间,让文本水平居中 + layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + return layout.Dimensions{} + }), + // 实际文本 + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + text := material.Body1(u.theme, fmt.Sprintf("%.1f%%", u.progress*100)) + if u.progress < 0.5 { + text.Color = color.NRGBA{R: 60, G: 60, B: 60, A: 255} + } else { + text.Color = color.NRGBA{R: 255, G: 255, B: 255, A: 255} + } + return text.Layout(gtx) + }), + // 占位空间,让文本水平居中 + layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + return layout.Dimensions{} + }), + ) + }), + // 占位空间,让文本垂直居中 + layout.Flexed(50, func(gtx layout.Context) layout.Dimensions { + return layout.Dimensions{} + }), + ) + }) +} + +// drawFileInfo 绘制文件信息 +func (u *UpdaterApp) drawFileInfo(gtx layout.Context) layout.Dimensions { + return layout.Inset{ + Left: unit.Dp(20), + Right: unit.Dp(20), + Bottom: unit.Dp(10), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Horizontal, + Spacing: layout.SpaceAround, + Alignment: layout.Middle, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + fileCount := fmt.Sprintf("文件: %d/%d", u.completedFiles, u.totalFiles) + return material.Caption(u.theme, fileCount).Layout(gtx) + }), + + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + var sizeInfo string + if u.totalSize > 0 { + sizeInfo = fmt.Sprintf("大小: %.2f/%.2f MB", + float64(u.downloaded)/(1024*1024), + float64(u.totalSize)/(1024*1024)) + } else { + sizeInfo = "大小: 计算中..." + } + return material.Caption(u.theme, sizeInfo).Layout(gtx) + }), + + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + speedInfo := fmt.Sprintf("速度: %.2f MB/s", u.currentSpeed) + return material.Caption(u.theme, speedInfo).Layout(gtx) + }), + ) + }) +} + +// drawLogArea 绘制日志区域 +func (u *UpdaterApp) drawLogArea(gtx layout.Context) layout.Dimensions { + if !u.showConsole { + return layout.Dimensions{} + } + + return layout.Inset{ + Left: unit.Dp(20), + Right: unit.Dp(20), + Top: unit.Dp(10), + Bottom: unit.Dp(10), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return material.List(u.theme, &u.scroll).Layout(gtx, len(u.logEntries), func(gtx layout.Context, index int) layout.Dimensions { + return layout.Inset{ + Bottom: unit.Dp(4), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + logText := material.Body2(u.theme, u.logEntries[index]) + logText.Color = color.NRGBA{R: 100, G: 100, B: 100, A: 255} + + // 根据日志类型设置颜色 + logTextStr := u.logEntries[index] + if strings.Contains(logTextStr, "[错误]") { + logText.Color = color.NRGBA{R: 231, G: 76, B: 60, A: 255} + } else if strings.Contains(logTextStr, "[警告]") { + logText.Color = color.NRGBA{R: 241, G: 196, B: 15, A: 255} + } else if strings.Contains(logTextStr, "[成功]") { + logText.Color = color.NRGBA{R: 46, G: 204, B: 113, A: 255} + } else if strings.Contains(logTextStr, "[信息]") { + logText.Color = color.NRGBA{R: 52, G: 152, B: 219, A: 255} + } else if strings.Contains(logTextStr, "[下载]") { + logText.Color = color.NRGBA{R: 155, G: 89, B: 182, A: 255} + } + + return logText.Layout(gtx) + }) + }) + }) +} + +// drawFooter 绘制底部控制栏 +func (u *UpdaterApp) drawFooter(gtx layout.Context) layout.Dimensions { + return layout.Inset{ + Top: unit.Dp(10), + Bottom: unit.Dp(15), + Left: unit.Dp(20), + Right: unit.Dp(20), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Horizontal, + Spacing: layout.SpaceBetween, + Alignment: layout.Middle, + }.Layout(gtx, + //layout.Rigid(func(gtx layout.Context) layout.Dimensions { + // hint := material.Caption(u.theme, "提示: 按 Ctrl+C 切换日志显示") + // hint.Color = color.NRGBA{R: 150, G: 150, B: 150, A: 255} + // return hint.Layout(gtx) + //}), + + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + var btnText string + var btnColor color.NRGBA + + if u.isComplete { + btnText = "启动核价软件" + btnColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255} + } else if u.isPaused { + btnText = "继续更新" + btnColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255} + } else { + btnText = "暂停更新" + btnColor = color.NRGBA{R: 241, G: 196, B: 15, A: 255} + } + + btn := material.Button(u.theme, &u.actionBtn, btnText) + btn.CornerRadius = unit.Dp(8) + btn.TextSize = unit.Sp(14) + btn.Background = btnColor + btn.Color = color.NRGBA{R: 255, G: 255, B: 255, A: 255} + btn.Inset = layout.UniformInset(unit.Dp(12)) + + return btn.Layout(gtx) + }), + ) + }) +} + +// ============================ 更新过程 ============================ +func (u *UpdaterApp) startUpdateProcess() { + // 初始化 + u.updateStatus("正在初始化...") + u.updateProgress(0.05) + + // 获取当前目录 + currentDir, err := os.Getwd() + if err != nil { + u.updateStatus(fmt.Sprintf("获取当前目录失败: %v", err)) + return + } + u.currentDir = currentDir + u.yamlPath = filepath.Join(currentDir, "version.yaml") + + // 步骤1: 检查核价软件版本 + u.isChecking = true + u.updateStatus("正在检查核价软件版本...") + u.updateProgress(0.1) + u.verifyPriceVersion, err = getVerifyPriceVersionJSON() + if err != nil { + u.updateStatus(fmt.Sprintf("读取核价软件版本失败: %v", err)) + u.isChecking = false + return + } + u.isChecking = false + + // 步骤2: 读取本地配置 + u.updateStatus("正在读取本地配置...") + u.updateProgress(0.15) + data, err := ioutil.ReadFile(u.yamlPath) + if err == nil { + yaml.Unmarshal(data, &u.localConfig) + } else { + u.localConfig = VerifyPriceYAML{} + } + + // 步骤3: 检查并下载主程序 + u.updateStatus("检查核价软件主程序...") + u.updateProgress(0.2) + err = u.checkAndDownloadMainApp() + if err != nil { + u.updateStatus(fmt.Sprintf("主程序处理失败: %v", err)) + } + + // 步骤4: 检查DLL组件版本 + u.isChecking = true + u.updateStatus("正在检查DLL组件版本...") + u.updateProgress(0.3) + u.versionsJSON, err = getVersionsJSON() + if err != nil { + u.updateStatus(fmt.Sprintf("读取DLL版本失败: %v", err)) + u.isChecking = false + return + } + u.isChecking = false + + // 步骤5: 下载所有DLL文件 + u.isDownloading = true + u.updateStatus("开始更新DLL组件...") + u.updateProgress(0.4) + err = u.updateAllDLLs() + if err != nil { + u.updateStatus(fmt.Sprintf("DLL更新失败: %v", err)) + } + u.isDownloading = false + + // 步骤6: 完成 + u.updateStatus("所有更新已完成!") + u.updateProgress(1.0) + u.isComplete = true + u.isUpdating = false + + // 自动启动核价软件(3秒后) + go func() { + time.Sleep(3 * time.Second) + if u.isComplete { + u.startVerifyPriceApp() + } + }() +} + +func (u *UpdaterApp) checkAndDownloadMainApp() error { + exePath := filepath.Join(u.currentDir, "VerifyPriceApp.exe") + + if _, err := os.Stat(exePath); os.IsNotExist(err) { + // 文件缺失,下载 + u.updateStatus("发现核价软件缺失,开始下载...") + url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", + u.verifyPriceVersion.VerifyPriceLatestVersion) + err = u.downloadWithProgress(url, exePath, "VerifyPriceApp.exe") + if err != nil { + return fmt.Errorf("下载失败: %v", err) + } + u.updateStatus("核价软件下载完成") + u.localConfig.VerifyPriceLatestVersion = u.verifyPriceVersion.VerifyPriceLatestVersion + writeYAML(u.yamlPath, u.localConfig) + } else { + // 检查版本 + if u.verifyPriceVersion.VerifyPriceLatestVersion != u.localConfig.VerifyPriceLatestVersion { + u.updateStatus("发现新版本,开始更新...") + url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", + u.verifyPriceVersion.VerifyPriceLatestVersion) + err = u.downloadWithProgress(url, exePath, "VerifyPriceApp.exe") + if err != nil { + return fmt.Errorf("更新失败: %v", err) + } + u.updateStatus("核价软件更新完成") + u.localConfig.VerifyPriceLatestVersion = u.verifyPriceVersion.VerifyPriceLatestVersion + writeYAML(u.yamlPath, u.localConfig) + } else { + u.updateStatus("核价软件已是最新版本") + } + } + + u.completedFiles++ + u.updateFileCount(u.completedFiles) + + return nil +} + +func (u *UpdaterApp) updateAllDLLs() error { + dlls := []struct { + name string + version string + checker func(string) (string, error) + }{ + {"csv.dll", u.versionsJSON.CsvVersion, csvDllVersion}, + {"kongfz.dll", u.versionsJSON.KongfzVersion, kongfzDllVersion}, + {"logger.dll", u.versionsJSON.LoggerVersion, loggerDllVersion}, + {"module-centerBook.dll", u.versionsJSON.ModuleCenterBookVersion, moduleCenterBookDllVersion}, + {"module-login.dll", u.versionsJSON.ModuleLoginVersion, moduleLoginDllVersion}, + {"module-erp.dll", u.versionsJSON.ModuleErpVersion, moduleErpDllVersion}, + {"module-kongfz.dll", u.versionsJSON.ModuleKongfzVersion, moduleKongfzDllVersion}, + {"module-taskPool.dll", u.versionsJSON.ModuleTaskPoolVersion, moduleTaskPoolDllVersion}, + {"module-verifyPrice.dll", u.versionsJSON.ModuleVerifyPriceVersion, moduleVerifyPriceDllVersion}, + {"picTool.dll", u.versionsJSON.PicToolVersion, picToolDllVersion}, + {"proxy.dll", u.versionsJSON.ProxyVersion, proxyDllVersion}, + } + + for i, dll := range dlls { + // 检查是否暂停 + for u.isPaused { + time.Sleep(100 * time.Millisecond) + } + + u.updateStatus(fmt.Sprintf("检查 %s...", dll.name)) + u.currentFile = dll.name + + // 检查当前版本 + currentVersion, err := dll.checker(u.currentDir) + if err != nil { + u.updateStatus(fmt.Sprintf("检查 %s 失败: %v", dll.name, err)) + continue + } + + // 如果需要更新 + if dll.version != currentVersion && currentVersion != "" { + u.updateStatus(fmt.Sprintf("更新 %s...", dll.name)) + dllPath := filepath.Join(u.currentDir, "dll", dll.name) + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, dll.name, "") + if err != nil { + u.updateStatus(fmt.Sprintf("更新 %s 失败: %v", dll.name, err)) + } else { + u.updateStatus(fmt.Sprintf("%s 更新完成", dll.name)) + } + } else { + u.updateStatus(fmt.Sprintf("%s 已是最新版本", dll.name)) + } + + // 更新进度 + u.completedFiles++ + progress := 0.4 + 0.5*(float32(i+1)/float32(len(dlls))) + u.updateProgress(progress) + u.updateFileCount(u.completedFiles) + + // 短暂暂停以避免UI卡顿 + time.Sleep(100 * time.Millisecond) + } + + return nil +} + +func (u *UpdaterApp) downloadWithProgress(url, filePath, fileName string) error { + u.currentFile = fileName + u.startTime = time.Now() + u.downloaded = 0 + + // 创建目录 + os.MkdirAll(filepath.Dir(filePath), 0755) + + // 发送请求 + client := &http.Client{Timeout: 30 * time.Minute} + resp, err := client.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("HTTP错误: %d", resp.StatusCode) + } + + // 获取文件大小 + u.totalSize = resp.ContentLength + + // 创建临时文件 + tempPath := filePath + ".tmp" + file, err := os.Create(tempPath) + if err != nil { + return err + } + defer file.Close() + + // 下载文件 + buf := make([]byte, 32*1024) // 32KB缓冲区 + for { + // 检查是否暂停 + for u.isPaused { + time.Sleep(100 * time.Millisecond) + } + + n, err := resp.Body.Read(buf) + if n > 0 { + _, writeErr := file.Write(buf[:n]) + if writeErr != nil { + return writeErr + } + u.downloaded += int64(n) + u.updateFileProgress(fileName, u.downloaded, u.totalSize) + } + + if err != nil { + if err == io.EOF { + break + } + return err + } + } + + // 关闭文件 + file.Close() + + // 重命名为最终文件 + if err := os.Rename(tempPath, filePath); err != nil { + // 如果重命名失败,尝试复制 + if err := copyFile(tempPath, filePath); err != nil { + return err + } + } + + // 清理临时文件 + os.Remove(tempPath) + + return nil +} + +func (u *UpdaterApp) startVerifyPriceApp() { + exePath := filepath.Join(u.currentDir, "VerifyPriceApp.exe") + + u.updateStatus("正在启动核价软件...") + + cmd := exec.Command(exePath) + cmd.Dir = u.currentDir + + err := cmd.Start() + if err != nil { + u.updateStatus(fmt.Sprintf("启动失败: %v", err)) + u.updateStatus("请手动运行 VerifyPriceApp.exe") + } else { + u.updateStatus("核价软件已启动,即将退出...") + time.Sleep(2 * time.Second) + os.Exit(0) + } +} + +// ============================ 主函数 ============================ +func main() { + //// 隐藏控制台窗口(仅Windows) + //hideConsoleWindow() + + // 检查是否以控制台模式运行 + if len(os.Args) > 1 && os.Args[1] == "--console" { + runConsoleMode() + return + } + + // GUI模式 + go func() { + app := NewUpdaterApp() + if err := app.Run(); err != nil { + fmt.Fprintf(os.Stderr, "错误: %v\n", err) + os.Exit(1) + } + }() + app.Main() +} + +//// hideConsoleWindow 隐藏控制台窗口(仅Windows有效) +//func hideConsoleWindow() { +// // 仅在Windows平台下生效 +// kernel32 := syscall.NewLazyDLL("kernel32.dll") +// getConsoleWindow := kernel32.NewProc("GetConsoleWindow") +// showWindow := syscall.NewLazyDLL("user32.dll").NewProc("ShowWindow") +// +// if hwnd, _, _ := getConsoleWindow.Call(); hwnd != 0 { +// showWindow.Call(hwnd, 0) // 0 = SW_HIDE +// } +//} + +func runConsoleMode() { + // 控制台模式(原main.go的逻辑) + printBanner("核价软件更新器") + printWarning("更新时请勿操作!!!") + + // 获取当前工作目录 + currentDir, err := os.Getwd() + if err != nil { + printError("获取当前目录失败: %v", err) + return + } + printInfo("当前目录: %v", currentDir) + + // 获取选品中心中verify_price_version.json + printInfo("正在检查核价软件版本...") + verifyPriceVersionJSON, err := getVerifyPriceVersionJSON() + if err != nil { + printError("读取核价软件版本信息失败: %v", err) + return + } + + // 直接读取yaml文件 + yamlPath := filepath.Join(currentDir, "version.yaml") + data, err := ioutil.ReadFile(yamlPath) + if err != nil { + printError("读取YAML配置文件失败: %v", err) + } + + // 解析 YAML + var config VerifyPriceYAML + err = yaml.Unmarshal(data, &config) + if err != nil { + printError("解析 YAML 配置失败: %v", err) + } + printVersionInfo("当前核价软件版本号", config.VerifyPriceLatestVersion) + + // 检查并下载核价软件主程序 + printInfo("检查核价软件主程序...") + if _, err := os.Stat(filepath.Join(currentDir, "VerifyPriceApp.exe")); os.IsNotExist(err) { + printDownload("发现核价软件缺失,开始下载...") + url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", verifyPriceVersionJSON.VerifyPriceLatestVersion) + err = downloadEXE(url, currentDir, "VerifyPriceApp.exe") + if err != nil { + if err.Error() == "The process cannot access the file because it is being used by another process." { + printWarning("文件被占用,请重新启动与书同行.exe软件") + } else { + printError("下载核价软件失败: %v", err) + } + } else { + printSuccess("核价软件下载完成") + } + //修改yaml文件 + config.VerifyPriceLatestVersion = verifyPriceVersionJSON.VerifyPriceLatestVersion + err = writeYAML(yamlPath, config) + if err != nil { + printError("更新YAML配置文件失败: %v", err) + } + } else { + if verifyPriceVersionJSON.VerifyPriceLatestVersion != config.VerifyPriceLatestVersion || config.VerifyPriceLatestVersion == "" { + printDownload("发现新版本核价软件,开始更新...") + url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", verifyPriceVersionJSON.VerifyPriceLatestVersion) + err = downloadEXE(url, currentDir, "VerifyPriceApp.exe") + if err != nil { + if err.Error() == "The process cannot access the file because it is being used by another process." { + printWarning("文件被占用,请重新启动与书同行.exe软件") + } else { + printError("下载核价软件失败: %v", err) + } + } else { + printSuccess("核价软件更新完成") + } + //修改yaml文件 + config.VerifyPriceLatestVersion = verifyPriceVersionJSON.VerifyPriceLatestVersion + err = writeYAML(yamlPath, config) + if err != nil { + printError("更新YAML配置文件失败: %v", err) + } + } else { + printSuccess("核价软件已是最新版本") + } + } + + // 获取ES2中version.json + printInfo("正在检查DLL组件版本...") + versionsJSON, err := getVersionsJSON() + if err != nil { + printError("读取DLL版本信息失败: %v", err) + return + } + + // 打印版本信息标题 + printBanner("版本信息") + + // 打印所有版本信息 + printVersionInfo("最新核价软件版本", verifyPriceVersionJSON.VerifyPriceLatestVersion) + printVersionInfo("csv.dll版本号:", versionsJSON.CsvVersion) + printVersionInfo("kongfz.dll版本号:", versionsJSON.KongfzVersion) + printVersionInfo("logger.dll版本号:", versionsJSON.LoggerVersion) + printVersionInfo("module-centerBook.dll版本号:", versionsJSON.ModuleCenterBookVersion) + printVersionInfo("module-login.dll版本号:", versionsJSON.ModuleLoginVersion) + printVersionInfo("module-erp.dll版本号:", versionsJSON.ModuleErpVersion) + printVersionInfo("module-kongfz.dll版本号:", versionsJSON.ModuleKongfzVersion) + printVersionInfo("module-taskPool.dll版本号:", versionsJSON.ModuleTaskPoolVersion) + printVersionInfo("module-verifyPrice.dll版本号:", versionsJSON.ModuleVerifyPriceVersion) + printVersionInfo("picTool.dll版本号:", versionsJSON.PicToolVersion) + printVersionInfo("proxy.dll版本号:", versionsJSON.ProxyVersion) + + // 验证并更新DLL文件 + printBanner("组件检查与更新") + + // 验证csv.dll文件版本 + printInfo("检查csv.dll...") + csvVersion, err := csvDllVersion(currentDir) + if err != nil { + printWarning("获取csv.dll版本失败: %v", err) + } + if versionsJSON.CsvVersion != csvVersion && csvVersion != "" { + printDownload("csv.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "csv.dll"), "csv.dll", "") + if err != nil { + printError("下载csv.dll失败: %v", err) + } else { + printSuccess("csv.dll更新完成") + } + } else { + printSuccess("csv.dll已是最新版本") + } + + // 验证kongfz.dll文件版本 + printInfo("检查kongfz.dll...") + kongfzVersion, err := kongfzDllVersion(currentDir) + if err != nil { + printWarning("获取kongfz.dll版本失败: %v", err) + } + if versionsJSON.KongfzVersion != kongfzVersion && kongfzVersion != "" { + printDownload("kongfz.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "kongfz.dll"), "kongfz.dll", "") + if err != nil { + printError("下载kongfz.dll失败: %v", err) + } else { + printSuccess("kongfz.dll更新完成") + } + } else { + printSuccess("kongfz.dll已是最新版本") + } + + // 验证logger.dll文件版本 + printInfo("检查logger.dll...") + loggerVersion, err := loggerDllVersion(currentDir) + if err != nil { + printWarning("获取logger.dll版本失败: %v", err) + } + if versionsJSON.LoggerVersion != loggerVersion && loggerVersion != "" { + printDownload("logger.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "logger.dll"), "logger.dll", "") + if err != nil { + printError("下载logger.dll失败: %v", err) + } else { + printSuccess("logger.dll更新完成") + } + } else { + printSuccess("logger.dll已是最新版本") + } + + // 验证module-centerBook.dll文件版本 + printInfo("检查module-centerBook.dll...") + moduleCenterBookVersion, err := moduleCenterBookDllVersion(currentDir) + if err != nil { + printWarning("获取module-centerBook.dll版本失败: %v", err) + } + if versionsJSON.ModuleCenterBookVersion != moduleCenterBookVersion && moduleCenterBookVersion != "" { + printDownload("module-centerBook.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-centerBook.dll"), "module-centerBook.dll", "") + if err != nil { + printError("下载module-centerBook.dll失败: %v", err) + } else { + printSuccess("module-centerBook.dll更新完成") + } + } else { + printSuccess("module-centerBook.dll已是最新版本") + } + + // 验证module-login.dll文件版本 + printInfo("检查module-login.dll...") + moduleLoginVersion, err := moduleLoginDllVersion(currentDir) + if err != nil { + printWarning("获取module-login.dll版本失败: %v", err) + } + if versionsJSON.ModuleLoginVersion != moduleLoginVersion && moduleLoginVersion != "" { + printDownload("module-login.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-login.dll"), "module-login.dll", "") + if err != nil { + printError("下载module-login.dll失败: %v", err) + } else { + printSuccess("module-login.dll更新完成") + } + } else { + printSuccess("module-login.dll已是最新版本") + } + + // 验证module-erp.dll文件版本 + printInfo("检查module-erp.dll...") + moduleErpVersion, err := moduleErpDllVersion(currentDir) + if err != nil { + printWarning("获取module-erp.dll版本失败: %v", err) + } + if versionsJSON.ModuleErpVersion != moduleErpVersion && moduleErpVersion != "" { + printDownload("module-erp.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-erp.dll"), "module-erp.dll", "") + if err != nil { + printError("下载module-erp.dll失败: %v", err) + } else { + printSuccess("module-erp.dll更新完成") + } + } else { + printSuccess("module-erp.dll已是最新版本") + } + + // 验证module-kongfz.dll文件版本 + printInfo("检查module-kongfz.dll...") + moduleKongfzVersion, err := moduleKongfzDllVersion(currentDir) + if err != nil { + printWarning("获取module-kongfz.dll版本失败: %v", err) + } + if versionsJSON.ModuleKongfzVersion != moduleKongfzVersion && moduleKongfzVersion != "" { + printDownload("module-kongfz.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-kongfz.dll"), "module-kongfz.dll", "") + if err != nil { + printError("下载module-kongfz.dll失败: %v", err) + } else { + printSuccess("module-kongfz.dll更新完成") + } + } else { + printSuccess("module-kongfz.dll已是最新版本") + } + + // 验证module-taskPool.dll文件版本 + printInfo("检查module-taskPool.dll...") + moduleTaskPoolVersion, err := moduleTaskPoolDllVersion(currentDir) + if err != nil { + printWarning("获取module-taskPool.dll版本失败: %v", err) + } + if versionsJSON.ModuleTaskPoolVersion != moduleTaskPoolVersion && moduleTaskPoolVersion != "" { + printDownload("module-taskPool.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-taskPool.dll"), "module-taskPool.dll", "") + if err != nil { + printError("下载module-taskPool.dll失败: %v", err) + } else { + printSuccess("module-taskPool.dll更新完成") + } + } else { + printSuccess("module-taskPool.dll已是最新版本") + } + + // 验证module-verifyPrice.dll文件版本 + printInfo("检查module-verifyPrice.dll...") + moduleVerifyPriceVersion, err := moduleVerifyPriceDllVersion(currentDir) + if err != nil { + printWarning("获取module-verifyPrice.dll版本失败: %v", err) + } + if versionsJSON.ModuleVerifyPriceVersion != moduleVerifyPriceVersion && moduleVerifyPriceVersion != "" { + printDownload("module-verifyPrice.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-verifyPrice.dll"), "module-verifyPrice.dll", "") + if err != nil { + printError("下载module-verifyPrice.dll失败: %v", err) + } else { + printSuccess("module-verifyPrice.dll更新完成") + } + } else { + printSuccess("module-verifyPrice.dll已是最新版本") + } + + // 验证picTool.dll文件版本 + printInfo("检查picTool.dll...") + picToolVersion, err := picToolDllVersion(currentDir) + if err != nil { + printWarning("获取picTool.dll版本失败: %v", err) + } + if versionsJSON.PicToolVersion != picToolVersion && picToolVersion != "" { + printDownload("picTool.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "picTool.dll"), "picTool.dll", "") + if err != nil { + printError("下载picTool.dll失败: %v", err) + } else { + printSuccess("picTool.dll更新完成") + } + } else { + printSuccess("picTool.dll已是最新版本") + } + + // 验证proxy.dll文件版本 + printInfo("检查proxy.dll...") + proxyVersion, err := proxyDllVersion(currentDir) + if err != nil { + printWarning("获取proxy.dll版本失败: %v", err) + } + if versionsJSON.ProxyVersion != proxyVersion && proxyVersion != "" { + printDownload("proxy.dll需要更新") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "proxy.dll"), "proxy.dll", "") + if err != nil { + printError("下载proxy.dll失败: %v", err) + } else { + printSuccess("proxy.dll更新完成") + } + } else { + printSuccess("proxy.dll已是最新版本") + } + + printWarning("更新完成!按回车键打开核价软件...") + + // 等待用户输入 + bufio.NewReader(os.Stdin).ReadBytes('\n') + + // 启动核价软件 + verifyPriceAppPath := filepath.Join(currentDir, "VerifyPriceApp.exe") + if _, err := os.Stat(verifyPriceAppPath); os.IsNotExist(err) { + printError("核价软件主程序不存在: %s", verifyPriceAppPath) + } else { + cmd := exec.Command(verifyPriceAppPath) + cmd.Dir = currentDir + err := cmd.Start() + if err != nil { + printError("启动核价软件失败: %v", err) + } else { + printSuccess("核价软件已启动 (PID: %d)", cmd.Process.Pid) + } + } + + printBanner("程序执行完毕") +} + +// ============================ 原main.go的函数 ============================ +func writeYAML(filePath string, config VerifyPriceYAML) error { + yamlData, err := yaml.Marshal(config) + if err != nil { + return fmt.Errorf("序列化YAML失败: %v", err) + } + + backupFilePath := filePath + ".bak" + err = backupFile(filePath, backupFilePath) + if err != nil { + printDebug("创建备份文件失败: %v", err) + } + + err = ioutil.WriteFile(filePath, yamlData, 0755) + if err != nil { + return fmt.Errorf("写入文件失败: %v", err) + } + + return nil +} + +func backupFile(originalPath, backupPath string) error { + data, err := ioutil.ReadFile(originalPath) + if err != nil { + return err + } + return ioutil.WriteFile(backupPath, data, 0755) +} + +// 下载exe文件 +func downloadEXE(url, folderPath, filename string) error { + if err := os.MkdirAll(folderPath, 0755); err != nil { + return fmt.Errorf("创建文件夹失败: %v", err) + } + + filePath := filepath.Join(folderPath, filename) + printDebug("下载到: %s", filePath) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("下载失败,状态码: %d", resp.StatusCode) + } + + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("创建文件失败: %v", err) + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return fmt.Errorf("写入文件失败: %v", err) + } + + return nil +} + +func getVerifyPriceVersionJSON() (*VersionInfo, error) { + url := "https://newverifyprice.buzhiyushu.cn/verify_price_version.json" + client := &http.Client{Timeout: 30 * time.Second} + + resp, err := client.Get(url) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + var versionInfo VersionInfo + err = json.Unmarshal(body, &versionInfo) + if err != nil { + return nil, fmt.Errorf("解析JSON失败: %v", err) + } + return &versionInfo, nil +} + +func getVersionsJSON() (*VersionConfig, error) { + url := "http://36.212.20.113:53300/version.json" + client := &http.Client{Timeout: 30 * time.Second} + + resp, err := client.Get(url) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + var versionConfig VersionConfig + err = json.Unmarshal(body, &versionConfig) + if err != nil { + return nil, fmt.Errorf("解析JSON失败: %v", err) + } + return &versionConfig, nil +} + +func cStr(ptr uintptr) string { + if ptr == 0 { + return "" + } + var b []byte + for { + c := *(*byte)(unsafe.Pointer(ptr)) + if c == 0 { + break + } + b = append(b, c) + ptr++ + } + return string(b) +} + +func csvDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "csv.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("csv.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "csv.dll", "") + if err != nil { + return "", fmt.Errorf("下载 csv.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载csv.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找csv.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用csv.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func kongfzDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "kongfz.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("kongfz.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "kongfz.dll", "") + if err != nil { + return "", fmt.Errorf("下载 kongfz.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载kongfz.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找kongfz.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用kongfz.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func loggerDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "logger.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("logger.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "logger.dll", "") + if err != nil { + return "", fmt.Errorf("下载 logger.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载logger.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找logger.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用logger.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func moduleCenterBookDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "module-centerBook.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("module-centerBook.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-centerBook.dll", "") + if err != nil { + return "", fmt.Errorf("下载 module-centerBook.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载module-centerBook.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找module-centerBook.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用module-centerBook.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func moduleLoginDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "module-login.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("module-login.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-login.dll", "") + if err != nil { + return "", fmt.Errorf("下载 module-login.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载module-login.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找module-login.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用module-login.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func moduleErpDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "module-erp.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("module-erp.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-erp.dll", "") + if err != nil { + return "", fmt.Errorf("下载 module-erp.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载module-erp.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找module-erp.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用module-erp.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func moduleKongfzDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "module-kongfz.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("module-kongfz.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-kongfz.dll", "") + if err != nil { + return "", fmt.Errorf("下载 module-kongfz.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载module-kongfz.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找module-kongfz.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用module-kongfz.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func moduleTaskPoolDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "module-taskPool.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("module-taskPool.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-taskPool.dll", "") + if err != nil { + return "", fmt.Errorf("下载 module-taskPool.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载module-taskPool.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找module-taskPool.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用module-taskPool.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func moduleVerifyPriceDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "module-verifyPrice.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("module-verifyPrice.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-verifyPrice.dll", "") + if err != nil { + return "", fmt.Errorf("下载 module-verifyPrice.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载module-verifyPrice.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找module-verifyPrice.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用module-verifyPrice.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func picToolDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "picTool.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("picTool.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "picTool.dll", "") + if err != nil { + return "", fmt.Errorf("下载 picTool.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载picTool.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找picTool.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用picTool.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func proxyDllVersion(currentDir string) (string, error) { + dllPath := filepath.Join(currentDir, "dll", "proxy.dll") + + _, err := os.Stat(dllPath) + if err != nil { + if os.IsNotExist(err) { + printDownload("proxy.dll文件缺失,开始下载...") + err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "proxy.dll", "") + if err != nil { + return "", fmt.Errorf("下载 proxy.dll 文件失败: %v", err) + } + return "", nil + } + } + + dll, err := syscall.LoadDLL(dllPath) + if err != nil { + return "", fmt.Errorf("加载proxy.dll文件失败: %v", err) + } + + proc, err := dll.FindProc("GetVersion") + if err != nil { + return "", fmt.Errorf("查找proxy.dll GetVersion 函数失败: %v", err) + } + + ret, _, err := proc.Call() + if err != nil && err.Error() != "The operation completed successfully." { + return "", fmt.Errorf("调用proxy.dll GetVersion 函数失败: %v", err) + } + str := cStr(ret) + + if proc, err = dll.FindProc("FreeCString"); err == nil { + defer proc.Call(ret) + } + return str, nil +} + +func downloadDLL(url, outputPath, filename, version string) error { + // 1. 确保目录存在 + dir := filepath.Dir(outputPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("创建目录失败: %v", err) + } + + // 2. 创建临时文件路径 + tempPath := outputPath + ".tmp" + + // 3. 清理可能存在的旧临时文件 + if _, err := os.Stat(tempPath); err == nil { + os.Remove(tempPath) + } + + // 4. 检查目标文件是否存在 + if _, err := os.Stat(outputPath); err == nil { + backupPath := outputPath + ".bak" + if err := os.Rename(outputPath, backupPath); err != nil { + // 重命名失败,尝试直接删除 + for i := 0; i < 3; i++ { + if err := os.Remove(outputPath); err == nil { + break + } + if i == 2 { + if strings.Contains(err.Error(), "Access is denied") { + return fmt.Errorf("文件被其他程序占用,请关闭可能使用此文件的程序后再试: %s", outputPath) + } + return fmt.Errorf("无法删除已存在的文件: %v", err) + } + time.Sleep(1 * time.Second) + } + } else { + // 异步清理备份文件 + go func() { + time.Sleep(30 * time.Second) + if _, err := os.Stat(backupPath); err == nil { + os.Remove(backupPath) + } + }() + } + } + + // 5. 打印下载信息 + if version == "" { + printDownload("正在下载: %s (版本: 最新版本)", filename) + } else { + printDownload("正在下载: %s (版本: %s)", filename, version) + } + + // 6. 首先尝试获取文件大小信息(HEAD请求) + headClient := &http.Client{ + Timeout: 30 * time.Second, + } + + headReq, err := http.NewRequest("HEAD", url, nil) + if err != nil { + printError("创建HEAD请求失败,使用默认超时设置: %v", err) + } else { + // 添加查询参数 + q := headReq.URL.Query() + q.Add("filename", filename) + if version != "" { + q.Add("version", version) + } + headReq.URL.RawQuery = q.Encode() + headReq.Header.Set("User-Agent", "DLL-Downloader/1.0") + + resp, err := headClient.Do(headReq) + if err == nil && resp.StatusCode == http.StatusOK { + if contentLength := resp.ContentLength; contentLength > 0 { + fmt.Printf(ColorGray+"获取到文件大小: %.2f MB\n"+ColorReset, + float64(contentLength)/1024/1024) + } + resp.Body.Close() + } + } + + // 7. 创建长时间下载的HTTP客户端 + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 15 * time.Second, + ExpectContinueTimeout: 5 * time.Second, + ResponseHeaderTimeout: 30 * time.Second, + ReadBufferSize: 128 * 1024, // 128KB + WriteBufferSize: 128 * 1024, + } + + client := &http.Client{ + Transport: transport, + } + + // 8. 创建下载请求 + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Hour) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return fmt.Errorf("创建请求失败: %v", err) + } + + // 添加查询参数 + q := req.URL.Query() + q.Add("filename", filename) + if version != "" { + q.Add("version", version) + } + req.URL.RawQuery = q.Encode() + + // 添加请求头 + req.Header.Set("User-Agent", "DLL-Downloader/1.0") + req.Header.Set("Accept", "*/*") + req.Header.Set("Accept-Encoding", "gzip, deflate") + req.Header.Set("Connection", "keep-alive") + + // 9. 发送请求 + resp, err := client.Do(req) + if err != nil { + if strings.Contains(err.Error(), "context deadline exceeded") { + return fmt.Errorf("连接超时,请检查网络连接: %v", err) + } + return fmt.Errorf("请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) + return fmt.Errorf("服务器返回错误: %d - %s", resp.StatusCode, string(body)) + } + + // 10. 获取文件大小 + totalSize := resp.ContentLength + if totalSize > 0 { + fmt.Printf(ColorGray+"文件大小: %.2f MB\n"+ColorReset, float64(totalSize)/1024/1024) + + // 根据文件大小提供预估时间 + minSpeed := 50.0 * 1024 + estimatedTime := time.Duration(float64(totalSize)/minSpeed) * time.Second + if estimatedTime > 30*time.Second { + fmt.Printf(ColorGray+"预估下载时间: %v (基于50KB/s最低速度)\n"+ColorReset, + estimatedTime.Round(time.Second)) + } + } else { + fmt.Println(ColorGray + "文件大小: 未知" + ColorReset) + } + + // 11. 创建临时文件 + out, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return fmt.Errorf("创建临时文件失败: %v", err) + } + defer out.Close() + + // 12. 确保下载失败时清理临时文件 + downloadSuccess := false + defer func() { + if !downloadSuccess { + os.Remove(tempPath) + } + }() + + // 13. 活动检测机制 + activityCh := make(chan time.Time, 100) + var lastActivity time.Time + lastActivity = time.Now() + + // 创建活动检测的reader + activityReader := &activityReader{ + reader: resp.Body, + activityCh: activityCh, + activityPtr: &lastActivity, + } + + // 启动活动检测goroutine + activityCtx, activityCancel := context.WithCancel(context.Background()) + defer activityCancel() + + go func() { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-activityCtx.Done(): + return + case activityTime := <-activityCh: + lastActivity = activityTime + case <-ticker.C: + if time.Since(lastActivity) > 60*time.Second { + printError("下载活动超时,60秒内没有收到数据") + cancel() + return + } + } + } + }() + + // 14. 下载进度显示 + startTime := time.Now() + var downloaded int64 = 0 + var lastUpdate time.Time + lastUpdate = time.Now() + + // 15. 创建缓冲区 + buf := make([]byte, 128*1024) + + // 显示初始进度 + fmt.Print(ColorCyan + "下载进度: 0.00% (0.00/0.00 MB)" + ColorReset) + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("下载被取消: %v", ctx.Err()) + default: + } + + n, err := activityReader.Read(buf) + if n > 0 { + if _, writeErr := out.Write(buf[:n]); writeErr != nil { + return fmt.Errorf("写入文件失败: %v", writeErr) + } + + downloaded += int64(n) + + if time.Since(lastUpdate) > 1*time.Second || err != nil { + if totalSize > 0 { + percent := float64(downloaded) / float64(totalSize) * 100 + elapsed := time.Since(startTime).Seconds() + speed := 0.0 + if elapsed > 0 { + speed = float64(downloaded) / elapsed / 1024 / 1024 + } + + remaining := time.Duration(0) + if speed > 0 && downloaded > 0 { + remainingSecs := float64(totalSize-downloaded) / (float64(downloaded) / elapsed) + remaining = time.Duration(remainingSecs) * time.Second + } + + fmt.Printf("\r\033[K"+ColorCyan+"下载进度: %.2f%% (%.2f/%.2f MB) 速度: %.2f MB/s 剩余: %v"+ColorReset, + percent, + float64(downloaded)/1024/1024, + float64(totalSize)/1024/1024, + speed, + remaining.Round(time.Second)) + } else { + elapsed := time.Since(startTime).Seconds() + speed := 0.0 + if elapsed > 0 { + speed = float64(downloaded) / elapsed / 1024 / 1024 + } + fmt.Printf("\r\033[K"+ColorCyan+"已下载: %.2f MB 速度: %.2f MB/s"+ColorReset, + float64(downloaded)/1024/1024, + speed) + } + lastUpdate = time.Now() + } + } + + if err != nil { + if err == io.EOF { + break + } + if ctx.Err() != nil { + return fmt.Errorf("下载中断: %v", ctx.Err()) + } + return fmt.Errorf("读取数据失败: %v", err) + } + } + + // 16. 下载完成,显示最终进度 + if totalSize > 0 && downloaded < totalSize { + downloaded = totalSize + } + + if totalSize > 0 { + fmt.Printf("\r\033[K"+ColorGreen+"下载完成: 100.00%% (%.2f/%.2f MB)\n"+ColorReset, + float64(downloaded)/1024/1024, + float64(totalSize)/1024/1024) + } else { + fmt.Printf("\r\033[K"+ColorGreen+"下载完成: %.2f MB\n"+ColorReset, + float64(downloaded)/1024/1024) + } + + activityCancel() + + // 17. 验证下载的文件大小 + if totalSize > 0 { + fi, err := os.Stat(tempPath) + if err != nil { + return fmt.Errorf("无法验证下载文件: %v", err) + } + if fi.Size() != totalSize { + return fmt.Errorf("文件大小不匹配: 期望 %d 字节, 实际 %d 字节", totalSize, fi.Size()) + } + } + + // 18. 将临时文件重命名为目标文件 + maxRetries := 5 + for i := 0; i < maxRetries; i++ { + if err := os.Rename(tempPath, outputPath); err == nil { + downloadSuccess = true + break + } + + if i == maxRetries-1 { + if err := copyFile(tempPath, outputPath); err != nil { + return fmt.Errorf("保存文件失败: %v", err) + } + downloadSuccess = true + } else { + time.Sleep(500 * time.Millisecond * time.Duration(i+1)) + } + } + + // 19. 验证最终文件 + fi, err := os.Stat(outputPath) + if err != nil { + return fmt.Errorf("最终文件验证失败: %v", err) + } + + elapsed := time.Since(startTime) + speed := float64(fi.Size()) / elapsed.Seconds() / 1024 / 1024 + printSuccess("下载完成: %s (大小: %.2f MB, 耗时: %v, 平均速度: %.2f MB/s)", + filepath.Base(outputPath), + float64(fi.Size())/1024/1024, + elapsed.Round(time.Second), + speed) + + return nil +} + +type activityReader struct { + reader io.Reader + activityCh chan<- time.Time + activityPtr *time.Time +} + +func (r *activityReader) Read(p []byte) (n int, err error) { + n, err = r.reader.Read(p) + if n > 0 { + now := time.Now() + r.activityCh <- now + if r.activityPtr != nil { + *r.activityPtr = now + } + } + return +} + +func copyFile(src, dst string) error { + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + + _, err = io.Copy(destination, source) + return err +}