diff --git a/csv/csv.go b/csv/csv.go index f51efd9..56caf40 100644 --- a/csv/csv.go +++ b/csv/csv.go @@ -1,2292 +1,2292 @@ package main -/* -#include -*/ -import "C" -import ( - "bufio" - "encoding/csv" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "sync" - "sync/atomic" - "time" - "unsafe" -) - -// ========================== 数据结构定义 ========================== - -// CSVHandle CSV文件句柄 -type CSVHandle struct { - ID int64 // 句柄唯一ID - Filename string // 文件名 - Delimiter rune // 分隔符 - HasHeader bool // 是否有表头 - Header []string // 表头(如果有) - File *os.File // 底层文件句柄 - CSVReader *csv.Reader // CSV阅读器 - CSVWriter *csv.Writer // CSV写入器 - TotalRows int64 // 总行数(如果已计算) - IsOpen bool // 是否已打开 - OpenTime time.Time // 打开时间 - AccessTime time.Time // 最后访问时间 - AccessCount int64 // 访问计数 - mu sync.RWMutex // 读写锁(保护数据结构) - autoCloseTimer *time.Timer // 自动关闭计时器 - cachedRowCount int64 // 缓存的行数(避免重复计算) - rowCountCached bool // 行数是否已缓存 - writeBuffer *bufio.Writer // 写入缓冲区 - - // 新增:引用计数和状态管理 - refCount int64 // 引用计数 - refCountMu sync.RWMutex // 引用计数锁 - closing bool // 正在关闭中 - closed bool // 已关闭 - statusMu sync.RWMutex // 状态锁 - activeOps int64 // 正在进行的操作数 -} - -// CSVManager CSV文件管理器 -type CSVManager struct { - handles sync.Map // map[int64]*CSVHandle - fileLocks sync.Map // map[string]*sync.RWMutex 文件级锁 - nextHandle int64 // 下一个句柄ID - maxOpen int // 最大打开文件数 - semaphore chan struct{} // 信号量控制并发打开 - config ManagerConfig // 管理器配置 -} - -// ManagerConfig 管理器配置 -type ManagerConfig struct { - MaxOpenFiles int // 最大打开文件数 - AutoCloseTimeout time.Duration // 自动关闭超时 - BufferSize int // 缓冲区大小 - UseMMap bool // 是否使用内存映射(大文件) - MMapThreshold int64 // 使用内存映射的阈值(字节) -} - -// MergeOptions 合并选项 -type MergeOptions struct { - AppendMode bool // true=追加模式,false=覆盖模式 - SkipHeader bool // 是否跳过源文件的表头 - SkipDuplicates bool // 是否跳过重复行 - ColumnMapping map[int]int // 列映射:源文件列索引 -> 目标文件列索引 - ConflictResolution ConflictStrategy // 冲突解决策略 - TransformFunc func([]string) ([]string, error) // 数据转换函数 -} - -// ConflictStrategy 冲突解决策略 -type ConflictStrategy int - -const ( - Overwrite ConflictStrategy = iota // 覆盖目标文件的数据 - Skip // 跳过冲突的行 - KeepBoth // 保留两者 - UseSource // 使用源数据 - UseTarget // 使用目标数据 -) - -// CSVResponse CSV响应结构体 -type CSVResponse struct { - Success bool `json:"success"` - Message string `json:"message,omitempty"` - Data interface{} `json:"data,omitempty"` -} - -// DefaultConfig 默认配置 -var DefaultConfig = ManagerConfig{ - MaxOpenFiles: 100, - AutoCloseTimeout: 5 * time.Minute, - BufferSize: 32 * 1024, // 32KB - UseMMap: true, - MMapThreshold: 10 * 1024 * 1024, // 10MB -} - -// 全局管理器实例 -var ( - globalManager *CSVManager - managerInitOnce sync.Once -) - -// ========================== 辅助函数 ========================== - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -// ========================== GetManager 获取全局管理器 ========================== - -func GetManager() *CSVManager { - managerInitOnce.Do(func() { - globalManager = NewCSVManager(DefaultConfig) - }) - return globalManager -} - -// ========================== NewCSVManager 创建新的CSV管理器 ========================== - -func NewCSVManager(config ManagerConfig) *CSVManager { - if config.MaxOpenFiles <= 0 { - config.MaxOpenFiles = DefaultConfig.MaxOpenFiles - } - return &CSVManager{ - handles: sync.Map{}, - fileLocks: sync.Map{}, - nextHandle: 1, - maxOpen: config.MaxOpenFiles, - semaphore: make(chan struct{}, config.MaxOpenFiles), - config: config, - } -} - -// ========================== CSVHandle 引用计数方法 ========================== - -// addRef 增加引用计数 -func (h *CSVHandle) addRef() { - atomic.AddInt64(&h.refCount, 1) - h.AccessTime = time.Now() - atomic.AddInt64(&h.AccessCount, 1) -} - -// releaseRef 减少引用计数,如果为0则返回true表示可以关闭 -func (h *CSVHandle) releaseRef() bool { - h.refCountMu.Lock() - defer h.refCountMu.Unlock() - - oldCount := atomic.AddInt64(&h.refCount, -1) - return oldCount <= 0 -} - -// getRefCount 获取引用计数 -func (h *CSVHandle) getRefCount() int64 { - return atomic.LoadInt64(&h.refCount) -} - -// isValid 检查句柄是否有效 -func (h *CSVHandle) isValid() bool { - h.statusMu.RLock() - defer h.statusMu.RUnlock() - return !h.closing && !h.closed && h.IsOpen -} - -// markClosing 标记为正在关闭 -func (h *CSVHandle) markClosing() { - h.statusMu.Lock() - defer h.statusMu.Unlock() - h.closing = true -} - -// markClosed 标记为已关闭 -func (h *CSVHandle) markClosed() { - h.statusMu.Lock() - defer h.statusMu.Unlock() - h.closing = false - h.closed = true - h.IsOpen = false -} - -// beginOperation 开始一个操作 -func (h *CSVHandle) beginOperation() bool { - h.statusMu.RLock() - if h.closing || h.closed || !h.IsOpen { - h.statusMu.RUnlock() - return false - } - atomic.AddInt64(&h.activeOps, 1) - h.statusMu.RUnlock() - return true -} - -// endOperation 结束一个操作 -func (h *CSVHandle) endOperation() { - atomic.AddInt64(&h.activeOps, -1) -} - -// waitForActiveOps 等待所有活动操作完成 -func (h *CSVHandle) waitForActiveOps(timeout time.Duration) bool { - start := time.Now() - for atomic.LoadInt64(&h.activeOps) > 0 { - if time.Since(start) > timeout { - return false - } - time.Sleep(10 * time.Millisecond) - } - return true -} - -// ========================== CSVHandle 文件操作方法 ========================== - -// close 关闭CSV句柄(内部方法) -func (h *CSVHandle) close() error { - h.mu.Lock() - defer h.mu.Unlock() - - if !h.IsOpen { - return nil - } - - // 停止自动关闭计时器 - if h.autoCloseTimer != nil { - h.autoCloseTimer.Stop() - h.autoCloseTimer = nil - } - - // 确保缓冲区数据写入文件 - if h.CSVWriter != nil { - h.CSVWriter.Flush() - } - if h.writeBuffer != nil { - h.writeBuffer.Flush() - } - - // 关闭文件 - if h.File != nil { - if err := h.File.Close(); err != nil { - return err - } - h.File = nil - } - - h.CSVReader = nil - h.CSVWriter = nil - h.writeBuffer = nil - h.IsOpen = false - h.markClosed() - - return nil -} - -// getHeader 获取表头 -func (h *CSVHandle) getHeader() []string { - h.mu.RLock() - defer h.mu.RUnlock() - return h.Header -} - -// calculateTotalRows 计算CSV文件的总行数 -func (h *CSVHandle) calculateTotalRows() (int64, error) { - h.mu.Lock() - defer h.mu.Unlock() - - // 如果已缓存,直接返回 - if h.rowCountCached { - return h.cachedRowCount, nil - } - - if !h.IsOpen { - return 0, fmt.Errorf("文件未打开") - } - - // 保存当前文件位置 - currentPos, err := h.File.Seek(0, 1) // 当前位置 - if err != nil { - return 0, fmt.Errorf("获取当前文件位置失败: %w", err) - } - - // 重置到文件开始 - if _, err := h.File.Seek(0, 0); err != nil { - return 0, fmt.Errorf("重置文件指针失败: %w", err) - } - - // 重置CSV阅读器 - reader := csv.NewReader(bufio.NewReader(h.File)) - reader.Comma = h.Delimiter - reader.LazyQuotes = true - reader.TrimLeadingSpace = true - reader.FieldsPerRecord = -1 // 允许字段数量可变(不检查每行字段数) - - // 统计行数 - var rowCount int64 = 0 - for { - _, err := reader.Read() - if err != nil { - if err == io.EOF { - break - } - // 恢复文件位置 - h.File.Seek(currentPos, 0) - return 0, fmt.Errorf("读取行失败: %w", err) - } - rowCount++ - } - - // 恢复文件位置 - if _, err := h.File.Seek(currentPos, 0); err != nil { - return 0, fmt.Errorf("恢复文件位置失败: %w", err) - } - - // 更新缓存 - h.cachedRowCount = rowCount - h.rowCountCached = true - h.TotalRows = rowCount - - // 如果有表头,需要减去表头行 - if h.HasHeader && rowCount > 0 { - h.cachedRowCount = rowCount - 1 - h.TotalRows = rowCount - 1 - } - - return h.cachedRowCount, nil -} - -// ========================== CSVManager 文件操作方法 ========================== - -// getFileLock 获取或创建文件锁 -func (mgr *CSVManager) getFileLock(filename string) *sync.RWMutex { - lock, _ := mgr.fileLocks.LoadOrStore(filename, &sync.RWMutex{}) - return lock.(*sync.RWMutex) -} - -// OpenCSVFile 打开CSV文件并返回句柄(线程安全版本) -func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bool) (int64, error) { - // 获取文件锁 - fileLock := mgr.getFileLock(filename) - fileLock.Lock() - defer fileLock.Unlock() - - // 首先检查文件是否已经有打开的句柄 - if existingHandleID := mgr.findHandleByFilename(filename); existingHandleID != -1 { - if handle, err := mgr.getHandle(existingHandleID); err == nil { - handle.addRef() - return existingHandleID, nil - } - } - - // 检查文件是否存在,如果不存在则创建 - fileInfo, err := os.Stat(filename) - fileExists := true - if os.IsNotExist(err) { - fileExists = false - // 创建文件 - file, createErr := os.Create(filename) - if createErr != nil { - return -1, fmt.Errorf("创建文件失败: %w", createErr) - } - file.Close() // 关闭文件,后续会重新打开 - - // 重新获取文件信息 - fileInfo, err = os.Stat(filename) - if err != nil { - return -1, fmt.Errorf("获取文件信息失败: %w", err) - } - } else if err != nil { - // 其他错误 - return -1, fmt.Errorf("检查文件状态失败: %w", err) - } - - // 如果是新创建的空文件,需要特殊处理 - if !fileExists && fileInfo.Size() == 0 { - return mgr.createEmptyCSVHandle(filename, delimiter, hasHeader) - } - - // 限制并发打开文件数 - mgr.semaphore <- struct{}{} - defer func() { <-mgr.semaphore }() - - // 生成唯一句柄ID - handleID := atomic.AddInt64(&mgr.nextHandle, 1) - - // 创建CSV句柄 - csvHandle := &CSVHandle{ - ID: handleID, - Filename: filename, - Delimiter: delimiter, - HasHeader: hasHeader, - OpenTime: time.Now(), - AccessTime: time.Now(), - AccessCount: 1, - refCount: 1, // 初始引用计数为1 - } - - // 打开文件 - if err := mgr.openFile(csvHandle); err != nil { - return -1, fmt.Errorf("打开文件失败: %w", err) - } - - // 如果是新创建的文件,可能需要写入表头 - if !fileExists && hasHeader { - // 创建空表头,用户后续可以写入实际表头 - csvHandle.Header = []string{} - } else if hasHeader && fileInfo.Size() > 0 { - // 读取现有文件的表头 - if err := mgr.readHeader(csvHandle); err != nil { - csvHandle.close() - return -1, fmt.Errorf("读取表头失败: %w", err) - } - } - - // 如果有数据行,计算总行数 - if fileInfo.Size() > 0 { - // 计算总行数 - rows, err := csvHandle.calculateTotalRows() - if err != nil { - csvHandle.close() - return -1, fmt.Errorf("计算总行数失败: %w", err) - } - csvHandle.TotalRows = rows - } else { - csvHandle.TotalRows = 0 - csvHandle.cachedRowCount = 0 - csvHandle.rowCountCached = true - } - - // 注册到管理器 - mgr.handles.Store(handleID, csvHandle) - - // 启动自动关闭计时器 - if mgr.config.AutoCloseTimeout > 0 { - csvHandle.startAutoClose(mgr.config.AutoCloseTimeout, mgr) - } - - return handleID, nil -} - -// findHandleByFilename 根据文件名查找已存在的句柄ID -func (mgr *CSVManager) findHandleByFilename(filename string) int64 { - var existingHandleID int64 = -1 - - // 遍历所有句柄,查找相同文件名的句柄 - mgr.handles.Range(func(key, value interface{}) bool { - handle := value.(*CSVHandle) - - // 检查文件名是否相同 - if handle.Filename == filename { - // 检查句柄是否有效 - if handle.isValid() { - existingHandleID = handle.ID - return false // 停止遍历 - } - } - return true // 继续遍历 - }) - - return existingHandleID -} - -// createEmptyCSVHandle 创建空的CSV文件句柄 -func (mgr *CSVManager) createEmptyCSVHandle(filename string, delimiter rune, hasHeader bool) (int64, error) { - // 限制并发打开文件数 - mgr.semaphore <- struct{}{} - defer func() { <-mgr.semaphore }() - - // 生成唯一句柄ID - handleID := atomic.AddInt64(&mgr.nextHandle, 1) - - // 创建CSV句柄 - csvHandle := &CSVHandle{ - ID: handleID, - Filename: filename, - Delimiter: delimiter, - HasHeader: hasHeader, - OpenTime: time.Now(), - AccessTime: time.Now(), - TotalRows: 0, - AccessCount: 1, - refCount: 1, // 初始引用计数为1 - } - - // 以读写模式打开文件 - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) - if err != nil { - return -1, fmt.Errorf("打开文件失败: %w", err) - } - - // 创建CSV写入器 - writer := csv.NewWriter(file) - writer.Comma = delimiter - - // 如果是新创建的文件且有表头,写入空表头占位 - if hasHeader { - // 写入空表头(用户后续需要设置实际表头) - if err := writer.Write([]string{}); err != nil { - file.Close() - return -1, fmt.Errorf("写入空表头失败: %w", err) - } - writer.Flush() - - // 重置到文件开始位置 - if _, err := file.Seek(0, 0); err != nil { - file.Close() - return -1, fmt.Errorf("重置文件指针失败: %w", err) - } - } - - // 创建CSV阅读器 - reader := csv.NewReader(bufio.NewReaderSize(file, mgr.config.BufferSize)) - reader.Comma = delimiter - reader.LazyQuotes = true - reader.TrimLeadingSpace = true - - // 创建写入缓冲区 - writeBuffer := bufio.NewWriterSize(file, mgr.config.BufferSize) - - csvHandle.File = file - csvHandle.CSVReader = reader - csvHandle.CSVWriter = csv.NewWriter(writeBuffer) - csvHandle.CSVWriter.Comma = delimiter - csvHandle.writeBuffer = writeBuffer - csvHandle.IsOpen = true - csvHandle.AccessTime = time.Now() - - // 如果有表头,初始化空表头数组 - if hasHeader { - csvHandle.Header = []string{} - } - - // 注册到管理器 - mgr.handles.Store(handleID, csvHandle) - - // 启动自动关闭计时器 - if mgr.config.AutoCloseTimeout > 0 { - csvHandle.startAutoClose(mgr.config.AutoCloseTimeout, mgr) - } - - return handleID, nil -} - -// getHandle 获取句柄对象(增加引用计数) -func (mgr *CSVManager) getHandle(handleID int64) (*CSVHandle, error) { - value, ok := mgr.handles.Load(handleID) - if !ok { - return nil, fmt.Errorf("句柄不存在: %d", handleID) - } - - handle := value.(*CSVHandle) - - // 检查句柄是否有效 - if !handle.isValid() { - return nil, fmt.Errorf("句柄无效: %d", handleID) - } - - // 增加引用计数 - handle.addRef() - - // 确保文件已打开 - handle.mu.RLock() - isOpen := handle.IsOpen - handle.mu.RUnlock() - - if !isOpen { - // 获取文件锁 - fileLock := mgr.getFileLock(handle.Filename) - fileLock.Lock() - defer fileLock.Unlock() - - // 重新检查,防止其他协程已经重新打开了 - handle.mu.RLock() - isOpen = handle.IsOpen - handle.mu.RUnlock() - - if !isOpen { - if err := mgr.openFile(handle); err != nil { - // 恢复引用计数 - handle.releaseRef() - return nil, fmt.Errorf("重新打开文件失败: %w", err) - } - } - } - - return handle, nil -} - -// releaseHandle 释放句柄引用 -func (mgr *CSVManager) releaseHandle(handleID int64) { - value, ok := mgr.handles.Load(handleID) - if !ok { - return - } - - handle := value.(*CSVHandle) - // 减少引用计数,如果为0则真正关闭 - if handle.releaseRef() { - mgr.closeHandleInternal(handleID, handle) - } -} - -// ========================== 句柄管理方法 ========================== - -// closeHandleInternal 内部关闭句柄方法 -func (mgr *CSVManager) closeHandleInternal(handleID int64, handle *CSVHandle) { - // 获取文件锁 - fileLock := mgr.getFileLock(handle.Filename) - fileLock.Lock() - defer fileLock.Unlock() - - // 再次检查引用计数,防止在获取锁期间有新的引用 - if handle.getRefCount() > 0 { - return - } - - // 标记为正在关闭 - handle.markClosing() - - // 等待所有活动操作完成 - if !handle.waitForActiveOps(30 * time.Second) { - // 超时,强制关闭 - fmt.Printf("警告:句柄 %d 关闭超时,强制关闭\n", handleID) - } - - // 真正关闭文件 - handle.close() - - // 从管理器移除 - mgr.handles.Delete(handleID) -} - -// CloseHandle 关闭指定句柄(外部调用) -func (mgr *CSVManager) CloseHandle(handleID int64) error { - value, ok := mgr.handles.Load(handleID) - if !ok { - return fmt.Errorf("句柄不存在: %d", handleID) - } - - handle := value.(*CSVHandle) - - // 减少引用计数,如果为0则真正关闭 - if handle.releaseRef() { - mgr.closeHandleInternal(handleID, handle) - } - - return nil -} - -// GracefulCloseHandle 优雅关闭句柄(等待所有操作完成) -func (mgr *CSVManager) GracefulCloseHandle(handleID int64, timeout time.Duration) error { - value, ok := mgr.handles.Load(handleID) - if !ok { - return fmt.Errorf("句柄不存在: %d", handleID) - } - - handle := value.(*CSVHandle) - - // 标记为正在关闭,阻止新操作 - handle.markClosing() - - // 等待现有操作完成 - done := make(chan bool, 1) - go func() { - // 等待引用计数降为1(只剩当前引用) - for handle.getRefCount() > 1 { - time.Sleep(100 * time.Millisecond) - } - done <- true - }() - - // 设置超时 - select { - case <-done: - // 真正关闭 - return mgr.ForceCloseHandle(handleID) - case <-time.After(timeout): - return fmt.Errorf("关闭句柄超时,仍有 %d 个引用", handle.getRefCount()) - } -} - -// ForceCloseHandle 强制关闭句柄(无论引用计数如何) -func (mgr *CSVManager) ForceCloseHandle(handleID int64) error { - value, ok := mgr.handles.Load(handleID) - if !ok { - return fmt.Errorf("句柄不存在: %d", handleID) - } - - handle := value.(*CSVHandle) - - // 强制设置引用计数为0 - handle.refCountMu.Lock() - atomic.StoreInt64(&handle.refCount, 0) - handle.refCountMu.Unlock() - - // 获取文件锁 - fileLock := mgr.getFileLock(handle.Filename) - fileLock.Lock() - defer fileLock.Unlock() - - // 标记为正在关闭 - handle.markClosing() - - // 等待所有活动操作完成 - handle.waitForActiveOps(5 * time.Second) - - // 真正关闭文件 - handle.close() - mgr.handles.Delete(handle.ID) - - return nil -} - -// closeAllHandles 关闭所有句柄 -func (mgr *CSVManager) closeAllHandles() { - // 收集所有句柄ID - var handleIDs []int64 - mgr.handles.Range(func(key, value interface{}) bool { - handleIDs = append(handleIDs, key.(int64)) - return true - }) - - // 关闭每个句柄 - for _, handleID := range handleIDs { - mgr.ForceCloseHandle(handleID) - } -} - -// ========================== 文件操作方法 ========================== - -// openFile 打开文件 -func (mgr *CSVManager) openFile(handle *CSVHandle) error { - handle.mu.Lock() - defer handle.mu.Unlock() - - if handle.IsOpen { - return nil - } - - // 获取文件大小 - fileInfo, err := os.Stat(handle.Filename) - if err != nil { - return err - } - - fileSize := fileInfo.Size() - - // 根据文件大小选择打开策略 - if mgr.config.UseMMap && fileSize > mgr.config.MMapThreshold { - return mgr.openFileWithMMap(handle, fileSize) - } - - return mgr.openFileNormal(handle) -} - -// 正常打开文件 -func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error { - // 以读写模式打开文件 - file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0755) - if err != nil { - return err - } - - // 创建带缓冲的CSV阅读器 - reader := csv.NewReader(bufio.NewReaderSize(file, mgr.config.BufferSize)) - reader.Comma = handle.Delimiter - reader.LazyQuotes = true - reader.TrimLeadingSpace = true - - // 创建写入缓冲区 - writeBuffer := bufio.NewWriterSize(file, mgr.config.BufferSize) - - handle.File = file - handle.CSVReader = reader - handle.CSVWriter = csv.NewWriter(writeBuffer) - handle.CSVWriter.Comma = handle.Delimiter - handle.writeBuffer = writeBuffer - handle.IsOpen = true - handle.AccessTime = time.Now() - - return nil -} - -// 使用内存映射打开大文件 -func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error { - // 对于大文件,使用只读模式打开,写入需要特殊处理 - file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0755) - if err != nil { - return err - } - - // 对于大文件,我们可以先只打开,按需读取 - reader := csv.NewReader(bufio.NewReaderSize(file, mgr.config.BufferSize)) - reader.Comma = handle.Delimiter - reader.LazyQuotes = true - - // 创建写入缓冲区 - writeBuffer := bufio.NewWriterSize(file, mgr.config.BufferSize) - - handle.File = file - handle.CSVReader = reader - handle.CSVWriter = csv.NewWriter(writeBuffer) - handle.CSVWriter.Comma = handle.Delimiter - handle.writeBuffer = writeBuffer - handle.IsOpen = true - handle.AccessTime = time.Now() - - return nil -} - -// 读取CSV表头 -func (mgr *CSVManager) readHeader(handle *CSVHandle) error { - handle.mu.Lock() - defer handle.mu.Unlock() - - if !handle.IsOpen { - return fmt.Errorf("文件未打开") - } - - // 确保文件指针在开头 - if _, err := handle.File.Seek(0, 0); err != nil { - return err - } - - // 重置CSV阅读器 - handle.CSVReader = csv.NewReader(bufio.NewReader(handle.File)) - handle.CSVReader.Comma = handle.Delimiter - - // 读取表头 - header, err := handle.CSVReader.Read() - if err != nil { - return err - } - - handle.Header = header - return nil -} - -// startAutoClose 启动自动关闭计时器 -func (h *CSVHandle) startAutoClose(timeout time.Duration, mgr *CSVManager) { - h.autoCloseTimer = time.AfterFunc(timeout, func() { - h.mu.Lock() - defer h.mu.Unlock() - - // 检查是否长时间未访问 - if h.IsOpen && time.Since(h.AccessTime) > timeout { - // 检查引用计数 - if h.getRefCount() == 0 { - h.close() - // 从管理器移除 - mgr.handles.Delete(h.ID) - } - } - }) -} - -// ========================== 写入功能 ========================== - -// WriteHeader 写入CSV文件表头 -func (mgr *CSVManager) WriteHeader(handleID int64, header []string) error { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.Lock() - defer handle.mu.Unlock() - - if !handle.IsOpen { - return fmt.Errorf("文件未打开") - } - - // 检查文件是否已经有内容 - if handle.TotalRows > 0 { - return fmt.Errorf("文件已有数据,无法修改表头") - } - - // 移动到文件开头 - if _, err := handle.File.Seek(0, 0); err != nil { - return fmt.Errorf("移动文件指针失败: %w", err) - } - - // 清空文件内容 - if err := handle.File.Truncate(0); err != nil { - return fmt.Errorf("清空文件失败: %w", err) - } - - // 写入表头 - if err := handle.CSVWriter.Write(header); err != nil { - return fmt.Errorf("写入表头失败: %w", err) - } - - handle.CSVWriter.Flush() - if handle.writeBuffer != nil { - handle.writeBuffer.Flush() - } - - // 更新句柄状态 - handle.Header = header - handle.HasHeader = true - - return nil -} - -// WriteRow 写入单行数据到CSV文件 -func (mgr *CSVManager) WriteRow(handleID int64, row []string) error { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.Lock() - defer handle.mu.Unlock() - - if !handle.IsOpen { - return fmt.Errorf("文件未打开") - } - - // 检查是否有表头且表头长度是否匹配 - if handle.HasHeader && len(handle.Header) > 0 && len(row) != len(handle.Header) { - return fmt.Errorf("行数据列数(%d)与表头列数(%d)不匹配", len(row), len(handle.Header)) - } - - // 移动到文件末尾 - if _, err := handle.File.Seek(0, 2); err != nil { - return fmt.Errorf("移动到文件末尾失败: %w", err) - } - - // 写入行数据 - if err := handle.CSVWriter.Write(row); err != nil { - return fmt.Errorf("写入行失败: %w", err) - } - - // 更新行数统计 - handle.TotalRows++ - handle.cachedRowCount = handle.TotalRows - handle.rowCountCached = true - - return nil -} - -// WriteRows 批量写入多行数据到CSV文件 -func (mgr *CSVManager) WriteRows(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 -1, fmt.Errorf("WriteRows 获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return -1, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.Lock() - defer handle.mu.Unlock() - - if !handle.IsOpen { - return -1, fmt.Errorf("文件未打开") - } - - // 检查是否有表头 - if handle.HasHeader && len(handle.Header) > 0 { - // 验证每行的列数 - for i, row := range rows { - if len(row) != len(handle.Header) { - return -1, fmt.Errorf("第%d行列数(%d)与表头列数(%d)不匹配", i+1, len(row), len(handle.Header)) - } - } - } - - // 移动到文件末尾 - if _, err := handle.File.Seek(0, 2); err != nil { - return -1, fmt.Errorf("移动到文件末尾失败: %w", err) - } - - // 批量写入行数据 - for _, row := range rows { - if err := handle.CSVWriter.Write(row); err != nil { - return -1, fmt.Errorf("写入行失败: %w", err) - } - } - - // 更新行数统计 - handle.TotalRows += int64(len(rows)) - handle.cachedRowCount = handle.TotalRows - handle.rowCountCached = true - - 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 { - // 获取句柄信息(但不锁定,因为我们只是读取元数据) - 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, - } - - // 序列化为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 -} - -// 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 { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return fmt.Errorf("Flush 获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.Lock() - defer handle.mu.Unlock() - - if !handle.IsOpen { - return fmt.Errorf("文件未打开") - } - - if handle.CSVWriter == nil { - return fmt.Errorf("CSV写入器未初始化") - } - - // 刷新缓冲区 - handle.CSVWriter.Flush() - if handle.writeBuffer != nil { - if err := handle.writeBuffer.Flush(); err != nil { - return fmt.Errorf("刷新写入缓冲区失败: %w", err) - } - } - - return nil -} - -// AppendRow 追加行数据(自动Flush) -func (mgr *CSVManager) AppendRow(handleID int64, row []string) error { - if err := mgr.WriteRow(handleID, row); err != nil { - return err - } - return mgr.Flush(handleID) -} - -// AppendRows 批量追加行数据(自动Flush) -func (mgr *CSVManager) AppendRows(handleID int64, rows [][]string) (int64, error) { - totalRows, err := mgr.WriteRows(handleID, rows) - if err != nil { - return -1, err - } - err = mgr.Flush(handleID) - if err != nil { - return -1, err - } - 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) { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return nil, fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return nil, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.RLock() - defer handle.mu.RUnlock() - - if !handle.HasHeader { - return nil, fmt.Errorf("文件没有表头") - } - - return handle.Header, nil -} - -// ========================== 获取总行数 ========================== - -// GetTotalRows 快速获取总行数(如果已计算过则直接返回,否则重新计算) -func (mgr *CSVManager) GetTotalRows(handleID int64) (int64, error) { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return 0, fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return 0, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.RLock() - // 如果已缓存,直接返回缓存值 - if handle.rowCountCached { - count := handle.cachedRowCount - handle.mu.RUnlock() - return count, nil - } - handle.mu.RUnlock() - - // 否则计算总行数 - return handle.calculateTotalRows() -} - -// ========================== 修改指定行功能 ========================== - -// UpdateRow 修改指定行的数据 -func (mgr *CSVManager) UpdateRow(handleID int64, rowNumber int64, newData []string) error { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - //handle.mu.Lock() - //defer handle.mu.Unlock() - - if !handle.IsOpen { - return fmt.Errorf("文件未打开") - } - - // 检查行号是否有效 - if rowNumber < 0 { - return fmt.Errorf("行号不能为负数: %d", rowNumber) - } - - // 获取总行数 - totalRows, err := handle.calculateTotalRows() - if err != nil { - return fmt.Errorf("获取总行数失败: %w", err) - } - - if rowNumber >= totalRows { - return fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) - } - - // 检查新数据列数是否匹配 - if handle.HasHeader && len(handle.Header) > 0 { - if len(newData) != len(handle.Header) { - return fmt.Errorf("新数据列数(%d)与表头列数(%d)不匹配", len(newData), len(handle.Header)) - } - } - - // 读取整个文件到内存 - allRows, err := mgr.readAllRows(handle) - if err != nil { - return fmt.Errorf("读取文件失败: %w", err) - } - - // 修改指定行的数据 - if handle.HasHeader && len(allRows) > 0 { - // 如果有表头,第一行是表头,实际数据从第二行开始 - if rowNumber+1 >= int64(len(allRows)) { - return fmt.Errorf("行号超出范围: %d, 总数据行数: %d", rowNumber, len(allRows)-1) - } - allRows[rowNumber+1] = newData - } else { - if rowNumber >= int64(len(allRows)) { - return fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, len(allRows)) - } - allRows[rowNumber] = newData - } - - // 写回文件 - if err := mgr.writeAllRows(handle, allRows); err != nil { - return fmt.Errorf("写回文件失败: %w", err) - } - - // 更新缓存的行数 - handle.rowCountCached = false - - // 重新计算总行数 - if _, err := handle.calculateTotalRows(); err != nil { - return fmt.Errorf("重新计算总行数失败: %w", err) - } - - return nil -} - -// UpdateRows 批量修改多行数据 -func (mgr *CSVManager) UpdateRows(handleID int64, updates map[int64][]string) (int, error) { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return 0, fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return 0, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - handle.mu.Lock() - defer handle.mu.Unlock() - - if !handle.IsOpen { - return 0, fmt.Errorf("文件未打开") - } - - // 检查行号是否有效 - for rowNumber := range updates { - if rowNumber < 0 { - return 0, fmt.Errorf("行号不能为负数: %d", rowNumber) - } - } - - // 获取总行数 - totalRows, err := handle.calculateTotalRows() - if err != nil { - return 0, fmt.Errorf("获取总行数失败: %w", err) - } - - // 检查新数据列数是否匹配 - if handle.HasHeader && len(handle.Header) > 0 { - for rowNumber, newData := range updates { - if rowNumber >= totalRows { - return 0, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) - } - if len(newData) != len(handle.Header) { - return 0, fmt.Errorf("行%d的新数据列数(%d)与表头列数(%d)不匹配", rowNumber, len(newData), len(handle.Header)) - } - } - } - - // 读取整个文件到内存 - allRows, err := mgr.readAllRows(handle) - if err != nil { - return 0, fmt.Errorf("读取文件失败: %w", err) - } - - // 修改指定行的数据 - updatedCount := 0 - for rowNumber, newData := range updates { - if handle.HasHeader && len(allRows) > 0 { - // 如果有表头,第一行是表头,实际数据从第二行开始 - if rowNumber+1 >= int64(len(allRows)) { - return 0, fmt.Errorf("行号超出范围: %d, 总数据行数: %d", rowNumber, len(allRows)-1) - } - allRows[rowNumber+1] = newData - updatedCount++ - } else { - if rowNumber >= int64(len(allRows)) { - return 0, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, len(allRows)) - } - allRows[rowNumber] = newData - updatedCount++ - } - } - - // 写回文件 - if err := mgr.writeAllRows(handle, allRows); err != nil { - return 0, fmt.Errorf("写回文件失败: %w", err) - } - - // 更新缓存的行数 - handle.rowCountCached = false - - // 重新计算总行数 - if _, err := handle.calculateTotalRows(); err != nil { - return 0, fmt.Errorf("重新计算总行数失败: %w", err) - } - - return updatedCount, nil -} - -// readAllRows 读取文件中的所有行 -func (mgr *CSVManager) readAllRows(handle *CSVHandle) ([][]string, error) { - // 保存当前文件位置 - 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) - } - - // 重置CSV阅读器 - reader := csv.NewReader(bufio.NewReader(handle.File)) - reader.Comma = handle.Delimiter - reader.LazyQuotes = true - reader.TrimLeadingSpace = true - - // 读取所有行 - var allRows [][]string - for { - row, err := reader.Read() - if err != nil { - if err == io.EOF { - break - } - // 恢复文件位置 - handle.File.Seek(currentPos, 0) - return nil, fmt.Errorf("读取行失败: %w", err) - } - allRows = append(allRows, row) - } - - // 恢复文件位置 - if _, err := handle.File.Seek(currentPos, 0); err != nil { - return nil, fmt.Errorf("恢复文件位置失败: %w", err) - } - - return allRows, nil -} - -// writeAllRows 将所有行写回文件 -func (mgr *CSVManager) writeAllRows(handle *CSVHandle, rows [][]string) error { - // 清空文件 - if err := handle.File.Truncate(0); err != nil { - return fmt.Errorf("清空文件失败: %w", err) - } - - // 移动到文件开头 - if _, err := handle.File.Seek(0, 0); err != nil { - return fmt.Errorf("移动文件指针失败: %w", err) - } - - // 写入所有行 - for _, row := range rows { - if err := handle.CSVWriter.Write(row); err != nil { - return fmt.Errorf("写入行失败: %w", err) - } - } - - // 刷新缓冲区 - handle.CSVWriter.Flush() - if handle.writeBuffer != nil { - if err := handle.writeBuffer.Flush(); err != nil { - return fmt.Errorf("刷新写入缓冲区失败: %w", err) - } - } - - return nil -} - -// GetRow 获取指定行的数据 -func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) { - // 获取句柄 - handle, err := mgr.getHandle(handleID) - if err != nil { - return nil, fmt.Errorf("获取句柄失败: %w", err) - } - defer mgr.releaseHandle(handleID) - - // 检查句柄状态 - if !handle.beginOperation() { - return nil, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) - } - defer handle.endOperation() - - //handle.mu.RLock() - //defer handle.mu.RUnlock() - - if !handle.IsOpen { - return nil, fmt.Errorf("文件未打开") - } - - // 检查行号是否有效 - if rowNumber < 0 { - return nil, fmt.Errorf("行号不能为负数: %d", rowNumber) - } - - // 获取总行数 - totalRows, err := handle.calculateTotalRows() - if err != nil { - return nil, fmt.Errorf("获取总行数失败: %w", err) - } - - if rowNumber >= totalRows { - return nil, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) - } - - //// 保存当前文件位置 - //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) - } - defer file.Close() - - // 创建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 - for { - row, err := reader.Read() - if err != nil { - if err == io.EOF { - break - } - // 恢复文件位置 - //handle.File.Seek(currentPos, 0) - return nil, fmt.Errorf("读取行失败: %w", err) - } - - // 判断是否到达目标行 - if handle.HasHeader { - // 如果有表头,跳过第一行 - if currentRow == rowNumber+1 { - targetRow = row - break - } - } else { - // 如果没有表头,直接匹配 - if currentRow == rowNumber { - targetRow = row - break - } - } - currentRow++ - } - - //// 恢复文件位置 - //if _, err := handle.File.Seek(currentPos, 0); err != nil { - // return nil, fmt.Errorf("恢复文件位置失败: %w", err) - //} - - if targetRow == nil { - return nil, fmt.Errorf("未找到行号 %d", rowNumber) - } - - return targetRow, nil -} - -// ========================== 文件合并功能 ========================== - -// MergeCSVFiles 合并两个CSV文件 -func (mgr *CSVManager) MergeCSVFiles(srcHandleID, dstHandleID int64, options MergeOptions) (int64, error) { - // 获取源文件句柄 - srcHandle, err := mgr.getHandle(srcHandleID) - if err != nil { - return 0, fmt.Errorf("获取源文件句柄失败: %w", err) - } - defer mgr.releaseHandle(srcHandleID) - - // 获取目标文件句柄 - dstHandle, err := mgr.getHandle(dstHandleID) - if err != nil { - return 0, fmt.Errorf("获取目标文件句柄失败: %w", err) - } - defer mgr.releaseHandle(dstHandleID) - - // 检查句柄状态 - if !srcHandle.beginOperation() { - return 0, fmt.Errorf("源文件句柄已关闭或正在关闭: %d", srcHandleID) - } - defer srcHandle.endOperation() - - if !dstHandle.beginOperation() { - return 0, fmt.Errorf("目标文件句柄已关闭或正在关闭: %d", dstHandleID) - } - defer dstHandle.endOperation() - - srcHandle.mu.RLock() - dstHandle.mu.Lock() - defer func() { - srcHandle.mu.RUnlock() - dstHandle.mu.Unlock() - }() - - if !srcHandle.IsOpen { - return 0, fmt.Errorf("源文件未打开") - } - if !dstHandle.IsOpen { - return 0, fmt.Errorf("目标文件未打开") - } - - // 1. 检查表头兼容性 - if err := mgr.checkHeaderCompatibility(srcHandle, dstHandle, options); err != nil { - return 0, fmt.Errorf("表头不兼容: %w", err) - } - - // 2. 读取源文件的所有行 - srcRows, err := mgr.readAllRows(srcHandle) - if err != nil { - return 0, fmt.Errorf("读取源文件失败: %w", err) - } - - // 3. 合并数据 - mergedRows, err := mgr.mergeDataRows(srcRows, srcHandle, dstHandle, options) - if err != nil { - return 0, fmt.Errorf("合并数据失败: %w", err) - } - - // 4. 写入目标文件 - if options.AppendMode { - // 追加模式:写入到文件末尾 - if err := mgr.appendRowsToFile(dstHandle, mergedRows); err != nil { - return 0, fmt.Errorf("追加数据失败: %w", err) - } - } else { - // 覆盖模式:先读取目标文件的所有行,然后合并后写回 - dstRows, err := mgr.readAllRows(dstHandle) - if err != nil { - return 0, fmt.Errorf("读取目标文件失败: %w", err) - } - - // 如果是第一次合并且目标文件为空,可能需要保留表头 - allRows := mgr.mergeAllRows(dstRows, mergedRows, dstHandle, srcHandle, options) - if err := mgr.writeAllRows(dstHandle, allRows); err != nil { - return 0, fmt.Errorf("写入合并数据失败: %w", err) - } - } - - // 5. 更新目标文件的行数缓存 - dstHandle.rowCountCached = false - newTotalRows, err := dstHandle.calculateTotalRows() - if err != nil { - return 0, fmt.Errorf("重新计算行数失败: %w", err) - } - - return newTotalRows, nil -} - -// 检查表头兼容性 -func (mgr *CSVManager) checkHeaderCompatibility(srcHandle, dstHandle *CSVHandle, options MergeOptions) error { - // 如果两个文件都有表头 - if srcHandle.HasHeader && dstHandle.HasHeader { - srcHeader := srcHandle.Header - dstHeader := dstHandle.Header - - // 如果指定了列映射,跳过表头检查 - if len(options.ColumnMapping) > 0 { - return nil - } - - // 检查列数是否相同 - if len(srcHeader) != len(dstHeader) && !options.SkipHeader { - return fmt.Errorf("源文件列数(%d)与目标文件列数(%d)不一致", len(srcHeader), len(dstHeader)) - } - - // 检查列名是否相同(可选) - for i := 0; i < min(len(srcHeader), len(dstHeader)); i++ { - if srcHeader[i] != dstHeader[i] { - fmt.Printf("警告: 第%d列列名不同: 源='%s', 目标='%s'\n", i+1, srcHeader[i], dstHeader[i]) - } - } - } else if srcHandle.HasHeader != dstHandle.HasHeader { - // 一个有表头,一个没有表头 - if !options.SkipHeader { - return fmt.Errorf("源文件与目标文件的表头设置不一致") - } - } - - return nil -} - -// 合并数据行 -func (mgr *CSVManager) mergeDataRows(srcRows [][]string, srcHandle, dstHandle *CSVHandle, options MergeOptions) ([][]string, error) { - var result [][]string - - startIndex := 0 - // 如果源文件有表头且需要跳过表头 - if srcHandle.HasHeader && options.SkipHeader { - if len(srcRows) > 0 { - startIndex = 1 - } - } - - // 处理列映射 - for i := startIndex; i < len(srcRows); i++ { - srcRow := srcRows[i] - transformedRow, err := mgr.transformRow(srcRow, srcHandle, dstHandle, options) - if err != nil { - return nil, fmt.Errorf("转换第%d行数据失败: %w", i+1, err) - } - - // 跳过空行 - if transformedRow == nil { - continue - } - - // 检查重复行 - if options.SkipDuplicates && mgr.isDuplicateRow(result, transformedRow) { - continue - } - - result = append(result, transformedRow) - } - - return result, nil -} - -// 转换行数据 -func (mgr *CSVManager) transformRow(srcRow []string, srcHandle, dstHandle *CSVHandle, options MergeOptions) ([]string, error) { - // 如果有自定义转换函数,优先使用 - if options.TransformFunc != nil { - return options.TransformFunc(srcRow) - } - - // 如果有列映射,按照映射转换 - if len(options.ColumnMapping) > 0 { - // 确定目标行的最大列数 - maxCol := 0 - for _, dstIndex := range options.ColumnMapping { - if dstIndex > maxCol { - maxCol = dstIndex - } - } - - // 创建目标行 - dstRow := make([]string, maxCol+1) - for srcIndex, dstIndex := range options.ColumnMapping { - if srcIndex < len(srcRow) && dstIndex >= 0 && dstIndex < len(dstRow) { - dstRow[dstIndex] = srcRow[srcIndex] - } - } - return dstRow, nil - } - - // 如果没有列映射,直接复制 - dstRow := make([]string, len(srcRow)) - copy(dstRow, srcRow) - - // 如果目标文件有表头且列数不同,进行调整 - if dstHandle.HasHeader && len(dstRow) != len(dstHandle.Header) { - if len(dstRow) < len(dstHandle.Header) { - // 源列数少,用空字符串填充 - for i := len(dstRow); i < len(dstHandle.Header); i++ { - dstRow = append(dstRow, "") - } - } else { - // 源列数多,截断 - dstRow = dstRow[:len(dstHandle.Header)] - } - } - - return dstRow, nil -} - -// 检查是否为重复行 -func (mgr *CSVManager) isDuplicateRow(rows [][]string, newRow []string) bool { - for _, row := range rows { - if mgr.compareRows(row, newRow) { - return true - } - } - return false -} - -// 比较两行是否相同 -func (mgr *CSVManager) compareRows(row1, row2 []string) bool { - if len(row1) != len(row2) { - return false - } - for i := range row1 { - if row1[i] != row2[i] { - return false - } - } - return true -} - -// 追加行到文件末尾 -func (mgr *CSVManager) appendRowsToFile(handle *CSVHandle, rows [][]string) error { - // 移动到文件末尾 - if _, err := handle.File.Seek(0, 2); err != nil { - return fmt.Errorf("移动到文件末尾失败: %w", err) - } - - // 批量写入行数据 - for _, row := range rows { - if err := handle.CSVWriter.Write(row); err != nil { - return fmt.Errorf("写入行失败: %w", err) - } - } - - // 刷新缓冲区 - handle.CSVWriter.Flush() - if handle.writeBuffer != nil { - if err := handle.writeBuffer.Flush(); err != nil { - return fmt.Errorf("刷新写入缓冲区失败: %w", err) - } - } - - // 更新行数统计 - handle.TotalRows += int64(len(rows)) - handle.cachedRowCount = handle.TotalRows - handle.rowCountCached = true - - return nil -} - -// 合并所有行(用于覆盖模式) -func (mgr *CSVManager) mergeAllRows(dstRows, srcRows [][]string, dstHandle, srcHandle *CSVHandle, options MergeOptions) [][]string { - var result [][]string - - // 处理表头 - if dstHandle.HasHeader && len(dstRows) > 0 { - // 保留目标文件的表头 - result = append(result, dstRows[0]) - dstRows = dstRows[1:] - } - - // 根据冲突解决策略合并数据 - switch options.ConflictResolution { - case Overwrite: - // 覆盖模式:源数据完全替换目标数据 - result = append(result, srcRows...) - case Skip: - // 跳过模式:只保留目标数据中不冲突的行 - result = append(result, dstRows...) - for _, srcRow := range srcRows { - if !mgr.isDuplicateRow(result, srcRow) { - result = append(result, srcRow) - } - } - case KeepBoth: - // 保留两者:先写目标数据,再写源数据 - result = append(result, dstRows...) - result = append(result, srcRows...) - case UseSource: - // 使用源数据:源数据优先级更高 - result = append(result, srcRows...) - case UseTarget: - // 使用目标数据:目标数据优先级更高 - result = append(result, dstRows...) - default: - // 默认:追加模式 - result = append(result, dstRows...) - result = append(result, srcRows...) - } - - return result -} - -// MergeCSVFilesSimple 简单合并CSV文件(默认选项) -func (mgr *CSVManager) MergeCSVFilesSimple(srcHandleID, dstHandleID int64, appendMode bool) (int64, error) { - options := MergeOptions{ - AppendMode: appendMode, - SkipHeader: true, // 跳过源文件表头 - SkipDuplicates: false, // 不跳过重复行 - ConflictResolution: KeepBoth, // 保留两者 - } - return mgr.MergeCSVFiles(srcHandleID, dstHandleID, options) -} - -// ========================== C 导出函数 ========================== - -// OpenCSVFile 打开CSV文件 +///* +//#include +//*/ +//import "C" +//import ( +// "bufio" +// "encoding/csv" +// "encoding/json" +// "fmt" +// "io" +// "os" +// "path/filepath" +// "sync" +// "sync/atomic" +// "time" +// "unsafe" +//) // -//export OpenCSVFile -func OpenCSVFile(filename *C.char, delimiter C.char, hasHeader C.int) *C.char { - filenameStr := C.GoString(filename) - delimiterStr := rune(delimiter) - handleID, err := GetManager().OpenCSVFile(filenameStr, delimiterStr, hasHeader != 0) - response := struct { - HandleID int64 `json:"handleID"` - }{ - HandleID: handleID, - } - var csvResponse CSVResponse - 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)) -} - -// WriteHeader 写入表头 +//// ========================== 数据结构定义 ========================== // -//export WriteHeader -func WriteHeader(handleID C.int, header *C.char) *C.char { - var csvResponse CSVResponse - goHandleID := int64(handleID) - goHeader := C.GoString(header) - var data []string - err := json.Unmarshal([]byte(goHeader), &data) - if err != nil { - csvResponse = CSVResponse{ - Success: false, - Message: fmt.Sprintf("header JSON解析失败: %v", err), - } - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) - } - errs := GetManager().WriteHeader(goHandleID, data) - if errs != nil { - csvResponse = CSVResponse{ - Success: false, - Message: errs.Error(), - } - } else { - csvResponse = CSVResponse{ - Success: true, - } - } - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) -} - -// WriteRows 写入/覆盖行数据 +//// CSVHandle CSV文件句柄 +//type CSVHandle struct { +// ID int64 // 句柄唯一ID +// Filename string // 文件名 +// Delimiter rune // 分隔符 +// HasHeader bool // 是否有表头 +// Header []string // 表头(如果有) +// File *os.File // 底层文件句柄 +// CSVReader *csv.Reader // CSV阅读器 +// CSVWriter *csv.Writer // CSV写入器 +// TotalRows int64 // 总行数(如果已计算) +// IsOpen bool // 是否已打开 +// OpenTime time.Time // 打开时间 +// AccessTime time.Time // 最后访问时间 +// AccessCount int64 // 访问计数 +// mu sync.RWMutex // 读写锁(保护数据结构) +// autoCloseTimer *time.Timer // 自动关闭计时器 +// cachedRowCount int64 // 缓存的行数(避免重复计算) +// rowCountCached bool // 行数是否已缓存 +// writeBuffer *bufio.Writer // 写入缓冲区 // -//export WriteRows -func WriteRows(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)) - } - // 写入数据 - rows, err := GetManager().AppendRows(goHandleID, data) - response := struct { - TotalRows int64 `json:"totalRows"` - }{ - TotalRows: rows, - } - 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)) -} - -// 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 -func UpdateRow(handleID C.int, rowNumber C.int, rowData *C.char) *C.char { - var csvResponse CSVResponse - goHandleID := int64(handleID) - goRowNumber := int64(rowNumber) - goRowData := C.GoString(rowData) - - // JSON解析行数据 - var data []string - err := json.Unmarshal([]byte(goRowData), &data) - if err != nil { - csvResponse = CSVResponse{ - Success: false, - Message: fmt.Sprintf("rowsData JSON解析失败: %v", err), - } - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) - } - // 修改行数据 - err = GetManager().UpdateRow(goHandleID, goRowNumber, data) - if err != nil { - csvResponse = CSVResponse{ - Success: false, - Message: err.Error(), - } - } else { - csvResponse = CSVResponse{ - Success: true, - Message: fmt.Sprintf("成功修改第%d行数据", goRowNumber), - } - } - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) -} - -// GetRow C导出函数 - 获取指定行数据 -// -//export GetRow -func GetRow(handleID C.int, rowNumber C.int) *C.char { - goHandleID := int64(handleID) - goRowNumber := int64(rowNumber) - - // 获取行数据 - rowData, err := GetManager().GetRow(goHandleID, goRowNumber) - var csvResponse CSVResponse - if err != nil { - csvResponse = CSVResponse{ - Success: false, - Message: err.Error(), - } - } else { - csvResponse = CSVResponse{ - Success: true, - Data: rowData, - } - } - - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) -} - -// MergeCSVFilesSimple 合并两个csv文件 -// -//export MergeCSVFilesSimple -func MergeCSVFilesSimple(srcHandleID, dstHandleID, appendMode C.int) *C.char { - var csvResponse CSVResponse - goSrcHandleID := int64(srcHandleID) - goDstHandleID := int64(dstHandleID) - goAppendMode := int(appendMode) - rows, err := GetManager().MergeCSVFilesSimple(goSrcHandleID, goDstHandleID, goAppendMode == 0) - response := struct { - TotalRows int64 `json:"totalRows"` - }{ - TotalRows: rows, - } - 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)) -} - -// CloseHandles 关闭指定句柄(使用优雅关闭) -// -//export CloseHandles -func CloseHandles(handleID C.int) *C.char { - goHandleID := int64(handleID) - - // 使用优雅关闭 - err := GetManager().CloseHandle(goHandleID) - - var csvResponse CSVResponse - if err != nil { - csvResponse = CSVResponse{ - Success: false, - Message: err.Error(), - } - } else { - csvResponse = CSVResponse{ - Success: true, - Message: fmt.Sprintf("成功关闭句柄 %d", goHandleID), - } - } - - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) -} - -// CloseAllHandles 关闭所有句柄 -// -//export CloseAllHandles -func CloseAllHandles() *C.char { - GetManager().closeAllHandles() - - csvResponse := CSVResponse{ - Success: true, - Message: "成功关闭所有句柄", - } - - csvResponseStr, _ := json.Marshal(csvResponse) - return C.CString(string(csvResponseStr)) -} - -// FreeCString 释放C字符串内存 -// -//export FreeCString -func FreeCString(str *C.char) { - C.free(unsafe.Pointer(str)) -} - -// 主函数 -//func main() { +// // 新增:引用计数和状态管理 +// refCount int64 // 引用计数 +// refCountMu sync.RWMutex // 引用计数锁 +// closing bool // 正在关闭中 +// closed bool // 已关闭 +// statusMu sync.RWMutex // 状态锁 +// activeOps int64 // 正在进行的操作数 //} +// +//// CSVManager CSV文件管理器 +//type CSVManager struct { +// handles sync.Map // map[int64]*CSVHandle +// fileLocks sync.Map // map[string]*sync.RWMutex 文件级锁 +// nextHandle int64 // 下一个句柄ID +// maxOpen int // 最大打开文件数 +// semaphore chan struct{} // 信号量控制并发打开 +// config ManagerConfig // 管理器配置 +//} +// +//// ManagerConfig 管理器配置 +//type ManagerConfig struct { +// MaxOpenFiles int // 最大打开文件数 +// AutoCloseTimeout time.Duration // 自动关闭超时 +// BufferSize int // 缓冲区大小 +// UseMMap bool // 是否使用内存映射(大文件) +// MMapThreshold int64 // 使用内存映射的阈值(字节) +//} +// +//// MergeOptions 合并选项 +//type MergeOptions struct { +// AppendMode bool // true=追加模式,false=覆盖模式 +// SkipHeader bool // 是否跳过源文件的表头 +// SkipDuplicates bool // 是否跳过重复行 +// ColumnMapping map[int]int // 列映射:源文件列索引 -> 目标文件列索引 +// ConflictResolution ConflictStrategy // 冲突解决策略 +// TransformFunc func([]string) ([]string, error) // 数据转换函数 +//} +// +//// ConflictStrategy 冲突解决策略 +//type ConflictStrategy int +// +//const ( +// Overwrite ConflictStrategy = iota // 覆盖目标文件的数据 +// Skip // 跳过冲突的行 +// KeepBoth // 保留两者 +// UseSource // 使用源数据 +// UseTarget // 使用目标数据 +//) +// +//// CSVResponse CSV响应结构体 +//type CSVResponse struct { +// Success bool `json:"success"` +// Message string `json:"message,omitempty"` +// Data interface{} `json:"data,omitempty"` +//} +// +//// DefaultConfig 默认配置 +//var DefaultConfig = ManagerConfig{ +// MaxOpenFiles: 100, +// AutoCloseTimeout: 5 * time.Minute, +// BufferSize: 32 * 1024, // 32KB +// UseMMap: true, +// MMapThreshold: 10 * 1024 * 1024, // 10MB +//} +// +//// 全局管理器实例 +//var ( +// globalManager *CSVManager +// managerInitOnce sync.Once +//) +// +//// ========================== 辅助函数 ========================== +// +//func min(a, b int) int { +// if a < b { +// return a +// } +// return b +//} +// +//// ========================== GetManager 获取全局管理器 ========================== +// +//func GetManager() *CSVManager { +// managerInitOnce.Do(func() { +// globalManager = NewCSVManager(DefaultConfig) +// }) +// return globalManager +//} +// +//// ========================== NewCSVManager 创建新的CSV管理器 ========================== +// +//func NewCSVManager(config ManagerConfig) *CSVManager { +// if config.MaxOpenFiles <= 0 { +// config.MaxOpenFiles = DefaultConfig.MaxOpenFiles +// } +// return &CSVManager{ +// handles: sync.Map{}, +// fileLocks: sync.Map{}, +// nextHandle: 1, +// maxOpen: config.MaxOpenFiles, +// semaphore: make(chan struct{}, config.MaxOpenFiles), +// config: config, +// } +//} +// +//// ========================== CSVHandle 引用计数方法 ========================== +// +//// addRef 增加引用计数 +//func (h *CSVHandle) addRef() { +// atomic.AddInt64(&h.refCount, 1) +// h.AccessTime = time.Now() +// atomic.AddInt64(&h.AccessCount, 1) +//} +// +//// releaseRef 减少引用计数,如果为0则返回true表示可以关闭 +//func (h *CSVHandle) releaseRef() bool { +// h.refCountMu.Lock() +// defer h.refCountMu.Unlock() +// +// oldCount := atomic.AddInt64(&h.refCount, -1) +// return oldCount <= 0 +//} +// +//// getRefCount 获取引用计数 +//func (h *CSVHandle) getRefCount() int64 { +// return atomic.LoadInt64(&h.refCount) +//} +// +//// isValid 检查句柄是否有效 +//func (h *CSVHandle) isValid() bool { +// h.statusMu.RLock() +// defer h.statusMu.RUnlock() +// return !h.closing && !h.closed && h.IsOpen +//} +// +//// markClosing 标记为正在关闭 +//func (h *CSVHandle) markClosing() { +// h.statusMu.Lock() +// defer h.statusMu.Unlock() +// h.closing = true +//} +// +//// markClosed 标记为已关闭 +//func (h *CSVHandle) markClosed() { +// h.statusMu.Lock() +// defer h.statusMu.Unlock() +// h.closing = false +// h.closed = true +// h.IsOpen = false +//} +// +//// beginOperation 开始一个操作 +//func (h *CSVHandle) beginOperation() bool { +// h.statusMu.RLock() +// if h.closing || h.closed || !h.IsOpen { +// h.statusMu.RUnlock() +// return false +// } +// atomic.AddInt64(&h.activeOps, 1) +// h.statusMu.RUnlock() +// return true +//} +// +//// endOperation 结束一个操作 +//func (h *CSVHandle) endOperation() { +// atomic.AddInt64(&h.activeOps, -1) +//} +// +//// waitForActiveOps 等待所有活动操作完成 +//func (h *CSVHandle) waitForActiveOps(timeout time.Duration) bool { +// start := time.Now() +// for atomic.LoadInt64(&h.activeOps) > 0 { +// if time.Since(start) > timeout { +// return false +// } +// time.Sleep(10 * time.Millisecond) +// } +// return true +//} +// +//// ========================== CSVHandle 文件操作方法 ========================== +// +//// close 关闭CSV句柄(内部方法) +//func (h *CSVHandle) close() error { +// h.mu.Lock() +// defer h.mu.Unlock() +// +// if !h.IsOpen { +// return nil +// } +// +// // 停止自动关闭计时器 +// if h.autoCloseTimer != nil { +// h.autoCloseTimer.Stop() +// h.autoCloseTimer = nil +// } +// +// // 确保缓冲区数据写入文件 +// if h.CSVWriter != nil { +// h.CSVWriter.Flush() +// } +// if h.writeBuffer != nil { +// h.writeBuffer.Flush() +// } +// +// // 关闭文件 +// if h.File != nil { +// if err := h.File.Close(); err != nil { +// return err +// } +// h.File = nil +// } +// +// h.CSVReader = nil +// h.CSVWriter = nil +// h.writeBuffer = nil +// h.IsOpen = false +// h.markClosed() +// +// return nil +//} +// +//// getHeader 获取表头 +//func (h *CSVHandle) getHeader() []string { +// h.mu.RLock() +// defer h.mu.RUnlock() +// return h.Header +//} +// +//// calculateTotalRows 计算CSV文件的总行数 +//func (h *CSVHandle) calculateTotalRows() (int64, error) { +// h.mu.Lock() +// defer h.mu.Unlock() +// +// // 如果已缓存,直接返回 +// if h.rowCountCached { +// return h.cachedRowCount, nil +// } +// +// if !h.IsOpen { +// return 0, fmt.Errorf("文件未打开") +// } +// +// // 保存当前文件位置 +// currentPos, err := h.File.Seek(0, 1) // 当前位置 +// if err != nil { +// return 0, fmt.Errorf("获取当前文件位置失败: %w", err) +// } +// +// // 重置到文件开始 +// if _, err := h.File.Seek(0, 0); err != nil { +// return 0, fmt.Errorf("重置文件指针失败: %w", err) +// } +// +// // 重置CSV阅读器 +// reader := csv.NewReader(bufio.NewReader(h.File)) +// reader.Comma = h.Delimiter +// reader.LazyQuotes = true +// reader.TrimLeadingSpace = true +// reader.FieldsPerRecord = -1 // 允许字段数量可变(不检查每行字段数) +// +// // 统计行数 +// var rowCount int64 = 0 +// for { +// _, err := reader.Read() +// if err != nil { +// if err == io.EOF { +// break +// } +// // 恢复文件位置 +// h.File.Seek(currentPos, 0) +// return 0, fmt.Errorf("读取行失败: %w", err) +// } +// rowCount++ +// } +// +// // 恢复文件位置 +// if _, err := h.File.Seek(currentPos, 0); err != nil { +// return 0, fmt.Errorf("恢复文件位置失败: %w", err) +// } +// +// // 更新缓存 +// h.cachedRowCount = rowCount +// h.rowCountCached = true +// h.TotalRows = rowCount +// +// // 如果有表头,需要减去表头行 +// if h.HasHeader && rowCount > 0 { +// h.cachedRowCount = rowCount - 1 +// h.TotalRows = rowCount - 1 +// } +// +// return h.cachedRowCount, nil +//} +// +//// ========================== CSVManager 文件操作方法 ========================== +// +//// getFileLock 获取或创建文件锁 +//func (mgr *CSVManager) getFileLock(filename string) *sync.RWMutex { +// lock, _ := mgr.fileLocks.LoadOrStore(filename, &sync.RWMutex{}) +// return lock.(*sync.RWMutex) +//} +// +//// OpenCSVFile 打开CSV文件并返回句柄(线程安全版本) +//func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bool) (int64, error) { +// // 获取文件锁 +// fileLock := mgr.getFileLock(filename) +// fileLock.Lock() +// defer fileLock.Unlock() +// +// // 首先检查文件是否已经有打开的句柄 +// if existingHandleID := mgr.findHandleByFilename(filename); existingHandleID != -1 { +// if handle, err := mgr.getHandle(existingHandleID); err == nil { +// handle.addRef() +// return existingHandleID, nil +// } +// } +// +// // 检查文件是否存在,如果不存在则创建 +// fileInfo, err := os.Stat(filename) +// fileExists := true +// if os.IsNotExist(err) { +// fileExists = false +// // 创建文件 +// file, createErr := os.Create(filename) +// if createErr != nil { +// return -1, fmt.Errorf("创建文件失败: %w", createErr) +// } +// file.Close() // 关闭文件,后续会重新打开 +// +// // 重新获取文件信息 +// fileInfo, err = os.Stat(filename) +// if err != nil { +// return -1, fmt.Errorf("获取文件信息失败: %w", err) +// } +// } else if err != nil { +// // 其他错误 +// return -1, fmt.Errorf("检查文件状态失败: %w", err) +// } +// +// // 如果是新创建的空文件,需要特殊处理 +// if !fileExists && fileInfo.Size() == 0 { +// return mgr.createEmptyCSVHandle(filename, delimiter, hasHeader) +// } +// +// // 限制并发打开文件数 +// mgr.semaphore <- struct{}{} +// defer func() { <-mgr.semaphore }() +// +// // 生成唯一句柄ID +// handleID := atomic.AddInt64(&mgr.nextHandle, 1) +// +// // 创建CSV句柄 +// csvHandle := &CSVHandle{ +// ID: handleID, +// Filename: filename, +// Delimiter: delimiter, +// HasHeader: hasHeader, +// OpenTime: time.Now(), +// AccessTime: time.Now(), +// AccessCount: 1, +// refCount: 1, // 初始引用计数为1 +// } +// +// // 打开文件 +// if err := mgr.openFile(csvHandle); err != nil { +// return -1, fmt.Errorf("打开文件失败: %w", err) +// } +// +// // 如果是新创建的文件,可能需要写入表头 +// if !fileExists && hasHeader { +// // 创建空表头,用户后续可以写入实际表头 +// csvHandle.Header = []string{} +// } else if hasHeader && fileInfo.Size() > 0 { +// // 读取现有文件的表头 +// if err := mgr.readHeader(csvHandle); err != nil { +// csvHandle.close() +// return -1, fmt.Errorf("读取表头失败: %w", err) +// } +// } +// +// // 如果有数据行,计算总行数 +// if fileInfo.Size() > 0 { +// // 计算总行数 +// rows, err := csvHandle.calculateTotalRows() +// if err != nil { +// csvHandle.close() +// return -1, fmt.Errorf("计算总行数失败: %w", err) +// } +// csvHandle.TotalRows = rows +// } else { +// csvHandle.TotalRows = 0 +// csvHandle.cachedRowCount = 0 +// csvHandle.rowCountCached = true +// } +// +// // 注册到管理器 +// mgr.handles.Store(handleID, csvHandle) +// +// // 启动自动关闭计时器 +// if mgr.config.AutoCloseTimeout > 0 { +// csvHandle.startAutoClose(mgr.config.AutoCloseTimeout, mgr) +// } +// +// return handleID, nil +//} +// +//// findHandleByFilename 根据文件名查找已存在的句柄ID +//func (mgr *CSVManager) findHandleByFilename(filename string) int64 { +// var existingHandleID int64 = -1 +// +// // 遍历所有句柄,查找相同文件名的句柄 +// mgr.handles.Range(func(key, value interface{}) bool { +// handle := value.(*CSVHandle) +// +// // 检查文件名是否相同 +// if handle.Filename == filename { +// // 检查句柄是否有效 +// if handle.isValid() { +// existingHandleID = handle.ID +// return false // 停止遍历 +// } +// } +// return true // 继续遍历 +// }) +// +// return existingHandleID +//} +// +//// createEmptyCSVHandle 创建空的CSV文件句柄 +//func (mgr *CSVManager) createEmptyCSVHandle(filename string, delimiter rune, hasHeader bool) (int64, error) { +// // 限制并发打开文件数 +// mgr.semaphore <- struct{}{} +// defer func() { <-mgr.semaphore }() +// +// // 生成唯一句柄ID +// handleID := atomic.AddInt64(&mgr.nextHandle, 1) +// +// // 创建CSV句柄 +// csvHandle := &CSVHandle{ +// ID: handleID, +// Filename: filename, +// Delimiter: delimiter, +// HasHeader: hasHeader, +// OpenTime: time.Now(), +// AccessTime: time.Now(), +// TotalRows: 0, +// AccessCount: 1, +// refCount: 1, // 初始引用计数为1 +// } +// +// // 以读写模式打开文件 +// file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) +// if err != nil { +// return -1, fmt.Errorf("打开文件失败: %w", err) +// } +// +// // 创建CSV写入器 +// writer := csv.NewWriter(file) +// writer.Comma = delimiter +// +// // 如果是新创建的文件且有表头,写入空表头占位 +// if hasHeader { +// // 写入空表头(用户后续需要设置实际表头) +// if err := writer.Write([]string{}); err != nil { +// file.Close() +// return -1, fmt.Errorf("写入空表头失败: %w", err) +// } +// writer.Flush() +// +// // 重置到文件开始位置 +// if _, err := file.Seek(0, 0); err != nil { +// file.Close() +// return -1, fmt.Errorf("重置文件指针失败: %w", err) +// } +// } +// +// // 创建CSV阅读器 +// reader := csv.NewReader(bufio.NewReaderSize(file, mgr.config.BufferSize)) +// reader.Comma = delimiter +// reader.LazyQuotes = true +// reader.TrimLeadingSpace = true +// +// // 创建写入缓冲区 +// writeBuffer := bufio.NewWriterSize(file, mgr.config.BufferSize) +// +// csvHandle.File = file +// csvHandle.CSVReader = reader +// csvHandle.CSVWriter = csv.NewWriter(writeBuffer) +// csvHandle.CSVWriter.Comma = delimiter +// csvHandle.writeBuffer = writeBuffer +// csvHandle.IsOpen = true +// csvHandle.AccessTime = time.Now() +// +// // 如果有表头,初始化空表头数组 +// if hasHeader { +// csvHandle.Header = []string{} +// } +// +// // 注册到管理器 +// mgr.handles.Store(handleID, csvHandle) +// +// // 启动自动关闭计时器 +// if mgr.config.AutoCloseTimeout > 0 { +// csvHandle.startAutoClose(mgr.config.AutoCloseTimeout, mgr) +// } +// +// return handleID, nil +//} +// +//// getHandle 获取句柄对象(增加引用计数) +//func (mgr *CSVManager) getHandle(handleID int64) (*CSVHandle, error) { +// value, ok := mgr.handles.Load(handleID) +// if !ok { +// return nil, fmt.Errorf("句柄不存在: %d", handleID) +// } +// +// handle := value.(*CSVHandle) +// +// // 检查句柄是否有效 +// if !handle.isValid() { +// return nil, fmt.Errorf("句柄无效: %d", handleID) +// } +// +// // 增加引用计数 +// handle.addRef() +// +// // 确保文件已打开 +// handle.mu.RLock() +// isOpen := handle.IsOpen +// handle.mu.RUnlock() +// +// if !isOpen { +// // 获取文件锁 +// fileLock := mgr.getFileLock(handle.Filename) +// fileLock.Lock() +// defer fileLock.Unlock() +// +// // 重新检查,防止其他协程已经重新打开了 +// handle.mu.RLock() +// isOpen = handle.IsOpen +// handle.mu.RUnlock() +// +// if !isOpen { +// if err := mgr.openFile(handle); err != nil { +// // 恢复引用计数 +// handle.releaseRef() +// return nil, fmt.Errorf("重新打开文件失败: %w", err) +// } +// } +// } +// +// return handle, nil +//} +// +//// releaseHandle 释放句柄引用 +//func (mgr *CSVManager) releaseHandle(handleID int64) { +// value, ok := mgr.handles.Load(handleID) +// if !ok { +// return +// } +// +// handle := value.(*CSVHandle) +// // 减少引用计数,如果为0则真正关闭 +// if handle.releaseRef() { +// mgr.closeHandleInternal(handleID, handle) +// } +//} +// +//// ========================== 句柄管理方法 ========================== +// +//// closeHandleInternal 内部关闭句柄方法 +//func (mgr *CSVManager) closeHandleInternal(handleID int64, handle *CSVHandle) { +// // 获取文件锁 +// fileLock := mgr.getFileLock(handle.Filename) +// fileLock.Lock() +// defer fileLock.Unlock() +// +// // 再次检查引用计数,防止在获取锁期间有新的引用 +// if handle.getRefCount() > 0 { +// return +// } +// +// // 标记为正在关闭 +// handle.markClosing() +// +// // 等待所有活动操作完成 +// if !handle.waitForActiveOps(30 * time.Second) { +// // 超时,强制关闭 +// fmt.Printf("警告:句柄 %d 关闭超时,强制关闭\n", handleID) +// } +// +// // 真正关闭文件 +// handle.close() +// +// // 从管理器移除 +// mgr.handles.Delete(handleID) +//} +// +//// CloseHandle 关闭指定句柄(外部调用) +//func (mgr *CSVManager) CloseHandle(handleID int64) error { +// value, ok := mgr.handles.Load(handleID) +// if !ok { +// return fmt.Errorf("句柄不存在: %d", handleID) +// } +// +// handle := value.(*CSVHandle) +// +// // 减少引用计数,如果为0则真正关闭 +// if handle.releaseRef() { +// mgr.closeHandleInternal(handleID, handle) +// } +// +// return nil +//} +// +//// GracefulCloseHandle 优雅关闭句柄(等待所有操作完成) +//func (mgr *CSVManager) GracefulCloseHandle(handleID int64, timeout time.Duration) error { +// value, ok := mgr.handles.Load(handleID) +// if !ok { +// return fmt.Errorf("句柄不存在: %d", handleID) +// } +// +// handle := value.(*CSVHandle) +// +// // 标记为正在关闭,阻止新操作 +// handle.markClosing() +// +// // 等待现有操作完成 +// done := make(chan bool, 1) +// go func() { +// // 等待引用计数降为1(只剩当前引用) +// for handle.getRefCount() > 1 { +// time.Sleep(100 * time.Millisecond) +// } +// done <- true +// }() +// +// // 设置超时 +// select { +// case <-done: +// // 真正关闭 +// return mgr.ForceCloseHandle(handleID) +// case <-time.After(timeout): +// return fmt.Errorf("关闭句柄超时,仍有 %d 个引用", handle.getRefCount()) +// } +//} +// +//// ForceCloseHandle 强制关闭句柄(无论引用计数如何) +//func (mgr *CSVManager) ForceCloseHandle(handleID int64) error { +// value, ok := mgr.handles.Load(handleID) +// if !ok { +// return fmt.Errorf("句柄不存在: %d", handleID) +// } +// +// handle := value.(*CSVHandle) +// +// // 强制设置引用计数为0 +// handle.refCountMu.Lock() +// atomic.StoreInt64(&handle.refCount, 0) +// handle.refCountMu.Unlock() +// +// // 获取文件锁 +// fileLock := mgr.getFileLock(handle.Filename) +// fileLock.Lock() +// defer fileLock.Unlock() +// +// // 标记为正在关闭 +// handle.markClosing() +// +// // 等待所有活动操作完成 +// handle.waitForActiveOps(5 * time.Second) +// +// // 真正关闭文件 +// handle.close() +// mgr.handles.Delete(handle.ID) +// +// return nil +//} +// +//// closeAllHandles 关闭所有句柄 +//func (mgr *CSVManager) closeAllHandles() { +// // 收集所有句柄ID +// var handleIDs []int64 +// mgr.handles.Range(func(key, value interface{}) bool { +// handleIDs = append(handleIDs, key.(int64)) +// return true +// }) +// +// // 关闭每个句柄 +// for _, handleID := range handleIDs { +// mgr.ForceCloseHandle(handleID) +// } +//} +// +//// ========================== 文件操作方法 ========================== +// +//// openFile 打开文件 +//func (mgr *CSVManager) openFile(handle *CSVHandle) error { +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if handle.IsOpen { +// return nil +// } +// +// // 获取文件大小 +// fileInfo, err := os.Stat(handle.Filename) +// if err != nil { +// return err +// } +// +// fileSize := fileInfo.Size() +// +// // 根据文件大小选择打开策略 +// if mgr.config.UseMMap && fileSize > mgr.config.MMapThreshold { +// return mgr.openFileWithMMap(handle, fileSize) +// } +// +// return mgr.openFileNormal(handle) +//} +// +//// 正常打开文件 +//func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error { +// // 以读写模式打开文件 +// file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0755) +// if err != nil { +// return err +// } +// +// // 创建带缓冲的CSV阅读器 +// reader := csv.NewReader(bufio.NewReaderSize(file, mgr.config.BufferSize)) +// reader.Comma = handle.Delimiter +// reader.LazyQuotes = true +// reader.TrimLeadingSpace = true +// +// // 创建写入缓冲区 +// writeBuffer := bufio.NewWriterSize(file, mgr.config.BufferSize) +// +// handle.File = file +// handle.CSVReader = reader +// handle.CSVWriter = csv.NewWriter(writeBuffer) +// handle.CSVWriter.Comma = handle.Delimiter +// handle.writeBuffer = writeBuffer +// handle.IsOpen = true +// handle.AccessTime = time.Now() +// +// return nil +//} +// +//// 使用内存映射打开大文件 +//func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error { +// // 对于大文件,使用只读模式打开,写入需要特殊处理 +// file, err := os.OpenFile(handle.Filename, os.O_RDWR, 0755) +// if err != nil { +// return err +// } +// +// // 对于大文件,我们可以先只打开,按需读取 +// reader := csv.NewReader(bufio.NewReaderSize(file, mgr.config.BufferSize)) +// reader.Comma = handle.Delimiter +// reader.LazyQuotes = true +// +// // 创建写入缓冲区 +// writeBuffer := bufio.NewWriterSize(file, mgr.config.BufferSize) +// +// handle.File = file +// handle.CSVReader = reader +// handle.CSVWriter = csv.NewWriter(writeBuffer) +// handle.CSVWriter.Comma = handle.Delimiter +// handle.writeBuffer = writeBuffer +// handle.IsOpen = true +// handle.AccessTime = time.Now() +// +// return nil +//} +// +//// 读取CSV表头 +//func (mgr *CSVManager) readHeader(handle *CSVHandle) error { +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return fmt.Errorf("文件未打开") +// } +// +// // 确保文件指针在开头 +// if _, err := handle.File.Seek(0, 0); err != nil { +// return err +// } +// +// // 重置CSV阅读器 +// handle.CSVReader = csv.NewReader(bufio.NewReader(handle.File)) +// handle.CSVReader.Comma = handle.Delimiter +// +// // 读取表头 +// header, err := handle.CSVReader.Read() +// if err != nil { +// return err +// } +// +// handle.Header = header +// return nil +//} +// +//// startAutoClose 启动自动关闭计时器 +//func (h *CSVHandle) startAutoClose(timeout time.Duration, mgr *CSVManager) { +// h.autoCloseTimer = time.AfterFunc(timeout, func() { +// h.mu.Lock() +// defer h.mu.Unlock() +// +// // 检查是否长时间未访问 +// if h.IsOpen && time.Since(h.AccessTime) > timeout { +// // 检查引用计数 +// if h.getRefCount() == 0 { +// h.close() +// // 从管理器移除 +// mgr.handles.Delete(h.ID) +// } +// } +// }) +//} +// +//// ========================== 写入功能 ========================== +// +//// WriteHeader 写入CSV文件表头 +//func (mgr *CSVManager) WriteHeader(handleID int64, header []string) error { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return fmt.Errorf("文件未打开") +// } +// +// // 检查文件是否已经有内容 +// if handle.TotalRows > 0 { +// return fmt.Errorf("文件已有数据,无法修改表头") +// } +// +// // 移动到文件开头 +// if _, err := handle.File.Seek(0, 0); err != nil { +// return fmt.Errorf("移动文件指针失败: %w", err) +// } +// +// // 清空文件内容 +// if err := handle.File.Truncate(0); err != nil { +// return fmt.Errorf("清空文件失败: %w", err) +// } +// +// // 写入表头 +// if err := handle.CSVWriter.Write(header); err != nil { +// return fmt.Errorf("写入表头失败: %w", err) +// } +// +// handle.CSVWriter.Flush() +// if handle.writeBuffer != nil { +// handle.writeBuffer.Flush() +// } +// +// // 更新句柄状态 +// handle.Header = header +// handle.HasHeader = true +// +// return nil +//} +// +//// WriteRow 写入单行数据到CSV文件 +//func (mgr *CSVManager) WriteRow(handleID int64, row []string) error { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return fmt.Errorf("文件未打开") +// } +// +// // 检查是否有表头且表头长度是否匹配 +// if handle.HasHeader && len(handle.Header) > 0 && len(row) != len(handle.Header) { +// return fmt.Errorf("行数据列数(%d)与表头列数(%d)不匹配", len(row), len(handle.Header)) +// } +// +// // 移动到文件末尾 +// if _, err := handle.File.Seek(0, 2); err != nil { +// return fmt.Errorf("移动到文件末尾失败: %w", err) +// } +// +// // 写入行数据 +// if err := handle.CSVWriter.Write(row); err != nil { +// return fmt.Errorf("写入行失败: %w", err) +// } +// +// // 更新行数统计 +// handle.TotalRows++ +// handle.cachedRowCount = handle.TotalRows +// handle.rowCountCached = true +// +// return nil +//} +// +//// WriteRows 批量写入多行数据到CSV文件 +//func (mgr *CSVManager) WriteRows(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 -1, fmt.Errorf("WriteRows 获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return -1, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return -1, fmt.Errorf("文件未打开") +// } +// +// // 检查是否有表头 +// if handle.HasHeader && len(handle.Header) > 0 { +// // 验证每行的列数 +// for i, row := range rows { +// if len(row) != len(handle.Header) { +// return -1, fmt.Errorf("第%d行列数(%d)与表头列数(%d)不匹配", i+1, len(row), len(handle.Header)) +// } +// } +// } +// +// // 移动到文件末尾 +// if _, err := handle.File.Seek(0, 2); err != nil { +// return -1, fmt.Errorf("移动到文件末尾失败: %w", err) +// } +// +// // 批量写入行数据 +// for _, row := range rows { +// if err := handle.CSVWriter.Write(row); err != nil { +// return -1, fmt.Errorf("写入行失败: %w", err) +// } +// } +// +// // 更新行数统计 +// handle.TotalRows += int64(len(rows)) +// handle.cachedRowCount = handle.TotalRows +// handle.rowCountCached = true +// +// 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 { +// // 获取句柄信息(但不锁定,因为我们只是读取元数据) +// 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, +// } +// +// // 序列化为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 +//} +// +//// 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 { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return fmt.Errorf("Flush 获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return fmt.Errorf("文件未打开") +// } +// +// if handle.CSVWriter == nil { +// return fmt.Errorf("CSV写入器未初始化") +// } +// +// // 刷新缓冲区 +// handle.CSVWriter.Flush() +// if handle.writeBuffer != nil { +// if err := handle.writeBuffer.Flush(); err != nil { +// return fmt.Errorf("刷新写入缓冲区失败: %w", err) +// } +// } +// +// return nil +//} +// +//// AppendRow 追加行数据(自动Flush) +//func (mgr *CSVManager) AppendRow(handleID int64, row []string) error { +// if err := mgr.WriteRow(handleID, row); err != nil { +// return err +// } +// return mgr.Flush(handleID) +//} +// +//// AppendRows 批量追加行数据(自动Flush) +//func (mgr *CSVManager) AppendRows(handleID int64, rows [][]string) (int64, error) { +// totalRows, err := mgr.WriteRows(handleID, rows) +// if err != nil { +// return -1, err +// } +// err = mgr.Flush(handleID) +// if err != nil { +// return -1, err +// } +// 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) { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return nil, fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return nil, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.RLock() +// defer handle.mu.RUnlock() +// +// if !handle.HasHeader { +// return nil, fmt.Errorf("文件没有表头") +// } +// +// return handle.Header, nil +//} +// +//// ========================== 获取总行数 ========================== +// +//// GetTotalRows 快速获取总行数(如果已计算过则直接返回,否则重新计算) +//func (mgr *CSVManager) GetTotalRows(handleID int64) (int64, error) { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return 0, fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return 0, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.RLock() +// // 如果已缓存,直接返回缓存值 +// if handle.rowCountCached { +// count := handle.cachedRowCount +// handle.mu.RUnlock() +// return count, nil +// } +// handle.mu.RUnlock() +// +// // 否则计算总行数 +// return handle.calculateTotalRows() +//} +// +//// ========================== 修改指定行功能 ========================== +// +//// UpdateRow 修改指定行的数据 +//func (mgr *CSVManager) UpdateRow(handleID int64, rowNumber int64, newData []string) error { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// //handle.mu.Lock() +// //defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return fmt.Errorf("文件未打开") +// } +// +// // 检查行号是否有效 +// if rowNumber < 0 { +// return fmt.Errorf("行号不能为负数: %d", rowNumber) +// } +// +// // 获取总行数 +// totalRows, err := handle.calculateTotalRows() +// if err != nil { +// return fmt.Errorf("获取总行数失败: %w", err) +// } +// +// if rowNumber >= totalRows { +// return fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) +// } +// +// // 检查新数据列数是否匹配 +// if handle.HasHeader && len(handle.Header) > 0 { +// if len(newData) != len(handle.Header) { +// return fmt.Errorf("新数据列数(%d)与表头列数(%d)不匹配", len(newData), len(handle.Header)) +// } +// } +// +// // 读取整个文件到内存 +// allRows, err := mgr.readAllRows(handle) +// if err != nil { +// return fmt.Errorf("读取文件失败: %w", err) +// } +// +// // 修改指定行的数据 +// if handle.HasHeader && len(allRows) > 0 { +// // 如果有表头,第一行是表头,实际数据从第二行开始 +// if rowNumber+1 >= int64(len(allRows)) { +// return fmt.Errorf("行号超出范围: %d, 总数据行数: %d", rowNumber, len(allRows)-1) +// } +// allRows[rowNumber+1] = newData +// } else { +// if rowNumber >= int64(len(allRows)) { +// return fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, len(allRows)) +// } +// allRows[rowNumber] = newData +// } +// +// // 写回文件 +// if err := mgr.writeAllRows(handle, allRows); err != nil { +// return fmt.Errorf("写回文件失败: %w", err) +// } +// +// // 更新缓存的行数 +// handle.rowCountCached = false +// +// // 重新计算总行数 +// if _, err := handle.calculateTotalRows(); err != nil { +// return fmt.Errorf("重新计算总行数失败: %w", err) +// } +// +// return nil +//} +// +//// UpdateRows 批量修改多行数据 +//func (mgr *CSVManager) UpdateRows(handleID int64, updates map[int64][]string) (int, error) { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return 0, fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return 0, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// handle.mu.Lock() +// defer handle.mu.Unlock() +// +// if !handle.IsOpen { +// return 0, fmt.Errorf("文件未打开") +// } +// +// // 检查行号是否有效 +// for rowNumber := range updates { +// if rowNumber < 0 { +// return 0, fmt.Errorf("行号不能为负数: %d", rowNumber) +// } +// } +// +// // 获取总行数 +// totalRows, err := handle.calculateTotalRows() +// if err != nil { +// return 0, fmt.Errorf("获取总行数失败: %w", err) +// } +// +// // 检查新数据列数是否匹配 +// if handle.HasHeader && len(handle.Header) > 0 { +// for rowNumber, newData := range updates { +// if rowNumber >= totalRows { +// return 0, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) +// } +// if len(newData) != len(handle.Header) { +// return 0, fmt.Errorf("行%d的新数据列数(%d)与表头列数(%d)不匹配", rowNumber, len(newData), len(handle.Header)) +// } +// } +// } +// +// // 读取整个文件到内存 +// allRows, err := mgr.readAllRows(handle) +// if err != nil { +// return 0, fmt.Errorf("读取文件失败: %w", err) +// } +// +// // 修改指定行的数据 +// updatedCount := 0 +// for rowNumber, newData := range updates { +// if handle.HasHeader && len(allRows) > 0 { +// // 如果有表头,第一行是表头,实际数据从第二行开始 +// if rowNumber+1 >= int64(len(allRows)) { +// return 0, fmt.Errorf("行号超出范围: %d, 总数据行数: %d", rowNumber, len(allRows)-1) +// } +// allRows[rowNumber+1] = newData +// updatedCount++ +// } else { +// if rowNumber >= int64(len(allRows)) { +// return 0, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, len(allRows)) +// } +// allRows[rowNumber] = newData +// updatedCount++ +// } +// } +// +// // 写回文件 +// if err := mgr.writeAllRows(handle, allRows); err != nil { +// return 0, fmt.Errorf("写回文件失败: %w", err) +// } +// +// // 更新缓存的行数 +// handle.rowCountCached = false +// +// // 重新计算总行数 +// if _, err := handle.calculateTotalRows(); err != nil { +// return 0, fmt.Errorf("重新计算总行数失败: %w", err) +// } +// +// return updatedCount, nil +//} +// +//// readAllRows 读取文件中的所有行 +//func (mgr *CSVManager) readAllRows(handle *CSVHandle) ([][]string, error) { +// // 保存当前文件位置 +// 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) +// } +// +// // 重置CSV阅读器 +// reader := csv.NewReader(bufio.NewReader(handle.File)) +// reader.Comma = handle.Delimiter +// reader.LazyQuotes = true +// reader.TrimLeadingSpace = true +// +// // 读取所有行 +// var allRows [][]string +// for { +// row, err := reader.Read() +// if err != nil { +// if err == io.EOF { +// break +// } +// // 恢复文件位置 +// handle.File.Seek(currentPos, 0) +// return nil, fmt.Errorf("读取行失败: %w", err) +// } +// allRows = append(allRows, row) +// } +// +// // 恢复文件位置 +// if _, err := handle.File.Seek(currentPos, 0); err != nil { +// return nil, fmt.Errorf("恢复文件位置失败: %w", err) +// } +// +// return allRows, nil +//} +// +//// writeAllRows 将所有行写回文件 +//func (mgr *CSVManager) writeAllRows(handle *CSVHandle, rows [][]string) error { +// // 清空文件 +// if err := handle.File.Truncate(0); err != nil { +// return fmt.Errorf("清空文件失败: %w", err) +// } +// +// // 移动到文件开头 +// if _, err := handle.File.Seek(0, 0); err != nil { +// return fmt.Errorf("移动文件指针失败: %w", err) +// } +// +// // 写入所有行 +// for _, row := range rows { +// if err := handle.CSVWriter.Write(row); err != nil { +// return fmt.Errorf("写入行失败: %w", err) +// } +// } +// +// // 刷新缓冲区 +// handle.CSVWriter.Flush() +// if handle.writeBuffer != nil { +// if err := handle.writeBuffer.Flush(); err != nil { +// return fmt.Errorf("刷新写入缓冲区失败: %w", err) +// } +// } +// +// return nil +//} +// +//// GetRow 获取指定行的数据 +//func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) { +// // 获取句柄 +// handle, err := mgr.getHandle(handleID) +// if err != nil { +// return nil, fmt.Errorf("获取句柄失败: %w", err) +// } +// defer mgr.releaseHandle(handleID) +// +// // 检查句柄状态 +// if !handle.beginOperation() { +// return nil, fmt.Errorf("句柄已关闭或正在关闭: %d", handleID) +// } +// defer handle.endOperation() +// +// //handle.mu.RLock() +// //defer handle.mu.RUnlock() +// +// if !handle.IsOpen { +// return nil, fmt.Errorf("文件未打开") +// } +// +// // 检查行号是否有效 +// if rowNumber < 0 { +// return nil, fmt.Errorf("行号不能为负数: %d", rowNumber) +// } +// +// // 获取总行数 +// totalRows, err := handle.calculateTotalRows() +// if err != nil { +// return nil, fmt.Errorf("获取总行数失败: %w", err) +// } +// +// if rowNumber >= totalRows { +// return nil, fmt.Errorf("行号超出范围: %d, 总行数: %d", rowNumber, totalRows) +// } +// +// //// 保存当前文件位置 +// //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) +// } +// defer file.Close() +// +// // 创建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 +// for { +// row, err := reader.Read() +// if err != nil { +// if err == io.EOF { +// break +// } +// // 恢复文件位置 +// //handle.File.Seek(currentPos, 0) +// return nil, fmt.Errorf("读取行失败: %w", err) +// } +// +// // 判断是否到达目标行 +// if handle.HasHeader { +// // 如果有表头,跳过第一行 +// if currentRow == rowNumber+1 { +// targetRow = row +// break +// } +// } else { +// // 如果没有表头,直接匹配 +// if currentRow == rowNumber { +// targetRow = row +// break +// } +// } +// currentRow++ +// } +// +// //// 恢复文件位置 +// //if _, err := handle.File.Seek(currentPos, 0); err != nil { +// // return nil, fmt.Errorf("恢复文件位置失败: %w", err) +// //} +// +// if targetRow == nil { +// return nil, fmt.Errorf("未找到行号 %d", rowNumber) +// } +// +// return targetRow, nil +//} +// +//// ========================== 文件合并功能 ========================== +// +//// MergeCSVFiles 合并两个CSV文件 +//func (mgr *CSVManager) MergeCSVFiles(srcHandleID, dstHandleID int64, options MergeOptions) (int64, error) { +// // 获取源文件句柄 +// srcHandle, err := mgr.getHandle(srcHandleID) +// if err != nil { +// return 0, fmt.Errorf("获取源文件句柄失败: %w", err) +// } +// defer mgr.releaseHandle(srcHandleID) +// +// // 获取目标文件句柄 +// dstHandle, err := mgr.getHandle(dstHandleID) +// if err != nil { +// return 0, fmt.Errorf("获取目标文件句柄失败: %w", err) +// } +// defer mgr.releaseHandle(dstHandleID) +// +// // 检查句柄状态 +// if !srcHandle.beginOperation() { +// return 0, fmt.Errorf("源文件句柄已关闭或正在关闭: %d", srcHandleID) +// } +// defer srcHandle.endOperation() +// +// if !dstHandle.beginOperation() { +// return 0, fmt.Errorf("目标文件句柄已关闭或正在关闭: %d", dstHandleID) +// } +// defer dstHandle.endOperation() +// +// srcHandle.mu.RLock() +// dstHandle.mu.Lock() +// defer func() { +// srcHandle.mu.RUnlock() +// dstHandle.mu.Unlock() +// }() +// +// if !srcHandle.IsOpen { +// return 0, fmt.Errorf("源文件未打开") +// } +// if !dstHandle.IsOpen { +// return 0, fmt.Errorf("目标文件未打开") +// } +// +// // 1. 检查表头兼容性 +// if err := mgr.checkHeaderCompatibility(srcHandle, dstHandle, options); err != nil { +// return 0, fmt.Errorf("表头不兼容: %w", err) +// } +// +// // 2. 读取源文件的所有行 +// srcRows, err := mgr.readAllRows(srcHandle) +// if err != nil { +// return 0, fmt.Errorf("读取源文件失败: %w", err) +// } +// +// // 3. 合并数据 +// mergedRows, err := mgr.mergeDataRows(srcRows, srcHandle, dstHandle, options) +// if err != nil { +// return 0, fmt.Errorf("合并数据失败: %w", err) +// } +// +// // 4. 写入目标文件 +// if options.AppendMode { +// // 追加模式:写入到文件末尾 +// if err := mgr.appendRowsToFile(dstHandle, mergedRows); err != nil { +// return 0, fmt.Errorf("追加数据失败: %w", err) +// } +// } else { +// // 覆盖模式:先读取目标文件的所有行,然后合并后写回 +// dstRows, err := mgr.readAllRows(dstHandle) +// if err != nil { +// return 0, fmt.Errorf("读取目标文件失败: %w", err) +// } +// +// // 如果是第一次合并且目标文件为空,可能需要保留表头 +// allRows := mgr.mergeAllRows(dstRows, mergedRows, dstHandle, srcHandle, options) +// if err := mgr.writeAllRows(dstHandle, allRows); err != nil { +// return 0, fmt.Errorf("写入合并数据失败: %w", err) +// } +// } +// +// // 5. 更新目标文件的行数缓存 +// dstHandle.rowCountCached = false +// newTotalRows, err := dstHandle.calculateTotalRows() +// if err != nil { +// return 0, fmt.Errorf("重新计算行数失败: %w", err) +// } +// +// return newTotalRows, nil +//} +// +//// 检查表头兼容性 +//func (mgr *CSVManager) checkHeaderCompatibility(srcHandle, dstHandle *CSVHandle, options MergeOptions) error { +// // 如果两个文件都有表头 +// if srcHandle.HasHeader && dstHandle.HasHeader { +// srcHeader := srcHandle.Header +// dstHeader := dstHandle.Header +// +// // 如果指定了列映射,跳过表头检查 +// if len(options.ColumnMapping) > 0 { +// return nil +// } +// +// // 检查列数是否相同 +// if len(srcHeader) != len(dstHeader) && !options.SkipHeader { +// return fmt.Errorf("源文件列数(%d)与目标文件列数(%d)不一致", len(srcHeader), len(dstHeader)) +// } +// +// // 检查列名是否相同(可选) +// for i := 0; i < min(len(srcHeader), len(dstHeader)); i++ { +// if srcHeader[i] != dstHeader[i] { +// fmt.Printf("警告: 第%d列列名不同: 源='%s', 目标='%s'\n", i+1, srcHeader[i], dstHeader[i]) +// } +// } +// } else if srcHandle.HasHeader != dstHandle.HasHeader { +// // 一个有表头,一个没有表头 +// if !options.SkipHeader { +// return fmt.Errorf("源文件与目标文件的表头设置不一致") +// } +// } +// +// return nil +//} +// +//// 合并数据行 +//func (mgr *CSVManager) mergeDataRows(srcRows [][]string, srcHandle, dstHandle *CSVHandle, options MergeOptions) ([][]string, error) { +// var result [][]string +// +// startIndex := 0 +// // 如果源文件有表头且需要跳过表头 +// if srcHandle.HasHeader && options.SkipHeader { +// if len(srcRows) > 0 { +// startIndex = 1 +// } +// } +// +// // 处理列映射 +// for i := startIndex; i < len(srcRows); i++ { +// srcRow := srcRows[i] +// transformedRow, err := mgr.transformRow(srcRow, srcHandle, dstHandle, options) +// if err != nil { +// return nil, fmt.Errorf("转换第%d行数据失败: %w", i+1, err) +// } +// +// // 跳过空行 +// if transformedRow == nil { +// continue +// } +// +// // 检查重复行 +// if options.SkipDuplicates && mgr.isDuplicateRow(result, transformedRow) { +// continue +// } +// +// result = append(result, transformedRow) +// } +// +// return result, nil +//} +// +//// 转换行数据 +//func (mgr *CSVManager) transformRow(srcRow []string, srcHandle, dstHandle *CSVHandle, options MergeOptions) ([]string, error) { +// // 如果有自定义转换函数,优先使用 +// if options.TransformFunc != nil { +// return options.TransformFunc(srcRow) +// } +// +// // 如果有列映射,按照映射转换 +// if len(options.ColumnMapping) > 0 { +// // 确定目标行的最大列数 +// maxCol := 0 +// for _, dstIndex := range options.ColumnMapping { +// if dstIndex > maxCol { +// maxCol = dstIndex +// } +// } +// +// // 创建目标行 +// dstRow := make([]string, maxCol+1) +// for srcIndex, dstIndex := range options.ColumnMapping { +// if srcIndex < len(srcRow) && dstIndex >= 0 && dstIndex < len(dstRow) { +// dstRow[dstIndex] = srcRow[srcIndex] +// } +// } +// return dstRow, nil +// } +// +// // 如果没有列映射,直接复制 +// dstRow := make([]string, len(srcRow)) +// copy(dstRow, srcRow) +// +// // 如果目标文件有表头且列数不同,进行调整 +// if dstHandle.HasHeader && len(dstRow) != len(dstHandle.Header) { +// if len(dstRow) < len(dstHandle.Header) { +// // 源列数少,用空字符串填充 +// for i := len(dstRow); i < len(dstHandle.Header); i++ { +// dstRow = append(dstRow, "") +// } +// } else { +// // 源列数多,截断 +// dstRow = dstRow[:len(dstHandle.Header)] +// } +// } +// +// return dstRow, nil +//} +// +//// 检查是否为重复行 +//func (mgr *CSVManager) isDuplicateRow(rows [][]string, newRow []string) bool { +// for _, row := range rows { +// if mgr.compareRows(row, newRow) { +// return true +// } +// } +// return false +//} +// +//// 比较两行是否相同 +//func (mgr *CSVManager) compareRows(row1, row2 []string) bool { +// if len(row1) != len(row2) { +// return false +// } +// for i := range row1 { +// if row1[i] != row2[i] { +// return false +// } +// } +// return true +//} +// +//// 追加行到文件末尾 +//func (mgr *CSVManager) appendRowsToFile(handle *CSVHandle, rows [][]string) error { +// // 移动到文件末尾 +// if _, err := handle.File.Seek(0, 2); err != nil { +// return fmt.Errorf("移动到文件末尾失败: %w", err) +// } +// +// // 批量写入行数据 +// for _, row := range rows { +// if err := handle.CSVWriter.Write(row); err != nil { +// return fmt.Errorf("写入行失败: %w", err) +// } +// } +// +// // 刷新缓冲区 +// handle.CSVWriter.Flush() +// if handle.writeBuffer != nil { +// if err := handle.writeBuffer.Flush(); err != nil { +// return fmt.Errorf("刷新写入缓冲区失败: %w", err) +// } +// } +// +// // 更新行数统计 +// handle.TotalRows += int64(len(rows)) +// handle.cachedRowCount = handle.TotalRows +// handle.rowCountCached = true +// +// return nil +//} +// +//// 合并所有行(用于覆盖模式) +//func (mgr *CSVManager) mergeAllRows(dstRows, srcRows [][]string, dstHandle, srcHandle *CSVHandle, options MergeOptions) [][]string { +// var result [][]string +// +// // 处理表头 +// if dstHandle.HasHeader && len(dstRows) > 0 { +// // 保留目标文件的表头 +// result = append(result, dstRows[0]) +// dstRows = dstRows[1:] +// } +// +// // 根据冲突解决策略合并数据 +// switch options.ConflictResolution { +// case Overwrite: +// // 覆盖模式:源数据完全替换目标数据 +// result = append(result, srcRows...) +// case Skip: +// // 跳过模式:只保留目标数据中不冲突的行 +// result = append(result, dstRows...) +// for _, srcRow := range srcRows { +// if !mgr.isDuplicateRow(result, srcRow) { +// result = append(result, srcRow) +// } +// } +// case KeepBoth: +// // 保留两者:先写目标数据,再写源数据 +// result = append(result, dstRows...) +// result = append(result, srcRows...) +// case UseSource: +// // 使用源数据:源数据优先级更高 +// result = append(result, srcRows...) +// case UseTarget: +// // 使用目标数据:目标数据优先级更高 +// result = append(result, dstRows...) +// default: +// // 默认:追加模式 +// result = append(result, dstRows...) +// result = append(result, srcRows...) +// } +// +// return result +//} +// +//// MergeCSVFilesSimple 简单合并CSV文件(默认选项) +//func (mgr *CSVManager) MergeCSVFilesSimple(srcHandleID, dstHandleID int64, appendMode bool) (int64, error) { +// options := MergeOptions{ +// AppendMode: appendMode, +// SkipHeader: true, // 跳过源文件表头 +// SkipDuplicates: false, // 不跳过重复行 +// ConflictResolution: KeepBoth, // 保留两者 +// } +// return mgr.MergeCSVFiles(srcHandleID, dstHandleID, options) +//} +// +//// ========================== C 导出函数 ========================== +// +//// OpenCSVFile 打开CSV文件 +//// +////export OpenCSVFile +//func OpenCSVFile(filename *C.char, delimiter C.char, hasHeader C.int) *C.char { +// filenameStr := C.GoString(filename) +// delimiterStr := rune(delimiter) +// handleID, err := GetManager().OpenCSVFile(filenameStr, delimiterStr, hasHeader != 0) +// response := struct { +// HandleID int64 `json:"handleID"` +// }{ +// HandleID: handleID, +// } +// var csvResponse CSVResponse +// 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)) +//} +// +//// WriteHeader 写入表头 +//// +////export WriteHeader +//func WriteHeader(handleID C.int, header *C.char) *C.char { +// var csvResponse CSVResponse +// goHandleID := int64(handleID) +// goHeader := C.GoString(header) +// var data []string +// err := json.Unmarshal([]byte(goHeader), &data) +// if err != nil { +// csvResponse = CSVResponse{ +// Success: false, +// Message: fmt.Sprintf("header JSON解析失败: %v", err), +// } +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +// } +// errs := GetManager().WriteHeader(goHandleID, data) +// if errs != nil { +// csvResponse = CSVResponse{ +// Success: false, +// Message: errs.Error(), +// } +// } else { +// csvResponse = CSVResponse{ +// Success: true, +// } +// } +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +//} +// +//// WriteRows 写入/覆盖行数据 +//// +////export WriteRows +//func WriteRows(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)) +// } +// // 写入数据 +// rows, err := GetManager().AppendRows(goHandleID, data) +// response := struct { +// TotalRows int64 `json:"totalRows"` +// }{ +// TotalRows: rows, +// } +// 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)) +//} +// +//// 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 +//func UpdateRow(handleID C.int, rowNumber C.int, rowData *C.char) *C.char { +// var csvResponse CSVResponse +// goHandleID := int64(handleID) +// goRowNumber := int64(rowNumber) +// goRowData := C.GoString(rowData) +// +// // JSON解析行数据 +// var data []string +// err := json.Unmarshal([]byte(goRowData), &data) +// if err != nil { +// csvResponse = CSVResponse{ +// Success: false, +// Message: fmt.Sprintf("rowsData JSON解析失败: %v", err), +// } +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +// } +// // 修改行数据 +// err = GetManager().UpdateRow(goHandleID, goRowNumber, data) +// if err != nil { +// csvResponse = CSVResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// csvResponse = CSVResponse{ +// Success: true, +// Message: fmt.Sprintf("成功修改第%d行数据", goRowNumber), +// } +// } +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +//} +// +//// GetRow C导出函数 - 获取指定行数据 +//// +////export GetRow +//func GetRow(handleID C.int, rowNumber C.int) *C.char { +// goHandleID := int64(handleID) +// goRowNumber := int64(rowNumber) +// +// // 获取行数据 +// rowData, err := GetManager().GetRow(goHandleID, goRowNumber) +// var csvResponse CSVResponse +// if err != nil { +// csvResponse = CSVResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// csvResponse = CSVResponse{ +// Success: true, +// Data: rowData, +// } +// } +// +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +//} +// +//// MergeCSVFilesSimple 合并两个csv文件 +//// +////export MergeCSVFilesSimple +//func MergeCSVFilesSimple(srcHandleID, dstHandleID, appendMode C.int) *C.char { +// var csvResponse CSVResponse +// goSrcHandleID := int64(srcHandleID) +// goDstHandleID := int64(dstHandleID) +// goAppendMode := int(appendMode) +// rows, err := GetManager().MergeCSVFilesSimple(goSrcHandleID, goDstHandleID, goAppendMode == 0) +// response := struct { +// TotalRows int64 `json:"totalRows"` +// }{ +// TotalRows: rows, +// } +// 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)) +//} +// +//// CloseHandles 关闭指定句柄(使用优雅关闭) +//// +////export CloseHandles +//func CloseHandles(handleID C.int) *C.char { +// goHandleID := int64(handleID) +// +// // 使用优雅关闭 +// err := GetManager().CloseHandle(goHandleID) +// +// var csvResponse CSVResponse +// if err != nil { +// csvResponse = CSVResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// csvResponse = CSVResponse{ +// Success: true, +// Message: fmt.Sprintf("成功关闭句柄 %d", goHandleID), +// } +// } +// +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +//} +// +//// CloseAllHandles 关闭所有句柄 +//// +////export CloseAllHandles +//func CloseAllHandles() *C.char { +// GetManager().closeAllHandles() +// +// csvResponse := CSVResponse{ +// Success: true, +// Message: "成功关闭所有句柄", +// } +// +// csvResponseStr, _ := json.Marshal(csvResponse) +// return C.CString(string(csvResponseStr)) +//} +// +//// FreeCString 释放C字符串内存 +//// +////export FreeCString +//func FreeCString(str *C.char) { +// C.free(unsafe.Pointer(str)) +//} +// +//// 主函数 +////func main() { +////} diff --git a/csv/csvDll.go b/csv/csvDll.go index 01edc59..6d0e596 100644 --- a/csv/csvDll.go +++ b/csv/csvDll.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "sync" ) // @@ -848,53 +847,53 @@ import ( // fmt.Println("\n测试完成!") //} -func main() { - filename := "csv/taskLog.csv" - handleID, err := GetManager().OpenCSVFile(filename, ',', true) - if err != nil { - fmt.Println(err.Error()) - } - fmt.Println(handleID) - headers := []string{"ID", "序号", "姓名", "年龄"} - err = GetManager().WriteHeader(handleID, headers) - if err != nil { - fmt.Println(err.Error()) - } - - //rowss, i, err := GetManager().AppendRowss(handleID, rows) - //if err != nil { - // fmt.Println(err.Error()) - //} - //fmt.Println(rowss) - //fmt.Println(i) - - var wg sync.WaitGroup - results := make([][]int64, 100) - - 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)}, - } - wg.Add(1) - go func(idx int) { - defer wg.Done() - rowsss, _ := GetManager().AppendRowsNum(handleID, rows) - fmt.Println(rows, ":", rowsss) - results[idx] = rowsss - }(i) - } - - wg.Wait() - - //rowsss, err := GetManager().AppendRowsss(handleID, rows) - //if err != nil { - // fmt.Println(err.Error()) - //} - //fmt.Println(rowsss) - - GetManager().CloseHandle(handleID) - -} +//func main() { +// filename := "csv/taskLog.csv" +// handleID, err := GetManager().OpenCSVFile(filename, ',', true) +// if err != nil { +// fmt.Println(err.Error()) +// } +// fmt.Println(handleID) +// headers := []string{"ID", "序号", "姓名", "年龄"} +// err = GetManager().WriteHeader(handleID, headers) +// if err != nil { +// fmt.Println(err.Error()) +// } +// +// //rowss, i, err := GetManager().AppendRowss(handleID, rows) +// //if err != nil { +// // fmt.Println(err.Error()) +// //} +// //fmt.Println(rowss) +// //fmt.Println(i) +// +// var wg sync.WaitGroup +// results := make([][]int64, 100) +// +// 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)}, +// } +// wg.Add(1) +// go func(idx int) { +// defer wg.Done() +// rowsss, _ := GetManager().AppendRowsNum(handleID, rows) +// fmt.Println(rows, ":", rowsss) +// results[idx] = rowsss +// }(i) +// } +// +// wg.Wait() +// +// //rowsss, err := GetManager().AppendRowsss(handleID, rows) +// //if err != nil { +// // fmt.Println(err.Error()) +// //} +// //fmt.Println(rowsss) +// +// GetManager().CloseHandle(handleID) +// +//} // WriteRowss 批量写入多行数据到CSV文件 // 返回写入的起始行号和写入的行数,错误时返回(-1, -1, error) diff --git a/csv/newcsv.go b/csv/newcsv.go index ac3b05f..41283c5 100644 --- a/csv/newcsv.go +++ b/csv/newcsv.go @@ -1,1272 +1,1272 @@ package main -///* -//#include -//*/ -//import "C" -//import ( -// "encoding/csv" -// "encoding/json" -// "fmt" -// "io" -// "os" -// "path/filepath" -// "strings" -// "sync" -// "sync/atomic" -// "time" -// "unsafe" -//) -// -//// CSVManager CSV全局管理器 -//type CSVManager struct { -// files sync.Map // handle -> *CSVFile (并发安全的映射) -// nextHandle int64 // 生成唯一句柄 -// errorMsg string // 全局错误信息 -// mu sync.RWMutex // 管理器级别的锁 -//} -// -//// CSVFile 文件结构 -//type CSVFile struct { -// filename string // 实际文件路径 -// handle int64 // 唯一句柄 -// delimiter rune // 分隔符(如 ',') -// hasHeader bool // 是否有标题行 -// -// // 内存数据缓存 -// data [][]string // 内存中的数据行 -// header []string // 标题行(如果有) -// fileSize int64 // 文件大小 -// modified bool // 标记是否被修改 -// -// // 并发控制 -// mu sync.RWMutex // 文件级锁 -// rowLocks []*sync.RWMutex // 行级锁 -// rowMu sync.Mutex // 行锁数组保护 -// readers int32 // 活跃读取器计数 -// writers int32 // 活跃写入器计数 -// saving int32 // 正在保存的goroutine计数 -// saveErr chan error // 保存错误通道 -// done chan struct{} // 关闭信号 -//} -// -//// 单例模式的管理器 -//var manager *CSVManager -//var once sync.Once -// -//// newCSVFile 创建新的CSVFile对象 -//func newCSVFile(filename string, delimiter rune, hasHeader bool) *CSVFile { -// return &CSVFile{ -// filename: filename, -// delimiter: delimiter, -// hasHeader: hasHeader, -// data: make([][]string, 0), -// rowLocks: make([]*sync.RWMutex, 0), -// saveErr: make(chan error, 1), -// done: make(chan struct{}), -// } -//} -// -//// getManager 获取全局管理器(单例) -//func getManager() *CSVManager { -// once.Do(func() { -// manager = &CSVManager{ -// nextHandle: 1, -// } -// }) -// return manager -//} -// -//// setError 设置错误信息 -//func setError(err string) { -// mgr := getManager() -// mgr.mu.Lock() -// mgr.errorMsg = err -// mgr.mu.Unlock() -//} -// -//// 初始化管理器 -//func initCSVManager() int64 { -// _ = getManager() -// return 0 // 成功 -//} -// -//// 打开/创建CSV文件(句柄) -//func openCSVFile(filename string, delimiter rune, hasHeader bool) int64 { -// // 创建CSV全局管理器 -// mgr := getManager() -// -// // 先检查文件是否已经打开 -// var existingHandle int64 = -1 -// mgr.files.Range(func(key, value interface{}) bool { -// file := value.(*CSVFile) -// // 比较文件路径(使用绝对路径确保一致性) -// absFilename, _ := filepath.Abs(filename) -// absExisting, _ := filepath.Abs(file.filename) -// -// if absFilename == absExisting { -// fmt.Println("absFilename", absFilename) -// fmt.Println("absExisting", absExisting) -// fmt.Println("bool", absFilename == absExisting) -// -// existingHandle = key.(int64) -// return false // 停止遍历 -// } -// return true // 继续遍历 -// }) -// -// // 如果文件已经打开,返回现有句柄 -// if existingHandle != -1 { -// fmt.Printf("文件已打开,返回现有句柄: %d\n", existingHandle) -// return existingHandle -// } -// -// // 生成句柄 -// handle := atomic.AddInt64(&mgr.nextHandle, 1) -// -// // 创建文件对象 -// file := newCSVFile(filename, delimiter, hasHeader) -// file.handle = handle -// -// // 加载文件数据 -// if err := file.load(); err != nil { -// setError(err.Error()) -// return -1 -// } -// -// // 存储到管理器 -// mgr.files.Store(handle, file) -// return handle -//} -// -//// 读取多行数据 -//func readRows(handle int64, startRow, count int64, buffer []byte) int64 { -// mgr := getManager() -// -// // 获取文件对象 -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("Invalid file handle") -// return -1 -// } -// -// file := val.(*CSVFile) -// atomic.AddInt32(&file.readers, 1) -// defer atomic.AddInt32(&file.readers, -1) -// -// // 获取读取锁 -// file.mu.RLock() -// defer file.mu.RUnlock() -// -// totalRows := int64(len(file.data)) -// if startRow < 0 || startRow >= totalRows { -// return 0 -// } -// -// // 计算实际读取行数 -// endRow := startRow + count -// if endRow > totalRows { -// endRow = totalRows -// } -// -// rowsToRead := endRow - startRow -// -// // 将数据复制到缓冲区 -// bytesWritten := 0 -// -// for i := startRow; i < endRow; i++ { -// row := file.data[i] -// -// // 获取行读锁 -// if rowLock := file.getRowLock(int(i)); rowLock != nil { -// rowLock.RLock() -// } -// -// for _, cell := range row { -// // 写入单元格长度 -// cellLen := len(cell) -// if bytesWritten+4 > len(buffer) { -// break -// } -// -// // 写入4字节长度 -// buffer[bytesWritten] = byte(cellLen & 0xFF) -// buffer[bytesWritten+1] = byte((cellLen >> 8) & 0xFF) -// buffer[bytesWritten+2] = byte((cellLen >> 16) & 0xFF) -// buffer[bytesWritten+3] = byte((cellLen >> 24) & 0xFF) -// bytesWritten += 4 -// -// // 写入单元格数据 -// if bytesWritten+cellLen > len(buffer) { -// // 缓冲区不足,回退长度写入 -// bytesWritten -= 4 -// break -// } -// -// copy(buffer[bytesWritten:bytesWritten+cellLen], cell) -// bytesWritten += cellLen -// } -// -// // 写入行结束标记 -// if bytesWritten+4 <= len(buffer) { -// // 4字节的0表示行结束 -// buffer[bytesWritten] = 0 -// buffer[bytesWritten+1] = 0 -// buffer[bytesWritten+2] = 0 -// buffer[bytesWritten+3] = 0 -// bytesWritten += 4 -// } -// -// // 释放行锁 -// if rowLock := file.getRowLock(int(i)); rowLock != nil { -// rowLock.RUnlock() -// } -// -// if bytesWritten >= len(buffer) { -// break -// } -// } -// -// return rowsToRead -//} -// -//// 写入/覆盖行数据 -//func writeRows(handle int64, rowData [][]string, header int) int64 { -// mgr := getManager() -// -// // 获取文件对象 -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("文件无效句柄!") -// return -1 -// } -// -// file := val.(*CSVFile) -// -// // 获取写入锁 -// file.mu.Lock() -// defer file.mu.Unlock() -// -// atomic.AddInt32(&file.writers, 1) -// defer atomic.AddInt32(&file.writers, -1) -// -// // 清空现有数据(因为这是覆盖写入) -// file.data = make([][]string, 0, len(rowData)) -// if header == 0 { -// file.header = rowData[0] -// // 添加新数据 -// file.data = rowData[1:] -// } else { -// // 添加新数据 -// for _, row := range rowData { -// file.data = append(file.data, row) -// } -// } -// -// // 扩展行锁数组 -// file.rowMu.Lock() -// file.rowLocks = make([]*sync.RWMutex, len(file.data)) -// for i := range file.rowLocks { -// file.rowLocks[i] = &sync.RWMutex{} -// } -// file.rowMu.Unlock() -// -// file.modified = true -// // 异步保存到文件 -// go file.saveAsync() -// return int64(len(file.data)) -//} -// -//// 加载CSV文件到内存 -//func (f *CSVFile) load() error { -// f.mu.Lock() -// defer f.mu.Unlock() -// -// // 打开文件 -// file, err := os.Open(f.filename) -// if err != nil { -// // 文件不存在则创建空文件 -// if os.IsNotExist(err) { -// f.data = make([][]string, 0) // 初始化空数据 -// f.rowLocks = make([]*sync.RWMutex, 0) // 初始化空行锁数组 -// return nil -// } -// return fmt.Errorf("文件不存在: %v", err) -// } -// // 确保函数结束时关闭文件 -// defer file.Close() -// -// // 获取文件大小 -// stat, _ := file.Stat() // 获取文件信息 -// f.fileSize = stat.Size() // 获取文件大小 -// -// // 创建CSV读取器 -// reader := csv.NewReader(file) -// reader.Comma = f.delimiter // 设置分隔符(默认逗号) -// reader.LazyQuotes = true // 允许宽松的引号解析 -// reader.TrimLeadingSpace = true // 去除字段前的空格 -// -// // 关键修改:允许变长记录,不强制检查字段数量 -// reader.FieldsPerRecord = -1 -// firstRecord, err := reader.Read() -// if err != nil { -// if err == io.EOF { -// // 空文件 -// f.data = make([][]string, 0) -// f.rowLocks = make([]*sync.RWMutex, 0) -// return nil -// } -// return err -// } -// maxColumns := len(firstRecord) -// allData := [][]string{firstRecord} -// -// // 读取剩余行 -// for { -// record, err := reader.Read() -// if err == io.EOF { -// break -// } -// if err != nil { -// // 对于有问题的行,可以填充或跳过 -// continue -// } -// -// // 确保所有行都有相同的列数 -// if len(record) < maxColumns { -// // 填充缺失的列为空字符串 -// paddedRecord := make([]string, maxColumns) -// copy(paddedRecord, record) -// for i := len(record); i < maxColumns; i++ { -// paddedRecord[i] = "" -// } -// record = paddedRecord -// } else if len(record) > maxColumns { -// // 如果行有更多列,更新最大列数 -// maxColumns = len(record) -// // 重新处理之前的所有行 -// for i := range allData { -// if len(allData[i]) < maxColumns { -// paddedRecord := make([]string, maxColumns) -// copy(paddedRecord, allData[i]) -// allData[i] = paddedRecord -// } -// } -// } -// -// allData = append(allData, record) -// } -// -// if len(allData) == 0 { -// f.data = make([][]string, 0) -// f.rowLocks = make([]*sync.RWMutex, 0) -// return nil -// } -// -// // 处理表头 -// if f.hasHeader && len(allData) > 0 { -// f.header = allData[0] -// f.data = allData[1:] -// } else { -// f.data = allData -// } -// -// // 初始化行锁 -// f.initRowLocks() -// -// return nil -//} -// -//// initRowLocks 初始化行锁数组 -//func (f *CSVFile) initRowLocks() { -// count := len(f.data) -// f.rowLocks = make([]*sync.RWMutex, count) -// for i := 0; i < count; i++ { -// f.rowLocks[i] = &sync.RWMutex{} -// } -//} -// -//// getRowLock 获取行锁(线程安全) -//func (f *CSVFile) getRowLock(rowIndex int) *sync.RWMutex { -// if rowIndex < 0 { -// return nil -// } -// -// f.rowMu.Lock() -// defer f.rowMu.Unlock() -// -// // 确保行锁存在 -// if rowIndex >= len(f.rowLocks) { -// // 扩展行锁数组 -// newLocks := make([]*sync.RWMutex, rowIndex+1) -// copy(newLocks, f.rowLocks) -// for i := len(f.rowLocks); i <= rowIndex; i++ { -// newLocks[i] = &sync.RWMutex{} -// } -// f.rowLocks = newLocks -// } -// -// return f.rowLocks[rowIndex] -//} -// -//// save 保存到文件 -//func (f *CSVFile) save() error { -// // 标记正在保存 -// if !atomic.CompareAndSwapInt32(&f.saving, 0, 1) { -// // 已经在保存中,直接返回 -// return nil -// } -// defer atomic.StoreInt32(&f.saving, 0) -// -// // 如果没有修改,直接返回 -// f.mu.RLock() -// if !f.modified { -// f.mu.RUnlock() -// return nil -// } -// // 复制数据 -// dataCopy := make([][]string, len(f.data)) -// -// for i := range f.data { -// dataCopy[i] = make([]string, len(f.data[i])) -// copy(dataCopy[i], f.data[i]) -// } -// // 复制表头 -// headerCopy := make([]string, len(f.header)) -// copy(headerCopy, f.header) -// // 复制配置(值类型,直接赋值) -// hasHeader := f.hasHeader -// delimiter := f.delimiter -// filename := f.filename -// f.mu.RUnlock() // 释放读锁 -// -// // 创建临时文件(使用不同的扩展名避免冲突) -// tempFile := filename + ".tmp" -// file, err := os.Create(tempFile) -// if err != nil { -// return err -// } -// -// // 创建CSV写入器 -// writer := csv.NewWriter(file) -// writer.Comma = delimiter -// -// // 写入数据 -// if hasHeader && len(headerCopy) > 0 { -// if err := writer.Write(headerCopy); err != nil { -// file.Close() -// os.Remove(tempFile) -// return err -// } -// } -// -// if err := writer.WriteAll(dataCopy); err != nil { -// file.Close() -// os.Remove(tempFile) -// return err -// } -// -// writer.Flush() -// if err := writer.Error(); err != nil { -// file.Close() -// os.Remove(tempFile) -// return err -// } -// -// // 关闭文件,确保数据写入磁盘 -// if err := file.Close(); err != nil { -// os.Remove(tempFile) -// return err -// } -// -// // 尝试重命名,如果失败可能是文件被占用 -// var renameErr error -// for retry := 0; retry < 3; retry++ { -// renameErr = os.Rename(tempFile, filename) -// if renameErr == nil { -// break -// } -// time.Sleep(100 * time.Millisecond) // 等待重试 -// } -// -// if renameErr != nil { -// os.Remove(tempFile) -// return renameErr -// } -// -// // 标记为已保存 -// f.mu.Lock() -// f.modified = false -// f.mu.Unlock() -// -// return nil -//} -// -//// saveAsync 异步保存,带错误处理 -//func (f *CSVFile) saveAsync() { -// select { -// case f.saveErr <- f.save(): -// // 保存完成 -// default: -// // 通道已满,忽略错误 -// } -//} -// -//// 追加行数据 -//func appendRows(handle int64, rowsData []byte, rowCount int64) int { -// mgr := getManager() -// -// // 获取文件对象 -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("Invalid file handle") -// return -1 -// } -// -// file := val.(*CSVFile) -// -// // 获取写入锁 -// file.mu.Lock() -// atomic.AddInt32(&file.writers, 1) -// -// // 解析行数据 -// offset := 0 -// -// for i := int64(0); i < rowCount; i++ { -// var row []string -// -// // 读取行数据 -// for { -// if offset+4 > len(rowsData) { -// break -// } -// -// cellLen := int(uint32(rowsData[offset]) | -// uint32(rowsData[offset+1])<<8 | -// uint32(rowsData[offset+2])<<16 | -// uint32(rowsData[offset+3])<<24) -// offset += 4 -// -// if cellLen == 0 { -// break -// } -// -// if offset+cellLen > len(rowsData) { -// break -// } -// -// cell := string(rowsData[offset : offset+cellLen]) -// offset += cellLen -// row = append(row, cell) -// } -// -// // 追加到数据 -// file.data = append(file.data, row) -// } -// -// file.modified = true -// -// atomic.AddInt32(&file.writers, -1) -// file.mu.Unlock() -// -// // 异步保存 -// go file.saveAsync() -// -// // 获取总行数 -// count := getRowCount(handle) -// fmt.Println("count:", count) -// return int(count) -//} -// -//// 打开/创建CSV文件(句柄) -//func createOpenCSVFile(filename string, delimiter rune, hasHeader bool) int64 { -// // 创建CSV全局管理器 -// mgr := getManager() -// -// // 生成句柄 -// handle := atomic.AddInt64(&mgr.nextHandle, 1) -// -// // 创建文件对象 -// file := newCSVFile(filename, delimiter, hasHeader) -// file.handle = handle -// -// //// 加载文件数据 -// //if err := file.load(); err != nil { -// // setError(err.Error()) -// // return -1 -// //} -// -// // 存储到管理器 -// mgr.files.Store(handle, file) -// -// return handle -//} -// -//// updateCSVRowSafe 修改csv文件行数据 -//func updateCSVRowSafe(handleID int64, rowNum int, newRow []string) error { -// mgr := getManager() -// -// // 获取文件对象 -// val, ok := mgr.files.Load(handleID) -// if !ok { -// setError("Invalid file handle") -// return fmt.Errorf("无效句柄: %d", handleID) -// } -// file := val.(*CSVFile) -// -// // 1. 创建临时文件 -// tempDir := filepath.Dir(file.filename) -// if tempDir == "" { -// tempDir = "." -// } -// -// tempFile, err := os.CreateTemp(tempDir, "temp_*.csv") -// if err != nil { -// return fmt.Errorf("创建临时文件失败: %v", err) -// } -// tempFileName := tempFile.Name() -// -// // 确保临时文件被关闭和清理 -// defer func() { -// tempFile.Close() -// // 如果临时文件还存在(替换失败),则清理它 -// if _, err := os.Stat(tempFileName); err == nil { -// os.Remove(tempFileName) -// } -// }() -// -// // 2. 读取原始文件 -// sourceFile, err := os.Open(file.filename) -// if err != nil { -// return fmt.Errorf("打开源文件失败: %v", err) -// } -// defer sourceFile.Close() -// -// // 3. 读取并处理数据 -// reader := csv.NewReader(sourceFile) -// allRows, err := reader.ReadAll() -// if err != nil { -// return fmt.Errorf("读取CSV失败: %v", err) -// } -// -// // 4. 验证并更新 -// if rowNum < 1 || rowNum > len(allRows) { -// return fmt.Errorf("行号 %d 超出范围 (1-%d)", rowNum, len(allRows)) -// } -// -// // 显示修改前后的对比 -// oldRow := allRows[rowNum-1] -// fmt.Printf("修改前第 %d 行: %v\n", rowNum, oldRow) -// fmt.Printf("修改后第 %d 行: %v\n", rowNum, newRow) -// -// allRows[rowNum-1] = newRow -// -// // 5. 写入临时文件 -// writer := csv.NewWriter(tempFile) -// if err := writer.WriteAll(allRows); err != nil { -// return fmt.Errorf("写入临时文件失败: %v", err) -// } -// writer.Flush() -// -// if err := writer.Error(); err != nil { -// return fmt.Errorf("刷新写入失败: %v", err) -// } -// -// // 6. 确保数据写入磁盘 -// if err := tempFile.Sync(); err != nil { -// return fmt.Errorf("同步文件失败: %v", err) -// } -// tempFile.Close() -// -// // 7. 使用复制而不是重命名(解决Windows文件占用问题) -// if err := copyFile(tempFileName, file.filename); err != nil { -// return fmt.Errorf("复制文件失败: %v", err) -// } -// -// fmt.Printf("✅ 成功更新第 %d 行,文件已直接更新\n", rowNum) -// -// return nil -//} -// -//// copyFile 复制文件内容 -//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) -// if err != nil { -// return err -// } -// -// // 确保数据写入磁盘 -// err = destination.Sync() -// if err != nil { -// return err -// } -// -// return nil -//} -// -//// 获取总行数 -//func getRowCount(handle int64) int64 { -// mgr := getManager() -// -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("Invalid file handle") -// return -1 -// } -// -// file := val.(*CSVFile) -// file.mu.RLock() -// defer file.mu.RUnlock() -// if file.header != nil { -// return int64(len(file.data) + 1) -// } -// return int64(len(file.data)) -//} -// -//// 搜索行 -//func findRows(handle int64, searchText string, columnIndex int64, resultBuffer []byte, maxResults int64) int64 { -// mgr := getManager() -// -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("Invalid file handle") -// return -1 -// } -// -// file := val.(*CSVFile) -// -// file.mu.RLock() -// defer file.mu.RUnlock() -// -// atomic.AddInt32(&file.readers, 1) -// defer atomic.AddInt32(&file.readers, -1) -// -// var foundRows []int64 -// -// // 搜索行 -// for i, row := range file.data { -// if maxResults > 0 && int64(len(foundRows)) >= maxResults { -// break -// } -// -// // 获取行读锁 -// if rowLock := file.getRowLock(i); rowLock != nil { -// rowLock.RLock() -// } -// -// // 检查列 -// if columnIndex < 0 || columnIndex >= int64(len(row)) { -// // 搜索所有列 -// for _, cell := range row { -// if cell == searchText { -// foundRows = append(foundRows, int64(i)) -// break -// } -// } -// } else if row[columnIndex] == searchText { -// foundRows = append(foundRows, int64(i)) -// } -// -// // 释放行锁 -// if rowLock := file.getRowLock(i); rowLock != nil { -// rowLock.RUnlock() -// } -// } -// -// // 写入结果到缓冲区 -// if resultBuffer != nil && len(foundRows) > 0 { -// bytesWritten := 0 -// -// for _, rowIndex := range foundRows { -// if bytesWritten+8 > len(resultBuffer) { -// break -// } -// -// // 写入行索引(8字节) -// for j := 0; j < 8; j++ { -// resultBuffer[bytesWritten] = byte((rowIndex >> (uint(j) * 8)) & 0xFF) -// bytesWritten++ -// } -// } -// } -// -// return int64(len(foundRows)) -//} -// -//// 合并多个CSV文件(线程安全) -//func mergeCSVFiles(handles []int64, outputFilename string, delimiter rune, hasHeader bool) int64 { -// mgr := getManager() -// -// // 验证所有句柄并获取文件对象 -// files := make([]*CSVFile, 0, len(handles)) -// for _, handle := range handles { -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("Invalid file handle: " + string(handle)) -// return -1 -// } -// files = append(files, val.(*CSVFile)) -// } -// -// // 创建输出文件对象 -// outputFile := newCSVFile(outputFilename, delimiter, hasHeader) -// outputFile.modified = true // 标记为需要保存 -// -// // 第一步:合并表头 -// mergedHeader := make([]string, 0) -// headerSet := make(map[string]bool) -// -// if hasHeader { -// // 收集所有不重复的表头 -// for _, file := range files { -// file.mu.RLock() -// if file.hasHeader && len(file.header) > 0 { -// for _, h := range file.header { -// if !headerSet[h] { -// headerSet[h] = true -// mergedHeader = append(mergedHeader, h) -// } -// } -// } -// file.mu.RUnlock() -// } -// -// // 如果没有找到表头,创建一个默认的表头 -// if len(mergedHeader) == 0 && len(files) > 0 { -// files[0].mu.RLock() -// maxColumns := len(files[0].data[0]) -// files[0].mu.RUnlock() -// -// for i := 0; i < maxColumns; i++ { -// mergedHeader = append(mergedHeader, fmt.Sprintf("Column%d", i+1)) -// } -// } -// } -// outputFile.header = mergedHeader -// -// // 第二步:合并数据 -// mergedData := make([][]string, 0) -// -// // 为每个输入文件创建读取锁并并发读取 -// var wg sync.WaitGroup -// dataChan := make(chan [][]string, len(files)) -// errorChan := make(chan error, len(files)) -// -// for idx, file := range files { -// wg.Add(1) -// go func(fileIdx int, f *CSVFile) { -// defer wg.Done() -// -// // 获取文件的读取锁 -// f.mu.RLock() -// defer f.mu.RUnlock() -// -// // 增加活跃读取器计数 -// atomic.AddInt32(&f.readers, 1) -// defer atomic.AddInt32(&f.readers, -1) -// -// // 读取数据 -// fileData := make([][]string, len(f.data)) -// for i := 0; i < len(f.data); i++ { -// // 获取行读锁 -// if rowLock := f.getRowLock(i); rowLock != nil { -// rowLock.RLock() -// } -// -// // 复制行数据 -// row := make([]string, len(f.data[i])) -// copy(row, f.data[i]) -// -// // 释放行锁 -// if rowLock := f.getRowLock(i); rowLock != nil { -// rowLock.RUnlock() -// } -// -// fileData[i] = row -// } -// -// // 如果需要处理表头映射 -// if hasHeader && f.hasHeader && len(f.header) > 0 { -// // 创建列映射:源列 -> 目标列 -// columnMapping := make(map[int]int) -// for srcIdx, srcHeader := range f.header { -// for dstIdx, dstHeader := range mergedHeader { -// if srcHeader == dstHeader { -// columnMapping[srcIdx] = dstIdx -// break -// } -// } -// } -// -// // 重新排列数据以匹配合并后的表头 -// for i := 0; i < len(fileData); i++ { -// newRow := make([]string, len(mergedHeader)) -// for srcIdx, dstIdx := range columnMapping { -// if srcIdx < len(fileData[i]) { -// newRow[dstIdx] = fileData[i][srcIdx] -// } -// } -// fileData[i] = newRow -// } -// } else if hasHeader && len(mergedHeader) > 0 { -// // 文件没有表头,但输出需要表头 -// // 简单地将数据填充到对应位置 -// for i := 0; i < len(fileData); i++ { -// newRow := make([]string, len(mergedHeader)) -// for j := 0; j < len(fileData[i]) && j < len(newRow); j++ { -// newRow[j] = fileData[i][j] -// } -// fileData[i] = newRow -// } -// } -// -// // 将处理后的数据发送到通道 -// dataChan <- fileData -// }(idx, file) -// } -// -// // 等待所有goroutine完成 -// go func() { -// wg.Wait() -// close(dataChan) -// close(errorChan) -// }() -// -// // 收集所有数据 -// for data := range dataChan { -// mergedData = append(mergedData, data...) -// } -// -// // 检查是否有错误 -// select { -// case err := <-errorChan: -// if err != nil { -// setError("Error merging files: " + err.Error()) -// return -1 -// } -// default: -// } -// -// // 第三步:设置输出文件的数据 -// outputFile.data = mergedData -// outputFile.initRowLocks() -// -// // 第四步:保存到文件 -// if err := outputFile.save(); err != nil { -// setError("Error saving merged file: " + err.Error()) -// return -1 -// } -// -// // 第五步:将输出文件添加到管理器 -// handle := atomic.AddInt64(&mgr.nextHandle, 1) -// outputFile.handle = handle -// mgr.files.Store(handle, outputFile) -// -// return handle -//} -// -//// 关闭文件 -//func closeCSVFile(handle int64) int64 { -// mgr := getManager() -// -// val, ok := mgr.files.Load(handle) -// if !ok { -// setError("文件句柄无效!") -// return -1 -// } -// -// file := val.(*CSVFile) -// -// // 等待所有读写操作完成 -// for atomic.LoadInt32(&file.readers) > 0 || atomic.LoadInt32(&file.writers) > 0 { -// time.Sleep(time.Millisecond) -// } -// -// // 等待异步保存完成 -// for i := 0; i < 100; i++ { // 最多等待100ms -// if atomic.LoadInt32(&file.saving) == 0 { -// break -// } -// time.Sleep(time.Millisecond) -// } -// -// // 如果有正在进行的保存,等待一小段时间 -// if atomic.LoadInt32(&file.saving) > 0 { -// time.Sleep(50 * time.Millisecond) -// } -// -// // 检查是否需要保存 -// file.mu.RLock() -// needSave := file.modified -// file.mu.RUnlock() -// -// if needSave { -// // 同步保存修改 -// if err := file.save(); err != nil { -// setError(err.Error()) -// return -1 -// } -// } -// -// // 从管理器移除 -// mgr.files.Delete(handle) -// -// //// 安全关闭通道(如果存在) -// //if file.done != nil { -// // close(file.done) -// //} -// -// return 0 -//} -// -//// 获取错误信息 -//func getError() string { -// mgr := getManager() -// mgr.mu.RLock() -// defer mgr.mu.RUnlock() -// -// if mgr.errorMsg == "" { -// return "" -// } -// -// err := mgr.errorMsg -// mgr.errorMsg = "" // 清空错误 -// -// return err -//} -// -//func parseSimpleTable(goData string) [][]string { -// lines := strings.Split(strings.TrimSpace(goData), "\n") -// result := make([][]string, len(lines)) -// -// for i, line := range lines { -// // 根据你的分隔符分割,这里用逗号举例 -// fields := strings.Split(line, ",") -// // 如果需要去除每个字段的空格 -// for j := range fields { -// fields[j] = strings.TrimSpace(fields[j]) -// } -// result[i] = fields -// } -// -// return result -//} -// -//// ============ C 导出接口 ============ -// -////export InitCSVManager -//func InitCSVManager() C.longlong { -// return C.longlong(initCSVManager()) -//} -// -//// OpenCSVFile 打开/创建CSV文件 -//// -////export OpenCSVFile -//func OpenCSVFile(filename *C.char, delimiter C.char, hasHeader C.int) C.longlong { -// return C.longlong(openCSVFile(C.GoString(filename), rune(delimiter), hasHeader != 0)) -//} -// -//// CSV响应结构体 -//type CSVResponse struct { -// Success bool `json:"success"` -// Message string `json:"message,omitempty"` -// Data interface{} `json:"data,omitempty"` -//} -// -//// UpdateCSVRowSafe 修改csv文件行数据 -//// -////export UpdateCSVRowSafe -//func UpdateCSVRowSafe(handleID C.longlong, rowNum C.int, newRow *C.char) *C.char { -// goHandle := int64(handleID) -// goRowNum := int(rowNum) -// goNewRow := C.GoString(newRow) -// var row []string -// var csvResponse CSVResponse -// // 解析JSON -// err := json.Unmarshal([]byte(goNewRow), &row) -// if err != nil { -// csvResponse = CSVResponse{ -// Success: false, -// Message: fmt.Sprintf("解析JSON失败: %v", err), -// } -// errorJson, _ := json.Marshal(csvResponse) -// return C.CString(string(errorJson)) -// } -// // 修改csv行数据 -// err = updateCSVRowSafe(goHandle, goRowNum, row) -// if err != nil { -// csvResponse = CSVResponse{ -// Success: false, -// Message: fmt.Sprintf(err.Error()), -// } -// errorJson, _ := json.Marshal(csvResponse) -// return C.CString(string(errorJson)) -// } else { -// csvResponse = CSVResponse{ -// Success: true, -// } -// } -// // 转换为JSON字符串 -// jsonData, err := json.Marshal(csvResponse) -// if err != nil { -// // 如果JSON序列化失败,返回错误信息 -// csvResponse = CSVResponse{ -// Success: false, -// Message: fmt.Sprintf("JSON序列化失败: %v", err), -// } -// errorJson, _ := json.Marshal(csvResponse) -// return C.CString(string(errorJson)) -// } -// return C.CString(string(jsonData)) -//} -// -////export CreateOpenCSVFile -//func CreateOpenCSVFile(filename *C.char, delimiter C.char, hasHeader C.int) *C.char { -// goFilename := C.GoString(filename) -// goDelimiter := rune(delimiter) -// var goHasHeader bool -// if int(hasHeader) == 0 { -// goHasHeader = true -// } -// goHasHeader = false -// handle := createOpenCSVFile(goFilename, goDelimiter, goHasHeader) -// var csvResponse CSVResponse -// response := struct { -// HandleID int64 `json:"handleID"` -// }{ -// HandleID: handle, -// } -// csvResponse.Success = true -// csvResponse.Data = response -// jsonData, _ := json.Marshal(csvResponse) -// return C.CString(string(jsonData)) -//} -// -//// ReadRows 读取多行数据 -//// -////export ReadRows -//func ReadRows(handle C.longlong, startRow C.longlong, count C.longlong, buffer *C.char, bufferSize C.int) C.longlong { -// // 将 C 缓冲区转换为 Go 的字节切片 -// goBuffer := unsafe.Slice((*byte)(unsafe.Pointer(buffer)), int(bufferSize)) -// result := readRows(int64(handle), int64(startRow), int64(count), goBuffer) -// return C.longlong(result) -//} -// -//// WriteRows 写入/覆盖行数据 -//// -////export WriteRows -//func WriteRows(handle C.longlong, rowsData *C.char, header C.int) C.int { -// goData := C.GoString(rowsData) -// goHeader := int(header) -// //data := parseSimpleTable(goData) -// var data [][]string -// err := json.Unmarshal([]byte(goData), &data) -// if err != nil { -// setError(err.Error()) -// } -// result := writeRows(int64(handle), data, goHeader) -// return C.int(result) -//} -// -//// AppendRows 追加行数据 -//// -////export AppendRows -//func AppendRows(handle C.longlong, rowsData *C.char, dataSize C.int, rowCount C.longlong) C.int { -// goData := unsafe.Slice((*byte)(unsafe.Pointer(rowsData)), int(dataSize)) -// result := appendRows(int64(handle), goData, int64(rowCount)) -// return C.int(result) -//} -// -//// GetRowCount 获取总行数 -//// -////export GetRowCount -//func GetRowCount(handle C.longlong) C.longlong { -// result := getRowCount(int64(handle)) -// return C.longlong(result) -//} -// -//// FindRows 搜索行 -//// -////export FindRows -//func FindRows(handle C.longlong, searchText *C.char, columnIndex C.longlong, resultBuffer *C.char, bufferSize C.int, maxResults C.longlong) C.longlong { -// goSearchText := C.GoString(searchText) -// goResultBuffer := unsafe.Slice((*byte)(unsafe.Pointer(resultBuffer)), int(bufferSize)) -// result := findRows(int64(handle), goSearchText, int64(columnIndex), goResultBuffer, int64(maxResults)) -// return C.longlong(result) -//} -// -//// MergeCSVFiles 合并多个CSV文件(线程安全) -//// -////export MergeCSVFiles -//func MergeCSVFiles(handlesPtr *C.longlong, handlesCount C.int, outputFilename *C.char, delimiter C.char, hasHeader C.int) C.longlong { -// // 将C数组转换为Go切片 -// goHandles := unsafe.Slice(handlesPtr, int(handlesCount)) -// handles := make([]int64, len(goHandles)) -// for i := 0; i < len(goHandles); i++ { -// handles[i] = int64(goHandles[i]) -// } -// // 调用合并函数 -// result := mergeCSVFiles(handles, C.GoString(outputFilename), rune(delimiter), hasHeader != 0) -// return C.longlong(result) -//} -// -//// CloseCSVFile 关闭文件 -//// -////export CloseCSVFile -//func CloseCSVFile(handle C.longlong) C.int { -// result := closeCSVFile(int64(handle)) -// return C.int(result) -//} -// -//// GetError 获取错误信息 -//// -////export GetError -//func GetError(buffer *C.char, bufferSize C.int) C.int { -// errMsg := getError() -// if errMsg == "" { -// return 0 -// } -// -// // 将错误信息复制到缓冲区 -// goBuffer := unsafe.Slice((*byte)(unsafe.Pointer(buffer)), int(bufferSize)) -// n := copy(goBuffer, errMsg) -// return C.int(n) -//} -// -//// 导出函数:释放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) -//} -// -//// main 函数是必需的,即使为空 -//func main() { -//} +/* +#include +*/ +import "C" +import ( + "encoding/csv" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" + "unsafe" +) + +// CSVManager CSV全局管理器 +type CSVManager struct { + files sync.Map // handle -> *CSVFile (并发安全的映射) + nextHandle int64 // 生成唯一句柄 + errorMsg string // 全局错误信息 + mu sync.RWMutex // 管理器级别的锁 +} + +// CSVFile 文件结构 +type CSVFile struct { + filename string // 实际文件路径 + handle int64 // 唯一句柄 + delimiter rune // 分隔符(如 ',') + hasHeader bool // 是否有标题行 + + // 内存数据缓存 + data [][]string // 内存中的数据行 + header []string // 标题行(如果有) + fileSize int64 // 文件大小 + modified bool // 标记是否被修改 + + // 并发控制 + mu sync.RWMutex // 文件级锁 + rowLocks []*sync.RWMutex // 行级锁 + rowMu sync.Mutex // 行锁数组保护 + readers int32 // 活跃读取器计数 + writers int32 // 活跃写入器计数 + saving int32 // 正在保存的goroutine计数 + saveErr chan error // 保存错误通道 + done chan struct{} // 关闭信号 +} + +// 单例模式的管理器 +var manager *CSVManager +var once sync.Once + +// newCSVFile 创建新的CSVFile对象 +func newCSVFile(filename string, delimiter rune, hasHeader bool) *CSVFile { + return &CSVFile{ + filename: filename, + delimiter: delimiter, + hasHeader: hasHeader, + data: make([][]string, 0), + rowLocks: make([]*sync.RWMutex, 0), + saveErr: make(chan error, 1), + done: make(chan struct{}), + } +} + +// getManager 获取全局管理器(单例) +func getManager() *CSVManager { + once.Do(func() { + manager = &CSVManager{ + nextHandle: 1, + } + }) + return manager +} + +// setError 设置错误信息 +func setError(err string) { + mgr := getManager() + mgr.mu.Lock() + mgr.errorMsg = err + mgr.mu.Unlock() +} + +// 初始化管理器 +func initCSVManager() int64 { + _ = getManager() + return 0 // 成功 +} + +// 打开/创建CSV文件(句柄) +func openCSVFile(filename string, delimiter rune, hasHeader bool) int64 { + // 创建CSV全局管理器 + mgr := getManager() + + // 先检查文件是否已经打开 + var existingHandle int64 = -1 + mgr.files.Range(func(key, value interface{}) bool { + file := value.(*CSVFile) + // 比较文件路径(使用绝对路径确保一致性) + absFilename, _ := filepath.Abs(filename) + absExisting, _ := filepath.Abs(file.filename) + + if absFilename == absExisting { + fmt.Println("absFilename", absFilename) + fmt.Println("absExisting", absExisting) + fmt.Println("bool", absFilename == absExisting) + + existingHandle = key.(int64) + return false // 停止遍历 + } + return true // 继续遍历 + }) + + // 如果文件已经打开,返回现有句柄 + if existingHandle != -1 { + fmt.Printf("文件已打开,返回现有句柄: %d\n", existingHandle) + return existingHandle + } + + // 生成句柄 + handle := atomic.AddInt64(&mgr.nextHandle, 1) + + // 创建文件对象 + file := newCSVFile(filename, delimiter, hasHeader) + file.handle = handle + + // 加载文件数据 + if err := file.load(); err != nil { + setError(err.Error()) + return -1 + } + + // 存储到管理器 + mgr.files.Store(handle, file) + return handle +} + +// 读取多行数据 +func readRows(handle int64, startRow, count int64, buffer []byte) int64 { + mgr := getManager() + + // 获取文件对象 + val, ok := mgr.files.Load(handle) + if !ok { + setError("Invalid file handle") + return -1 + } + + file := val.(*CSVFile) + atomic.AddInt32(&file.readers, 1) + defer atomic.AddInt32(&file.readers, -1) + + // 获取读取锁 + file.mu.RLock() + defer file.mu.RUnlock() + + totalRows := int64(len(file.data)) + if startRow < 0 || startRow >= totalRows { + return 0 + } + + // 计算实际读取行数 + endRow := startRow + count + if endRow > totalRows { + endRow = totalRows + } + + rowsToRead := endRow - startRow + + // 将数据复制到缓冲区 + bytesWritten := 0 + + for i := startRow; i < endRow; i++ { + row := file.data[i] + + // 获取行读锁 + if rowLock := file.getRowLock(int(i)); rowLock != nil { + rowLock.RLock() + } + + for _, cell := range row { + // 写入单元格长度 + cellLen := len(cell) + if bytesWritten+4 > len(buffer) { + break + } + + // 写入4字节长度 + buffer[bytesWritten] = byte(cellLen & 0xFF) + buffer[bytesWritten+1] = byte((cellLen >> 8) & 0xFF) + buffer[bytesWritten+2] = byte((cellLen >> 16) & 0xFF) + buffer[bytesWritten+3] = byte((cellLen >> 24) & 0xFF) + bytesWritten += 4 + + // 写入单元格数据 + if bytesWritten+cellLen > len(buffer) { + // 缓冲区不足,回退长度写入 + bytesWritten -= 4 + break + } + + copy(buffer[bytesWritten:bytesWritten+cellLen], cell) + bytesWritten += cellLen + } + + // 写入行结束标记 + if bytesWritten+4 <= len(buffer) { + // 4字节的0表示行结束 + buffer[bytesWritten] = 0 + buffer[bytesWritten+1] = 0 + buffer[bytesWritten+2] = 0 + buffer[bytesWritten+3] = 0 + bytesWritten += 4 + } + + // 释放行锁 + if rowLock := file.getRowLock(int(i)); rowLock != nil { + rowLock.RUnlock() + } + + if bytesWritten >= len(buffer) { + break + } + } + + return rowsToRead +} + +// 写入/覆盖行数据 +func writeRows(handle int64, rowData [][]string, header int) int64 { + mgr := getManager() + + // 获取文件对象 + val, ok := mgr.files.Load(handle) + if !ok { + setError("文件无效句柄!") + return -1 + } + + file := val.(*CSVFile) + + // 获取写入锁 + file.mu.Lock() + defer file.mu.Unlock() + + atomic.AddInt32(&file.writers, 1) + defer atomic.AddInt32(&file.writers, -1) + + // 清空现有数据(因为这是覆盖写入) + file.data = make([][]string, 0, len(rowData)) + if header == 0 { + file.header = rowData[0] + // 添加新数据 + file.data = rowData[1:] + } else { + // 添加新数据 + for _, row := range rowData { + file.data = append(file.data, row) + } + } + + // 扩展行锁数组 + file.rowMu.Lock() + file.rowLocks = make([]*sync.RWMutex, len(file.data)) + for i := range file.rowLocks { + file.rowLocks[i] = &sync.RWMutex{} + } + file.rowMu.Unlock() + + file.modified = true + // 异步保存到文件 + go file.saveAsync() + return int64(len(file.data)) +} + +// 加载CSV文件到内存 +func (f *CSVFile) load() error { + f.mu.Lock() + defer f.mu.Unlock() + + // 打开文件 + file, err := os.Open(f.filename) + if err != nil { + // 文件不存在则创建空文件 + if os.IsNotExist(err) { + f.data = make([][]string, 0) // 初始化空数据 + f.rowLocks = make([]*sync.RWMutex, 0) // 初始化空行锁数组 + return nil + } + return fmt.Errorf("文件不存在: %v", err) + } + // 确保函数结束时关闭文件 + defer file.Close() + + // 获取文件大小 + stat, _ := file.Stat() // 获取文件信息 + f.fileSize = stat.Size() // 获取文件大小 + + // 创建CSV读取器 + reader := csv.NewReader(file) + reader.Comma = f.delimiter // 设置分隔符(默认逗号) + reader.LazyQuotes = true // 允许宽松的引号解析 + reader.TrimLeadingSpace = true // 去除字段前的空格 + + // 关键修改:允许变长记录,不强制检查字段数量 + reader.FieldsPerRecord = -1 + firstRecord, err := reader.Read() + if err != nil { + if err == io.EOF { + // 空文件 + f.data = make([][]string, 0) + f.rowLocks = make([]*sync.RWMutex, 0) + return nil + } + return err + } + maxColumns := len(firstRecord) + allData := [][]string{firstRecord} + + // 读取剩余行 + for { + record, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + // 对于有问题的行,可以填充或跳过 + continue + } + + // 确保所有行都有相同的列数 + if len(record) < maxColumns { + // 填充缺失的列为空字符串 + paddedRecord := make([]string, maxColumns) + copy(paddedRecord, record) + for i := len(record); i < maxColumns; i++ { + paddedRecord[i] = "" + } + record = paddedRecord + } else if len(record) > maxColumns { + // 如果行有更多列,更新最大列数 + maxColumns = len(record) + // 重新处理之前的所有行 + for i := range allData { + if len(allData[i]) < maxColumns { + paddedRecord := make([]string, maxColumns) + copy(paddedRecord, allData[i]) + allData[i] = paddedRecord + } + } + } + + allData = append(allData, record) + } + + if len(allData) == 0 { + f.data = make([][]string, 0) + f.rowLocks = make([]*sync.RWMutex, 0) + return nil + } + + // 处理表头 + if f.hasHeader && len(allData) > 0 { + f.header = allData[0] + f.data = allData[1:] + } else { + f.data = allData + } + + // 初始化行锁 + f.initRowLocks() + + return nil +} + +// initRowLocks 初始化行锁数组 +func (f *CSVFile) initRowLocks() { + count := len(f.data) + f.rowLocks = make([]*sync.RWMutex, count) + for i := 0; i < count; i++ { + f.rowLocks[i] = &sync.RWMutex{} + } +} + +// getRowLock 获取行锁(线程安全) +func (f *CSVFile) getRowLock(rowIndex int) *sync.RWMutex { + if rowIndex < 0 { + return nil + } + + f.rowMu.Lock() + defer f.rowMu.Unlock() + + // 确保行锁存在 + if rowIndex >= len(f.rowLocks) { + // 扩展行锁数组 + newLocks := make([]*sync.RWMutex, rowIndex+1) + copy(newLocks, f.rowLocks) + for i := len(f.rowLocks); i <= rowIndex; i++ { + newLocks[i] = &sync.RWMutex{} + } + f.rowLocks = newLocks + } + + return f.rowLocks[rowIndex] +} + +// save 保存到文件 +func (f *CSVFile) save() error { + // 标记正在保存 + if !atomic.CompareAndSwapInt32(&f.saving, 0, 1) { + // 已经在保存中,直接返回 + return nil + } + defer atomic.StoreInt32(&f.saving, 0) + + // 如果没有修改,直接返回 + f.mu.RLock() + if !f.modified { + f.mu.RUnlock() + return nil + } + // 复制数据 + dataCopy := make([][]string, len(f.data)) + + for i := range f.data { + dataCopy[i] = make([]string, len(f.data[i])) + copy(dataCopy[i], f.data[i]) + } + // 复制表头 + headerCopy := make([]string, len(f.header)) + copy(headerCopy, f.header) + // 复制配置(值类型,直接赋值) + hasHeader := f.hasHeader + delimiter := f.delimiter + filename := f.filename + f.mu.RUnlock() // 释放读锁 + + // 创建临时文件(使用不同的扩展名避免冲突) + tempFile := filename + ".tmp" + file, err := os.Create(tempFile) + if err != nil { + return err + } + + // 创建CSV写入器 + writer := csv.NewWriter(file) + writer.Comma = delimiter + + // 写入数据 + if hasHeader && len(headerCopy) > 0 { + if err := writer.Write(headerCopy); err != nil { + file.Close() + os.Remove(tempFile) + return err + } + } + + if err := writer.WriteAll(dataCopy); err != nil { + file.Close() + os.Remove(tempFile) + return err + } + + writer.Flush() + if err := writer.Error(); err != nil { + file.Close() + os.Remove(tempFile) + return err + } + + // 关闭文件,确保数据写入磁盘 + if err := file.Close(); err != nil { + os.Remove(tempFile) + return err + } + + // 尝试重命名,如果失败可能是文件被占用 + var renameErr error + for retry := 0; retry < 3; retry++ { + renameErr = os.Rename(tempFile, filename) + if renameErr == nil { + break + } + time.Sleep(100 * time.Millisecond) // 等待重试 + } + + if renameErr != nil { + os.Remove(tempFile) + return renameErr + } + + // 标记为已保存 + f.mu.Lock() + f.modified = false + f.mu.Unlock() + + return nil +} + +// saveAsync 异步保存,带错误处理 +func (f *CSVFile) saveAsync() { + select { + case f.saveErr <- f.save(): + // 保存完成 + default: + // 通道已满,忽略错误 + } +} + +// 追加行数据 +func appendRows(handle int64, rowsData []byte, rowCount int64) int { + mgr := getManager() + + // 获取文件对象 + val, ok := mgr.files.Load(handle) + if !ok { + setError("Invalid file handle") + return -1 + } + + file := val.(*CSVFile) + + // 获取写入锁 + file.mu.Lock() + atomic.AddInt32(&file.writers, 1) + + // 解析行数据 + offset := 0 + + for i := int64(0); i < rowCount; i++ { + var row []string + + // 读取行数据 + for { + if offset+4 > len(rowsData) { + break + } + + cellLen := int(uint32(rowsData[offset]) | + uint32(rowsData[offset+1])<<8 | + uint32(rowsData[offset+2])<<16 | + uint32(rowsData[offset+3])<<24) + offset += 4 + + if cellLen == 0 { + break + } + + if offset+cellLen > len(rowsData) { + break + } + + cell := string(rowsData[offset : offset+cellLen]) + offset += cellLen + row = append(row, cell) + } + + // 追加到数据 + file.data = append(file.data, row) + } + + file.modified = true + + atomic.AddInt32(&file.writers, -1) + file.mu.Unlock() + + // 异步保存 + go file.saveAsync() + + // 获取总行数 + count := getRowCount(handle) + fmt.Println("count:", count) + return int(count) +} + +// 打开/创建CSV文件(句柄) +func createOpenCSVFile(filename string, delimiter rune, hasHeader bool) int64 { + // 创建CSV全局管理器 + mgr := getManager() + + // 生成句柄 + handle := atomic.AddInt64(&mgr.nextHandle, 1) + + // 创建文件对象 + file := newCSVFile(filename, delimiter, hasHeader) + file.handle = handle + + //// 加载文件数据 + //if err := file.load(); err != nil { + // setError(err.Error()) + // return -1 + //} + + // 存储到管理器 + mgr.files.Store(handle, file) + + return handle +} + +// updateCSVRowSafe 修改csv文件行数据 +func updateCSVRowSafe(handleID int64, rowNum int, newRow []string) error { + mgr := getManager() + + // 获取文件对象 + val, ok := mgr.files.Load(handleID) + if !ok { + setError("Invalid file handle") + return fmt.Errorf("无效句柄: %d", handleID) + } + file := val.(*CSVFile) + + // 1. 创建临时文件 + tempDir := filepath.Dir(file.filename) + if tempDir == "" { + tempDir = "." + } + + tempFile, err := os.CreateTemp(tempDir, "temp_*.csv") + if err != nil { + return fmt.Errorf("创建临时文件失败: %v", err) + } + tempFileName := tempFile.Name() + + // 确保临时文件被关闭和清理 + defer func() { + tempFile.Close() + // 如果临时文件还存在(替换失败),则清理它 + if _, err := os.Stat(tempFileName); err == nil { + os.Remove(tempFileName) + } + }() + + // 2. 读取原始文件 + sourceFile, err := os.Open(file.filename) + if err != nil { + return fmt.Errorf("打开源文件失败: %v", err) + } + defer sourceFile.Close() + + // 3. 读取并处理数据 + reader := csv.NewReader(sourceFile) + allRows, err := reader.ReadAll() + if err != nil { + return fmt.Errorf("读取CSV失败: %v", err) + } + + // 4. 验证并更新 + if rowNum < 1 || rowNum > len(allRows) { + return fmt.Errorf("行号 %d 超出范围 (1-%d)", rowNum, len(allRows)) + } + + // 显示修改前后的对比 + oldRow := allRows[rowNum-1] + fmt.Printf("修改前第 %d 行: %v\n", rowNum, oldRow) + fmt.Printf("修改后第 %d 行: %v\n", rowNum, newRow) + + allRows[rowNum-1] = newRow + + // 5. 写入临时文件 + writer := csv.NewWriter(tempFile) + if err := writer.WriteAll(allRows); err != nil { + return fmt.Errorf("写入临时文件失败: %v", err) + } + writer.Flush() + + if err := writer.Error(); err != nil { + return fmt.Errorf("刷新写入失败: %v", err) + } + + // 6. 确保数据写入磁盘 + if err := tempFile.Sync(); err != nil { + return fmt.Errorf("同步文件失败: %v", err) + } + tempFile.Close() + + // 7. 使用复制而不是重命名(解决Windows文件占用问题) + if err := copyFile(tempFileName, file.filename); err != nil { + return fmt.Errorf("复制文件失败: %v", err) + } + + fmt.Printf("✅ 成功更新第 %d 行,文件已直接更新\n", rowNum) + + return nil +} + +// copyFile 复制文件内容 +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) + if err != nil { + return err + } + + // 确保数据写入磁盘 + err = destination.Sync() + if err != nil { + return err + } + + return nil +} + +// 获取总行数 +func getRowCount(handle int64) int64 { + mgr := getManager() + + val, ok := mgr.files.Load(handle) + if !ok { + setError("Invalid file handle") + return -1 + } + + file := val.(*CSVFile) + file.mu.RLock() + defer file.mu.RUnlock() + if file.header != nil { + return int64(len(file.data) + 1) + } + return int64(len(file.data)) +} + +// 搜索行 +func findRows(handle int64, searchText string, columnIndex int64, resultBuffer []byte, maxResults int64) int64 { + mgr := getManager() + + val, ok := mgr.files.Load(handle) + if !ok { + setError("Invalid file handle") + return -1 + } + + file := val.(*CSVFile) + + file.mu.RLock() + defer file.mu.RUnlock() + + atomic.AddInt32(&file.readers, 1) + defer atomic.AddInt32(&file.readers, -1) + + var foundRows []int64 + + // 搜索行 + for i, row := range file.data { + if maxResults > 0 && int64(len(foundRows)) >= maxResults { + break + } + + // 获取行读锁 + if rowLock := file.getRowLock(i); rowLock != nil { + rowLock.RLock() + } + + // 检查列 + if columnIndex < 0 || columnIndex >= int64(len(row)) { + // 搜索所有列 + for _, cell := range row { + if cell == searchText { + foundRows = append(foundRows, int64(i)) + break + } + } + } else if row[columnIndex] == searchText { + foundRows = append(foundRows, int64(i)) + } + + // 释放行锁 + if rowLock := file.getRowLock(i); rowLock != nil { + rowLock.RUnlock() + } + } + + // 写入结果到缓冲区 + if resultBuffer != nil && len(foundRows) > 0 { + bytesWritten := 0 + + for _, rowIndex := range foundRows { + if bytesWritten+8 > len(resultBuffer) { + break + } + + // 写入行索引(8字节) + for j := 0; j < 8; j++ { + resultBuffer[bytesWritten] = byte((rowIndex >> (uint(j) * 8)) & 0xFF) + bytesWritten++ + } + } + } + + return int64(len(foundRows)) +} + +// 合并多个CSV文件(线程安全) +func mergeCSVFiles(handles []int64, outputFilename string, delimiter rune, hasHeader bool) int64 { + mgr := getManager() + + // 验证所有句柄并获取文件对象 + files := make([]*CSVFile, 0, len(handles)) + for _, handle := range handles { + val, ok := mgr.files.Load(handle) + if !ok { + setError("Invalid file handle: " + string(handle)) + return -1 + } + files = append(files, val.(*CSVFile)) + } + + // 创建输出文件对象 + outputFile := newCSVFile(outputFilename, delimiter, hasHeader) + outputFile.modified = true // 标记为需要保存 + + // 第一步:合并表头 + mergedHeader := make([]string, 0) + headerSet := make(map[string]bool) + + if hasHeader { + // 收集所有不重复的表头 + for _, file := range files { + file.mu.RLock() + if file.hasHeader && len(file.header) > 0 { + for _, h := range file.header { + if !headerSet[h] { + headerSet[h] = true + mergedHeader = append(mergedHeader, h) + } + } + } + file.mu.RUnlock() + } + + // 如果没有找到表头,创建一个默认的表头 + if len(mergedHeader) == 0 && len(files) > 0 { + files[0].mu.RLock() + maxColumns := len(files[0].data[0]) + files[0].mu.RUnlock() + + for i := 0; i < maxColumns; i++ { + mergedHeader = append(mergedHeader, fmt.Sprintf("Column%d", i+1)) + } + } + } + outputFile.header = mergedHeader + + // 第二步:合并数据 + mergedData := make([][]string, 0) + + // 为每个输入文件创建读取锁并并发读取 + var wg sync.WaitGroup + dataChan := make(chan [][]string, len(files)) + errorChan := make(chan error, len(files)) + + for idx, file := range files { + wg.Add(1) + go func(fileIdx int, f *CSVFile) { + defer wg.Done() + + // 获取文件的读取锁 + f.mu.RLock() + defer f.mu.RUnlock() + + // 增加活跃读取器计数 + atomic.AddInt32(&f.readers, 1) + defer atomic.AddInt32(&f.readers, -1) + + // 读取数据 + fileData := make([][]string, len(f.data)) + for i := 0; i < len(f.data); i++ { + // 获取行读锁 + if rowLock := f.getRowLock(i); rowLock != nil { + rowLock.RLock() + } + + // 复制行数据 + row := make([]string, len(f.data[i])) + copy(row, f.data[i]) + + // 释放行锁 + if rowLock := f.getRowLock(i); rowLock != nil { + rowLock.RUnlock() + } + + fileData[i] = row + } + + // 如果需要处理表头映射 + if hasHeader && f.hasHeader && len(f.header) > 0 { + // 创建列映射:源列 -> 目标列 + columnMapping := make(map[int]int) + for srcIdx, srcHeader := range f.header { + for dstIdx, dstHeader := range mergedHeader { + if srcHeader == dstHeader { + columnMapping[srcIdx] = dstIdx + break + } + } + } + + // 重新排列数据以匹配合并后的表头 + for i := 0; i < len(fileData); i++ { + newRow := make([]string, len(mergedHeader)) + for srcIdx, dstIdx := range columnMapping { + if srcIdx < len(fileData[i]) { + newRow[dstIdx] = fileData[i][srcIdx] + } + } + fileData[i] = newRow + } + } else if hasHeader && len(mergedHeader) > 0 { + // 文件没有表头,但输出需要表头 + // 简单地将数据填充到对应位置 + for i := 0; i < len(fileData); i++ { + newRow := make([]string, len(mergedHeader)) + for j := 0; j < len(fileData[i]) && j < len(newRow); j++ { + newRow[j] = fileData[i][j] + } + fileData[i] = newRow + } + } + + // 将处理后的数据发送到通道 + dataChan <- fileData + }(idx, file) + } + + // 等待所有goroutine完成 + go func() { + wg.Wait() + close(dataChan) + close(errorChan) + }() + + // 收集所有数据 + for data := range dataChan { + mergedData = append(mergedData, data...) + } + + // 检查是否有错误 + select { + case err := <-errorChan: + if err != nil { + setError("Error merging files: " + err.Error()) + return -1 + } + default: + } + + // 第三步:设置输出文件的数据 + outputFile.data = mergedData + outputFile.initRowLocks() + + // 第四步:保存到文件 + if err := outputFile.save(); err != nil { + setError("Error saving merged file: " + err.Error()) + return -1 + } + + // 第五步:将输出文件添加到管理器 + handle := atomic.AddInt64(&mgr.nextHandle, 1) + outputFile.handle = handle + mgr.files.Store(handle, outputFile) + + return handle +} + +// 关闭文件 +func closeCSVFile(handle int64) int64 { + mgr := getManager() + + val, ok := mgr.files.Load(handle) + if !ok { + setError("文件句柄无效!") + return -1 + } + + file := val.(*CSVFile) + + // 等待所有读写操作完成 + for atomic.LoadInt32(&file.readers) > 0 || atomic.LoadInt32(&file.writers) > 0 { + time.Sleep(time.Millisecond) + } + + // 等待异步保存完成 + for i := 0; i < 100; i++ { // 最多等待100ms + if atomic.LoadInt32(&file.saving) == 0 { + break + } + time.Sleep(time.Millisecond) + } + + // 如果有正在进行的保存,等待一小段时间 + if atomic.LoadInt32(&file.saving) > 0 { + time.Sleep(50 * time.Millisecond) + } + + // 检查是否需要保存 + file.mu.RLock() + needSave := file.modified + file.mu.RUnlock() + + if needSave { + // 同步保存修改 + if err := file.save(); err != nil { + setError(err.Error()) + return -1 + } + } + + // 从管理器移除 + mgr.files.Delete(handle) + + //// 安全关闭通道(如果存在) + //if file.done != nil { + // close(file.done) + //} + + return 0 +} + +// 获取错误信息 +func getError() string { + mgr := getManager() + mgr.mu.RLock() + defer mgr.mu.RUnlock() + + if mgr.errorMsg == "" { + return "" + } + + err := mgr.errorMsg + mgr.errorMsg = "" // 清空错误 + + return err +} + +func parseSimpleTable(goData string) [][]string { + lines := strings.Split(strings.TrimSpace(goData), "\n") + result := make([][]string, len(lines)) + + for i, line := range lines { + // 根据你的分隔符分割,这里用逗号举例 + fields := strings.Split(line, ",") + // 如果需要去除每个字段的空格 + for j := range fields { + fields[j] = strings.TrimSpace(fields[j]) + } + result[i] = fields + } + + return result +} + +// ============ C 导出接口 ============ + +//export InitCSVManager +func InitCSVManager() C.longlong { + return C.longlong(initCSVManager()) +} + +// OpenCSVFile 打开/创建CSV文件 +// +//export OpenCSVFile +func OpenCSVFile(filename *C.char, delimiter C.char, hasHeader C.int) C.longlong { + return C.longlong(openCSVFile(C.GoString(filename), rune(delimiter), hasHeader != 0)) +} + +// CSV响应结构体 +type CSVResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Data interface{} `json:"data,omitempty"` +} + +// UpdateCSVRowSafe 修改csv文件行数据 +// +//export UpdateCSVRowSafe +func UpdateCSVRowSafe(handleID C.longlong, rowNum C.int, newRow *C.char) *C.char { + goHandle := int64(handleID) + goRowNum := int(rowNum) + goNewRow := C.GoString(newRow) + var row []string + var csvResponse CSVResponse + // 解析JSON + err := json.Unmarshal([]byte(goNewRow), &row) + if err != nil { + csvResponse = CSVResponse{ + Success: false, + Message: fmt.Sprintf("解析JSON失败: %v", err), + } + errorJson, _ := json.Marshal(csvResponse) + return C.CString(string(errorJson)) + } + // 修改csv行数据 + err = updateCSVRowSafe(goHandle, goRowNum, row) + if err != nil { + csvResponse = CSVResponse{ + Success: false, + Message: fmt.Sprintf(err.Error()), + } + errorJson, _ := json.Marshal(csvResponse) + return C.CString(string(errorJson)) + } else { + csvResponse = CSVResponse{ + Success: true, + } + } + // 转换为JSON字符串 + jsonData, err := json.Marshal(csvResponse) + if err != nil { + // 如果JSON序列化失败,返回错误信息 + csvResponse = CSVResponse{ + Success: false, + Message: fmt.Sprintf("JSON序列化失败: %v", err), + } + errorJson, _ := json.Marshal(csvResponse) + return C.CString(string(errorJson)) + } + return C.CString(string(jsonData)) +} + +//export CreateOpenCSVFile +func CreateOpenCSVFile(filename *C.char, delimiter C.char, hasHeader C.int) *C.char { + goFilename := C.GoString(filename) + goDelimiter := rune(delimiter) + var goHasHeader bool + if int(hasHeader) == 0 { + goHasHeader = true + } + goHasHeader = false + handle := createOpenCSVFile(goFilename, goDelimiter, goHasHeader) + var csvResponse CSVResponse + response := struct { + HandleID int64 `json:"handleID"` + }{ + HandleID: handle, + } + csvResponse.Success = true + csvResponse.Data = response + jsonData, _ := json.Marshal(csvResponse) + return C.CString(string(jsonData)) +} + +// ReadRows 读取多行数据 +// +//export ReadRows +func ReadRows(handle C.longlong, startRow C.longlong, count C.longlong, buffer *C.char, bufferSize C.int) C.longlong { + // 将 C 缓冲区转换为 Go 的字节切片 + goBuffer := unsafe.Slice((*byte)(unsafe.Pointer(buffer)), int(bufferSize)) + result := readRows(int64(handle), int64(startRow), int64(count), goBuffer) + return C.longlong(result) +} + +// WriteRows 写入/覆盖行数据 +// +//export WriteRows +func WriteRows(handle C.longlong, rowsData *C.char, header C.int) C.int { + goData := C.GoString(rowsData) + goHeader := int(header) + //data := parseSimpleTable(goData) + var data [][]string + err := json.Unmarshal([]byte(goData), &data) + if err != nil { + setError(err.Error()) + } + result := writeRows(int64(handle), data, goHeader) + return C.int(result) +} + +// AppendRows 追加行数据 +// +//export AppendRows +func AppendRows(handle C.longlong, rowsData *C.char, dataSize C.int, rowCount C.longlong) C.int { + goData := unsafe.Slice((*byte)(unsafe.Pointer(rowsData)), int(dataSize)) + result := appendRows(int64(handle), goData, int64(rowCount)) + return C.int(result) +} + +// GetRowCount 获取总行数 +// +//export GetRowCount +func GetRowCount(handle C.longlong) C.longlong { + result := getRowCount(int64(handle)) + return C.longlong(result) +} + +// FindRows 搜索行 +// +//export FindRows +func FindRows(handle C.longlong, searchText *C.char, columnIndex C.longlong, resultBuffer *C.char, bufferSize C.int, maxResults C.longlong) C.longlong { + goSearchText := C.GoString(searchText) + goResultBuffer := unsafe.Slice((*byte)(unsafe.Pointer(resultBuffer)), int(bufferSize)) + result := findRows(int64(handle), goSearchText, int64(columnIndex), goResultBuffer, int64(maxResults)) + return C.longlong(result) +} + +// MergeCSVFiles 合并多个CSV文件(线程安全) +// +//export MergeCSVFiles +func MergeCSVFiles(handlesPtr *C.longlong, handlesCount C.int, outputFilename *C.char, delimiter C.char, hasHeader C.int) C.longlong { + // 将C数组转换为Go切片 + goHandles := unsafe.Slice(handlesPtr, int(handlesCount)) + handles := make([]int64, len(goHandles)) + for i := 0; i < len(goHandles); i++ { + handles[i] = int64(goHandles[i]) + } + // 调用合并函数 + result := mergeCSVFiles(handles, C.GoString(outputFilename), rune(delimiter), hasHeader != 0) + return C.longlong(result) +} + +// CloseCSVFile 关闭文件 +// +//export CloseCSVFile +func CloseCSVFile(handle C.longlong) C.int { + result := closeCSVFile(int64(handle)) + return C.int(result) +} + +// GetError 获取错误信息 +// +//export GetError +func GetError(buffer *C.char, bufferSize C.int) C.int { + errMsg := getError() + if errMsg == "" { + return 0 + } + + // 将错误信息复制到缓冲区 + goBuffer := unsafe.Slice((*byte)(unsafe.Pointer(buffer)), int(bufferSize)) + n := copy(goBuffer, errMsg) + return C.int(n) +} + +// 导出函数:释放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) +} + +// main 函数是必需的,即使为空 +func main() { +} diff --git a/expressDeliveryOrder/dll/expressDeliveryOrder.dll b/expressDeliveryOrder/dll/expressDeliveryOrder.dll deleted file mode 100644 index 1e09076..0000000 Binary files a/expressDeliveryOrder/dll/expressDeliveryOrder.dll and /dev/null differ diff --git a/expressDeliveryOrder/dll/expressDeliveryOrder.h b/expressDeliveryOrder/dll/expressDeliveryOrder.h deleted file mode 100644 index d4ee74e..0000000 --- a/expressDeliveryOrder/dll/expressDeliveryOrder.h +++ /dev/null @@ -1,125 +0,0 @@ -/* 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 index b986f05..82c062a 100644 --- a/expressDeliveryOrder/expressDeliveryOrder.go +++ b/expressDeliveryOrder/expressDeliveryOrder.go @@ -11,13 +11,16 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/parnurzeal/gorequest" - "github.com/tjfoc/gmsm/sm4" + "io" + "mime/multipart" "net/http" "sort" "strings" "time" "unsafe" + + "github.com/parnurzeal/gorequest" + "github.com/tjfoc/gmsm/sm4" ) // ZtoOrderRequest 中通订单请求结构体 @@ -117,10 +120,10 @@ type Cabinet struct { 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" // 测试环境 @@ -131,15 +134,14 @@ func ztoOpenCreateOrder(requestJSON, appKey, appSecret string) (string, error) { if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } - fmt.Println(dataDigest) - // 创建gorequest实例 + // 创建请求实例 req := gorequest.New() - // 使用gorequest发送POST请求 + // 发送POST请求 resp, body, errs := req.Post(ztoUrl). Set("Content-Type", "application/json"). Set("x-appKey", appKey). - Set("x-datadigest", dataDigest). + Set("x-dataDigest", dataDigest). Set("x-timestamp", fmt.Sprintf("%d", time.Now().Unix())). Send(requestJSON). End() @@ -216,7 +218,6 @@ func ztoGenerateSign(data, appSecret string) (string, error) { buf.WriteString("}") sortedJSON := buf.String() - fmt.Printf("排序后的JSON: %s\n", sortedJSON) // 按文档要求的格式 signString := sortedJSON + appSecret @@ -226,44 +227,33 @@ func ztoGenerateSign(data, appSecret string) (string, error) { // 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) - +// 中通快递--取消订单接口 +func ztoOpenCancelPreOrder(requestJSON, appKey, appSecret string) (string, error) { + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") + } // 正式环境 - jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder" + ztoUrl := "https://japi.zto.com/zto.open.cancelPreOrder" // 测试环境 - //jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + //ztoUrl := "https://japi-test.zto.com/zto.open.cancelPreOrder" // 生成签名 - digest, err := jtGenerateDigest(requestJSON, privateKey) + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) 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"). + // 发送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() @@ -295,95 +285,31 @@ func jtOrderAddOrder(requestJSON, apiAccount, privateKey string) (string, error) 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) +// 中通快递--查询订单接口 +func ztoOpenGetOrderInfo(requestJSON, appKey, appSecret string) (string, error) { + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } - 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" + ztoUrl := "https://japi.zto.com/zto.open.getOrderInfo" // 测试环境 - //emsUrl := "https://api.ems.com.cn/amp-prod-api/f/amp/api/test" + //ztoUrl := "https://japi-test.zto.com/zto.open.getOrderInfo" - // 当前时间戳 - timestamp := fmt.Sprintf("%d", time.Now().Unix()) - - contentToEncrypt := requestJSON + secretKey - - signature, err := GenerateSM4Signature(contentToEncrypt, secretKey) + // 生成签名 + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { - return "", fmt.Errorf("生成签名失败: %v", err) + return "", fmt.Errorf("生成签名失败: %w", 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). + // 发送POST请求 + resp, body, errs := req.Post(ztoUrl). 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). + Set("x-appKey", appKey). + Set("x-dataDigest", dataDigest). + Set("x-timestamp", fmt.Sprintf("%d", time.Now().Unix())). + Send(requestJSON). End() // 处理错误 @@ -414,6 +340,1477 @@ func emsAmpApiOpen(requestJSON, secretKey string) (string, error) { return string(responseJSON), nil } +// 中通快递--获取打单余额 +func ztoOpenQueryAvailableBalanceNew(requestJSON, appKey, appSecret string) (string, error) { + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") + } + // 正式环境 + ztoUrl := "https://japi.zto.com/zto.open.queryAvailableBalanceNew" + // 测试环境 + //ztoUrl := "https://japi-test.zto.com/zto.open.queryAvailableBalanceNew" + + // 生成签名 + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 创建请求实例 + req := gorequest.New() + // 发送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 +} + +// 中通快递--请求生成面单图片/PDF +func ztoOpenOrderPrint(requestJSON, appKey, appSecret string) (string, error) { + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") + } + // 正式环境 + ztoUrl := "https://japi.zto.com/zto.open.order.print" + // 测试环境 + //ztoUrl := "https://japi-test.zto.com/zto.open.order.print" + + // 生成签名 + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 创建请求实例 + req := gorequest.New() + // 发送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 ztoOpenBindingEaccount(requestJSON, appKey, appSecret string) (string, error) { + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") + } + // 正式环境 + ztoUrl := "https://japi.zto.com/zto.open.bindingEaccount" + // 测试环境 + //ztoUrl := "https://japi-test.zto.com/zto.open.bindingEaccount" + + // 生成签名 + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 创建请求实例 + req := gorequest.New() + // 发送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 +} + +// 中通快递--网点code查询网点信息 +func ztoVipQuerySiteInfoByCode(requestJSON, appKey, appSecret string) (string, error) { + if appKey == "" || appSecret == "" { + return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") + } + // 正式环境 + ztoUrl := "https://japi.zto.com/zto.vip.querySiteInfoByCode" + // 测试环境 + //ztoUrl := "https://japi-test.zto.com/zto.vip.querySiteInfoByCode" + + // 生成签名 + dataDigest, err := ztoGenerateSign(requestJSON, appSecret) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 创建请求实例 + req := gorequest.New() + // 发送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 jtOrderAddOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--创建订单(带运单号) +func jtOrderV2AddOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/v2/addOrder" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/v2/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--查询订单 +func jtOrderGetOrders(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/getOrders" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/getOrders?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--取消订单 +func jtOrderCancelOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/cancelOrder" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/cancelOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--电子面单账号检验 +func jtVipCheckCusPwd(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/vip/checkCusPwd" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/vip/checkCusPwd?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--电子面单余额查询 +func jtEssBalance(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/ess/balance" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/ess/balance?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--获取电子面单 +func jtBillCodeGetBatchBillCode(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/billCode/getBatchBillCode" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/billCode/getBatchBillCode?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// 极兔快递--面单打印 +func jtOrderPrintOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { + // 正式环境 + jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/printOrder" + // 测试环境 + // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/printOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" + + // 生成签名(传入bizContent的JSON字符串) + digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) + if err != nil { + return "", fmt.Errorf("生成签名失败: %w", err) + } + + // 当前时间戳 + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + // 构造请求体 + requestBody := map[string]string{ + "bizContent": bizContentJSONStr, + } + + // 创建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). + Send(requestBody). + 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, 响应: %s", resp.StatusCode, body) + } + + return body, nil +} + +// jtGenerateDigest 极兔生成Headers中的digest签名 +// bizContentJSONStr: 业务参数的JSON字符串 +// privateKey: 平台分配的私钥 +func jtGenerateDigest(bizContentJSONStr, privateKey string) (string, error) { + // 1. 拼接JSON字符串和私钥 + signStr := bizContentJSONStr + privateKey + + // 2. 计算MD5 + hash := md5.Sum([]byte(signStr)) + + // 3. Base64编码 + digest := base64.StdEncoding.EncodeToString(hash[:]) + + return digest, nil +} + +// GenerateBusinessDigest 极兔生成业务参数中的digest签名(用于bizContent内部) +// customerCode: 客户编码,如"J0086474299" +// plainPassword: 明文密码,如"H5CD3zE6" +// businessKey: 业务密钥(固定为"jadada236t2") +// privateKey: 平台分配的私钥 +// 返回:加密后的密码和digest签名 +func GenerateBusinessDigest(customerCode, plainPassword, businessKey, privateKey string) (encryptedPwd, digest string, err error) { + // 1. 计算加密后的密码 pwd = MD5(明文密码 + 业务密钥),32位大写 + pwdStr := plainPassword + businessKey + pwdHash := md5.Sum([]byte(pwdStr)) + encryptedPwd = strings.ToUpper(hex.EncodeToString(pwdHash[:])) + + // 2. 拼接 customerCode + pwd + privateKey + signStr := customerCode + encryptedPwd + privateKey + + // 3. 计算MD5 + hash := md5.Sum([]byte(signStr)) + + // 4. Base64编码 + digest = base64.StdEncoding.EncodeToString(hash[:]) + + return encryptedPwd, digest, 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[:]) +} + +// 韵达快递-电子面单取消 +func ydCancelBmOrder(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/cancelBmOrder" + // 测试环境 + //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/cancelBmOrder" + + // 1. 构建基础参数(用于签名和请求头) + timestamp := time.Now().UnixMilli() + reqTime := fmt.Sprintf("%d", timestamp) + + // 针对JSON字符串进行升序排序 + key, err := reorderJSONFieldsByKey(requestJSON, true) + if err != nil { + return "", fmt.Errorf("排序失败: %v", err) + } + fmt.Println("排序后的JSON字符串:", key) + + sign := ydGenerateSign(key, appSecret) + + // 3. 构建 Headers(根据文档) + headers := map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + "app-key": appKey, + "req-time": reqTime, + "sign": sign, // 需要实现签名方法 + } + + // 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 +} + +// 韵达快递--电子面单下单 +// 参数:requestJSON:业务参数JSON字符串 appid:合作商appid(等同app-key) appSecret:签名 partnerID +func ydCreateBmOrder(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/createBmOrder" + // 测试环境 + //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/createBmOrder" + + // 1. 构建基础参数(用于签名和请求头) + timestamp := time.Now().UnixMilli() + reqTime := fmt.Sprintf("%d", timestamp) + + // 针对JSON字符串进行升序排序 + key, err := reorderJSONFieldsByKey(requestJSON, true) + if err != nil { + return "", fmt.Errorf("排序失败: %v", err) + } + fmt.Println("排序后的JSON字符串:", key) + + sign := ydGenerateSign(key, appSecret) + + // 3. 构建 Headers(根据文档) + headers := map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + "app-key": appKey, + "req-time": reqTime, + "sign": sign, // 需要实现签名方法 + } + + // 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 +} + +// 按键名排序JSON字段 +func reorderJSONFieldsByKey(requestJSON string, sortAscending bool) (string, error) { + // 解析JSON + var obj map[string]interface{} + if err := json.Unmarshal([]byte(requestJSON), &obj); err != nil { + return "", fmt.Errorf("JSON解析失败: %v", err) + } + + // 获取所有键并排序 + keys := make([]string, 0, len(obj)) + for k := range obj { + keys = append(keys, k) + } + + if sortAscending { + sort.Strings(keys) // 升序 + } else { + sort.Sort(sort.Reverse(sort.StringSlice(keys))) // 降序 + } + + // 创建有序结果 + orderedObj := make(map[string]interface{}) + for _, key := range keys { + orderedObj[key] = obj[key] + } + + marshal, _ := json.Marshal(orderedObj) + return string(marshal), nil +} + +// 韵达快递计算签名 +func ydGenerateSign(content, secretKey string) string { + text := content + "_" + secretKey + // 计算 MD5 + hash := md5.Sum([]byte(text)) + + return fmt.Sprintf("%x", hash[:]) +} + +// 韵达快递-电子面单余量查询接口 +func ydSearchCount(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/searchCount" + // 测试环境 + //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/searchCount" + // 排序 + key, err := reorderJSONFieldsByKey(requestJSON, true) + if err != nil { + return "", fmt.Errorf("排序失败: %v", err) + } + fmt.Println("排序后的JSON字符串:", key) + + // 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(key, 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 ydBmGetPdfInfo(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + baseURL := "https://openapi.yundaex.com/openapi/outer/v1/bm/getPdfInfo" + // 测试环境 + //baseURL := "https://u-openapi.yundasys.com/openapi/outer/v1/bm/getPdfInfo" + + // 排序 + key, err := reorderJSONFieldsByKey(requestJSON, true) + if err != nil { + return "", fmt.Errorf("排序失败: %v", err) + } + fmt.Println("排序后的JSON字符串:", key) + + // 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(key, 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 ydUpdateBmOrder(requestJSON, appKey, appSecret string) (string, error) { + // 确定环境 + baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/updateBmOrder" + // 测试环境 + //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/updateBmOrder" + + // 1. 构建基础参数(用于签名和请求头) + timestamp := time.Now().UnixMilli() + reqTime := fmt.Sprintf("%d", timestamp) + + // 针对JSON字符串进行升序排序 + key, err := reorderJSONFieldsByKey(requestJSON, true) + if err != nil { + return "", fmt.Errorf("排序失败: %v", err) + } + fmt.Println("排序后的JSON字符串:", key) + + sign := ydGenerateSign(key, appSecret) + + // 3. 构建 Headers(根据文档) + headers := map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + "app-key": appKey, + "req-time": reqTime, + "sign": sign, // 需要实现签名方法 + } + + // 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 +} + +// ========================== 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) +} + +// ZtoOpenCancelPreOrder 中通快递--取消订单接口 +// +//export ZtoOpenCancelPreOrder +func ZtoOpenCancelPreOrder(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoOpenCancelPreOrder(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// ZtoOpenGetOrderInfo 中通快递--查询订单接口 +// +//export ZtoOpenGetOrderInfo +func ZtoOpenGetOrderInfo(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoOpenGetOrderInfo(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// ZtoOpenQueryAvailableBalanceNew 中通快递--获取打单余额 +// +//export ZtoOpenQueryAvailableBalanceNew +func ZtoOpenQueryAvailableBalanceNew(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoOpenQueryAvailableBalanceNew(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// ZtoOpenOrderPrint 中通快递--请求生成面单图片/PDF +// +//export ZtoOpenOrderPrint +func ZtoOpenOrderPrint(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoOpenOrderPrint(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// ZtoOpenBindingEaccount 中通快递--绑定电子面单 +// +//export ZtoOpenBindingEaccount +func ZtoOpenBindingEaccount(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoOpenBindingEaccount(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// ZtoVipQuerySiteInfoByCode 中通快递--网点code查询网点信息 +// +//export ZtoVipQuerySiteInfoByCode +func ZtoVipQuerySiteInfoByCode(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ztoVipQuerySiteInfoByCode(requestJSONStr, appKeyStr, appSecretStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtOrderAddOrder 极兔快递--创建订单接口 +// +//export JtOrderAddOrder +func JtOrderAddOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtOrderAddOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtOrderV2AddOrder 极兔快递--创建订单(带运单号) +// +//export JtOrderV2AddOrder +func JtOrderV2AddOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtOrderV2AddOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtOrderGetOrders 极兔快递--查询订单 +// +//export JtOrderGetOrders +func JtOrderGetOrders(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtOrderGetOrders(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtOrderCancelOrder 极兔快递--取消订单 +// +//export JtOrderCancelOrder +func JtOrderCancelOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtOrderCancelOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtVipCheckCusPwd 极兔快递--电子面单账号检验 +// +//export JtVipCheckCusPwd +func JtVipCheckCusPwd(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtVipCheckCusPwd(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtEssBalance 极兔快递--电子面单余额查询 +// +//export JtEssBalance +func JtEssBalance(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtEssBalance(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtBillCodeGetBatchBillCode 极兔快递--获取电子面单 +// +//export JtBillCodeGetBatchBillCode +func JtBillCodeGetBatchBillCode(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtBillCodeGetBatchBillCode(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// JtOrderPrintOrder 极兔快递--面单打印 +// +//export JtOrderPrintOrder +func JtOrderPrintOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { + bizContentJSONStr := C.GoString(bizContentJSON) + apiAccountStr := C.GoString(apiAccount) + privateKeyStr := C.GoString(privateKey) + info, err := jtOrderPrintOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// EmsAmpApiOpen 邮政快递--订单接入接口 +// +//export EmsAmpApiOpen +func EmsAmpApiOpen(apiCode, senderNo, authorization, secretKey, requestJSON *C.char) *C.char { + apiCodeStr := C.GoString(apiCode) + senderNoStr := C.GoString(senderNo) + authorizationStr := C.GoString(authorization) + secretKeyStr := C.GoString(secretKey) + requestJSONStr := C.GoString(requestJSON) + info, err := emsAmpApiOpen(apiCodeStr, senderNoStr, authorizationStr, secretKeyStr, requestJSONStr) + 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) +} + +// YdCancelBmOrder 韵达快递-电子面单取消 +// +//export YdCancelBmOrder +func YdCancelBmOrder(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ydCancelBmOrder(requestJSONStr, appKeyStr, appSecretStr) + 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) +} + +// YdSearchCount 韵达快递-电子面单余量查询接口 +// +//export YdSearchCount +func YdSearchCount(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ydSearchCount(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) +} + +// YdUpdateBmOrder 韵达快递-电子面单更新接口 +// +//export YdUpdateBmOrder +func YdUpdateBmOrder(requestJSON, appKey, appSecret *C.char) *C.char { + requestJSONStr := C.GoString(requestJSON) + appKeyStr := C.GoString(appKey) + appSecretStr := C.GoString(appSecret) + info, err := ydUpdateBmOrder(requestJSONStr, appKeyStr, appSecretStr) + 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)) +} + +///**********************************************************邮政开始***************************************************************// + +// MultipartFormData 存储multipart表单数据 +type MultipartFormData struct { + fields map[string]string + files map[string]FileInfo +} + +// FileInfo 文件信息结构 +type FileInfo struct { + FieldName string + FileName string + FileData []byte +} + +// NewMultipartFormData 创建新的multipart表单数据对象 +func NewMultipartFormData() *MultipartFormData { + return &MultipartFormData{ + fields: make(map[string]string), + files: make(map[string]FileInfo), + } +} + +/* + 邮政快递--订单接入接口 + 请求参数: + apiCode 接口代码 + senderNo 生产协议客户号 + authorization 授权码 + secretKey 密钥 + requestJSON 业务报文JSON字符串 +*/ +func emsAmpApiOpen(apiCode, senderNo, authorization, secretKey, requestJSON string) (string, error) { + + url := "https://api.ems.com.cn/amp-prod-api/f/amp/api/open" + + contentToEncrypt := requestJSON + secretKey + signature, err := GenerateSM4Signature(contentToEncrypt, secretKey) + if err != nil { + return "", err + } + + formData := NewMultipartFormData() + formData.AddField("apiCode", apiCode) + formData.AddField("senderNo", senderNo) + formData.AddField("authorization", authorization) + formData.AddField("timeStamp", time.Now().Format("2006-01-02 15:04:05")) + formData.AddField("logitcsInterface", signature) + + headers := map[string]string{ + "User-Agent": "ZTO-API-Client/1.0", + } + + response, err := PostMultipartRequest(url, formData, headers, 30*time.Second) + if err != nil { + return "", err + } + return string(response), nil +} + const ( CIPHERTEXT_PREFIX_THIRD_SM4ECB = "|$4|" ) @@ -577,717 +1974,195 @@ func VerifySignature(ciphertext, params, key string) (bool, error) { 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" +// PostMultipartRequest POST multipart/form-data请求 +func PostMultipartRequest(url string, formData *MultipartFormData, headers map[string]string, timeout time.Duration) ([]byte, error) { + // 创建payload缓冲区 + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) - signature := stoGenerateSign(requestJSON, secretKey) + // 添加表单字段 + for key, value := range formData.fields { + if err := writer.WriteField(key, value); err != nil { + return nil, fmt.Errorf("写入字段 %s 失败: %v", key, err) + } + } - // 准备请求参数 - params := map[string]string{ - "api_name": "OMS_EXPRESS_ORDER_CREATE", - "content": requestJSON, - "from_appkey": fromAppkey, - "from_code": fromCode, + // 添加文件 + for _, fileInfo := range formData.files { + part, err := writer.CreateFormFile(fileInfo.FieldName, fileInfo.FileName) + if err != nil { + return nil, fmt.Errorf("创建文件字段 %s 失败: %v", fileInfo.FieldName, err) + } + if _, err := part.Write(fileInfo.FileData); err != nil { + return nil, fmt.Errorf("写入文件数据失败: %v", err) + } + } + + // 关闭writer + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("关闭writer失败: %v", err) + } + + // 创建HTTP客户端 + client := &http.Client{ + Timeout: timeout, + } + + // 创建请求 + req, err := http.NewRequest("POST", url, payload) + if err != nil { + return nil, fmt.Errorf("创建请求失败: %v", err) + } + + // 设置Content-Type + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // 设置自定义请求头 + for key, value := range headers { + req.Header.Set(key, value) + } + + // 发送请求 + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("发送请求失败: %v", err) + } + defer resp.Body.Close() + + // 读取响应体 + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + // 检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return body, fmt.Errorf("请求返回非200状态码: %d, 响应: %s", resp.StatusCode, string(body)) + } + + return body, nil +} + +// AddField 添加表单字段 +func (m *MultipartFormData) AddField(key, value string) { + m.fields[key] = value +} + +// AddFile 添加文件 +func (m *MultipartFormData) AddFile(fieldName, fileName string, fileData []byte) { + m.files[fieldName] = FileInfo{ + FieldName: fieldName, + FileName: fileName, + FileData: fileData, + } +} + +///**********************************************************邮政结束***************************************************************// + +///**********************************************************申通开始***************************************************************// + +// 构建请求数据 +func getStoHeader(apiName string, appkey string, code string, secretKey string, content string) map[string]string { + dataDigest := getStoSignature(content, secretKey) + var param = map[string]string{ + "api_name": apiName, + "content": content, + "from_appkey": appkey, + "from_code": code, "to_appkey": "sto_oms", "to_code": "sto_oms", - "data_digest": signature, + "data_digest": dataDigest, } + return param +} - // 创建gorequest实例 - req := gorequest.New() +func requestSto(apiName string, appkey string, code string, secretKey string, content string) (string, error) { + //var url = "http://cloudinter-linkgatewaytest.sto.cn/gateway/link.do" + url := "https://cloudinter-linkgateway.sto.cn/gateway/link.do" - // 设置超时和重试策略 - req = req.Timeout(30*time.Second). - Retry(3, 5*time.Second, http.StatusInternalServerError, http.StatusBadGateway) + // 获取请求参数 + params := getStoHeader(apiName, appkey, code, secretKey, content) - // 使用gorequest发送POST请求 - req.Post(stoUrl). - Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") + // 创建 multipart form-data 请求体 + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) - // 添加表单参数 + // 添加所有参数到 form-data 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() + "; " + err := writer.WriteField(key, value) + if err != nil { + return "", err } - 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) + // 关闭 writer 以完成 multipart 数据 + err := writer.Close() if err != nil { - return "", fmt.Errorf("JSON序列化失败: %v", err) + return "", err } - return string(responseJSON), nil + + // 创建 HTTP 请求 + req, err := http.NewRequest("POST", url, body) + if err != nil { + return "", err + } + + // 设置 Content-Type 头,包含 boundary + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // 发送请求 + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // 读取响应 + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(responseBody), nil } -// 申通快递计算签名 -func stoGenerateSign(content, secretKey string) string { +// 生成签名 +func getStoSignature(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 中通快递--创建订单接口 +// RequestSto 申通快递统一请求为入口 // -//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) +//export RequestSto +func RequestSto(apiName, appkey, code, secretKey, requestJSON *C.char) *C.char { + apiNameStr := C.GoString(apiName) + appkeyStr := C.GoString(appkey) + codeStr := C.GoString(code) 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) + info, err := requestSto(apiNameStr, appkeyStr, codeStr, secretKeyStr, requestJSONStr) 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() { + //apiName := "OMS_EXPRESS_ORDER_CREATE" + //appkey := "CAKgwDceADOybmU" + //code := "CAKgwDceADOybmU" + //secretKey := "2kLMr75pURbVh3FnWzY6SCr7SM6qsoO3" + //content := "{\"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\"}" + //info, err := requestSto(apiName, appkey, code, secretKey, content) + //if err != nil { + // fmt.Println(err) + // return + //} + //fmt.Println(info) } diff --git a/expressDeliveryOrder/expressDeliveryOrderTest.go b/expressDeliveryOrder/expressDeliveryOrderTest.go deleted file mode 100644 index 2a6ad8b..0000000 --- a/expressDeliveryOrder/expressDeliveryOrderTest.go +++ /dev/null @@ -1,20 +0,0 @@ -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/expressDeliveryOrder/maintest.go b/expressDeliveryOrder/maintest.go new file mode 100644 index 0000000..57b60c7 --- /dev/null +++ b/expressDeliveryOrder/maintest.go @@ -0,0 +1,86 @@ +package main + +func main() { + //apiName := "OMS_EXPRESS_ORDER_CREATE" + //content := "{\"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\"}" + //stock, err := billCodeQueryUserStock(apiName, content) + //if err != nil { + // println(err.Error()) + // return + //} + //fmt.Println(string(stock)) +} + +// 构建请求数据 +//func getStoHeader(apiName string, content string) map[string]string { +// secretKey := "2kLMr75pURbVh3FnWzY6SCr7SM6qsoO3" +// dataDigest := getStoSignature(content, secretKey) +// var param = map[string]string{ +// "api_name": apiName, +// "content": content, +// "from_appkey": "CAKgwDceADOybmU", +// "from_code": "CAKgwDceADOybmU", +// "to_appkey": "sto_oms", +// "to_code": "sto_oms", +// "data_digest": dataDigest, +// } +// return param +//} +// +//func requestSto(apiName string, content string) ([]byte, error) { +// var url = "http://cloudinter-linkgatewaytest.sto.cn/gateway/link.do" +// //url = "https://cloudinter-linkgateway.sto.cn/gateway/link.do" +// +// // 获取请求参数 +// params := getStoHeader(apiName, content) +// +// // 创建 multipart form-data 请求体 +// body := &bytes.Buffer{} +// writer := multipart.NewWriter(body) +// +// // 添加所有参数到 form-data +// for key, value := range params { +// err := writer.WriteField(key, value) +// if err != nil { +// return nil, err +// } +// } +// +// // 关闭 writer 以完成 multipart 数据 +// err := writer.Close() +// if err != nil { +// return nil, err +// } +// +// // 创建 HTTP 请求 +// req, err := http.NewRequest("POST", url, body) +// if err != nil { +// return nil, err +// } +// +// // 设置 Content-Type 头,包含 boundary +// req.Header.Set("Content-Type", writer.FormDataContentType()) +// +// // 发送请求 +// client := &http.Client{} +// resp, err := client.Do(req) +// if err != nil { +// return nil, err +// } +// defer resp.Body.Close() +// +// // 读取响应 +// responseBody, err := io.ReadAll(resp.Body) +// if err != nil { +// return nil, err +// } +// +// return responseBody, nil +//} +// +//// 生成签名 +//func getStoSignature(content, secretKey string) string { +// text := content + secretKey +// hash := md5.Sum([]byte(text)) +// return base64.StdEncoding.EncodeToString(hash[:]) +//} diff --git a/go.mod b/go.mod index d6938a6..79ca0d8 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module kfzgw-info -go 1.25 +go 1.26 require ( gioui.org v0.9.0 github.com/PuerkitoBio/goquery v1.10.3 github.com/boombuler/barcode v1.1.0 + github.com/chromedp/chromedp v0.15.1 github.com/disintegration/imaging v1.6.2 github.com/elastic/go-elasticsearch/v8 v8.19.0 github.com/fogleman/gg v1.3.0 @@ -15,11 +16,13 @@ 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.18.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/tjfoc/gmsm v1.4.1 + github.com/valyala/fasthttp v1.69.0 github.com/xuri/excelize/v2 v2.10.0 golang.org/x/image v0.26.0 - golang.org/x/sys v0.37.0 + golang.org/x/sys v0.42.0 gopkg.in/ini.v1 v1.67.1 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.44.3 @@ -28,25 +31,35 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect gioui.org/shader v1.0.8 // indirect + github.com/andybalholm/brotli v1.2.0 // 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/chromedp/cdproto v0.0.0-20260321001828-e3e3800016bc // indirect + github.com/chromedp/sysutil v1.1.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 github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-text/typesetting v0.3.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.4.0 // indirect github.com/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/compress v1.18.2 // 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 @@ -66,21 +79,23 @@ require ( github.com/tiendc/go-deepcopy v1.7.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/xuri/efp v0.0.1 // indirect github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.43.0 // indirect + golang.org/x/crypto v0.46.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.38.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.32.0 // indirect + golang.org/x/tools v0.39.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 diff --git a/go.sum b/go.sum index ff9cdb7..bdb1ee5 100644 --- a/go.sum +++ b/go.sum @@ -11,15 +11,29 @@ 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/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/chromedp/cdproto v0.0.0-20260321001828-e3e3800016bc h1:wkN/LMi5vc60pBRWx6qpbk/aEvq3/ZVNpnMvsw8PVVU= +github.com/chromedp/cdproto v0.0.0-20260321001828-e3e3800016bc/go.mod h1:cbyjALe67vDvlvdiG9369P8w5U2w6IshwtyD2f2Tvag= +github.com/chromedp/chromedp v0.15.1 h1:EJWiPm7BNqDqjYy6U0lTSL5wNH+iNt9GjC3a4gfjNyQ= +github.com/chromedp/chromedp v0.15.1/go.mod h1:CdTHtUqD/dqaFw/cvFWtTydoEQS44wLBuwbMR9EkOY4= +github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= +github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= 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= @@ -28,6 +42,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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= @@ -49,6 +65,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao= +github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -68,6 +86,12 @@ github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML 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/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= +github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/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= @@ -105,12 +129,16 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr 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/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 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/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/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= @@ -127,6 +155,8 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF 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/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI= github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -141,6 +171,8 @@ 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.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= 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= @@ -175,13 +207,21 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= +github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4= github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= @@ -190,6 +230,8 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= @@ -202,8 +244,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 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= @@ -220,8 +262,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 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= @@ -237,8 +279,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 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= @@ -249,8 +291,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.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/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.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= @@ -266,8 +308,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -286,8 +328,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 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= @@ -298,8 +340,8 @@ 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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 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= diff --git a/image/123.png b/image/123.png deleted file mode 100644 index 7e029c6..0000000 Binary files a/image/123.png and /dev/null differ diff --git a/image/dll/image.dll b/image/dll/image.dll index 171ae94..ccf6bdc 100644 Binary files a/image/dll/image.dll and b/image/dll/image.dll differ diff --git a/image/dll/image.h b/image/dll/image.h index 2bdd12d..671e8e8 100644 --- a/image/dll/image.h +++ b/image/dll/image.h @@ -86,43 +86,17 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; extern "C" { #endif - -// =================== C 导出函数 ======================= -// 检测图片纯白占比 -// -extern __declspec(dllexport) char* ProcessImage(char* jsonConfig); - -// 根据原始图片生成新的白底图片 -// -extern __declspec(dllexport) char* CreateWhiteBottomCenteredImage(char* jsonConfig, int width, int height); - -// 根据高度生成等比例图片 -// -extern __declspec(dllexport) char* ResizeToHeightQuality(char* jsonConfig, int targetHeight); - -// 去掉白边并转PNG图片工具 -// -extern __declspec(dllexport) char* RemoveWhiteBorderAndPNG(char* jsonConfig); - -// ResizeWTToHeightQuality 图片缩放 -// -extern __declspec(dllexport) char* ResizeWTToHeightQuality(char* jsonConfig, int dsWidth, int dsHeight); - -// CropImage 图片裁切 -// -extern __declspec(dllexport) char* CropImage(char* jsonConfig, int x, int y, int width, int height); - -// CreateChineseTextImage 创建带中文字体的文本图片,支持超出部分显示... -// -extern __declspec(dllexport) char* CreateChineseTextImage(char* text, int width, int height, char* fontSize, char* outputPath); - -// DrawChineseInfo 绘制书名,作者,出版社信息 -// -extern __declspec(dllexport) char* DrawChineseInfo(char* filePath, char* title, char* author, char* publisher, char* outputPath); - -// 导出函数:释放C字符串内存 -// -extern __declspec(dllexport) void FreeCString(char* str); +extern char* ProcessImage(char* jsonConfig); +extern char* CreateWhiteBottomCenteredImage(char* jsonConfig, int width, int height); +extern char* ResizeToHeightQuality(char* jsonConfig, int targetHeight); +extern char* RemoveWhiteBorderAndPNG(char* jsonConfig); +extern char* ResizeWTToHeightQuality(char* jsonConfig, int dsWidth, int dsHeight); +extern char* CropImage(char* jsonConfig, int x, int y, int width, int height); +extern char* CreateChineseTextImage(char* text, int width, int height, char* fontSize, char* outputPath); +extern char* DrawChineseInfo(char* filePath, char* title, char* author, char* publisher, char* outputPath); +extern char* GenerateBarcode(char* barcodeType, char* content, char* filename); +extern char* AddWatermarkFromURLEx(char* jsonConfig); +extern void FreeCString(char* str); #ifdef __cplusplus } diff --git a/image/image.go b/image/image.go index 5f0f3ed..dd0ff04 100644 --- a/image/image.go +++ b/image/image.go @@ -3,6 +3,8 @@ package main // #include import "C" import ( + "bytes" + "encoding/base64" "encoding/json" "fmt" "github.com/boombuler/barcode" @@ -31,7 +33,10 @@ import ( "runtime" "strconv" "strings" + "time" "unsafe" + + "github.com/valyala/fasthttp" ) // Config 配置结构体 @@ -250,11 +255,10 @@ func createWhiteBottomCenteredImage(config *Config, width, height int) (string, // 设置背景颜色 var bgColor color.Color - - bgColor = color.RGBA{255, 255, 255, 255} // 白色 + bgColor = color.RGBA{R: 0, G: 0, B: 0, A: 0} // 白色 // 填充透明背景 - draw.Draw(dst, dst.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src) + draw.Draw(dst, dst.Bounds(), &image.Uniform{C: bgColor}, image.Point{}, draw.Src) // 计算居中位置 srcBounds := img.Bounds() @@ -305,7 +309,10 @@ func resizeToHeightQuality(config *Config, targetHeight int) (string, error) { // 保存图片到指定目录下 filename := filepath.Base(config.FileName) destPath := filepath.Join(config.OutputDir, config.EqualHeightDir, filename) - saveImage(destPath, imageNew, format) + err = saveImage(destPath, imageNew, format) + if err != nil { + return "", fmt.Errorf("保存图片失败: %v", err) + } return destPath, nil } @@ -1689,6 +1696,503 @@ func absDiff(a, b uint8) uint8 { return b - a } +// =========================== 添加水印 =============================== + +// WatermarkConfig 水印配置结构体 +type WatermarkConfig struct { + SourceImageURL string // 源图片URL地址 + WatermarkURL string // 水印图片URL地址 + WatermarkBase64 string // 水印图片base64编码字符串(新增,优先使用) + Opacity float64 // 不透明度 (0.0-1.0) + Position string // 位置: center, top-left, top-right, bottom-left, bottom-right, tile + TileSpacing int // 平铺时的间距 + Scale float64 // 水印缩放比例 (0.0-1.0) + Rotation float64 // 旋转角度 (度数) + XOffset int // X轴偏移量AddWatermarkFromURLEx + YOffset int // Y轴偏移量 + Timeout int // 下载超时时间(秒),默认30秒 + OutputFormat string // 输出格式: "jpeg", "png", "auto"(默认auto,根据源图片格式) + JPEGQuality int // JPEG质量 (1-100),默认95 + TargetWidth int // 目标宽度(0表示不缩放) + TargetHeight int // 目标高度(0表示不缩放) + ResizeMode string // 缩放模式: "fit"(适应,保持比例,可能有黑边), "fill"(填充,裁剪), "stretch"(拉伸) +} + +// 将客户端声明为全局变量或缓存 +var httpClient = &fasthttp.Client{ + MaxConnsPerHost: 100, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, +} + +// loadImageFromURL 直接从URL加载图片 +func loadImageFromURL(url string, timeout int) (image.Image, string, error) { + // 设置默认超时 + if timeout <= 0 { + timeout = 120 + } + + // 创建请求和响应对象 + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseRequest(req) + defer fasthttp.ReleaseResponse(resp) + + // 设置请求URL和方法 + req.SetRequestURI(url) + req.Header.SetMethod("GET") + + // 设置请求头 + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") + req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") + req.Header.Set("Cache-Control", "max-age=0") + req.Header.Set("If-Modified-Since", "Mon, 09 Mar 2026 07:43:59 GMT") + req.Header.Set("Priority", "u=0, i") + req.Header.Set("Sec-Ch-Ua", `"Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"`) + req.Header.Set("Sec-Ch-Ua-Mobile", "?0") + req.Header.Set("Sec-Ch-Ua-Platform", "Windows") + req.Header.Set("Sec-Fetch-Dest", "document") + req.Header.Set("Sec-Fetch-Mode", "navigate") + req.Header.Set("Sec-Fetch-Site", "none") + req.Header.Set("Sec-Fetch-User", "?1") + req.Header.Set("Upgrade-Insecure-Requests", "1") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0") + + // 发送请求 + err := httpClient.Do(req, resp) + if err != nil { + return nil, "", fmt.Errorf("请求失败: %v\n", err) + } + + // 复制body数据,避免引用问题 + body := make([]byte, len(resp.Body())) + copy(body, resp.Body()) + + // 直接从响应体解码图片 + img, format, err := image.Decode(bytes.NewReader(body)) + // 主动释放body内存 + body = nil + if err != nil { + return nil, "", fmt.Errorf("解码图片失败: %v", err) + } + + return img, format, nil +} + +// 添加新的辅助函数:从base64字符串加载图片 +func loadImageFromBase64(base64Str string) (image.Image, string, error) { + // 移除可能的 data:image/xxx;base64, 前缀 + base64Str = strings.TrimPrefix(base64Str, "data:image/jpeg;base64,") + base64Str = strings.TrimPrefix(base64Str, "data:image/jpg;base64,") + base64Str = strings.TrimPrefix(base64Str, "data:image/png;base64,") + base64Str = strings.TrimPrefix(base64Str, "data:image/gif;base64,") + base64Str = strings.TrimPrefix(base64Str, "data:image/webp;base64,") + + // 解码base64 + imgData, err := base64.StdEncoding.DecodeString(base64Str) + if err != nil { + return nil, "", fmt.Errorf("解码base64失败: %v", err) + } + + // 检测图片格式 + format := detectImageFormat(imgData) + + // 解码图片 + img, _, err := image.Decode(bytes.NewReader(imgData)) + // 主动释放数据内存 + imgData = nil + if err != nil { + return nil, "", fmt.Errorf("解码base64图片失败: %v", err) + } + + return img, format, nil +} + +// 检测图片格式的辅助函数 +func detectImageFormat(data []byte) string { + if len(data) < 12 { + return "unknown" + } + + // PNG: 137 80 78 71 13 10 26 10 + if len(data) >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 { + return "png" + } + + // JPEG: 255 216 255 + if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF { + return "jpg" + } + + // GIF: 71 73 70 + if len(data) >= 3 && data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 { + return "gif" + } + + // WEBP: 82 73 70 70 + if len(data) >= 12 && data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 { + return "webp" + } + + return "unknown" +} + +// 判断HTTP状态码是否值得重试 +func isRetryableStatus(statusCode int) bool { + // 5xx服务器错误或429限流可以重试 + return statusCode >= 500 || statusCode == 429 +} + +// AddWatermarkFromURL 从URL加载图片添加水印并返回字节集 +func AddWatermarkFromURL(config WatermarkConfig) ([]byte, string, error) { + // 确保函数退出时释放内存 + defer runtime.GC() // 可选,在低内存场景下使用 + + // 加载源图片 + srcImg, srcFormat, err := loadImageFromURL(config.SourceImageURL, config.Timeout) + if err != nil { + return nil, "", fmt.Errorf("加载源图片失败: %v", err) + } + + // 等比缩放原图到 TargetWidth x TargetHeight 范围内 + if config.TargetWidth > 0 || config.TargetHeight > 0 { + srcW := srcImg.Bounds().Dx() + srcH := srcImg.Bounds().Dy() + var newW, newH uint + if config.TargetWidth > 0 && config.TargetHeight > 0 { + scaleX := float64(config.TargetWidth) / float64(srcW) + scaleY := float64(config.TargetHeight) / float64(srcH) + scale := math.Min(scaleX, scaleY) + newW = uint(float64(srcW) * scale) + newH = uint(float64(srcH) * scale) + } else if config.TargetWidth > 0 { + newW = uint(config.TargetWidth) + newH = uint(float64(srcH) * float64(config.TargetWidth) / float64(srcW)) + } else { + newH = uint(config.TargetHeight) + newW = uint(float64(srcW) * float64(config.TargetHeight) / float64(srcH)) + } + srcImg = resize.Resize(newW, newH, srcImg, resize.Lanczos3) + } + + // 加载水印图片(支持URL或base64) + var watermarkImg image.Image + //var watermarkFormat string // 没用了 + + // 优先使用 base64 + if config.WatermarkBase64 != "" { + watermarkImg, _, err = loadImageFromBase64(config.WatermarkBase64) + if err != nil { + return nil, "", fmt.Errorf("从base64加载水印图片失败: %v", err) + } + } else if config.WatermarkURL != "" { + // 检查是否为base64格式的URL(以data:image开头) + if strings.HasPrefix(config.WatermarkURL, "data:image/") { + watermarkImg, _, err = loadImageFromBase64(config.WatermarkURL) + if err != nil { + return nil, "", fmt.Errorf("从base64 URL加载水印图片失败: %v", err) + } + } else { + // 普通URL + watermarkImg, _, err = loadImageFromURL(config.WatermarkURL, config.Timeout) + if err != nil { + return nil, "", fmt.Errorf("从URL加载水印图片失败: %v", err) + } + } + } else { + return nil, "", fmt.Errorf("必须提供水印图片(WatermarkURL或WatermarkBase64)") + } + + // 创建目标图片(RGBA格式以支持透明) + // 画布尺寸 = 水印尺寸 + watermarkBounds := watermarkImg.Bounds() + dst := image.NewRGBA(watermarkBounds) + + // 先绘制源图片(居中) + srcBounds := srcImg.Bounds() + x := (watermarkBounds.Dx() - srcBounds.Dx()) / 2 + y := (watermarkBounds.Dy() - srcBounds.Dy()) / 2 + draw.Draw(dst, image.Rect(x, y, x+srcBounds.Dx(), y+srcBounds.Dy()), srcImg, image.Point{}, draw.Src) + + // 处理水印 + err = applyWatermark(dst, watermarkImg, config) + if err != nil { + return nil, "", fmt.Errorf("应用水印失败: %v", err) + } + + // 按目标尺寸缩放 + finalImg := resizeOutputImage(dst, config) + + // 确定输出格式 + outputFormat := config.OutputFormat + if outputFormat == "" || outputFormat == "auto" { + outputFormat = srcFormat + } + + // 将图片编码为字节集 + imgBytes, err := encodeImageToBytes(finalImg, outputFormat, config.JPEGQuality) + if err != nil { + return nil, "", fmt.Errorf("编码图片失败: %v", err) + } + + return imgBytes, outputFormat, nil +} + +// encodeImageToBytes 将图片编码为字节集 +func encodeImageToBytes(img image.Image, format string, quality int) ([]byte, error) { + var buf bytes.Buffer + + switch strings.ToLower(format) { + case "jpeg", "jpg": + // 设置默认质量 + if quality <= 0 || quality > 100 { + quality = 95 + } + err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}) + if err != nil { + return nil, fmt.Errorf("JPEG编码失败: %v", err) + } + case "png": + err := png.Encode(&buf, img) + if err != nil { + return nil, fmt.Errorf("PNG编码失败: %v", err) + } + default: + // 默认使用PNG + err := png.Encode(&buf, img) + if err != nil { + return nil, fmt.Errorf("PNG编码失败: %v", err) + } + } + + return buf.Bytes(), nil +} + +// applyWatermark 应用水印到图片 +func applyWatermark(dst *image.RGBA, watermark image.Image, config WatermarkConfig) error { + // 缩放水印 + watermark = scaleWatermark(watermark, config.Scale) + + // 根据位置绘制水印 + switch config.Position { + case "tile": + drawTileWatermark(dst, watermark, config) + default: + drawSingleWatermark(dst, watermark, config) + } + + return nil +} + +// scaleWatermark 缩放水印 +func scaleWatermark(watermark image.Image, scale float64) image.Image { + if scale <= 0 || scale >= 1 { + return watermark + } + + bounds := watermark.Bounds() + newWidth := uint(float64(bounds.Dx()) * scale) + newHeight := uint(float64(bounds.Dy()) * scale) + + if newWidth > 0 && newHeight > 0 { + return resize.Resize(newWidth, newHeight, watermark, resize.Lanczos3) + } + return watermark +} + +// drawSingleWatermark 绘制单个水印 +func drawSingleWatermark(dst *image.RGBA, watermark image.Image, config WatermarkConfig) { + bounds := dst.Bounds() + watermarkBounds := watermark.Bounds() + watermarkWidth := watermarkBounds.Dx() + watermarkHeight := watermarkBounds.Dy() + + // 计算位置 + var x, y int + + switch config.Position { + case "center": + x = (bounds.Dx()-watermarkWidth)/2 + config.XOffset + y = (bounds.Dy()-watermarkHeight)/2 + config.YOffset + case "top-left": + x = config.XOffset + y = config.YOffset + case "top-right": + x = bounds.Dx() - watermarkWidth - config.XOffset + y = config.YOffset + case "bottom-left": + x = config.XOffset + y = bounds.Dy() - watermarkHeight - config.YOffset + case "bottom-right": + x = bounds.Dx() - watermarkWidth - config.XOffset + y = bounds.Dy() - watermarkHeight - config.YOffset + default: // 默认居中 + x = (bounds.Dx()-watermarkWidth)/2 + config.XOffset + y = (bounds.Dy()-watermarkHeight)/2 + config.YOffset + } + + // 确保不超出边界 + x = max(0, min(x, bounds.Dx()-watermarkWidth)) + y = max(0, min(y, bounds.Dy()-watermarkHeight)) + + // 绘制水印 + drawWatermarkWithOpacity(dst, watermark, x, y, config.Opacity) +} + +// drawTileWatermark 平铺水印 +func drawTileWatermark(dst *image.RGBA, watermark image.Image, config WatermarkConfig) { + bounds := dst.Bounds() + watermarkBounds := watermark.Bounds() + watermarkWidth := watermarkBounds.Dx() + watermarkHeight := watermarkBounds.Dy() + + spacing := config.TileSpacing + if spacing < 0 { + spacing = 0 + } + + stepX := watermarkWidth + spacing + stepY := watermarkHeight + spacing + + // 计算起始偏移,使水印均匀分布 + startX := (bounds.Dx() % stepX) / 2 + startY := (bounds.Dy() % stepY) / 2 + + for y := startY; y < bounds.Dy(); y += stepY { + for x := startX; x < bounds.Dx(); x += stepX { + drawWatermarkWithOpacity(dst, watermark, x, y, config.Opacity) + } + } +} + +// drawWatermarkWithOpacity 绘制带透明度的水印 +func drawWatermarkWithOpacity(dst *image.RGBA, watermark image.Image, x, y int, opacity float64) { + if opacity < 0 { + opacity = 0 + } + if opacity > 1 { + opacity = 1 + } + + watermarkBounds := watermark.Bounds() + for wy := 0; wy < watermarkBounds.Dy(); wy++ { + for wx := 0; wx < watermarkBounds.Dx(); wx++ { + // 计算目标位置 + dx := x + wx + dy := y + wy + + // 确保在目标图片范围内 + if dx < 0 || dx >= dst.Bounds().Dx() || dy < 0 || dy >= dst.Bounds().Dy() { + continue + } + + // 获取水印像素 + wColor := watermark.At(wx, wy) + wr, wg, wb, wa := wColor.RGBA() + + // 如果有透明度,考虑水印本身的透明度 + if wa > 0 { + // 转换为8位 + wr8 := uint8(wr >> 8) + wg8 := uint8(wg >> 8) + wb8 := uint8(wb >> 8) + wa8 := uint8(wa >> 8) + + // 获取目标像素 + dstColor := dst.At(dx, dy) + dr, dg, db, _ := dstColor.RGBA() + dr8 := uint8(dr >> 8) + dg8 := uint8(dg >> 8) + db8 := uint8(db >> 8) + + // 混合颜色(考虑水印透明度和设置的不透明度) + alpha := float64(wa8) / 255.0 * opacity + + r := uint8(float64(dr8)*(1-alpha) + float64(wr8)*alpha) + g := uint8(float64(dg8)*(1-alpha) + float64(wg8)*alpha) + b := uint8(float64(db8)*(1-alpha) + float64(wb8)*alpha) + + dst.Set(dx, dy, color.RGBA{r, g, b, 255}) + } + } + } +} + +// resizeOutputImage 根据配置缩放输出图片 +func resizeOutputImage(img image.Image, config WatermarkConfig) image.Image { + if config.TargetWidth <= 0 && config.TargetHeight <= 0 { + return img + } + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + + // 如果只设了一个维度,另一个按比例算 + targetW := config.TargetWidth + targetH := config.TargetHeight + if targetW <= 0 { + targetW = int(float64(targetH) * float64(srcW) / float64(srcH)) + } + if targetH <= 0 { + targetH = int(float64(targetW) * float64(srcH) / float64(srcW)) + } + + switch config.ResizeMode { + case "fit": + return resizeFit(img, srcW, srcH, targetW, targetH) + case "fill": + return resizeFill(img, srcW, srcH, targetW, targetH) + case "stretch": + fallthrough + default: + return resize.Resize(uint(targetW), uint(targetH), img, resize.Lanczos3) + } +} + +// resizeFit 等比缩放适应,多出部分填白 +func resizeFit(img image.Image, srcW, srcH, targetW, targetH int) image.Image { + scaleX := float64(targetW) / float64(srcW) + scaleY := float64(targetH) / float64(srcH) + scale := math.Min(scaleX, scaleY) + + newW := int(float64(srcW) * scale) + newH := int(float64(srcH) * scale) + + scaled := resize.Resize(uint(newW), uint(newH), img, resize.Lanczos3) + + // 居中放到白色画布上 + dst := image.NewRGBA(image.Rect(0, 0, targetW, targetH)) + draw.Draw(dst, dst.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src) + + x := (targetW - newW) / 2 + y := (targetH - newH) / 2 + draw.Draw(dst, image.Rect(x, y, x+newW, y+newH), scaled, image.Point{}, draw.Over) + + return dst +} + +// resizeFill 等比缩放覆盖,溢出部分居中裁剪 +func resizeFill(img image.Image, srcW, srcH, targetW, targetH int) image.Image { + scaleX := float64(targetW) / float64(srcW) + scaleY := float64(targetH) / float64(srcH) + scale := math.Max(scaleX, scaleY) + + newW := int(float64(srcW) * scale) + newH := int(float64(srcH) * scale) + + scaled := resize.Resize(uint(newW), uint(newH), img, resize.Lanczos3) + + // 居中裁剪 + cropX := (newW - targetW) / 2 + cropY := (newH - targetH) / 2 + + dst := image.NewRGBA(image.Rect(0, 0, targetW, targetH)) + draw.Draw(dst, dst.Bounds(), scaled, image.Point{X: cropX, Y: cropY}, draw.Src) + + return dst +} + // =================== C 导出函数 ======================= // 检测图片纯白占比 // @@ -1847,14 +2351,61 @@ func GenerateBarcode(barcodeType, content, filename *C.char) *C.char { contentStr := C.GoString(content) filenameStr := C.GoString(filename) - filename, err := generateBarcode(barcodeTypeStr, contentStr, filenameStr) + barcodeFilename, err := generateBarcode(barcodeTypeStr, contentStr, filenameStr) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } - return C.CString(filename) + return C.CString(barcodeFilename) } -// 导出函数:释放C字符串内存 +// AddWatermarkFromURLEx 从URL添加水印并返回字节集(C导出函数) +// +//export AddWatermarkFromURLEx +func AddWatermarkFromURLEx(jsonConfig *C.char) *C.char { + configStr := C.GoString(jsonConfig) + + var config WatermarkConfig + if err := json.Unmarshal([]byte(configStr), &config); err != nil { + return C.CString(fmt.Sprintf("ERROR:解析水印配置失败: %v", err)) + } + + imgBytes, format, err := AddWatermarkFromURL(config) + if err != nil { + return C.CString(fmt.Sprintf("ERROR:%v", err)) + } + + // 构建带MIME前缀的Base64数据 + var base64Data string + switch format { + case "jpeg", "jpg": + base64Data = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgBytes) + case "png": + base64Data = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes) + case "gif": + base64Data = "data:image/gif;base64," + base64.StdEncoding.EncodeToString(imgBytes) + default: + base64Data = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgBytes) + } + + // 构建返回结果:格式 + 字节集大小 + 字节集数据 + // 使用Base64编码字节集以便在JSON中传输 + result := struct { + Success bool `json:"success"` + Format string `json:"format"` + Data string `json:"data"` // Base64编码的图片数据 + Size int `json:"size"` + }{ + Success: true, + Format: format, + Data: base64Data, + Size: len(imgBytes), + } + + resultBytes, _ := json.Marshal(result) + return C.CString(string(resultBytes)) +} + +// FreeCString 导出函数:释放C字符串内存 // //export FreeCString func FreeCString(str *C.char) { @@ -1862,5 +2413,45 @@ func FreeCString(str *C.char) { } // main 函数是必需的,即使为空 -//func main() { -//} +func main() { + + //// 图片URL + //surl := "https://img.pddpic.com/open-gw/2026-05-13/2f79386387d1b72f4081a7b661a9849a.png" + // + //// 1. 下载图片 + //resp, err := http.Get(surl) + //if err != nil { + // panic(err) + //} + //defer resp.Body.Close() + // + //// 2. 读取图片二进制数据 + //imgData, err := io.ReadAll(resp.Body) + //if err != nil { + // panic(err) + //} + // + //// 3. 转换为Base64字符串 + //base64Str := base64.StdEncoding.EncodeToString(imgData) + // + //watermarkConfig := WatermarkConfig{ + // SourceImageURL: "http://booklibimg.kfzimg.com/data/book_lib_img_v2/isbn/1/b044/b044aae81122166798a30699e0f007d2_0_1_300_300.jpg", + // WatermarkBase64: base64Str, + // Position: "center", + // Opacity: 1.0, + // Scale: 1.0, + // TileSpacing: 50, + // Timeout: 30, + // OutputFormat: "jpeg", + // JPEGQuality: 95, + // TargetWidth: 800, + // TargetHeight: 800, + // ResizeMode: "fit", + //} + //url, s, err := AddWatermarkFromURL(watermarkConfig) + //if err != nil { + // fmt.Println(err) + //} else { + // fmt.Println("url:", url, "s:", s) + //} +} diff --git a/image/imageTest.go b/image/imageTest.go index d6aaf44..4ec2c54 100644 --- a/image/imageTest.go +++ b/image/imageTest.go @@ -1,5 +1,16 @@ package main +import ( + "fmt" + "golang.org/x/image/draw" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" + "strings" +) + //func main() { // //// 测试长文本 // //longText := "这是一段非常长的中文文本,需要测试自动换行功能。我们希望当文本超过图片宽度时,能够自动换行到下一行显示。这样就不需要手动添加换行符了。这段文本包含了中英文混合的内容,比如这里有一些English words mixed with中文。同时,我们也要测试标点符号的处理,例如逗号、句号、感叹号!问号?以及各种括号(包括圆括号、方括号[]、花括号{})等等。最后,我们还要测试一下当文本非常长,超过图片高度时的处理情况。" + @@ -37,3 +48,262 @@ package main // fmt.Println(err) // } //} + +// ScaleImage 缩放图片的主函数 +// imgPath: 原始图片路径 +// scaleWidth: 目标宽度(像素),如果为0则按比例自动计算 +// scaleHeight: 目标高度(像素),如果为0则按比例自动计算 +// quality: 图片质量(1-100),仅对JPEG有效 +// 返回:缩放后的图片保存路径和错误信息 +func ScaleImage(imgPath string, scaleWidth, scaleHeight int, quality int) (string, error) { + // 检查文件是否存在 + if _, err := os.Stat(imgPath); os.IsNotExist(err) { + return "", fmt.Errorf("图片文件不存在: %s", imgPath) + } + + // 打开原始图片 + file, err := os.Open(imgPath) + if err != nil { + return "", fmt.Errorf("打开图片失败: %v", err) + } + defer file.Close() + + // 解码图片 + srcImg, format, err := image.Decode(file) + if err != nil { + return "", fmt.Errorf("解码图片失败: %v", err) + } + + // 获取原始尺寸 + srcBounds := srcImg.Bounds() + srcWidth := srcBounds.Dx() + srcHeight := srcBounds.Dy() + + // 计算目标尺寸(保持宽高比) + dstWidth, dstHeight := calculateTargetSize(srcWidth, srcHeight, scaleWidth, scaleHeight) + + // 创建目标图片 + dstImg := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight)) + + // 使用高质量的缩放算法 + draw.ApproxBiLinear.Scale(dstImg, dstImg.Bounds(), srcImg, srcBounds, draw.Over, nil) + + // 生成保存路径 + outputPath := generateOutputPath(imgPath, dstWidth, dstHeight) + + // 保存图片 + if err := SaveImage(outputPath, dstImg, format, quality); err != nil { + return "", fmt.Errorf("保存图片失败: %v", err) + } + + return outputPath, nil +} + +// ScaleImageByWidth 按宽度缩放图片(高度自动适配) +func ScaleImageByWidth(imgPath string, targetWidth int, quality int) (string, error) { + return ScaleImage(imgPath, targetWidth, 0, quality) +} + +// ScaleImageByHeight 按高度缩放图片(宽度自动适配) +func ScaleImageByHeight(imgPath string, targetHeight int, quality int) (string, error) { + return ScaleImage(imgPath, 0, targetHeight, quality) +} + +// ScaleImageByPercent 按百分比缩放图片 +func ScaleImageByPercent(imgPath string, percent float64, quality int) (string, error) { + if percent <= 0 { + return "", fmt.Errorf("缩放百分比必须大于0") + } + + // 获取原始尺寸 + file, err := os.Open(imgPath) + if err != nil { + return "", err + } + defer file.Close() + + srcImg, _, err := image.Decode(file) + if err != nil { + return "", err + } + + srcBounds := srcImg.Bounds() + targetWidth := int(float64(srcBounds.Dx()) * percent) + targetHeight := int(float64(srcBounds.Dy()) * percent) + + return ScaleImage(imgPath, targetWidth, targetHeight, quality) +} + +// calculateTargetSize 计算目标尺寸,保持宽高比 +func calculateTargetSize(srcWidth, srcHeight, targetWidth, targetHeight int) (int, int) { + // 如果目标尺寸都未指定,返回原尺寸 + if targetWidth == 0 && targetHeight == 0 { + return srcWidth, srcHeight + } + + // 按宽度缩放 + if targetWidth != 0 && targetHeight == 0 { + ratio := float64(targetWidth) / float64(srcWidth) + return targetWidth, int(float64(srcHeight) * ratio) + } + + // 按高度缩放 + if targetHeight != 0 && targetWidth == 0 { + ratio := float64(targetHeight) / float64(srcHeight) + return int(float64(srcWidth) * ratio), targetHeight + } + + // 同时指定宽高,但保持原图宽高比,居中裁剪或留白 + // 这里采用完全缩放到指定尺寸(可能会变形) + // 如需保持比例,可以取消下面的注释 + /* + srcRatio := float64(srcWidth) / float64(srcHeight) + dstRatio := float64(targetWidth) / float64(targetHeight) + + if srcRatio > dstRatio { + // 原图更宽,按高度缩放 + newHeight := targetHeight + newWidth := int(float64(targetHeight) * srcRatio) + return newWidth, newHeight + } else { + // 原图更高,按宽度缩放 + newWidth := targetWidth + newHeight := int(float64(targetWidth) / srcRatio) + return newWidth, newHeight + } + */ + + return targetWidth, targetHeight +} + +// generateOutputPath 生成输出文件路径 +func generateOutputPath(srcPath string, width, height int) string { + dir := filepath.Dir(srcPath) + ext := filepath.Ext(srcPath) + name := strings.TrimSuffix(filepath.Base(srcPath), ext) + + outputName := fmt.Sprintf("%s_scaled_%dx%d%s", name, width, height, ext) + return filepath.Join(dir, outputName) +} + +// saveImage 保存图片到文件 +func SaveImage(path string, img image.Image, format string, quality int) error { + // 创建输出文件 + outFile, err := os.Create(path) + if err != nil { + return err + } + defer outFile.Close() + + // 根据原始格式保存 + switch strings.ToLower(format) { + case "jpeg", "jpg": + opts := &jpeg.Options{Quality: quality} + return jpeg.Encode(outFile, img, opts) + case "png": + return png.Encode(outFile, img) + default: + // 默认保存为PNG格式 + return png.Encode(outFile, img) + } +} + +// ScaleImageWithOptions 带更多选项的缩放函数 +type ScaleOptions struct { + Width int // 目标宽度 + Height int // 目标高度 + Quality int // 图片质量 (1-100) + OutputPath string // 自定义输出路径,为空则自动生成 + KeepRatio bool // 是否保持宽高比 + CropCenter bool // 是否居中裁剪(仅在KeepRatio为true且指定了宽高时有效) +} + +func ScaleImageWithOptions(imgPath string, opts ScaleOptions) (string, error) { + // 参数验证 + if opts.Quality < 1 || opts.Quality > 100 { + opts.Quality = 90 // 默认质量 + } + + // 检查文件是否存在 + if _, err := os.Stat(imgPath); os.IsNotExist(err) { + return "", fmt.Errorf("图片文件不存在: %s", imgPath) + } + + // 打开并解码图片 + file, err := os.Open(imgPath) + if err != nil { + return "", err + } + defer file.Close() + + srcImg, format, err := image.Decode(file) + if err != nil { + return "", err + } + + srcBounds := srcImg.Bounds() + srcWidth := srcBounds.Dx() + srcHeight := srcBounds.Dy() + + var dstWidth, dstHeight int + + if opts.KeepRatio { + // 保持宽高比 + dstWidth, dstHeight = calculateTargetSize(srcWidth, srcHeight, opts.Width, opts.Height) + } else { + // 不保持宽高比,直接使用指定尺寸 + dstWidth, dstHeight = opts.Width, opts.Height + if dstWidth == 0 { + dstWidth = srcWidth + } + if dstHeight == 0 { + dstHeight = srcHeight + } + } + + // 创建目标图片 + dstImg := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight)) + + // 如果需要居中裁剪 + if opts.CropCenter && opts.KeepRatio && opts.Width > 0 && opts.Height > 0 { + // 计算裁剪区域 + srcRatio := float64(srcWidth) / float64(srcHeight) + dstRatio := float64(opts.Width) / float64(opts.Height) + + var cropRect image.Rectangle + if srcRatio > dstRatio { + // 原图更宽,裁剪宽度 + cropWidth := int(float64(srcHeight) * dstRatio) + cropX := (srcWidth - cropWidth) / 2 + cropRect = image.Rect(cropX, 0, cropX+cropWidth, srcHeight) + } else { + // 原图更高,裁剪高度 + cropHeight := int(float64(srcWidth) / dstRatio) + cropY := (srcHeight - cropHeight) / 2 + cropRect = image.Rect(0, cropY, srcWidth, cropY+cropHeight) + } + + // 先裁剪,再缩放 + croppedImg := srcImg.(interface { + SubImage(r image.Rectangle) image.Image + }).SubImage(cropRect) + + draw.ApproxBiLinear.Scale(dstImg, dstImg.Bounds(), croppedImg, croppedImg.Bounds(), draw.Over, nil) + } else { + // 直接缩放 + draw.ApproxBiLinear.Scale(dstImg, dstImg.Bounds(), srcImg, srcBounds, draw.Over, nil) + } + + // 确定输出路径 + outputPath := opts.OutputPath + if outputPath == "" { + outputPath = generateOutputPath(imgPath, dstWidth, dstHeight) + } + + // 保存图片 + if err := SaveImage(outputPath, dstImg, format, opts.Quality); err != nil { + return "", err + } + + return outputPath, nil +} diff --git a/kongfz/dll/kongfz.dll b/kongfz/dll/kongfz.dll index 24b6ef2..994a31d 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 8459e0e..8cd69c2 100644 --- a/kongfz/dll/kongfz.h +++ b/kongfz/dll/kongfz.h @@ -87,94 +87,47 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 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); - -// OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID -// -extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum); - -// 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); +extern char* OutKfzLogin(char* username, char* password); +extern char* OutKfzGetUserInfo(char* token); +extern char* OutGetKfzShippingTemplate(char* token); +extern char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId); +extern char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* mouldId, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size); +extern char* OutGetTplFields(char* token, char* proxy, char* itemId); +extern char* OutAddGoods(char* token, char* proxy, char* formData); +extern char* OutUpdateGoodsPrice(char* token, char* proxy, char* itemId, char* updateType, char* value); +extern char* OutGetBaseSelectData(char* token); +extern char* OutAddGoodsAndFile(char* token, char* proxy, char* filePath, char* formData); +extern char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId); +extern char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg); +extern char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg); +extern char* OutGetAllGoods(char* token, char* proxy, int queryIndex, char* isbn, char* quality, char* itemName, char* author, char* press); +extern char* OutGetMidDetail(char* token, char* mid); +extern char* OutGetGoodsListMsgByShopId(char* token, int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum); +extern char* OutGetHisGoodsList(char* token, char* shopId, char* proxy, char* retPrice, char* isImage, char* sortType, char* sort, char* priceMin, char* priceMax, int pageNum, int returnNum); +extern char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy); +extern char* OutGetTopGoodsListMsg(int catId, char* proxy); +extern char* KongfzDeliveryMethodList(int appId, char* appSecret, char* accessToken); +extern char* KongfzOrderDeliver(int appId, char* appSecret, char* accessToken, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum); +extern char* KongfzOrderSynchronization(int appId, char* appSecret, char* accessToken, char* shippingComName, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum); +extern char* KongfzImageUpload(int appId, char* appSecret, char* accessToken, char* filePath, char* savePath); +extern char* KongfzShopItemAdd(int appId, char* appSecret, char* accessToken, char* shopItemAddJson); +extern char* KongfzOrderList(int appId, char* appSecret, char* accessToken, char* orderListJson); +extern char* KongfzOrderGet(int appId, char* appSecret, char* accessToken, char* userType, int orderId); +extern char* KongfzOrderRedeliver(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzOrderFlagAdd(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzOrderFlagDel(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopCategoryNameList(int appId, char* appSecret, char* accessToken); +extern char* KongfzCommonCategory(int appId, char* appSecret, char* accessToken); +extern char* KongfzShopItemDelisting(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopItemList(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopItemDetail(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopItemListing(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopItemNumberUpdate(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopItemPriceUpdate(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* KongfzShopItemDelete(int appId, char* appSecret, char* accessToken, char* requestJson); +extern char* Initialize(char* configJSON); +extern void FreeCString(char* str); +extern char* GetVersion(void); #ifdef __cplusplus } diff --git a/kongfz/kongfz.dll b/kongfz/kongfz.dll deleted file mode 100644 index 1eedaba..0000000 Binary files a/kongfz/kongfz.dll and /dev/null differ diff --git a/kongfz/kongfz.go b/kongfz/kongfz.go index ac03efd..c9c19dd 100644 --- a/kongfz/kongfz.go +++ b/kongfz/kongfz.go @@ -61,7 +61,8 @@ type BookInfo struct { Author string `json:"author"` // 作者 Publisher string `json:"publisher"` // 出版社 ISBN string `json:"isbn"` // ISBN - PublicationTime int64 `json:"publication_time"` // 出版时间 + PublicationTime int64 `json:"publication_time"` // 出版时间(时间戳) + PubDate string `json:"pub_date"` // 出版时间 Edition string `json:"edition"` // 版次 PrintTime string `json:"print_time"` // 印刷时间 FixPrice string `json:"fix_price"` // 定价 @@ -208,6 +209,20 @@ type ResponseStruct struct { ErrType string `json:"errType"` // 错误类型 } +// ShippingTemplateResponse 运费模板响应结构体 +type ShippingTemplateResponse struct { + Status bool `json:"status"` // 状态 + Data []ShippingTemplateInfo `json:"data"` // 运费模板信息 + Message string `json:"message"` // 消息 + ErrType string `json:"errType"` // 错误类型 +} + +// ShippingTemplateInfo 运费模板信息 +type ShippingTemplateInfo struct { + MouldId string `json:"mouldId"` + MouldName string `json:"mouldName"` +} + /* * 获取店铺里商品的快递费(定位到河南) * param params[ParamsInfo] 店铺快递费参数结构体 @@ -280,7 +295,7 @@ func getGoodsListShippingFee(params ParamsInfo, proxy string) ([]DataItem, error * Error 登录失败 * Error 登录失败,未知错误! */ -func outLogin(username, password string) (string, error) { +func outKfzLogin(username, password string) (string, error) { // 检查用户名和密码是否为空 if username == "" || password == "" { return "", fmt.Errorf("请输入用户名和密码!") @@ -361,7 +376,7 @@ func outLogin(username, password string) (string, error) { * Error 解析JSON失败 * Error 获取用户失败 */ -func outGetUserMsg(token string) (*UserInfo, error) { +func outKfzGetUserInfo(token string) (*UserInfo, error) { // 用户信息URL url := "https://user.kongfz.com/User/Index/getUserInfo/" @@ -375,7 +390,7 @@ func outGetUserMsg(token string) (*UserInfo, error) { Timeout(15 * time.Second). End() if len(errs) > 0 { - return nil, fmt.Errorf("查询请求失败: %v", errs) + return nil, fmt.Errorf("查询用户信息请求失败: %v", errs) } //检查HTTP状态码 @@ -408,6 +423,43 @@ func outGetUserMsg(token string) (*UserInfo, error) { return user, nil } +// 获取运费模板信息 +func outGetKfzShippingTemplate(token string) ([]ShippingTemplateInfo, error) { + if token == "" { + return nil, fmt.Errorf("请先登录获取Token") + } + + kfzUrl := "https://shop.kongfz.com/seller/delivery/index" + // 发送请求 + resp, body, errs := 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 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). + Set("Accept", "application/json, text/plain, */*"). + Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). + Timeout(15 * time.Second). + End() + if len(errs) > 0 { + return nil, fmt.Errorf("查询请求失败: %v", errs) + } + + //检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP错误: %s", resp.Status) + } + + var shippingTemplateResponse ShippingTemplateResponse + // 解析json + if err := json.Unmarshal([]byte(body), &shippingTemplateResponse); err != nil { + return nil, fmt.Errorf("解析运费模板响应结构体失败: %w", err) + } + + if !shippingTemplateResponse.Status { + return nil, fmt.Errorf(shippingTemplateResponse.Message) + } + return shippingTemplateResponse.Data, nil +} + /* * 获取商品模版-已登的店铺 * param token[string] 孔网token @@ -491,8 +543,23 @@ func outGetGoodsTplMsg(token, proxy, itemId string) (map[string]interface{}, err return nil, fmt.Errorf("API返回错误: %+v", data) } +type Kfz struct { + Token string `json:"token"` // 认证token + Proxy string `json:"proxy"` // 代理配置 + ItemSn string `json:"itemSn"` // 商品货号 + MouldId string `json:"mouldId"` // 运费模板ID(可选) + PriceMin string `json:"priceMin"` // 最低价格 + PriceMax string `json:"priceMax"` // 最高价格 + StartCreateTime string `json:"startCreateTime"` // 开始创建时间 + EndCreateTime string `json:"endCreateTime"` // 结束创建时间 + RequestType string `json:"requestType"` // 请求类型 + IsItemSnEqual int `json:"isItemSnEqual"` // 货号是否相等(0/1) + Page int `json:"page"` // 页码 + Size int `json:"size"` // 每页大小 +} + /* - * 获取商品列表-已登的店铺 + * 获取商品列表-已登的店铺--核价软件用 * param token[string] 孔网token * param proxy[string] 代理服务器IP * param itemSn[string] 货号 @@ -512,7 +579,7 @@ func outGetGoodsTplMsg(token, proxy, itemId string) (map[string]interface{}, err * Error 解析JSON失败 * Error API返回错误 */ -func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, +func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, mouldId, priceMin string, priceMax string, startCreateTime string, endCreateTime string, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) { // 判断登录token @@ -521,11 +588,12 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p } // 已登店铺的商品信息URL - url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/unSold/list" + outUrl := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/unSold/list" // 请求参数 formData := struct { ItemSn string `json:"itemSn"` + ShippingMould string `json:"shippingMould"` PriceMin string `json:"priceMin"` PriceMax string `json:"priceMax"` StartCreateTime string `json:"startCreateTime"` @@ -536,6 +604,7 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p Size int `json:"size,omitempty"` }{ ItemSn: itemSn, + ShippingMould: mouldId, PriceMin: priceMin, PriceMax: priceMax, StartCreateTime: startCreateTime, @@ -553,7 +622,7 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p request.Proxy(proxy) } // 发送POST请求 - resp, body, errs := request.Post(url). + resp, body, errs := request.Post(outUrl). 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, */*"). @@ -576,7 +645,6 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p isProxyError = true } } - log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") @@ -595,14 +663,98 @@ func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, p return nil, fmt.Errorf("解析JSON失败: %w", err) } - if _, ok := data["status"]; ok { - return data, nil + // 检查API返回状态 + if status, ok := data["status"].(bool); ok && status { + // 获取result字段 + if result, ok := data["result"].(map[string]interface{}); ok { + return result, nil + } + + // 如果result字段不存在或类型不对,返回错误 + return nil, fmt.Errorf("孔网商品获取失败: 缺少商品信息") } - return nil, fmt.Errorf("API返回错误: %+v", data) + + // 处理API错误情况 + errMsg, _ := data["errMessage"].(string) + return nil, fmt.Errorf("孔网商品获取错误: %s", errMsg) +} + +// 获取模板字段 +func outGetTplFields(token, proxy, itemId string) (map[string]interface{}, error) { + // 判断登录token + if token == "" { + return nil, fmt.Errorf("请先登录获取Token") + } + outUrl := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d", + itemId, time.Now().Unix()) + + // 创建请求 + request := gorequest.New() + + // 设置代理(如果有提供代理URL) + if proxy != "" { + request.Proxy(proxy) + } + + // 发送POST请求 + resp, body, errs := request.Get(outUrl). + 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, */*"). + Timeout(15 * time.Second). + End() + if errs != nil { + // 检查是否是代理相关错误 + var isProxyError bool + var errorDetails []string + for _, e := range errs { + 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 + } + } + if isProxyError { + // 处理代理失败 + return nil, fmt.Errorf("代理连接失败") + } + return nil, fmt.Errorf("查询请求失败: %v", errs) + } + + //检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP错误: %s", resp.Status) + } + + // 解析json + var data map[string]interface{} + if err := json.Unmarshal([]byte(body), &data); err != nil { + return nil, fmt.Errorf("解析JSON失败: %w", err) + } + + // 检查API返回状态 + if status, ok := data["status"].(float64); ok && status == 1 { + // 获取result字段 + if result, ok := data["data"].(map[string]interface{}); ok { + return result, nil + } + + // 如果result字段不存在或类型不对,返回错误 + return nil, fmt.Errorf("孔网获取模板字段失败: 缺少模板字段信息") + } + + message := data["message"].(string) + // 处理API错误情况 + return nil, fmt.Errorf("孔网获取模板字段失败: %v ", message) } /* - * 新增商品-已登的店铺 + * 新增商品-已登的店铺--核价软件用 * param token[string] 孔网token * param proxy[string] 代理服务器IP * param formData[string] 商品信息结构体字符串 @@ -620,7 +772,7 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) return nil, fmt.Errorf("请先登录获取Token") } // 新增商品的URL - url := "https://seller.kongfz.com/pc/itemInfo/add" + outUrl := "https://seller.kongfz.com/pc/itemInfo/add" var repData map[string]interface{} err := json.Unmarshal([]byte(formData), &repData) @@ -628,29 +780,13 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) return nil, fmt.Errorf("JSON解析失败: %v", err) } - //// 创建 multipart 请求体 - //body := &bytes.Buffer{} - //writer := multipart.NewWriter(body) - // - //// 写入所有表单字段 - //for key, value := range repData { - // if err := writer.WriteField(key, value); err != nil { - // return nil, fmt.Errorf("写入表单字段失败: %w", err) - // } - //} - // - //// 关闭writer以完成multipart写入 - //if err := writer.Close(); err != nil { - // return nil, fmt.Errorf("关闭multipart写入器失败: %w", err) - //} - // 创建请求 request := gorequest.New() // 设置代理(如果有提供代理URL) if proxy != "" { request.Proxy(proxy) } - resp, respBody, errs := request.Post(url). + resp, respBody, errs := request.Post(outUrl). 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, */*"). @@ -675,7 +811,100 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) isProxyError = true } } - log.Printf("[ERROR] 请求错误详情: %v", errorDetails) + if isProxyError { + // 处理代理失败 + return nil, fmt.Errorf("代理连接失败: %s", errs) + } + return nil, fmt.Errorf("查询请求失败: %v", errs) + } + //检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP错误: %s", resp.Status) + } + // 解析JSON响应 + var data map[string]interface{} + if err := json.Unmarshal([]byte(respBody), &data); err != nil { + return nil, fmt.Errorf("解析JSON失败: %w", err) + } + fmt.Println("data:", data) + // 检查响应状态 + if val, ok := data["status"]; ok && fmt.Sprint(val) == "1" { + return data, nil + } + // 安全获取message + var message string + if msgVal, ok := data["message"]; ok { + switch v := msgVal.(type) { + case string: + message = v + case map[string]interface{}: + // 如果是map,可以序列化成JSON字符串 + msgBytes, _ := json.Marshal(v) + message = string(msgBytes) + default: + message = fmt.Sprintf("%v", v) + } + } + return nil, fmt.Errorf("新增孔网商品失败: %v", message) +} + +/* + * 修改商品-已登的店铺--核价软件用 + * param token[string] 孔网token + * param proxy[string] 代理服务器IP + * param formData[string] 商品信息结构体字符串 + * return 新增商品响应信息结构体,错误信息 + * Error 请先登录获取Token + * Error 代理连接失败 + * Error 查询请求失败 + * Error HTTP错误 + * Error 解析JSON失败 + * Error API返回错误 + */ +func outUpdateGoods(token, proxy, formData string) (map[string]interface{}, error) { + // 判断登录token + if token == "" { + return nil, fmt.Errorf("请先登录获取Token") + } + // 新增商品的URL + outUrl := "https://seller.kongfz.com/pc/itemInfo/update" + + var repData map[string]interface{} + err := json.Unmarshal([]byte(formData), &repData) + if err != nil { + return nil, fmt.Errorf("JSON解析失败: %v", err) + } + + // 创建请求 + request := gorequest.New() + // 设置代理(如果有提供代理URL) + if proxy != "" { + request.Proxy(proxy) + } + resp, respBody, errs := request.Post(outUrl). + 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, */*"). + Type("multipart"). + Send(repData). + Timeout(15 * time.Second). + End() + if errs != nil { + // 检查是否是代理相关错误 + var isProxyError bool + var errorDetails []string + for _, e := range errs { + 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 + } + } if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败: %s", errs) @@ -695,7 +924,136 @@ func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) if val, ok := data["status"]; ok && fmt.Sprint(val) == "1" { return data, nil } - return nil, fmt.Errorf("API返回错误: %+v", data) + message := data["message"].(string) + return nil, fmt.Errorf("修改孔网商品失败: %v", message) +} + +// 修改商品价格-已登的店铺--店铺商品中价格下面的按钮修改--孔网核价软件用 +func outUpdateGoodsPrice(token, proxy, itemId, updateType, value string) (map[string]interface{}, error) { + // 判断登录token + if token == "" { + return nil, fmt.Errorf("请先登录获取Token") + } + // 新增商品的URL + outUrl := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/quickUpdate" + + repData := map[string]interface{}{ + "itemId": itemId, + "updateType": updateType, + "value": value, + } + + // 创建请求 + request := gorequest.New() + // 设置代理(如果有提供代理URL) + if proxy != "" { + request.Proxy(proxy) + } + resp, respBody, errs := request.Post(outUrl). + 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, */*"). + Send(repData). + Timeout(15 * time.Second). + End() + if errs != nil { + // 检查是否是代理相关错误 + var isProxyError bool + var errorDetails []string + for _, e := range errs { + 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 + } + } + if isProxyError { + // 处理代理失败 + return nil, fmt.Errorf("代理连接失败: %s", errs) + } + return nil, fmt.Errorf("查询请求失败: %v", errs) + } + //检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP错误: %s", resp.Status) + } + // 解析JSON响应 + var data map[string]interface{} + if err := json.Unmarshal([]byte(respBody), &data); err != nil { + return nil, fmt.Errorf("解析JSON失败: %w", err) + } + + // 检查响应状态 + if val, ok := data["status"]; ok && val == true { + return data, nil + } + message := data["errMessage"].(string) + return nil, fmt.Errorf("修改孔网商品失败: %v", message) +} + +// 获取孔网商品列表查询下拉选项--已登的店铺--店铺商品列表查询中下拉选项数据--孔网核价软件用 +// @param token[string] 孔网token +// @return 孔网商品列表查询下拉选项信息 +// @return 错误信息 +func outGetBaseSelectData(token string) (map[string]interface{}, error) { + // 判断登录token + if token == "" { + return nil, fmt.Errorf("请先登录获取Token") + } + // 新增商品的URL + outUrl := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/getBaseSelectData" + // 创建请求 + request := gorequest.New() + + resp, respBody, errs := request.Get(outUrl). + 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, */*"). + Timeout(15 * time.Second). + End() + if errs != nil { + // 检查是否是代理相关错误 + var isProxyError bool + var errorDetails []string + for _, e := range errs { + 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 + } + } + if isProxyError { + // 处理代理失败 + return nil, fmt.Errorf("代理连接失败: %s", errs) + } + return nil, fmt.Errorf("查询请求失败: %v", errs) + } + //检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP错误: %s", resp.Status) + } + // 解析JSON响应 + var data map[string]interface{} + if err := json.Unmarshal([]byte(respBody), &data); err != nil { + return nil, fmt.Errorf("解析JSON失败: %w", err) + } + + // 检查响应状态 + if val, ok := data["status"]; ok && val == true { + return data, nil + } + message := data["errMessage"].(string) + return nil, fmt.Errorf("修改孔网商品失败: %v", message) } /* @@ -1427,9 +1785,138 @@ func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, return nil, fmt.Errorf("查询失败,没有数据!") } -// 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息 +// 爬取孔网所有商品页面 https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list?dataType=0&keyword=9787802204461&page=1&quality=100~&quaSelect=2&actionPath=quality,sortType&sortType=7&userArea=13003000000 +// --孔网核价系统用 +func outGetAllGoods(token, proxy string, queryIndex int, isbn string, quality string, itemName, author, press string) (*BookInfo, error) { + var actionPathList []string + if token == "" { + return nil, fmt.Errorf("token 不能为空") + } + + kfzUrl := "https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list?dataType=0&page=1&sortType=7&userArea=13003000000&quaSelect=2" + // 整理查询参数-加上排序 + actionPathList = append(actionPathList, "sortType") + // isbn + if isbn != "" { + kfzUrl = kfzUrl + "&keyword=" + isbn + } + // 品相 + if quality != "" { + kfzUrl = kfzUrl + "&quality=" + quality + "~" + actionPathList = append(actionPathList, "quality") + } + // 书名 + if itemName != "" { + kfzUrl = kfzUrl + "&itemName=" + itemName + actionPathList = append(actionPathList, "itemName") + } + // 作者 + if author != "" { + kfzUrl = kfzUrl + "&author=" + author + actionPathList = append(actionPathList, "author") + } + // 出版社 + if press != "" { + kfzUrl = kfzUrl + "&press=" + press + actionPathList = append(actionPathList, "press") + } + // 参数进行分割 + actionPath := strings.Join(actionPathList, ",") + // 加入查询参数 + kfzUrl = kfzUrl + "&actionPath=" + actionPath + + //创建HTTP客户端 + requestSpt := gorequest.New() + //设置代理(如果有提供代理URL) + if proxy != "" { + requestSpt.Proxy(proxy) + } + // 发送请求 + respSpt, bodySpt, errsSpt := requestSpt.Get(kfzUrl). + 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(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 { + Title string `json:"title"` + Author string `json:"author"` + Press string `json:"press"` + PubDateText string `json:"pubDateText"` + Isbn string `json:"isbn"` + Price float64 `json:"price"` + Postage struct { + ShippingList []struct { + ShippingFee float64 `json:"shippingFee"` + } + } `json:"postage"` + } `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.Status != 1 { + return nil, fmt.Errorf("错误信息: %v,状态码: %s", apiSptResp.Message, apiSptResp.ErrType) + } + + bookInfo := &BookInfo{} + if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 { + if queryIndex > 0 && queryIndex <= len(apiSptResp.Data.ItemResponse.List) { + goodsInfo := apiSptResp.Data.ItemResponse.List[queryIndex-1] + bookInfo.BookName = goodsInfo.Title + bookInfo.ISBN = goodsInfo.Isbn + bookInfo.Author = goodsInfo.Author + bookInfo.Publisher = goodsInfo.Press + bookInfo.PubDate = goodsInfo.PubDateText + bookInfo.SellingPrice = fmt.Sprintf("%.2f", goodsInfo.Price) + for _, shipping := range goodsInfo.Postage.ShippingList { + bookInfo.ExpressDeliveryFee = fmt.Sprintf("%.2f", shipping.ShippingFee) + } + } else { + goodsInfo := apiSptResp.Data.ItemResponse.List[len(apiSptResp.Data.ItemResponse.List)-1] + bookInfo.BookName = goodsInfo.Title + bookInfo.ISBN = goodsInfo.Isbn + bookInfo.Author = goodsInfo.Author + bookInfo.Publisher = goodsInfo.Press + bookInfo.PubDate = goodsInfo.PubDateText + bookInfo.SellingPrice = fmt.Sprintf("%.2f", goodsInfo.Price) + for _, shipping := range goodsInfo.Postage.ShippingList { + bookInfo.ExpressDeliveryFee = fmt.Sprintf("%.2f", shipping.ShippingFee) + } + } + return bookInfo, nil + + } + return nil, fmt.Errorf("查询失败,没有数据!") +} + +// 爬取孔网 https://item.kongfz.com/pc-gw/item-library-service/client/pc/item/detail/48129308 网址商品信息 func outGetMidDetail(token string, mid string) (*BookInfo, error) { - detailUrl := fmt.Sprintf("https://item.kongfz.com/book/%s.html", mid) + detailUrl := fmt.Sprintf("https://item.kongfz.com/pc-gw/item-library-service/client/pc/item/detail/%s", mid) // 创建HTTP客户端,设置超时时间 client := &http.Client{ @@ -1444,11 +1931,9 @@ func outGetMidDetail(token string, mid string) (*BookInfo, error) { // 设置请求头,模拟浏览器访问 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", "application/json, text/plain, */*") 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)) // 发送请求 @@ -1463,236 +1948,154 @@ func outGetMidDetail(token string, mid string) (*BookInfo, error) { 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) + + // 解析API响应结构 + var apiResp struct { + Status bool `json:"status"` + ErrCode int `json:"errCode"` + ErrMessage string `json:"errMessage"` + Result struct { + ID int `json:"id"` + Mid string `json:"mid"` + Isbn string `json:"isbn"` + OriIsbn string `json:"oriIsbn"` + UnifiedIsbn string `json:"unifiedIsbn"` + BookName string `json:"bookName"` + ContentIntroduction string `json:"contentIntroduction"` + AuthorIntroduction string `json:"authorIntroduction"` + Directory string `json:"directory"` + FilteredContentIntroduction string `json:"filteredContentIntroduction"` + FilteredAuthorIntroduction string `json:"filteredAuthorIntroduction"` + FilteredDirectory string `json:"filteredDirectory"` + RightList []struct { + Name string `json:"name"` + Value string `json:"value"` + } `json:"rightList"` + LeftList []struct { + Name string `json:"name"` + Value string `json:"value"` + } `json:"leftList"` + BoughtNum int `json:"boughtNum"` + ImageNum int `json:"imageNum"` + ImageNumUrl string `json:"imageNumUrl"` + CoverImageWater string `json:"coverImageWater"` + CoverImageBig string `json:"coverImageBig"` + CoverImageMid string `json:"coverImageMid"` + CoverImageSmall string `json:"coverImageSmall"` + RelatedCateList []string `json:"relatedCateList"` + } `json:"result"` } - // 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("未找到详细信息区域") + // 解析JSON + if err := json.Unmarshal(body, &apiResp); err != nil { + return nil, fmt.Errorf("解析JSON失败: %w", err) } - // 左侧信息框 - 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 + // 检查API响应状态 + if !apiResp.Status { + return nil, fmt.Errorf("API返回错误: %s (代码: %d)", apiResp.ErrMessage, apiResp.ErrCode) } - return &bookInfo, nil + bookInfo := &BookInfo{} + result := apiResp.Result + + // 基本信息 + bookInfo.BookName = result.BookName + bookInfo.ISBN = result.Isbn + bookInfo.Mid, _ = strconv.ParseInt(result.Mid, 10, 64) + + // 内容简介、作者简介、目录(优先使用过滤后的内容) + bookInfo.Content = result.FilteredContentIntroduction + if bookInfo.Content == "" { + bookInfo.Content = result.ContentIntroduction + } + + bookInfo.AuthorIntroduction = result.FilteredAuthorIntroduction + if bookInfo.AuthorIntroduction == "" { + bookInfo.AuthorIntroduction = result.AuthorIntroduction + } + + bookInfo.Catalogue = result.FilteredDirectory + if bookInfo.Catalogue == "" { + bookInfo.Catalogue = result.Directory + } + + // 买过人数 + bookInfo.BuyCount = strconv.Itoa(result.BoughtNum) + + // 图书封面图 + bookInfo.BookPic = result.CoverImageBig + if bookInfo.BookPic == "" { + bookInfo.BookPic = result.CoverImageMid + } + + // 处理左侧信息(作者、出版社、出版时间、版次、ISBN、定价) + for _, item := range result.LeftList { + switch item.Name { + case "作者": + // 清理HTML标签,提取纯文本 + bookInfo.Author = cleanHTMLText(item.Value) + case "出版社": + bookInfo.Publisher = cleanHTMLText(item.Value) + case "出版时间": + bookInfo.PublicationTime = validateDateFormat(item.Value) + case "版次": + bookInfo.Edition = item.Value + case "ISBN": + bookInfo.ISBN = item.Value + case "定价": + bookInfo.FixPrice = item.Value + } + } + + // 处理右侧信息(装帧、开本、页数、字数、分类等) + for _, item := range result.RightList { + switch item.Name { + case "装帧": + bookInfo.BindingLayout = item.Value + case "开本": + bookInfo.Format = item.Value + case "页数": + bookInfo.Pages = item.Value + case "字数": + bookInfo.Wordage = item.Value + case "分类": + bookInfo.Category = cleanHTMLText(item.Value) + } + } + + return bookInfo, nil +} + +// 辅助函数:清理HTML标签,提取纯文本 +func cleanHTMLText(html string) string { + // 移除HTML标签 + re := regexp.MustCompile(`<[^>]*>`) + text := re.ReplaceAllString(html, "") + + // 替换HTML实体 + text = strings.ReplaceAll(text, " ", " ") + text = strings.ReplaceAll(text, "<", "<") + text = strings.ReplaceAll(text, ">", ">") + text = strings.ReplaceAll(text, "&", "&") + text = strings.ReplaceAll(text, """, "\"") + text = strings.ReplaceAll(text, "'", "'") + + // 清理空白字符 + text = strings.TrimSpace(text) + text = regexp.MustCompile(`\s+`).ReplaceAllString(text, " ") + + return text } /* - * 获取商品列表通过店铺ID + * 获取商品列表通过店铺ID--他店的商品 * param shopId[int] 店铺ID * param proxy[string] 代理服务器IP * param retPrice[int] 是否需要价格 - * param sortType[string] 排序类型 - * param sort[string] 排序 + * param isImage[int] 是否有图片 0:有图 1:无图 + * param sortType[string] 排序类型 默认值sort + * param sort[string] 排序 默认值desc * param priceMin[float32] 价格区间-低 * param priceMax[float32] 价格区间-高 * param pageNum[int] 页数 @@ -1923,8 +2326,225 @@ func outGetGoodsListMsgByShopId(token string, shopId int, proxy string, retPrice return books, goodsNum, pNum, nil } +// 获取孔网他店商品数据列表--他店的商品 +// @param token 登录凭证 +// @param shopId 他店店铺ID +// @param proxy 代理 +// @param retPrice 是否需要价格 +// @param isImage 是否有图片 0:有 1:无 +// @param sortType 排序类型 +// @param sort 排序方式 +// @param priceMin 价格最小值 +// @param priceMax 价格最大值 +// @param pageNum 页码 +// @param returnNum 一页图书数量 +// @return books 图书列表 +// @return goodsNum 商品数量 +// @return pNum 商品页数 +// @return error 错误信息 +func outGetHisGoodsList(token, shopId, proxy, retPrice, isImage, sortType, + sort, priceMin, priceMax string, pageNum, returnNum int) (books []BookInfo, goodsNum string, pNum string, err error) { + // 判断店铺ID + if shopId == "" { + return nil, "", "", fmt.Errorf("店铺编码为空!") + } + // 判断是否有图片,设置默认值0 + if isImage == "" { + isImage = "0" + } + + // 判断一页图书数量,设置默认值100 + if returnNum == 0 { + returnNum = 100 + } + // 判断页数,设置默认值1 + if pageNum == 0 { + pageNum = 1 + } + // 判断排序类型,设置默认值sort + if sortType == "" { + sortType = "sort" + } else { + validSortTypes := map[string]bool{ + "sort": true, + "putDate": true, + "newItem": true, + "price": true, + } + if !validSortTypes[sortType] { + return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: sort, putDate, newItem, price", sortType) + } + } + // 判断排序,设置默认值desc + if sort == "" { + sort = "desc" + } else { + validSorts := map[string]bool{ + "desc": true, + "asc": true, + } + if !validSorts[sort] { + return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: desc, asc", sort) + } + } + + if priceMin == "" { + priceMin = "0" + } + + if priceMax == "" { + priceMax = "0" + } + kfzUrl := fmt.Sprintf("https://shop.kongfz.com/%s/all/%s_%d_0_0_%d_%s_%s_%s_%s", shopId, isImage, returnNum, pageNum, sortType, sort, priceMin, priceMax) + log.Println("网址:", kfzUrl) + + // 发送请求(移除了重试机制) + 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 { + 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 { + return nil, "", "", fmt.Errorf("代理认证失败") + } + if timeoutError { + return nil, "", "", fmt.Errorf("请求超时,超时网址:%s", kfzUrl) + } + if connectionError { + return nil, "", "", fmt.Errorf("网络连接错误,错误网址:%s", kfzUrl) + } + return nil, "", "", fmt.Errorf("查询请求失败: %v,失败网址:%s", errors, kfzUrl) + } + + // 检查响应是否为空 + if response == nil { + return nil, "", "", fmt.Errorf("响应为空") + } + + // 检查HTTP状态码 + if response.StatusCode != http.StatusOK { + 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()), "/") + if found { + pNum = split + } else { + return nil, "", "", fmt.Errorf("未找到店铺页数!") + } + + // 提取商品信息 + infoDiv := doc.Find("div.list-content") + var params ParamsInfo + if infoDiv.Length() > 0 { + item := infoDiv.Find("div.item.clearfix") + for i := 0; i < item.Length(); i++ { + s := item.Eq(i) + book := BookInfo{} + // 书名 + book.BookName = strings.TrimSpace(s.Find("div.title a.link").Text()) + // 提取ISBN + book.ISBN = strings.TrimSpace(s.AttrOr("isbn", "")) + // 店铺ID + shopid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("shopid", ""))) + book.ShopId = int64(shopid) + // 商品ID + itemid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("itemid", ""))) + book.ItemId = int64(itemid) + // 详情URL + book.DetailUrl = s.Find("div.item-img a.img-box").AttrOr("href", "") + // 价格 + if retPrice == "0" { + book.SellingPrice = s.Find("div.f_right.red.price").Find("span.bold").Text() + params.Params = append(params.Params, struct { + UserId string `json:"userId"` + ItemId string `json:"itemId"` + }{UserId: strings.TrimSpace(s.AttrOr("userid", "")), ItemId: strings.TrimSpace(s.AttrOr("itemid", ""))}) + } + books = append(books, book) + } + // 如果需要查询快递费 + if params.Params != nil { + params.Area = "13003000000" + dataItem, err := getGoodsListShippingFee(params, proxy) + if err != nil { + return nil, "", "", fmt.Errorf("查询快递费失败: %v", err) + } + // 将快递费信息填充到图书信息中 + for i := 0; i < len(books); i++ { + itemId := fmt.Sprintf("%d", books[i].ItemId) + for _, data := range dataItem { + if itemId == data.ItemID { + books[i].ExpressDeliveryFee = data.Fee[0].TotalFee + } + } + } + } + } + return books, goodsNum, pNum, nil +} + /* - * 获取商品信息通过商品详情链接 + * 获取商品信息通过商品详情链接--他店的商品 * param detailUrl[string] 详情页url * param proxy[string] 代理服务器IP * return 商品列表响应信息结构体,错误信息 @@ -2655,10 +3275,21 @@ func kongfzOrderSynchronization(appId int, appSecret, accessToken string, shippi } // 根据快递公司名称查找对应的配送方式和快递公司代号 for _, shippingMethod := range kw.SuccessResponse { - for _, companies := range shippingMethod.Companies { - if shippingComName == companies.ShippingComName { - shippingId = shippingMethod.ShippingId - shippingCom = companies.ShippingCom + //中国邮政 特殊处理 + if shippingComName == "中国邮政" && shippingMethod.ShippingName == "快递包裹" { + for _, companies := range shippingMethod.Companies { + if companies.ShippingComName == "邮局" { + shippingId = shippingMethod.ShippingId + shippingCom = companies.ShippingCom + } + } + } else { + //正常快递 + for _, companies := range shippingMethod.Companies { + if shippingComName == companies.ShippingComName { + shippingId = shippingMethod.ShippingId + shippingCom = companies.ShippingCom + } } } } @@ -2834,11 +3465,203 @@ func kongfzShopItemAdd(appId int, appSecret, accessToken string, shopItemAddJson } params["sign"] = sign + marshal, _ := json.Marshal(params) + fmt.Println(string(marshal)) + request := gorequest.New() - resp, body, errs := request.Get(kUrl). + // 改为 POST 请求,并使用 TypeMultipartForm 或 TypeForm 格式提交 + resp, body, errs := request.Post(kUrl). 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"). // 设置表单格式 + Timeout(30 * time.Second). + Send(params). // gorequest 会自动将 map 转换为表单数据 + End() + + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 订单修改发货单号 +func kongfzOrderRedeliver(appId int, appSecret, accessToken, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.order.redeliver", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + + if requestJson == "" { + return "", fmt.Errorf("orderListJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 订单标记添加/修改 +func kongfzOrderFlagAdd(appId int, appSecret, accessToken, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.order.flag.add", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + + if requestJson == "" { + return "", fmt.Errorf("orderListJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 订单标记删除 +func kongfzOrderFlagDel(appId int, appSecret, accessToken, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.order.flag.del", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + + if requestJson == "" { + return "", fmt.Errorf("orderListJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). Timeout(30 * time.Second). Send(params). End() @@ -2996,6 +3819,551 @@ func kongfzOrderGet(appId int, appSecret, accessToken string, userType string, o return string(responseJSON), nil } +// 获取店铺本店分类名称列表 +// @param appId[int] appKey +// @param appSecret[string] app密钥 +// @param accessToken[string] 访问token +// @return json字符串 +// @return error 错误信息 +func kongfzShopCategoryNameList(appId int, appSecret, accessToken string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.category.name.list", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + // 生成签名 + sign := generateSign(params, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 获取公用分类数据 +// @param appId[int] appKey +// @param appSecret[string] app密钥 +// @param accessToken[string] 访问token +// @return json字符串 +// @return error 错误信息 +func kongfzCommonCategory(appId int, appSecret, accessToken string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.common.category", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + // 生成签名 + sign := generateSign(params, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 下架商品 +func kongfzShopItemDelisting(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.delisting", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 查询店铺商品列表 +func kongfzShopItemList(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.list", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 查询店铺商品详情 +func kongfzShopItemDetail(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.detail", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 上架商品 +func kongfzShopItemListing(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.listing", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 修改商品库存 +func kongfzShopItemNumberUpdate(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.number.update", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 修改商品价格 +func kongfzShopItemPriceUpdate(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.price.update", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 删除商品到回收站 +func kongfzShopItemDelete(appId int, appSecret, accessToken string, requestJson string) (string, error) { + kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") + dateTime := getCurrentTimeGMT8() + + // 生成签名参数 + params := map[string]interface{}{ + "method": "kongfz.shop.item.delete", + "appId": appId, + "accessToken": accessToken, + "datetime": dateTime, + "format": "json", + "v": "1.0", + "signMethod": "md5", + "simplify": 0, + } + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + // 生成签名 + sign := generateSign(toParams, appSecret) + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + request := gorequest.New() + resp, body, errs := request.Post(kUrl). + 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"). + Timeout(30 * time.Second). + Send(params). + End() + if len(errs) > 0 { + return "", fmt.Errorf("请求失败: %v", errs) + } + + if resp.StatusCode != 200 { + return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) + } + + // 解析响应 + var response map[string]interface{} + if err := json.Unmarshal([]byte(body), &response); err != nil { + return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + // =============== 辅助函数 ============== /* * 将结构体的字段添加到 params 映射中 @@ -3159,13 +4527,13 @@ func initializeConfig(config Config) { // =================== C 导出函数 ======================= -// OutLogin 孔网登录 +// OutKfzLogin 孔网登录 // -//export OutLogin -func OutLogin(username, password *C.char) *C.char { +//export OutKfzLogin +func OutKfzLogin(username, password *C.char) *C.char { goUsername := C.GoString(username) goPassword := C.GoString(password) - respToken, err := outLogin(goUsername, goPassword) + respToken, err := outKfzLogin(goUsername, goPassword) // 构建响应数据 resp := struct { Token string `json:"token"` @@ -3199,12 +4567,12 @@ func OutLogin(username, password *C.char) *C.char { return C.CString(string(jsonData)) } -// OutGetUserMsg 获取孔网用户信息 +// OutKfzGetUserInfo 获取孔网用户信息 // -//export OutGetUserMsg -func OutGetUserMsg(token *C.char) *C.char { +//export OutKfzGetUserInfo +func OutKfzGetUserInfo(token *C.char) *C.char { goToken := C.GoString(token) - userInfo, err := outGetUserMsg(goToken) + userInfo, err := outKfzGetUserInfo(goToken) // 构建API响应 var apiResponse APIResponse if err != nil { @@ -3232,6 +4600,39 @@ func OutGetUserMsg(token *C.char) *C.char { return C.CString(string(jsonData)) } +// OutGetKfzShippingTemplate 获取运费模板信息 +// +//export OutGetKfzShippingTemplate +func OutGetKfzShippingTemplate(token *C.char) *C.char { + tokenStr := C.GoString(token) + template, err := outGetKfzShippingTemplate(tokenStr) + // 构建API响应 + var apiResponse APIResponse + if err != nil { + apiResponse = APIResponse{ + Success: false, + Message: err.Error(), + } + } else { + apiResponse = APIResponse{ + Success: true, + Data: template, + } + } + // 转换为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)) +} + // OutGetGoodsTplMsg 获取商品模版--已登的店铺 // //export OutGetGoodsTplMsg @@ -3270,11 +4671,12 @@ func OutGetGoodsTplMsg(token, proxy, itemId *C.char) *C.char { // OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺 // //export OutGetGoodsListMsgFromSelfShop -func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char, +func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, mouldId, priceMin, priceMax *C.char, startCreateTime *C.char, endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char { goToken := C.GoString(token) goProxy := C.GoString(proxy) goItemSn := C.GoString(itemSn) + goMouldId := C.GoString(mouldId) goPriceMin := C.GoString(priceMin) goPriceMax := C.GoString(priceMax) goStartCreateTime := C.GoString(startCreateTime) @@ -3283,7 +4685,42 @@ func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C. goIsItemSnEqual := int(isItemSnEqual) goPage := int(page) goSize := int(size) - info, err := outGetGoodsListMsgFromSelfShop(goToken, goProxy, goItemSn, goPriceMin, goPriceMax, goStartCreateTime, goEndCreateTime, goRequestType, goIsItemSnEqual, goPage, goSize) + info, err := outGetGoodsListMsgFromSelfShop(goToken, goProxy, goItemSn, goMouldId, goPriceMin, goPriceMax, goStartCreateTime, goEndCreateTime, goRequestType, goIsItemSnEqual, goPage, goSize) + // 构建API响应 + var apiResponse APIResponse + if err != nil { + apiResponse = APIResponse{ + Success: false, + Message: err.Error(), + } + } else { + apiResponse = APIResponse{ + Success: true, + Data: info, + } + } + // 转换为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)) +} + +// OutGetTplFields 获取模板字段 +// +//export OutGetTplFields +func OutGetTplFields(token, proxy, itemId *C.char) *C.char { + tokenStr := C.GoString(token) + proxyStr := C.GoString(proxy) + itemIdStr := C.GoString(itemId) + info, err := outGetTplFields(tokenStr, proxyStr, itemIdStr) // 构建API响应 var apiResponse APIResponse if err != nil { @@ -3346,6 +4783,76 @@ func OutAddGoods(token, proxy, formData *C.char) *C.char { return C.CString(string(jsonData)) } +// OutUpdateGoodsPrice 修改商品价格-已登的店铺--核价软件用 +// +//export OutUpdateGoodsPrice +func OutUpdateGoodsPrice(token, proxy, itemId, updateType, value *C.char) *C.char { + tokenStr := C.GoString(token) + proxyStr := C.GoString(proxy) + itemIdStr := C.GoString(itemId) + updateTypeStr := C.GoString(updateType) + valueStr := C.GoString(value) + info, err := outUpdateGoodsPrice(tokenStr, proxyStr, itemIdStr, updateTypeStr, valueStr) + // 构建API响应 + var apiResponse APIResponse + if err != nil { + apiResponse = APIResponse{ + Success: false, + Message: err.Error(), + } + } else { + apiResponse = APIResponse{ + Success: true, + Data: info["result"], + } + } + // 转换为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)) +} + +// OutGetBaseSelectData 获取孔网商品列表查询下拉选项--已登的店铺--店铺商品列表查询中下拉选项数据--孔网核价软件用 +// +//export OutGetBaseSelectData +func OutGetBaseSelectData(token *C.char) *C.char { + tokenStr := C.GoString(token) + info, err := outGetBaseSelectData(tokenStr) + // 构建API响应 + var apiResponse APIResponse + if err != nil { + apiResponse = APIResponse{ + Success: false, + Message: err.Error(), + } + } else { + apiResponse = APIResponse{ + Success: true, + Data: info["result"], + } + } + // 转换为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)) +} + // OutAddGoodsAndFile 整合添加商品和获取孔网图片 // //export OutAddGoodsAndFile @@ -3471,6 +4978,46 @@ func OutGetImageByIsbn(token, proxy, isbn *C.char, isLiveImage C.int, isReturnMs return C.CString(string(jsonData)) } +// OutGetAllGoods 爬取孔网所有商品页面 +// +//export OutGetAllGoods +func OutGetAllGoods(token, proxy *C.char, queryIndex C.int, isbn, quality, itemName, author, press *C.char) *C.char { + tokenStr := C.GoString(token) + proxyStr := C.GoString(proxy) + queryIndexInt := int(queryIndex) + isbnStr := C.GoString(isbn) + qualityStr := C.GoString(quality) + itemNameStr := C.GoString(itemName) + authorStr := C.GoString(author) + pressStr := C.GoString(press) + info, err := outGetAllGoods(tokenStr, proxyStr, queryIndexInt, isbnStr, qualityStr, itemNameStr, authorStr, pressStr) + // 构建统一格式的响应 + var apiResponse APIResponse + if err != nil { + apiResponse = APIResponse{ + Success: false, + Message: err.Error(), + } + } else { + apiResponse = APIResponse{ + Success: true, + Data: info, + } + } + // 转换为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)) +} + // OutGetMidDetail 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息 // //export OutGetMidDetail @@ -3559,6 +5106,60 @@ func OutGetGoodsListMsgByShopId(token *C.char, shopId C.int, proxy *C.char, retP return C.CString(string(jsonData)) } +// OutGetHisGoodsList 获取孔网他店商品数据列表--他店的商品 +// +//export OutGetHisGoodsList +func OutGetHisGoodsList(token, shopId, proxy, retPrice, isImage, sortType, + sort, priceMin, priceMax *C.char, pageNum, returnNum C.int) *C.char { + tokenStr := C.GoString(token) + shopIdStr := C.GoString(shopId) + proxyStr := C.GoString(proxy) + retPriceStr := C.GoString(retPrice) + isImageStr := C.GoString(isImage) + sortTypeStr := C.GoString(sortType) + sortStr := C.GoString(sort) + priceMinStr := C.GoString(priceMin) + priceMaxStr := C.GoString(priceMax) + pageNumInt := int(pageNum) + returnNumInt := int(returnNum) + books, num, pNum, err := outGetHisGoodsList(tokenStr, shopIdStr, proxyStr, retPriceStr, isImageStr, sortTypeStr, sortStr, priceMinStr, priceMaxStr, pageNumInt, returnNumInt) + // 构建统一格式的响应 + bookInfo := struct { + GoodsNum string `json:"goods_num,omitempty"` + PNum string `json:"p_num,omitempty"` + BookInfo interface{} `json:"book_info,omitempty"` + }{ + GoodsNum: num, + PNum: pNum, + BookInfo: books, + } + // 构建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)) +} + // OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接 // //export OutGetGoodsMsgByDetailUrl @@ -3748,6 +5349,184 @@ func KongfzOrderGet(appId C.int, appSecret, accessToken *C.char, userType *C.cha return C.CString(orderGet) } +// KongfzOrderRedeliver 订单修改发货单号 +// +//export KongfzOrderRedeliver +func KongfzOrderRedeliver(appId C.int, appSecret, accessToken *C.char, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzOrderRedeliver(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzOrderFlagAdd 订单标记添加/修改 +// +//export KongfzOrderFlagAdd +func KongfzOrderFlagAdd(appId C.int, appSecret, accessToken *C.char, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzOrderFlagAdd(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzOrderFlagDel 订单标记删除 +// +//export KongfzOrderFlagDel +func KongfzOrderFlagDel(appId C.int, appSecret, accessToken *C.char, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzOrderFlagDel(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopCategoryNameList 获取店铺本店分类名称列表 +// +//export KongfzShopCategoryNameList +func KongfzShopCategoryNameList(appId C.int, appSecret, accessToken *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + info, err := kongfzShopCategoryNameList(appIdStr, appSecretStr, accessTokenStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzCommonCategory 获取公用分类数据 +// +//export KongfzCommonCategory +func KongfzCommonCategory(appId C.int, appSecret, accessToken *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + info, err := kongfzCommonCategory(appIdStr, appSecretStr, accessTokenStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemDelisting 下架商品 +// +//export KongfzShopItemDelisting +func KongfzShopItemDelisting(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemDelisting(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemList 查询店铺商品列表 +// +//export KongfzShopItemList +func KongfzShopItemList(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemList(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemDetail 查询店铺商品详情 +// +//export KongfzShopItemDetail +func KongfzShopItemDetail(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemDetail(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemListing 上架商品 +// +//export KongfzShopItemListing +func KongfzShopItemListing(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemListing(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemNumberUpdate 修改商品库存 +// +//export KongfzShopItemNumberUpdate +func KongfzShopItemNumberUpdate(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemNumberUpdate(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemPriceUpdate 修改商品价格 +// +//export KongfzShopItemPriceUpdate +func KongfzShopItemPriceUpdate(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemPriceUpdate(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + +// KongfzShopItemDelete 删除商品到回收站 +// +//export KongfzShopItemDelete +func KongfzShopItemDelete(appId C.int, appSecret, accessToken, requestJson *C.char) *C.char { + appIdStr := int(appId) + appSecretStr := C.GoString(appSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := kongfzShopItemDelete(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(fmt.Sprint(err)) + } + return C.CString(info) +} + // Initialize 初始化配置 // //export Initialize @@ -3771,18 +5550,27 @@ func FreeCString(str *C.char) { C.free(unsafe.Pointer(str)) } -// CSV_VERSION 版本号 +// KfzVersion 版本号 const ( - CSV_VERSION = "v3" + KfzVersion = "v2" ) -// 获取版本信息 +// GetVersion 获取版本信息 // //export GetVersion func GetVersion() *C.char { - return C.CString(CSV_VERSION) + return C.CString(KfzVersion) } // 空main函数,编译DLL时需要 func main() { + //appid := 576 + //key := "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8" + //token := "8642f80ddb95826c9ad36877784c69c26a0f84d73184d08ab026ca6c851dd964" + //list, err := kongfzOrderSynchronization(appid, key, token, "中国邮政", 0, "", "", "", "", "") + //if err != nil { + // fmt.Println(err.Error()) + // return + //} + //fmt.Println(list) } diff --git a/kongfz/kongfz.h b/kongfz/kongfz.h deleted file mode 100644 index 20a7521..0000000 --- a/kongfz/kongfz.h +++ /dev/null @@ -1,185 +0,0 @@ -/* 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 1fabfb3..f530e7e 100644 --- a/kongfz/kongfzDll.go +++ b/kongfz/kongfzDll.go @@ -1,106 +1,46 @@ package main //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) +// // fields, err := outGetTplFields("2aaf1b863a530d4dc0a41539292587f5b35e05da", "", "9811034490") +// // if err != nil { +// // fmt.Println(err) +// // } +// // marshal, _ := json.Marshal(fields) +// // fmt.Println("数据:", string(marshal)) +// // formData := `{"author":"朱自清、逯亮 著","bearShipping":"buyer","binding":"2","booklibId":"","catId":"32002004001000000","deliverTimeGroup":"1","images[0][imgDesc]":"","images[0][imgType]":"0","images[0][imgUrl]":"sw/kfz-cos/kfzimg/24175337/7174f9932b38d071_s.jpg","images[0][isMain]":"1","images[1][imgDesc]":"","images[1][imgType]":"0","images[1][imgUrl]":"sw/kfz-cos/kfzimg/24175337/271a68c88dad67f4_s.jpg","images[1][isMain]":"0","importantDesc":"","isDeliverTimeDefault":"48h","isOnSale":"1","isbn":"9787204155293","itemDesc":"","itemId":"9890668175","itemName":"朱自清散文集/新课标课外阅读经典文学名著","itemSn":"24-9","language":"","material":"","mouldId":"975623","number":"3","oriPrice":"0.01","pageNum":"169","pageSize":"16开","pageType":"edit","paper":"6","press":"内蒙古人民出版社","price":"759.00","pubDate":"2018-10","quality":"95","qualityDesc":"","sizeHeight":"0.00","sizeLength":"0.00","sizeWidth":"0.00","tpl":"13","weight":"1.00","weightPiece":"0.00","wordNum":"230","yearsGroup":"1"} +// //` +// // goods, err := outUpdateGoods("ea9f46cd9b1c8a93f143993a363b08a835640080", "", formData) +// // if err != nil { +// // fmt.Println(err.Error()) +// // } +// // marshal, _ := json.Marshal(goods) +// // fmt.Println(string(marshal)) // -// //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) +// info, err := outUpdateGoodsPrice("ea9f46cd9b1c8a93f143993a363b08a835640080", "", "9890668175", "price", "859.00") // if err != nil { -// fmt.Println(err) +// fmt.Println(err.Error()) // } -// 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)) +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: info["result"], +// } +// } +// marshal, _ := json.Marshal(apiResponse) +// fmt.Println(string(marshal)) + +//goods, err := outGetAllGoods("1acf1d5ce6f99fde2ae31f093e4201f9c9004e3f", "", 3, "", "100", "三国演义", "罗贯中", "中国画报出版社") +//if err != nil { +//fmt.Println(err) +//} +//marshal, _ := json.Marshal(goods) +//fmt.Println(string(marshal)) //} type ItemInfo struct { diff --git a/kongfz/kongfzLao.go b/kongfz/kongfzLao.go new file mode 100644 index 0000000..4d709a9 --- /dev/null +++ b/kongfz/kongfzLao.go @@ -0,0 +1,4262 @@ +package main + +///* +//#include +//*/ +//import "C" +//import ( +// "bytes" +// "crypto/md5" +// "encoding/json" +// "fmt" +// "io" +// "log" +// "math/rand" +// "mime/multipart" +// "net/http" +// "net/url" +// "os" +// "path" +// "path/filepath" +// "regexp" +// "sort" +// "strconv" +// "strings" +// "time" +// "unsafe" +// +// "github.com/PuerkitoBio/goquery" +// _ "github.com/go-sql-driver/mysql" +// "github.com/parnurzeal/gorequest" +//) +// +//// Config 结构体定义应用程序配置 +//type Config struct { +// App struct { +// MaxRetryTimes int `ini:"app.max_retry_times" json:"max_retry_times" default:"3"` // 最大重试次数 +// RateLimitDelay time.Duration `ini:"app.rate_limit_delay" json:"rate_limit_delay" default:"500ms"` // 请求延迟 +// Size int `ini:"app.size" json:"size" default:"5"` // 默认大小 +// DefaultUserAgent string `ini:"app.default_user_agent" json:"default_user_agent" default:"Mozilla/5.0"` // 默认 User-Agent +// } `ini:"app" json:"app"` +// +// API struct { +// LoginURL string `ini:"api.login_url" json:"login_url" default:"https://login.kongfz.com/Pc/Login/account"` // 登录 URL +// BookSearchURL string `ini:"api.book_search_url" json:"book_search_url" default:"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list"` // 图书搜索 URL +// ProductSearchURL string `ini:"api.product_search_url" json:"product_search_url" default:"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list"` // 商品搜索 URL +// } `ini:"api" json:"api"` +// +// Proxy struct { +// Servers string `ini:"proxy.servers" json:"servers" default:"http-dynamic.xiaoxiangdaili.com,http-dynamic-S02.xiaoxiangdaili.com,http-dynamic-S03.xiaoxiangdaili.com,http-dynamic-S04.xiaoxiangdaili.com"` // 代理服务器列表 +// Username string `ini:"proxy.username" json:"username" default:"1297757178467602432"` // 代理用户名 +// Password string `ini:"proxy.password" json:"password" default:"QgQBvP7f"` // 代理密码 +// TailMachineCode string `ini:"proxy.tail_machine_code" json:"tail_machine_code" default:"b7bf22a237ec692f13fcc2c43ee63252"` // 尾机编码 +// TailCardKey string `ini:"proxy.tail_card_key" json:"tail_card_key" default:"DL_20_YK_1920acb2129844c2aabade3896560a9b"` // 尾卡密钥 +// ProxyFilePath string `ini:"proxy.proxy_file_path" json:"proxy_file_path" default:"dll/proxyConfig.dll"` // 代理配置文件路径 +// } `ini:"proxy" json:"proxy"` +//} +// +//// BookInfo 图书详情结构体 +//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"` // 内容简介 +// 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 +// DetailUrl string `json:"detail_url"` // 商品详情url +//} +// +//// APIResponse 通用API响应结构体 +//type APIResponse struct { +// Success bool `json:"success"` // 请求是否成功 +// Message string `json:"message,omitempty"` // 错误信息(成功时可选) +// Data interface{} `json:"data,omitempty"` // 响应数据(失败时可选) +//} +// +//// UserInfo 孔网用户信息结构体 +//type UserInfo struct { +// UserID int64 `json:"userId"` // 用户ID +// Nickname string `json:"nickname"` // 用户昵称 +// Mobile string `json:"mobile"` // 手机号 +//} +// +//// 全局配置变量 +//var ( +// cf Config // 存储应用程序配置 +//) +// +//// ProductInfo 商品信息结构体 +//type ProductInfo struct { +// ItemID string `json:"itemId"` // 商品ID +// BookName string `json:"bookName"` // 书名 +// Price string `json:"price"` // 价格 +// ShippingFee string `json:"shippingFee"` // 运费 +//} +// +//// BookDetailResponse 图书详情响应结构体 +//type BookDetailResponse struct { +// Status bool `json:"status"` // 状态 +// Result BookList `json:"result"` // 结果数据 +// ErrMessage string `json:"errMessage"` // 错误信息 +// ErrCode int `json:"errCode"` // 错误码 +//} +// +//// BookList 图书列表结构体 +//type BookList struct { +// Current int `json:"current"` // 当前页码 +// Data []BookInformation `json:"data"` // 图书数据列表 +// Total int `json:"total"` // 总数 +//} +// +//// BookInformation 图书信息结构体 +//type BookInformation struct { +// Author string `json:"author"` // 作者 +// BookName string `json:"bookName"` // 书名 +// ContentIntroduction string `json:"contentIntroduction"` // 内容简介 +// ImgUrl string `json:"imgUrl"` // 图片URL +// Isbn string `json:"isbn"` // ISBN +// ItemUrls ItemUrls `json:"itemUrls"` // 商品URL +// Mid int `json:"mid"` // 商家ID +// NewMinPrice string `json:"newMinPrice"` // 新书最低价 +// OldMinPrice string `json:"oldMinPrice"` // 旧书最低价 +// Press string `json:"press"` // 出版社 +// Price string `json:"price"` // 价格 +// PubDate string `json:"pubDate"` // 出版日期 +// RiseTag string `json:"riseTag"` // 上涨标签 +// AuthorArr []AuthorInfo `json:"authorArr"` // 作者数组 +// PressUrl string `json:"pressUrl"` // 出版社URL +//} +// +//// AuthorInfo 作者信息结构体 +//type AuthorInfo struct { +// Name string `json:"name"` // 作者姓名 +// OriName string `json:"oriName"` // 原始姓名 +// Nationality string `json:"nationality"` // 国籍 +// Role string `json:"role"` // 角色 +// Url string `json:"url"` // 作者页面URL +//} +// +//// ItemUrls 商品链接结构体 +//type ItemUrls struct { +// AppUrl string `json:"appUrl"` // App链接 +// MUrl string `json:"mUrl"` // 移动端链接 +// MiniUrl string `json:"miniUrl"` // 小程序链接 +// PcUrl string `json:"pcUrl"` // PC端链接 +//} +// +//// ParamsInfo 店铺快递费参数结构体 +//type ParamsInfo struct { +// Params []Param `json:"params"` // 参数列表 +// Area string `json:"area"` // 地区编码 +//} +// +//// Param 单个参数结构体 +//type Param struct { +// UserId string `json:"userId"` // 用户ID +// ItemId string `json:"itemId"` // 商品ID +//} +// +//// DataItem 快递费用响应数据项 +//type DataItem struct { +// Fee []FeeInfo `json:"fee"` // 费用列表 +// IsSeller bool `json:"isSeller"` // 是否是卖家 +// ItemID string `json:"itemId"` // 商品ID +// UserID string `json:"userId"` // 用户ID +//} +// +//// FeeInfo 费用信息结构体 +//type FeeInfo struct { +// ShippingID string `json:"shippingId"` // 配送方式ID +// ShippingName string `json:"shippingName"` // 配送方式名称 +// TotalFee string `json:"totalFee"` // 总费用 +// ShippingText string `json:"shippingText"` // 配送说明 +// FilterTotalFee string `json:"filterTotalFee"` // 过滤后的总费用 +//} +// +//// ResponseStruct 快递费响应结构体 +//type ResponseStruct struct { +// Status bool `json:"status"` // 状态 +// Data []DataItem `json:"data"` // 数据 +// Message string `json:"message"` // 消息 +// ErrType string `json:"errType"` // 错误类型 +//} +// +//// ShippingTemplateResponse 运费模板响应结构体 +//type ShippingTemplateResponse struct { +// Status bool `json:"status"` // 状态 +// Data []ShippingTemplateInfo `json:"data"` // 运费模板信息 +// Message string `json:"message"` // 消息 +// ErrType string `json:"errType"` // 错误类型 +//} +// +//// ShippingTemplateInfo 运费模板信息 +//type ShippingTemplateInfo struct { +// MouldId string `json:"mouldId"` +// MouldName string `json:"mouldName"` +//} +// +///* +// * 获取店铺里商品的快递费(定位到河南) +// * param params[ParamsInfo] 店铺快递费参数结构体 +// * param proxy[string] 代理服务器IP +// * return 快递费用响应数据项数组,错误信息 +// * Error 没有商品信息 +// * Error 参数序列化失败 +// * Error 请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// */ +//func getGoodsListShippingFee(params ParamsInfo, proxy string) ([]DataItem, error) { +// // 检查参数是否为空 +// if params.Params == nil { +// return nil, fmt.Errorf("没有商品信息!") +// } +// // 序列化参数为JSON +// paramsJSON, err := json.Marshal(params) +// if err != nil { +// return nil, fmt.Errorf("参数序列化失败: %v", err) +// } +// // URL 编码参数 +// encodedParams := url.QueryEscape(string(paramsJSON)) +// // 构建请求URL +// url := fmt.Sprintf("https://shop.kongfz.com/book/shopsearch/getShippingFee?params=%s", encodedParams) +// // 创建请求对象 +// request := gorequest.New() +// // 设置代理 +// if proxy != "" { +// request.Proxy(proxy) +// } +// // 发送GET请求 +// resp, body, errs := request.Get(url). +// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). +// Set("Accept", "application/json, text/plain, */*"). +// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). +// End() +// // 错误处理 +// if len(errs) > 0 { +// return nil, fmt.Errorf("请求失败: %v", errs) +// } +// // 检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// // 解析JSON响应 +// responseStruct := ResponseStruct{} +// err = json.Unmarshal([]byte(body), &responseStruct) +// if err != nil { +// return nil, fmt.Errorf("解析JSON失败: %v", err) +// } +// // 提取数据项 +// var dataItem []DataItem +// for i := range responseStruct.Data { +// dataItem = append(dataItem, responseStruct.Data[i]) +// } +// return dataItem, nil +//} +// +///* +// * 孔网登录 +// * param username[string] 孔网用户名 +// * param password[string] 孔网密码 +// * return token,错误信息 +// * Error 登录请求失败 +// * Error 登录失败(HTTP状态码: %d) +// * Error 登录成功但未获取到Cookie +// * Error 登录失败: 未找到 PHPSESSID +// * Error 账号或密码错误 +// * Error 登录失败 +// * Error 登录失败,未知错误! +// */ +//func outKfzLogin(username, password string) (string, error) { +// // 检查用户名和密码是否为空 +// if username == "" || password == "" { +// return "", fmt.Errorf("请输入用户名和密码!") +// } +// +// // 准备POST请求的表单数据 +// formData := map[string]string{ +// "loginName": username, +// "loginPass": password, +// "returnUrl": "http://user.kongfz.com/", +// } +// // 孔网登录URL +// loginUrl := "https://login.kongfz.com/Pc/Login/account" +// +// // 发送登录请求 +// resp, body, errs := gorequest.New(). +// Post(loginUrl). +// Set("Content-Type", "application/x-www-form-urlencoded"). +// 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,image/webp,*/*;q=0.8"). +// Send(formData). +// Timeout(15 * time.Second). +// End() +// +// // 请求错误处理 +// if len(errs) > 0 { +// return "", fmt.Errorf("登录请求失败: %v", errs) +// } +// +// // 检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return "", fmt.Errorf("登录失败(HTTP状态码: %d)", resp.StatusCode) +// } +// +// // 提取Cookie +// cookie := resp.Header.Get("Set-Cookie") +// // 检查是否登录成功(通过响应内容判断) +// if strings.Contains(body, "window.location.href='https://login.kongfz.cn/Pc/Session/rsync") { +// if cookie == "" { +// return "", fmt.Errorf("登录成功但未获取到Cookie") +// } +// +// // 登录成功 +// if strings.Contains(cookie, "PHPSESSID=") { +// token := strings.Split(strings.Split(cookie, "PHPSESSID=")[1], ";")[0] +// return token, nil +// } +// +// return "", fmt.Errorf("登录失败: 未找到PHPSESSID") +// } +// +// // 错误信息 +// var res struct { +// Status bool `json:"status"` +// ErrCode int `json:"errCode"` +// ErrInfo string `json:"errInfo"` +// } +// // 解析json +// if err := json.Unmarshal([]byte(body), &res); err == nil { +// if res.ErrCode == 1001 || res.ErrCode == 1005 { +// return "", fmt.Errorf("账号或密码错误!") +// } +// +// if res.ErrInfo != "" { +// return "", fmt.Errorf("登录失败: %s", res.ErrInfo) +// } +// } +// +// return "", fmt.Errorf("登录失败,未知错误!") +//} +// +///* +// * 获取孔网用户信息 +// * param token[string] 孔网token +// * return 孔网用户信息结构体,错误信息 +// * Error 查询请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error 获取用户失败 +// */ +//func outKfzGetUserInfo(token string) (*UserInfo, error) { +// // 用户信息URL +// url := "https://user.kongfz.com/User/Index/getUserInfo/" +// +// // 发送请求 +// resp, body, errs := gorequest.New(). +// 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"). +// Timeout(15 * time.Second). +// End() +// if len(errs) > 0 { +// return nil, fmt.Errorf("查询用户信息请求失败: %v", errs) +// } +// +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// +// // 响应数据 +// var userInfo struct { +// Status bool `json:"status"` +// Data struct { +// UserID int64 `json:"userId"` +// Nickname string `json:"nickname"` +// Mobile string `json:"mobile"` +// } +// } +// // 解析json +// if err := json.Unmarshal([]byte(body), &userInfo); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// +// // 创建用户信息对象 +// user := &UserInfo{} +// if !userInfo.Status { +// return nil, fmt.Errorf("获取用户失败!") +// } +// user.UserID = userInfo.Data.UserID +// user.Nickname = userInfo.Data.Nickname +// user.Mobile = userInfo.Data.Mobile +// return user, nil +//} +// +//// 获取运费模板信息 +//func outGetKfzShippingTemplate(token string) ([]ShippingTemplateInfo, error) { +// if token == "" { +// return nil, fmt.Errorf("请先登录获取Token") +// } +// +// kfzUrl := "https://shop.kongfz.com/seller/delivery/index" +// // 发送请求 +// resp, body, errs := 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 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). +// Set("Accept", "application/json, text/plain, */*"). +// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). +// Timeout(15 * time.Second). +// End() +// if len(errs) > 0 { +// return nil, fmt.Errorf("查询请求失败: %v", errs) +// } +// +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// +// var shippingTemplateResponse ShippingTemplateResponse +// // 解析json +// if err := json.Unmarshal([]byte(body), &shippingTemplateResponse); err != nil { +// return nil, fmt.Errorf("解析运费模板响应结构体失败: %w", err) +// } +// +// if !shippingTemplateResponse.Status { +// return nil, fmt.Errorf(shippingTemplateResponse.Message) +// } +// return shippingTemplateResponse.Data, nil +//} +// +///* +// * 获取商品模版-已登的店铺 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param itemId[string] 商品ID +// * return 商品模板信息结构体,错误信息 +// * Error 请先登录获取Token +// * Error 代理连接失败 +// * Error 查询请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error API返回错误 +// */ +//func outGetGoodsTplMsg(token, proxy, itemId string) (map[string]interface{}, error) { +// // 判断登录token +// if token == "" { +// return nil, fmt.Errorf("请先登录获取Token") +// } +// // 商品模板URL +// url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d", +// itemId, time.Now().Unix()) +// +// // 创建请求 +// request := gorequest.New() +// +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(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(15 * time.Second). +// End() +// // 错误处理 +// if errs != nil { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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", errs) +// } +// +// // 检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// +// // 解析json +// var data map[string]interface{} +// if err := json.Unmarshal([]byte(body), &data); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// +// // 检查status状态 +// if val, ok := data["status"].(float64); ok && val == 1 { +// return data, nil +// } +// return nil, fmt.Errorf("API返回错误: %+v", data) +//} +// +///* +// * 获取商品列表-已登的店铺 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param itemSn[string] 货号 +// * param priceMin[string] 价格区间-低 +// * param priceMax[string] 价格区间-高 +// * param startCreateTime[string] 开始时间 +// * param endCreateTime[string] 结束时间 +// * param requestType[string] 请求类型 +// * param isItemSnEqual[int] 商品ID +// * param page[int] 页数 +// * param size[int] 大小 +// * return 商品列表响应信息结构体,错误信息 +// * Error 请先登录获取Token +// * Error 代理连接失败 +// * Error 查询请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error API返回错误 +// */ +//func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, +// startCreateTime string, endCreateTime string, requestType string, +// isItemSnEqual int, page int, size int) (map[string]interface{}, error) { +// // 判断登录token +// if token == "" { +// return nil, fmt.Errorf("请先登录获取Token") +// } +// +// // 已登店铺的商品信息URL +// url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/unSold/list" +// +// // 请求参数 +// formData := struct { +// ItemSn string `json:"itemSn"` +// PriceMin string `json:"priceMin"` +// PriceMax string `json:"priceMax"` +// StartCreateTime string `json:"startCreateTime"` +// EndCreateTime string `json:"endCreateTime"` +// RequestType string `json:"requestType,omitempty"` +// IsItemSnEqual int `json:"isItemSnEqual,omitempty"` +// Page int `json:"page,omitempty"` +// Size int `json:"size,omitempty"` +// }{ +// ItemSn: itemSn, +// PriceMin: priceMin, +// PriceMax: priceMax, +// StartCreateTime: startCreateTime, +// EndCreateTime: endCreateTime, +// RequestType: requestType, +// IsItemSnEqual: isItemSnEqual, +// Page: page, +// Size: size, +// } +// // 创建请求 +// request := gorequest.New() +// +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(proxy) +// } +// // 发送POST请求 +// resp, body, errs := request.Post(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, */*"). +// Send(formData). +// Timeout(15 * time.Second). +// End() +// if errs != nil { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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", errs) +// } +// +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// +// // 解析json +// var data map[string]interface{} +// if err := json.Unmarshal([]byte(body), &data); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// +// if status, ok := data["status"]; ok && status == true { +// return data, nil +// } +// return nil, fmt.Errorf("API返回错误: %+v", data) +//} +// +// +///* +// * 新增商品-已登的店铺 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param formData[string] 商品信息结构体字符串 +// * return 新增商品响应信息结构体,错误信息 +// * Error 请先登录获取Token +// * Error 代理连接失败 +// * Error 查询请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error API返回错误 +// */ +//func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) { +// // 判断登录token +// if token == "" { +// return nil, fmt.Errorf("请先登录获取Token") +// } +// // 新增商品的URL +// url := "https://seller.kongfz.com/pc/itemInfo/add" +// +// var repData map[string]interface{} +// err := json.Unmarshal([]byte(formData), &repData) +// if err != nil { +// return nil, fmt.Errorf("JSON解析失败: %v", err) +// } +// +// // 创建请求 +// request := gorequest.New() +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(proxy) +// } +// resp, respBody, errs := request.Post(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("Content-Type", writer.FormDataContentType()). +// Type("multipart"). +// Send(repData). +// Timeout(15 * time.Second). +// End() +// if errs != nil { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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("代理连接失败: %s", errs) +// } +// return nil, fmt.Errorf("查询请求失败: %v", errs) +// } +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// // 解析JSON响应 +// var data map[string]interface{} +// if err := json.Unmarshal([]byte(respBody), &data); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// // 检查响应状态 +// if val, ok := data["status"]; ok && fmt.Sprint(val) == "1" { +// return data, nil +// } +// return nil, fmt.Errorf("API返回错误: %+v", data) +//} +// +///* +// * 获取图片key +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * return 获取图片key响应信息结构体,错误信息 +// */ +//func outKfzimgKey(token, proxy string) (map[string]interface{}, error) { +// // 判断登录token +// if token == "" { +// return nil, fmt.Errorf("请先登录获取Token") +// } +// // 新增商品的URL +// url := fmt.Sprintf("https://seller.kongfz.com/kfzimg-key?type=%s", "book") +// // 创建请求 +// request := gorequest.New() +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(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, */*"). +// Timeout(15 * time.Second). +// End() +// if errs != nil { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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("代理连接失败: %s", errs) +// } +// return nil, fmt.Errorf("查询请求失败: %v", errs) +// } +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// +// // 去除括号 +// jsonStr := strings.TrimPrefix(body, "(") +// jsonStr = strings.TrimSuffix(jsonStr, ")") +// // 解析JSON响应 +// var data map[string]interface{} +// if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// // 检查响应状态 +// if val, ok := data["status"]; ok && val == "success" { +// return data, nil +// } +// return nil, fmt.Errorf("API返回错误: %+v", data) +//} +// +///* +// * 获取孔网图片 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * return 获取孔网图片响应信息结构体,错误信息 +// */ +//func outKfzimgUpload(token, proxy, filePath string) (string, error) { +// url := fmt.Sprint("https://seller.kongfz.com/kfzimg-upload") +// fileName := filepath.Base(filePath) +// // 创建multipart/form-data请求体 +// body := &bytes.Buffer{} +// writer := multipart.NewWriter(body) +// // 读取文件内容 +// fileData, err := os.ReadFile(filePath) +// if err != nil { +// return "", fmt.Errorf("读取文件失败: %v", err) +// } +// // 添加文件字段 +// part, err := writer.CreateFormFile("img", filepath.Base(filePath)) +// if err != nil { +// return "", fmt.Errorf("创建表单文件失败: %v", err) +// } +// _, err = part.Write(fileData) +// if err != nil { +// return "", fmt.Errorf("写入文件数据失败: %v", err) +// } +// _ = writer.WriteField("autorotated", "1") +// _ = writer.WriteField("name", fileName) +// _ = writer.Close() +// +// key, err := outKfzimgKey(token, proxy) +// if err != nil { +// return "", err +// } +// imageKey := key["imagekey"] +// +// // 在发送请求前添加 +// log.Printf("发送请求到: %s", url) +// log.Printf("Cookie: PHPSESSID=%s", token) +// log.Printf("文件: %s", fileName) +// log.Printf("Content-Type: %s", writer.FormDataContentType()) +// // 发送POST请求 +// request := gorequest.New() +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(proxy) +// } +// resp, respBody, errs := request.Post(url). +// //Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). +// Set("Cookie", fmt.Sprintf("PHPSESSID=%s; IMGKEY=%s", token, imageKey)). +// Set("Content-Type", writer.FormDataContentType()). // 设置Content-Type +// 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", "*/*"). +// Set("Host", "seller.kongfz.com"). +// Set("Connection", "keep-alive"). +// Send(body.String()). // 发送请求体 +// Timeout(30 * time.Second). +// End() +// +// if errs != nil { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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 "", fmt.Errorf("代理连接失败: %s", errs) +// } +// return "", fmt.Errorf("查询请求失败: %v", errs) +// } +// // 在得到响应后添加 +// log.Printf("响应状态码: %d", resp.StatusCode) +// log.Printf("响应头: %v", resp.Header) +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return "", fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// // 正则表达式匹配路径 +// re := regexp.MustCompile(`(sw/.*?\.jpg)`) +// matches := re.FindStringSubmatch(respBody) +// if len(matches) > 0 { +// return matches[1], nil +// } else { +// return "", fmt.Errorf("未找到匹配的路径") +// } +//} +// +///* +// * 整合添加商品和获取孔网图片 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param filePath[string] 图片路径 +// * param formData[string] 商品JSON字符串 +// * return 添加商品响应信息结构体,错误信息 +// */ +//func outAddGoodsAndFile(token, proxy, filePath, formData string) (string, error) { +// upload, err := outKfzimgUpload(token, proxy, filePath) +// if err != nil { +// return "", err +// } +// var data map[string]interface{} +// err = json.Unmarshal([]byte(formData), &data) +// if err != nil { +// return "", fmt.Errorf("JSON解析失败: %v", err) +// } +// data["images[0][imgUrl]"] = upload +// +// dataStr, err := json.Marshal(data) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// fmt.Println(string(dataStr)) +// goods, err := outAddGoods(token, proxy, string(dataStr)) +// if err != nil { +// return "", err +// } +// goodsStr, err := json.Marshal(goods) +// if err != nil { +// return "", fmt.Errorf("goodsStr JSON序列化失败: %v", err) +// } +// return string(goodsStr), nil +//} +// +///* +// * 删除商品-已登的店铺 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param itemId[string] 商品ID +// * return 删除商品响应信息结构体,错误信息 +// * Error 请先登录获取Token +// * Error 代理连接失败 +// * Error 查询请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error API返回错误 +// */ +//func outDelGoodsFromSelfShop(token, proxy, itemId string) (map[string]interface{}, error) { +// // 判断登录token +// if token == "" { +// return nil, fmt.Errorf("请先登录获取Token") +// } +// // 删除商品的URL +// url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/quickUpdate" +// formData := map[string]string{ +// "itemId": itemId, +// "updateType": "delete", +// "value": "1", +// } +// // 创建请求 +// request := gorequest.New() +// if proxy != "" { +// request.Proxy(proxy) +// } +// resp, body, errs := request.Post(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, */*"). +// Send(formData). +// Timeout(15 * time.Second). +// End() +// if errs != nil { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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", errs) +// } +// // 检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// // 解析json +// var data map[string]interface{} +// if err := json.Unmarshal([]byte(body), &data); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// // 判断是否响应成功 +// if status, ok := data["status"].(bool); ok && status { +// return data, nil +// } +// // 判断是否响应成功 +// if status, ok := data["status"].(float64); ok && status == 1 { +// return data, nil +// } +// +// return nil, fmt.Errorf("API返回错误: %+v", data) +//} +// +///* +// * 获取孔网商品图片(官图和拍图)-带有店铺过滤 +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param itemId[string] 商品ID +// * return 删除商品响应信息结构体,错误信息 +// * Error 请先登录获取Token +// * Error 代理连接失败 +// * Error 查询请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error API返回错误 +// */ +//func outGetImageFilterShopId(token string, proxy string, isbn string, shopId int, isLiveImage int, isReturnMsg int) (map[string]string, error) { +// // 判断是否为官图(isLiveImage=0) +// if isLiveImage == 0 { +// // 图书条目URL +// gtUrl := fmt.Sprintf("%s?keyword=%s", +// "https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", isbn) +// request := gorequest.New() +// if proxy != "" { +// request.Proxy(proxy) +// } +// resp, body, errs := request.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(errs) > 0 { +// // 检查是否是代理相关错误 +// var isProxyError bool +// var errorDetails []string +// for _, e := range errs { +// 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", errs) +// } +// //检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.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(body), &apiGtResp); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// if apiGtResp.ErrType == "102" { +// return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiGtResp.Message, apiGtResp.ErrType) +// } +// var info map[string]string +// // 如果找到条目,返回图片URL +// if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 { +// list := apiGtResp.Data.ItemResponse.List[0] +// info = map[string]string{ +// "book_name": list.BookName, +// "book_pic": list.ImgUrlEntity.BigImgUrl, +// "isbn": list.Isbn, +// } +// } +// return info, nil +// } +// // 处理实拍图(isLiveImage=1) +// 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) +// } +// var info map[string]string +// 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 +// } +// // 尝试3次获取有效的图片 +// 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 +// } +// // 过滤店铺 +// if item.ShopId == int64(shopId) { +// log.Printf("[DEBUG] 索引 %d 的店铺ID需要过滤,跳过", randomNum) +// continue +// } +// // 返回图片信息 +// info = map[string]string{ +// "book_name": item.Title, +// "book_pic_s": item.ImgUrl, +// "isbn": item.Isbn, +// } +// return info, nil +// } +// } +// } +// return nil, nil +//} +// +///* +// * 获取孔网商品图片和信息(官图和拍图) +// * param token[string] 孔网token +// * param proxy[string] 代理服务器IP +// * param isbn[string] isbn +// * param isLiveImage[int] 是否官图 0官图 1实拍图 +// * param isReturnMsg[int] 是否有其他信息 +// * return 孔网商品图片和信息响应信息结构体,错误信息 +// * Error 请先登录获取Token +// * Error 代理连接失败 +// * Error 查询请求失败 +// * Error 解析JSON失败 +// * Error 错误信息: %w,状态码: %s +// * Error 查询失败,没有数据! +// */ +//func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, isReturnMsg int) (*BookInfo, error) { +// // isLiveImage 1实拍图 0官图 ,isReturnMsg 0商品信息 +// bookInfo := &BookInfo{} +// // 处理官图(isLiveImage=0) +// 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) +// } +// // 发送GET请求 +// 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"` +// } +// // 解析JSON +// 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 +// bookInfo.Mid = list.Mid +// // 根据长度安全填充字段 +// 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 +// } +// // 处理实拍图(isLiveImage=1) +// 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). +// 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(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 +// } +// // 尝试3次获取有效的图片 +// 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("查询失败,没有数据!") +//} +// +//// 爬取孔网 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 +////} +// +//// 爬取孔网 https://item.kongfz.com/pc-gw/item-library-service/client/pc/item/detail/48129308 网址商品信息 +//func outGetMidDetail(token string, mid string) (*BookInfo, error) { +// detailUrl := fmt.Sprintf("https://item.kongfz.com/pc-gw/item-library-service/client/pc/item/detail/%s", 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", "application/json, text/plain, */*") +// req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") +// req.Header.Set("Connection", "keep-alive") +// 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) +// } +// +// // 解析API响应结构 +// var apiResp struct { +// Status bool `json:"status"` +// ErrCode int `json:"errCode"` +// ErrMessage string `json:"errMessage"` +// Result struct { +// ID int `json:"id"` +// Mid string `json:"mid"` +// Isbn string `json:"isbn"` +// OriIsbn string `json:"oriIsbn"` +// UnifiedIsbn string `json:"unifiedIsbn"` +// BookName string `json:"bookName"` +// ContentIntroduction string `json:"contentIntroduction"` +// AuthorIntroduction string `json:"authorIntroduction"` +// Directory string `json:"directory"` +// FilteredContentIntroduction string `json:"filteredContentIntroduction"` +// FilteredAuthorIntroduction string `json:"filteredAuthorIntroduction"` +// FilteredDirectory string `json:"filteredDirectory"` +// RightList []struct { +// Name string `json:"name"` +// Value string `json:"value"` +// } `json:"rightList"` +// LeftList []struct { +// Name string `json:"name"` +// Value string `json:"value"` +// } `json:"leftList"` +// BoughtNum int `json:"boughtNum"` +// ImageNum int `json:"imageNum"` +// ImageNumUrl string `json:"imageNumUrl"` +// CoverImageWater string `json:"coverImageWater"` +// CoverImageBig string `json:"coverImageBig"` +// CoverImageMid string `json:"coverImageMid"` +// CoverImageSmall string `json:"coverImageSmall"` +// RelatedCateList []string `json:"relatedCateList"` +// } `json:"result"` +// } +// +// // 解析JSON +// if err := json.Unmarshal(body, &apiResp); err != nil { +// return nil, fmt.Errorf("解析JSON失败: %w", err) +// } +// +// // 检查API响应状态 +// if !apiResp.Status { +// return nil, fmt.Errorf("API返回错误: %s (代码: %d)", apiResp.ErrMessage, apiResp.ErrCode) +// } +// +// bookInfo := &BookInfo{} +// result := apiResp.Result +// +// // 基本信息 +// bookInfo.BookName = result.BookName +// bookInfo.ISBN = result.Isbn +// bookInfo.Mid, _ = strconv.ParseInt(result.Mid, 10, 64) +// +// // 内容简介、作者简介、目录(优先使用过滤后的内容) +// bookInfo.Content = result.FilteredContentIntroduction +// if bookInfo.Content == "" { +// bookInfo.Content = result.ContentIntroduction +// } +// +// bookInfo.AuthorIntroduction = result.FilteredAuthorIntroduction +// if bookInfo.AuthorIntroduction == "" { +// bookInfo.AuthorIntroduction = result.AuthorIntroduction +// } +// +// bookInfo.Catalogue = result.FilteredDirectory +// if bookInfo.Catalogue == "" { +// bookInfo.Catalogue = result.Directory +// } +// +// // 买过人数 +// bookInfo.BuyCount = strconv.Itoa(result.BoughtNum) +// +// // 图书封面图 +// bookInfo.BookPic = result.CoverImageBig +// if bookInfo.BookPic == "" { +// bookInfo.BookPic = result.CoverImageMid +// } +// +// // 处理左侧信息(作者、出版社、出版时间、版次、ISBN、定价) +// for _, item := range result.LeftList { +// switch item.Name { +// case "作者": +// // 清理HTML标签,提取纯文本 +// bookInfo.Author = cleanHTMLText(item.Value) +// case "出版社": +// bookInfo.Publisher = cleanHTMLText(item.Value) +// case "出版时间": +// bookInfo.PublicationTime = validateDateFormat(item.Value) +// case "版次": +// bookInfo.Edition = item.Value +// case "ISBN": +// bookInfo.ISBN = item.Value +// case "定价": +// bookInfo.FixPrice = item.Value +// } +// } +// +// // 处理右侧信息(装帧、开本、页数、字数、分类等) +// for _, item := range result.RightList { +// switch item.Name { +// case "装帧": +// bookInfo.BindingLayout = item.Value +// case "开本": +// bookInfo.Format = item.Value +// case "页数": +// bookInfo.Pages = item.Value +// case "字数": +// bookInfo.Wordage = item.Value +// case "分类": +// bookInfo.Category = cleanHTMLText(item.Value) +// } +// } +// +// return bookInfo, nil +//} +// +//// 辅助函数:清理HTML标签,提取纯文本 +//func cleanHTMLText(html string) string { +// // 移除HTML标签 +// re := regexp.MustCompile(`<[^>]*>`) +// text := re.ReplaceAllString(html, "") +// +// // 替换HTML实体 +// text = strings.ReplaceAll(text, " ", " ") +// text = strings.ReplaceAll(text, "<", "<") +// text = strings.ReplaceAll(text, ">", ">") +// text = strings.ReplaceAll(text, "&", "&") +// text = strings.ReplaceAll(text, """, "\"") +// text = strings.ReplaceAll(text, "'", "'") +// +// // 清理空白字符 +// text = strings.TrimSpace(text) +// text = regexp.MustCompile(`\s+`).ReplaceAllString(text, " ") +// +// return text +//} +// +///* +// * 获取商品列表通过店铺ID +// * param shopId[int] 店铺ID +// * param proxy[string] 代理服务器IP +// * param retPrice[int] 是否需要价格 +// * param sortType[string] 排序类型 +// * param sort[string] 排序 +// * param priceMin[float32] 价格区间-低 +// * param priceMax[float32] 价格区间-高 +// * param pageNum[int] 页数 +// * param returnNum[int] 返回数量 +// * return 商品列表响应信息结构体,商品总数,总页数,错误信息 +// * Error 无效的排序类型: %s,可选值: sort, putDate, newItem, price +// * Error 无效的排序类型: %s,可选值: desc, asc +// */ +//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 +// if shopId == 0 { +// return nil, "", "", fmt.Errorf("店铺编码为空!") +// } +// // 判断是否有图片,设置默认值0 +// var isImageStr string +// if isImage == 0 { +// isImageStr = "0" +// } else { +// isImageStr = "1" +// } +// // 判断一页图书数量,设置默认值100 +// if returnNum == 0 { +// returnNum = 100 +// } +// // 判断页数,设置默认值1 +// if pageNum == 0 { +// pageNum = 1 +// } +// // 判断排序类型,设置默认值sort +// if sortType == "" { +// sortType = "sort" +// } else { +// validSortTypes := map[string]bool{ +// "sort": true, +// "putDate": true, +// "newItem": true, +// "price": true, +// } +// if !validSortTypes[sortType] { +// return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: sort, putDate, newItem, price", sortType) +// } +// } +// // 判断排序,设置默认值desc +// if sort == "" { +// sort = "desc" +// } else { +// validSorts := map[string]bool{ +// "desc": true, +// "asc": true, +// } +// if !validSorts[sort] { +// return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: desc, asc", sort) +// } +// } +// var kfzUrl string +// var pMin int +// pMin = 0 +// var pMax int +// pMax = 0 +// // 判断价格下限,设置默认值0 +// if priceMin == 0 && priceMax == 0 { +// // 调用的url +// 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 { +// 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 { +// 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 { +// 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) +// } +// +// // 发送请求(移除了重试机制) +// 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()), "/") +// if found { +// pNum = split +// } else { +// log.Printf("未找到页数!") +// } +// +// // 提取商品信息 +// infoDiv := doc.Find("div.list-content") +// var params ParamsInfo +// if infoDiv.Length() > 0 { +// item := infoDiv.Find("div.item.clearfix") +// for i := 0; i < item.Length(); i++ { +// s := item.Eq(i) +// book := BookInfo{} +// // 书名 +// book.BookName = strings.TrimSpace(s.Find("div.title a.link").Text()) +// // 提取ISBN +// book.ISBN = strings.TrimSpace(s.AttrOr("isbn", "")) +// // 店铺ID +// shopid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("shopid", ""))) +// book.ShopId = int64(shopid) +// // 商品ID +// itemid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("itemid", ""))) +// book.ItemId = int64(itemid) +// // 详情URL +// book.DetailUrl = s.Find("div.item-img a.img-box").AttrOr("href", "") +// // 价格 +// if retPrice == 0 { +// book.SellingPrice = s.Find("div.f_right.red.price").Find("span.bold").Text() +// params.Params = append(params.Params, struct { +// UserId string `json:"userId"` +// ItemId string `json:"itemId"` +// }{UserId: strings.TrimSpace(s.AttrOr("userid", "")), ItemId: strings.TrimSpace(s.AttrOr("itemid", ""))}) +// } +// books = append(books, book) +// } +// // 如果需要查询快递费 +// if params.Params != nil { +// params.Area = "13003000000" +// dataItem, err := getGoodsListShippingFee(params, proxy) +// if err != nil { +// fmt.Println("查询快递费失败") +// return nil, "", "", err +// } +// // 将快递费信息填充到图书信息中 +// for i := 0; i < len(books); i++ { +// itemId := fmt.Sprintf("%d", books[i].ItemId) +// for _, data := range dataItem { +// if itemId == data.ItemID { +// books[i].ExpressDeliveryFee = data.Fee[0].TotalFee +// } +// } +// } +// } +// } +// return books, goodsNum, pNum, nil +//} +// +///* +// * 获取商品信息通过商品详情链接 +// * param detailUrl[string] 详情页url +// * param proxy[string] 代理服务器IP +// * return 商品列表响应信息结构体,错误信息 +// * Error 未找到指定的contentSpan信息 +// * Error 无效的排序类型: %s,可选值: desc, asc +// */ +//func outGetGoodsMsgByDetailUrl(detailUrl, proxy string) (*BookInfo, error) { +// // 发送请求获取响应 +// response, err := fetchResponse(detailUrl, proxy) +// if err != nil { +// return nil, err +// } +// // 读取响应体 +// body, err := io.ReadAll(response.Body) +// if err != nil { +// return nil, err +// } +// // 解析HTML文档 +// document, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) +// if err != nil { +// return nil, err +// } +// // 获取商品信息的快递费 +// fee, err := getBookDetailShippingFee(detailUrl, proxy) +// if err != nil { +// return nil, err +// } +// book := BookInfo{} +// // 书名 +// book.BookName = strings.TrimSpace(document.Find("h1.title").Text()) +// // 提取作者、出版社等信息 +// topDiv := document.Find("div.keywords-define.keywords-define-1000.clear-fix") +// if topDiv.Length() > 0 { +// topDiv.Find("li").Each(func(i int, li *goquery.Selection) { +// titleSpan := li.Find("span.keywords-define-title") +// contentSpan := li.Find("span.keywords-define-txt") +// if contentSpan.Length() == 0 { +// fmt.Printf("未找到指定的contentSpan信息") +// } +// titleText := strings.TrimSpace(titleSpan.Text()) +// contentText := strings.TrimSpace(contentSpan.Text()) +// titleText = strings.TrimSpace(titleText) +// // 根据标题字段填充对应的图书信息字段 +// if strings.Contains(titleText, "作者") { +// book.Author = cleanString(contentText) +// } +// if strings.Contains(titleText, "出版社") { +// book.Publisher = contentText +// } +// if strings.Contains(titleText, "出版人") { +// book.Publisher = contentText +// } +// if strings.Contains(titleText, "ISBN") { +// book.ISBN = contentText +// } +// if strings.Contains(titleText, "出版时间") { +// book.PublicationTime = validateDateFormat(contentText) +// } +// if strings.Contains(titleText, "版次") { +// book.Edition = contentText +// } +// if strings.Contains(titleText, "装帧") { +// book.BindingLayout = contentText +// } +// if strings.Contains(titleText, "开本") { +// book.Format = contentText +// } +// if strings.Contains(titleText, "页数") { +// book.Pages = contentText +// } +// if strings.Contains(titleText, "字数") { +// book.Wordage = contentText +// } +// if strings.Contains(titleText, "纸张") { +// book.Paper = contentText +// } +// if strings.Contains(titleText, "年代") { +// book.Era = contentText +// } +// if strings.Contains(titleText, "刻印方式") { +// book.EngravingMethod = contentText +// } +// if strings.Contains(titleText, "尺寸") { +// book.Dimensions = contentText +// } +// if strings.Contains(titleText, "册数") { +// book.VolumeNumber = contentText +// } +// }) +// } else { +// // 备选提取方式 +// botDiv := document.Find("div.detail-lists.clear-fix") +// botDiv.Find("li").Each(func(i int, li *goquery.Selection) { +// spanText := strings.TrimSpace(li.Find("span").Text()) +// spanText = strings.TrimSpace(spanText) +// if strings.Contains(li.Text(), "作者") { +// book.Author = cleanString(li.Text()) +// book.Author = strings.ReplaceAll(book.Author, "作者:", "") +// book.Author = strings.ReplaceAll(book.Author, "著", "") +// } +// if strings.Contains(li.Text(), "出版社") { +// book.Publisher = spanText +// } +// if strings.Contains(li.Text(), "出版时间") { +// book.PublicationTime = validateDateFormat(spanText) +// } +// if strings.Contains(li.Text(), "ISBN") { +// book.ISBN = spanText +// } +// if strings.Contains(li.Text(), "装帧") { +// book.BindingLayout = spanText +// } +// if strings.Contains(li.Text(), "开本") { +// book.Format = spanText +// } +// if strings.Contains(li.Text(), "纸张") { +// book.Paper = spanText +// } +// if strings.Contains(li.Text(), "版次") { +// book.Edition = spanText +// } +// if strings.Contains(li.Text(), "页数") { +// book.Pages = spanText +// } +// if strings.Contains(li.Text(), "字数") { +// book.Wordage = spanText +// } +// }) +// } +// +// // 提取商品图片 +// var imgUrls []string +// tpUl := document.Find("ul.lg-list") +// tpUl.Find("img").Each(func(i int, s *goquery.Selection) { +// dataImgUrl, exists := s.Attr("data-imgurl") +// if exists && dataImgUrl != "" { +// imgUrls = append(imgUrls, dataImgUrl) +// } +// }) +// book.BookPicS = strings.Join(imgUrls, ",") +// // 提取售价 +// price := document.Find("i.now-price-text").Text() +// priceN := regexp.MustCompile(`(\d+\.?\d*)`) +// if match := priceN.FindStringSubmatch(price); len(match) > 0 { +// book.SellingPrice = match[1] +// } +// // 提取定价 +// fixPrice := document.Find("span.origin-price-text.clearfix").Text() +// fixPriceN := regexp.MustCompile(`(\d+\.?\d*)`) +// if match := fixPriceN.FindStringSubmatch(fixPrice); len(match) > 0 { +// book.FixPrice = match[1] +// } +// // 提取品相 +// text := document.Find("span.quality-text-cot.clearfix i").Text() +// book.Condition = strings.TrimSpace(text) +// // 设置快递费 +// book.ExpressDeliveryFee = fee +// return &book, nil +//} +// +///* +// * 获取商品信息的快递费(定位到河南) +// * param url[string] 获取快递费url +// * param proxy[string] 代理服务器IP +// * return 商品列表响应信息结构体,错误信息 +// * Error 无效的店铺编码: %s +// * Error 无效的图书编码: %s +// * Error 请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// */ +//func getBookDetailShippingFee(url, proxy string) (string, error) { +// // 从URL中提取店铺ID和商品ID +// compile := regexp.MustCompile(`kongfz\.com/(\d+)/(\d+)`) +// match := compile.FindStringSubmatch(url) +// var shippingFee string +// var shopId int +// var itemId int +// if len(match) == 3 { +// // 提取店铺ID +// firstNum, err := strconv.Atoi(match[1]) +// if err != nil { +// return "", fmt.Errorf("无效的店铺编码: %s", match[1]) +// } +// shopId = firstNum +// // 提取商品ID +// secondNum, err := strconv.Atoi(match[2]) +// if err != nil { +// return "", fmt.Errorf("无效的图书编码: %s", match[2]) +// } +// itemId = secondNum +// } +// // 构建快递费查询URL +// shippingFeeUrl := fmt.Sprintf("https://book.kongfz.com/store-web/pc/v1/mould/calculateFee?area=13003000000&itemId=%d&shopId=%d", itemId, shopId) +// // 创建HTTP客户端 +// request := gorequest.New() +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(proxy) +// } +// // 设置超时和其他配置 +// request.Timeout(30 * time.Second) +// // 发送请求 +// resp, body, errs := request.Get(shippingFeeUrl). +// 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 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// // 检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return "", fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// calculateFee := struct { +// ErrCode int `json:"errCode"` +// ErrMessage string `json:"errMessage"` +// Result struct { +// FeeList []struct { +// FreeCondition string `json:"freeCondition"` +// ShippingID string `json:"shippingId"` +// ShippingName string `json:"shippingName"` +// ShippingValue string `json:"shippingValue"` +// } `json:"feeList"` +// FeeText string `json:"feeText"` +// } `json:"result"` +// Status bool `json:"status"` +// }{} +// // 解析JSON +// err := json.Unmarshal([]byte(body), &calculateFee) +// if err != nil { +// return "", fmt.Errorf("解析JSON失败: %v", err) +// } +// // 提取快递费 +// for _, fee := range calculateFee.Result.FeeList { +// shippingFee = fee.ShippingValue +// } +// return shippingFee, nil +//} +// +///* +// * 公用发送请求方法 +// * param url[string] url +// * param proxy[string] 代理服务器IP +// * return 商品列表响应信息结构体,错误信息 +// * Error 请求失败 +// * Error 响应为空 (尝试 %d/%d) +// * Error HTTP状态码: %d,HTTP请求失败: %s (尝试 %d/%d) +// * Error 代理认证失败 +// * Error 请求超时,经过 %d 次尝试,超时网址:%s +// * Error 网络连接错误,经过 %d 次尝试,错误网址:%s +// * Error 查询请求失败,经过 %d 次尝试: %v,失败网址:%s +// * Error HTTP错误 +// */ +//func fetchResponse(url, proxy string) (*http.Response, error) { +// log.Printf("调用的URL: %s", url) +// maxRetries := 3 +// var detailsResp *http.Response +// var errors []error +// // 重试机制 +// for attempt := 0; attempt < maxRetries; attempt++ { +// if attempt > 0 { +// log.Printf("第 %d 次重试请求...", attempt) +// // 重试前等待,使用指数退避策略 +// waitTime := time.Duration(attempt*attempt) * 1000 // 平方退避 +// log.Printf("等待 %v 后重试", waitTime) +// time.Sleep(waitTime) +// } +// // 根据是否有代理发送请求 +// if proxy != "" { +// detailsResp, _, errors = gorequest.New(). +// Get(url). +// Proxy(proxy). +// 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 proxy == "" { +// detailsResp, _, errors = gorequest.New(). +// Get(url). +// 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() +// } +// // 检查是否需要重试 +// shouldRetry := false +// if len(errors) > 0 { +// shouldRetry = true +// log.Printf("请求失败 (尝试 %d/%d): %v", attempt+1, maxRetries+1, errors) +// } else if detailsResp == nil { +// shouldRetry = true +// log.Printf("响应为空 (尝试 %d/%d)", attempt+1, maxRetries+1) +// } else if detailsResp.StatusCode != http.StatusOK { +// // 只对服务器错误进行重试,不对客户端错误重试 +// if detailsResp.StatusCode >= 500 { +// shouldRetry = true +// } +// log.Printf("HTTP状态码: %d,HTTP请求失败: %s (尝试 %d/%d)", detailsResp.StatusCode, detailsResp.Body, attempt+1, maxRetries+1) +// } +// // 如果不需要重试,跳出循环 +// if !shouldRetry { +// break +// } +// // 如果是最后一次尝试,不继续重试 +// if attempt == maxRetries { +// break +// } +// // 关闭响应体(如果存在) +// if detailsResp != nil && detailsResp.Body != nil { +// detailsResp.Body.Close() +// } +// } +// // 检测请求是否错误 +// 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 { +// return nil, fmt.Errorf("代理认证失败") +// } +// +// if timeoutError { +// return nil, fmt.Errorf("请求超时,经过 %d 次尝试,超时网址:%s", maxRetries+1, url) +// } +// +// if connectionError { +// return nil, fmt.Errorf("网络连接错误,经过 %d 次尝试,错误网址:%s", maxRetries+1, url) +// } +// return nil, fmt.Errorf("查询请求失败,经过 %d 次尝试: %v,失败网址:%s", maxRetries+1, errors, url) +// } +// // 检查HTTP状态码 +// if detailsResp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", detailsResp.Status) +// } +// return detailsResp, nil +//} +// +///* +// * 获取销量榜商品列表 +// * param catId[int] 分类ID +// * param proxy[string] 代理服务器IP +// * return isbn数组,错误信息 +// * Error 请求失败 +// * Error HTTP错误 +// * Error 解析JSON失败 +// * Error API返回错误: %s (代码: %d) +// */ +//func outGetTopGoodsListMsg(catId int, proxy string) ([]string, error) { +// // 构建请求URL +// url := fmt.Sprintf("https://item.kongfz.com/api/pc/getSellWellListDetail?page=1&pageSize=100&timeRank=2&catId=%d", catId) +// // 创建HTTP客户端 +// request := gorequest.New() +// // 设置代理(如果有提供代理URL) +// if proxy != "" { +// request.Proxy(proxy) +// } +// // 设置超时和其他配置 +// request.Timeout(30 * time.Second) +// // 发送GET请求 +// resp, body, errs := request.Get(url). +// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). +// Set("Accept", "application/json, text/plain, */*"). +// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). +// Set("Referer", "https://item.kongfz.com/"). +// End() +// // 错误处理 +// if len(errs) > 0 { +// return nil, fmt.Errorf("请求失败: %v", errs) +// } +// // 检查HTTP状态码 +// if resp.StatusCode != http.StatusOK { +// return nil, fmt.Errorf("HTTP错误: %s", resp.Status) +// } +// // 解析响应 +// var bookDetailResponse BookDetailResponse +// err := json.Unmarshal([]byte(body), &bookDetailResponse) +// if err != nil { +// return nil, fmt.Errorf("解析JSON失败: %v", err) +// } +// +// // 检查响应状态 +// if !bookDetailResponse.Status { +// return nil, fmt.Errorf("API返回错误: %s (代码: %d)", bookDetailResponse.ErrMessage, bookDetailResponse.ErrCode) +// } +// +// // 提取ISBN列表 +// var isbnList []string +// for _, item := range bookDetailResponse.Result.Data { +// if item.Isbn != "" { +// isbnList = append(isbnList, item.Isbn) +// } +// } +// // 去除重复的ISBN +// isbnList = removeDuplicateISBNs(isbnList) +// return isbnList, nil +//} +// +///* +// * 去除重复的ISBN +// * param isbns[[]string] isbn数组 +// * return isbn数组 +// */ +//func removeDuplicateISBNs(isbns []string) []string { +// seen := make(map[string]bool) +// var result []string +// for _, isbn := range isbns { +// if !seen[isbn] { +// seen[isbn] = true +// result = append(result, isbn) +// } +// } +// return result +//} +// +///* +// * 生成签名 +// * param params[map[string]interface{}] 生成签名的字段map +// * param appSecret[string] app密钥 +// * return 签名 +// */ +//func generateSign(params map[string]interface{}, appSecret string) string { +// // 获取所有键并排序 +// keys := make([]string, 0, len(params)) +// for k := range params { +// keys = append(keys, k) +// } +// sort.Strings(keys) +// +// // 拼接签名字符串 +// var signStr strings.Builder +// for _, k := range keys { +// // 跳过 sign 参数 +// if strings.ToLower(k) == "sign" { +// continue +// } +// // 获取参数值 +// value := "" +// if params[k] != nil { +// value = fmt.Sprintf("%v", params[k]) +// } +// // 按照文档格式:参数名+参数值 +// signStr.WriteString(k + value) +// } +// +// // 根据签名方法生成签名 +// signMethod := "md5" +// if method, ok := params["signMethod"].(string); ok { +// signMethod = strings.ToLower(method) +// } +// +// signString := signStr.String() +// +// // 使用MD5算法生成签名 +// if signMethod == "md5" { +// // MD5算法:md5(appSecret + signString + appSecret) +// data := appSecret + signString + appSecret +// hash := md5.Sum([]byte(data)) +// result := strings.ToUpper(fmt.Sprintf("%x", hash)) +// //fmt.Printf("Debug: MD5签名结果: %s\n", result) +// return result +// } +// +// return "" +//} +// +//// KwAPIResponse 孔网API响应结构体 +//type KwAPIResponse struct { +// ErrorResponse *ErrorResponse `json:"errorResponse"` // 错误响应 +// SuccessResponse []ShippingMethod `json:"successResponse"` // 成功响应 +// RequestId string `json:"requestId"` // 请求ID +// RequestMethod string `json:"requestMethod"` // 请求方法 +//} +// +//// ErrorResponse 孔网API错误响应结构体 +//type ErrorResponse struct { +// Code int `json:"code"` // 错误码 +// Msg string `json:"msg"` // 错误信息 +// SubCode *int `json:"subCode"` // 使用指针类型,因为可能是null +// SubMsg *string `json:"subMsg"` // 使用指针类型,因为可能是null +//} +// +//type ShippingMethod struct { +// ShippingId string `json:"shippingId"` // 配送方式ID +// ShippingName string `json:"shippingName"` // 配送方式名称 +// IsDefault bool `json:"isDefault"` // 是否默认配送方式 +// Companies []Company `json:"companies"` // 快递公司列表 +//} +// +//type Company struct { +// ShippingCom string `json:"shippingCom"` // 快递公司代号 +// ShippingComName string `json:"shippingComName"` // 快递公司名称 +// IsDefault bool `json:"isDefault"` // 是否默认 +//} +// +///* +// * 获取配送方式列表 +// * param appId[int] appkey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * return 配送方式列表结构体字符串,错误信息 +// */ +//func kongfzDeliveryMethodList(appId int, appSecret, accessToken string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.delivery.method.list", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// // 生成签名 +// sign := generateSign(params, appSecret) +// +// // 构建请求体 +// formData := map[string]interface{}{ +// "method": "kongfz.delivery.method.list", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "sign": sign, +// "simplify": 0, +// } +// +// // 发送POST请求 +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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", "*/*"). +// Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"). +// Type("form"). // 关键:明确指定为表单格式 +// Timeout(30 * time.Second). +// Send(formData). +// End() +// // 错误处理 +// if len(errs) > 0 { +// return "", fmt.Errorf("登录请求失败: %v", errs) +// } +// // 检查HTTP状态码 +// 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["ErrorResponse"] != nil { +// var kwAPIResponse KwAPIResponse +// if err := json.Unmarshal([]byte(body), &kwAPIResponse); err != nil { +// return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body) +// } +// return "", fmt.Errorf("请求失败: %v, 错误码: %d", kwAPIResponse.ErrorResponse.Msg, kwAPIResponse.ErrorResponse.Code) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +///* +// * 订单发货 +// * param appId[int] appkey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * param orderId[int] 订单编号 +// * param shippingId[string] 配送方式 +// * param shippingCom[string] 快递公司。当shippingId!=noLogistics时,此参数为必填。 +// * param shipmentNum[string] 快递单号。当shippingId!=noLogistics时,此参数为必填。 +// * param userDefined[string] 用户自定义物流公司。当shippingCom=other时,此参数为必填。 +// * param moreShipmentNum[string] 填写更多的快递单号,以逗号分隔。 +// * return 订单发货结构体字符串,错误信息 +// */ +//func kongfzOrderDeliver(appId int, appSecret, accessToken string, +// orderId int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// // 参数验证 +// if shippingId != "noLogistics" { +// if shippingCom == "" { +// return "", fmt.Errorf("当 shippingId 不等于 noLogistics 时,shippingCom 参数为必填。shippingId是: %v", shippingId) +// } +// if shipmentNum == "" { +// return "", fmt.Errorf("当 shippingId 不等于 noLogistics 时, shipmentNum 参数为必填。shippingId是: %v", shippingId) +// } +// } +// if shipmentNum == "other" { +// if userDefined == "" { +// return "", fmt.Errorf("当 shippingCom 等于 other 时, userDefined 参数为必填。shipmentNum是: %v", shipmentNum) +// } +// } +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.order.deliver", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// "orderId": orderId, +// "shippingId": shippingId, +// "shippingCom": shippingCom, +// "shipmentNum": shipmentNum, +// "userDefined": userDefined, +// "moreShipmentNum": moreShipmentNum, +// } +// // 生成签名 +// sign := generateSign(params, appSecret) +// +// // 构建请求体 +// formData := map[string]interface{}{ +// "method": "kongfz.order.deliver", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "sign": sign, +// "simplify": 0, +// "orderId": orderId, +// "shippingId": shippingId, +// "shippingCom": shippingCom, +// "shipmentNum": shipmentNum, +// "userDefined": userDefined, +// "moreShipmentNum": moreShipmentNum, +// } +// // 发送POST请求 +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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", "*/*"). +// Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"). +// Type("form"). // 关键:明确指定为表单格式 +// Timeout(30 * time.Second). +// Send(formData). +// End() +// // 错误处理 +// if len(errs) > 0 { +// return "", fmt.Errorf("登录请求失败: %v", errs) +// } +// // 检查HTTP状态码 +// 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["ErrorResponse"] != nil { +// var kwAPIResponse KwAPIResponse +// if err := json.Unmarshal([]byte(body), &kwAPIResponse); err != nil { +// return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body) +// } +// return "", fmt.Errorf("请求失败: %v, 错误码: %d", kwAPIResponse.ErrorResponse.Msg, kwAPIResponse.ErrorResponse.Code) +// } +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +///* +// * 孔网订单同步 +// * param appId[int] appkey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * param shippingComName[string] 快递公司名称 +// * param orderId[int] 订单编号 +// * param shippingId[string] 配送方式 +// * param shippingCom[string] 快递公司。当shippingId!=noLogistics时,此参数为必填。 +// * param shipmentNum[string] 快递单号。当shippingId!=noLogistics时,此参数为必填。 +// * param userDefined[string] 用户自定义物流公司。当shippingCom=other时,此参数为必填。 +// * param moreShipmentNum[string] 填写更多的快递单号,以逗号分隔。 +// * return 订单同步结构体字符串,错误信息 +// */ +//func kongfzOrderSynchronization(appId int, appSecret, accessToken string, shippingComName string, +// orderId int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum string) (string, error) { +// // 获取配送方式列表 +// deliveryMethodList, err := kongfzDeliveryMethodList(appId, appSecret, accessToken) +// if err != nil { +// return "", err +// } +// // 解析配送方式 +// var kw KwAPIResponse +// if err := json.Unmarshal([]byte(deliveryMethodList), &kw); err != nil { +// return "", fmt.Errorf("解析JSON失败: %v", err) +// } +// // 根据快递公司名称查找对应的配送方式和快递公司代号 +// for _, shippingMethod := range kw.SuccessResponse { +// for _, companies := range shippingMethod.Companies { +// if shippingComName == companies.ShippingComName { +// shippingId = shippingMethod.ShippingId +// shippingCom = companies.ShippingCom +// } +// } +// } +// // 如果未找到,使用默认值 +// if shippingId == "" { +// shippingId = "express" +// shippingCom = "other" +// userDefined = shippingComName +// } +// // 执行订单发货 +// orderDeliver, err := kongfzOrderDeliver(appId, appSecret, accessToken, orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum) +// if err != nil { +// return "", err +// } +// return orderDeliver, err +//} +// +///* +// * 上传图片接口 +// * param appId[int] appKey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * param filePath[string] 图片url/本地图片路径 +// * param savePath[string] 图片下载路径,如果是图片url需要下载到本地 +// * return 上传图片响应结构体,错误信息 +// * Error 解析图片URL失败 +// * Error 读取文件失败 +// * Error 创建表单文件失败 +// * Error 写入文件数据失败 +// */ +//func kongfzImageUpload(appId int, appSecret, accessToken string, filePath, savePath string) (string, error) { +// var image string +// var needCleanup bool = false // 标记是否需要清理 +// defer func() { +// // 函数结束时删除临时图片 +// if needCleanup && image != "" { +// if err := os.Remove(image); err != nil { +// log.Printf("警告:删除临时图片失败 %s: %v", image, err) +// } else { +// log.Printf("已清理临时图片: %s", image) +// } +// } +// }() +// if strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://") { +// // 解析URL +// parse, err := url.Parse(filePath) +// if err != nil { +// return "", fmt.Errorf("解析图片URL失败: %v", err) +// } +// // 获取路径部分 +// fPath := parse.Path +// // 获取文件名 +// fileName := path.Base(fPath) +// savePath = filepath.Join(savePath, fileName) +// image, err = downloadImage(filePath, savePath) +// if err != nil { +// return "", err +// } +// needCleanup = true // 标记需要清理 +// } else { +// // 本地文件 +// image = filePath +// // 本地文件不需要清理,由调用者管理 +// } +// +// kUrl := fmt.Sprint("https://open.kongfz.com/router/image/upload") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.image.upload", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// params["bucket"] = "book" +// // 生成签名 +// sign := generateSign(params, appSecret) +// +// // 创建multipart/form-data请求体 +// body := &bytes.Buffer{} +// writer := multipart.NewWriter(body) +// // 读取文件内容 +// fileData, err := os.ReadFile(image) +// if err != nil { +// return "", fmt.Errorf("读取文件失败: %v", err) +// } +// // 添加文件字段 +// part, err := writer.CreateFormFile("image", filepath.Base(image)) +// if err != nil { +// return "", fmt.Errorf("创建表单文件失败: %v", err) +// } +// _, err = part.Write(fileData) +// if err != nil { +// return "", fmt.Errorf("写入文件数据失败: %v", err) +// } +// +// // 添加其他参数到multipart表单 +// for key, value := range params { +// writer.WriteField(key, fmt.Sprintf("%v", value)) +// } +// writer.WriteField("sign", sign) // 添加签名 +// writer.Close() // 关闭writer,完成表单构建 +// +// // 发送POST请求 +// request := gorequest.New() +// resp, respBody, errs := request.Post(kUrl). +// Timeout(30*time.Second). +// Set("Content-Type", writer.FormDataContentType()). // 设置Content-Type +// Send(body.String()). // 发送请求体 +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(respBody), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +///* +// * 添加店铺商品 +// * param appId[int] appKey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * param shopItemAddJson[string] 店铺商品请求结构体字符串 +// * return 添加店铺商品响应结构体,错误信息 +// */ +//func kongfzShopItemAdd(appId int, appSecret, accessToken string, shopItemAddJson string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.shop.item.add", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// +// if shopItemAddJson == "" { +// return "", fmt.Errorf("shopItemAddJson 参数为空!") +// } +// toParams, err := addStructToParams(shopItemAddJson, params) +// if err != nil { +// return "", err +// } +// // 生成签名 +// sign := generateSign(toParams, appSecret) +// if sign == "" { +// return "", fmt.Errorf("生成 sign 签名错误!") +// } +// params["sign"] = sign +// +// request := gorequest.New() +// resp, body, errs := request.Get(kUrl). +// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). +// Set("Accept", "application/json, text/plain, */*"). +// Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). +// Timeout(30 * time.Second). +// Send(params). +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(body), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +//// 订单修改发货单号 +//func kongfzOrderRedeliver(appId int, appSecret, accessToken, requestJson string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.order.redeliver", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// +// if requestJson == "" { +// return "", fmt.Errorf("orderListJson 参数为空!") +// } +// toParams, err := addStructToParams(requestJson, params) +// if err != nil { +// return "", err +// } +// // 生成签名 +// sign := generateSign(toParams, appSecret) +// if sign == "" { +// return "", fmt.Errorf("生成 sign 签名错误!") +// } +// params["sign"] = sign +// +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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"). +// Timeout(30 * time.Second). +// Send(params). +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(body), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +//// 订单标记添加/修改 +//func kongfzOrderFlagAdd(appId int, appSecret, accessToken, requestJson string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.order.flag.add", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// +// if requestJson == "" { +// return "", fmt.Errorf("orderListJson 参数为空!") +// } +// toParams, err := addStructToParams(requestJson, params) +// if err != nil { +// return "", err +// } +// // 生成签名 +// sign := generateSign(toParams, appSecret) +// if sign == "" { +// return "", fmt.Errorf("生成 sign 签名错误!") +// } +// params["sign"] = sign +// +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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"). +// Timeout(30 * time.Second). +// Send(params). +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(body), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +//// 订单标记删除 +//func kongfzOrderFlagDel(appId int, appSecret, accessToken, requestJson string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.order.flag.del", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// +// if requestJson == "" { +// return "", fmt.Errorf("orderListJson 参数为空!") +// } +// toParams, err := addStructToParams(requestJson, params) +// if err != nil { +// return "", err +// } +// // 生成签名 +// sign := generateSign(toParams, appSecret) +// if sign == "" { +// return "", fmt.Errorf("生成 sign 签名错误!") +// } +// params["sign"] = sign +// +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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"). +// Timeout(30 * time.Second). +// Send(params). +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(body), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +///* +// * 查询订单列表 +// * param appId[int] appKey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * param orderListJson[string] 查询订单请求结构体字符串 +// * return 添加店铺商品响应结构体,错误信息 +// */ +//func kongfzOrderList(appId int, appSecret, accessToken string, orderListJson string) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.order.list", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// if orderListJson == "" { +// return "", fmt.Errorf("orderListJson 参数为空!") +// } +// toParams, err := addStructToParams(orderListJson, params) +// if err != nil { +// return "", err +// } +// // 生成签名 +// sign := generateSign(toParams, appSecret) +// if sign == "" { +// return "", fmt.Errorf("生成 sign 签名错误!") +// } +// params["sign"] = sign +// +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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"). +// Timeout(30 * time.Second). +// Send(params). +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(body), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +///* +// * 查询单个订单 +// * param appId[int] appKey +// * param appSecret[string] app密钥 +// * param accessToken[string] 访问token +// * param userType[string] 查询订单请求结构体字符串 +// * param orderId[string] 查询订单请求结构体字符串 +// * return 添加店铺商品响应结构体,错误信息 +// */ +//func kongfzOrderGet(appId int, appSecret, accessToken string, userType string, orderId int) (string, error) { +// kUrl := fmt.Sprint("https://open.kongfz.com/router/rest") +// dateTime := getCurrentTimeGMT8() +// +// // 生成签名参数 +// params := map[string]interface{}{ +// "method": "kongfz.order.get", +// "appId": appId, +// "accessToken": accessToken, +// "datetime": dateTime, +// "format": "json", +// "v": "1.0", +// "signMethod": "md5", +// "simplify": 0, +// } +// params["userType"] = userType +// params["orderId"] = strconv.Itoa(orderId) +// // 生成签名 +// sign := generateSign(params, appSecret) +// if sign == "" { +// return "", fmt.Errorf("生成 sign 签名错误!") +// } +// params["sign"] = sign +// +// request := gorequest.New() +// resp, body, errs := request.Post(kUrl). +// 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"). +// Timeout(30 * time.Second). +// Send(params). +// End() +// if len(errs) > 0 { +// return "", fmt.Errorf("请求失败: %v", errs) +// } +// +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body) +// } +// +// // 解析响应 +// var response map[string]interface{} +// if err := json.Unmarshal([]byte(body), &response); err != nil { +// return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body) +// } +// +// // 转换成json字符串 +// responseJSON, err := json.Marshal(response) +// if err != nil { +// return "", fmt.Errorf("JSON序列化失败: %v", err) +// } +// return string(responseJSON), nil +//} +// +//// =============== 辅助函数 ============== +///* +// * 将结构体的字段添加到 params 映射中 +// * param req[string] 需要合并的json字符串 +// * param params[map[string]interface{}] 合并的map +// * return map响应结构体,错误信息 +// */ +//func addStructToParams(req string, params map[string]interface{}) (map[string]interface{}, error) { +// // 将JSON字符串解析为map +// var tempMap map[string]interface{} +// if err := json.Unmarshal([]byte(req), &tempMap); err != nil { +// return nil, fmt.Errorf("解析 req json失败:%v ", err) +// } +// // 合并到params +// for k, v := range tempMap { +// // 只添加非nil的值 +// if v != nil { +// params[k] = v +// } +// } +// return params, nil +//} +// +///* +// * 下载图片到本地 +// * param filePath[string] 文件路径 +// * param savePath[string] 需要保存的路径 +// * return 本地图片路径,错误信息 +// */ +//func downloadImage(filePath, savePath string) (string, error) { +// // 创建HTTP请求 +// request := gorequest.New() +// resp, body, err := request.Get(filePath). +// Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). +// EndBytes() +// if err != nil { +// return "", fmt.Errorf("请求失败: %v", err) +// } +// if resp.StatusCode != 200 { +// return "", fmt.Errorf("HTTP 状态码错误: %d", resp.StatusCode) +// } +// errs := os.WriteFile(savePath, body, 0644) +// if errs != nil { +// return "", fmt.Errorf("保存文件失败: %v", errs) +// } +// return savePath, nil +//} +// +///* +// * 获取GMT+8当前时间的字符串格式 +// * return 返回时间字符串,格式:2006-01-02 15:04:05 +// */ +//func getCurrentTimeGMT8() string { +// // 创建北京时间(GMT+8) +// loc, err := time.LoadLocation("Asia/Shanghai") +// if err != nil { +// loc = time.FixedZone("GMT+8", 8*60*60) +// } +// now := time.Now().In(loc) +// return now.Format("2006-01-02 15:04:05") +//} +// +///* +// * 替换所有空白字符为空格 +// * return 返回字符串 +// */ +//func cleanString(s string) string { +// s = strings.ReplaceAll(s, "\n", "") +// s = strings.ReplaceAll(s, "\r", "") +// s = strings.ReplaceAll(s, "\t", "") +// s = strings.ReplaceAll(s, " ", "") +// return removeDuplicates(s) +//} +// +///* +// * 字符串去重 +// * return 返回字符串 +// */ +//func removeDuplicates(s string) string { +// seen := make(map[rune]bool) +// var result strings.Builder +// for _, r := range s { +// if !seen[r] { +// seen[r] = true +// result.WriteRune(r) +// } +// } +// return result.String() +//} +// +///* +// * 验证日期格式并转换为时间戳 +// * param dateStr[string] 时间字符串 +// * return 时间戳 +// */ +//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() +//} +// +//// 初始化配置--暂时没用 +//func initializeConfig(config Config) { +// // 设置全局配置 +// cf = config +//} +// +//// =================== C 导出函数 ======================= +// +//// OutKfzLogin 孔网登录 +//// +////export OutKfzLogin +//func OutKfzLogin(username, password *C.char) *C.char { +// goUsername := C.GoString(username) +// goPassword := C.GoString(password) +// respToken, err := outKfzLogin(goUsername, goPassword) +// // 构建响应数据 +// resp := struct { +// Token string `json:"token"` +// }{ +// Token: respToken, +// } +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: resp, +// } +// } +// // 转换为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)) +//} +// +//// OutKfzGetUserInfo 获取孔网用户信息 +//// +////export OutKfzGetUserInfo +//func OutKfzGetUserInfo(token *C.char) *C.char { +// goToken := C.GoString(token) +// userInfo, err := outKfzGetUserInfo(goToken) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: userInfo, +// } +// } +// // 转换为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)) +//} +// +//// OutGetKfzShippingTemplate 获取运费模板信息 +//// +////export OutGetKfzShippingTemplate +//func OutGetKfzShippingTemplate(token *C.char) *C.char { +// tokenStr := C.GoString(token) +// template, err := outGetKfzShippingTemplate(tokenStr) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: template, +// } +// } +// // 转换为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)) +//} +// +//// OutGetGoodsTplMsg 获取商品模版--已登的店铺 +//// +////export OutGetGoodsTplMsg +//func OutGetGoodsTplMsg(token, proxy, itemId *C.char) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goItemId := C.GoString(itemId) +// info, err := outGetGoodsTplMsg(goToken, goProxy, goItemId) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: info, +// } +// } +// // 转换为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)) +//} +// +//// OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺 +//// +////export OutGetGoodsListMsgFromSelfShop +//func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char, +// endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goItemSn := C.GoString(itemSn) +// goPriceMin := C.GoString(priceMin) +// goPriceMax := C.GoString(priceMax) +// goStartCreateTime := C.GoString(startCreateTime) +// goEndCreateTime := C.GoString(endCreateTime) +// goRequestType := C.GoString(requestType) +// goIsItemSnEqual := int(isItemSnEqual) +// goPage := int(page) +// goSize := int(size) +// info, err := outGetGoodsListMsgFromSelfShop(goToken, goProxy, goItemSn, goPriceMin, goPriceMax, goStartCreateTime, goEndCreateTime, goRequestType, goIsItemSnEqual, goPage, goSize) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: info, +// } +// } +// // 转换为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)) +//} +// +//// OutAddGoods 新增商品-已登的店铺 +//// +////export OutAddGoods +//func OutAddGoods(token, proxy, formData *C.char) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goFormData := C.GoString(formData) +// info, err := outAddGoods(goToken, goProxy, goFormData) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: info, +// } +// } +// // 转换为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)) +//} +// +//// OutAddGoodsAndFile 整合添加商品和获取孔网图片 +//// +////export OutAddGoodsAndFile +//func OutAddGoodsAndFile(token, proxy, filePath, formData *C.char) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goFilePath := C.GoString(filePath) +// goFormData := C.GoString(formData) +// addGoodsAndFile, err := outAddGoodsAndFile(goToken, goProxy, goFilePath, goFormData) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(addGoodsAndFile) +//} +// +//// OutDelGoodsFromSelfShop 删除商品-已登的店铺 +//// +////export OutDelGoodsFromSelfShop +//func OutDelGoodsFromSelfShop(token, proxy, itemId *C.char) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goItemId := C.GoString(itemId) +// info, err := outDelGoodsFromSelfShop(goToken, goProxy, goItemId) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: info, +// } +// } +// // 转换为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)) +//} +// +//// OutGetImageFilterShopId 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤 +//// +////export OutGetImageFilterShopId +//func OutGetImageFilterShopId(token, proxy, isbn *C.char, shopId C.int, isLiveImage C.int, isReturnMsg C.int) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goIsbn := C.GoString(isbn) +// goShopId := int(shopId) +// goIsLiveImage := int(isLiveImage) +// goIsReturnMsg := int(isReturnMsg) +// info, err := outGetImageFilterShopId(goToken, goProxy, goIsbn, goShopId, goIsLiveImage, goIsReturnMsg) +// // 构建API响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: info, +// } +// } +// // 转换为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)) +//} +// +//// OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图) +//// +////export OutGetImageByIsbn +//func OutGetImageByIsbn(token, proxy, isbn *C.char, isLiveImage C.int, isReturnMsg C.int) *C.char { +// goToken := C.GoString(token) +// goProxy := C.GoString(proxy) +// goIsbn := C.GoString(isbn) +// goIsLiveImage := int(isLiveImage) +// goIsReturnMsg := int(isReturnMsg) +// bookInfo, err := outGetImageByIsbn(goToken, goProxy, goIsbn, goIsLiveImage, goIsReturnMsg) +// // 构建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)) +//} +// +//// 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(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) +// goIsImage := int(isImage) +// goSortType := C.GoString(sortType) +// goSort := C.GoString(sort) +// goPriceMin := float32(priceMin) +// goPriceMax := float32(priceMax) +// goPageNum := int(pageNum) +// goReturnNum := int(returnNum) +// books, num, pNum, err := outGetGoodsListMsgByShopId(goToken, goShopId, goProxy, goRetPrice, goIsImage, +// goSortType, goSort, goPriceMin, goPriceMax, goPageNum, goReturnNum) +// // 构建统一格式的响应 +// bookInfo := struct { +// GoodsNum string `json:"goods_num,omitempty"` +// PNum string `json:"pnum,omitempty"` +// BookInfo interface{} `json:"book_info,omitempty"` +// }{ +// GoodsNum: num, +// PNum: pNum, +// BookInfo: books, +// } +// // 构建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)) +//} +// +//// OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接 +//// +////export OutGetGoodsMsgByDetailUrl +//func OutGetGoodsMsgByDetailUrl(detailUrl, proxy *C.char) *C.char { +// goDetailUrl := C.GoString(detailUrl) +// goProxy := C.GoString(proxy) +// response, err := outGetGoodsMsgByDetailUrl(goDetailUrl, goProxy) +// // 构建统一格式的响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: response, +// } +// } +// // 转换为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)) +//} +// +//// OutGetTopGoodsListMsg 获取销量榜商品列表 +//// +////export OutGetTopGoodsListMsg +//func OutGetTopGoodsListMsg(catId C.int, proxy *C.char) *C.char { +// goCatId := int(catId) +// goProxy := C.GoString(proxy) +// response, err := outGetTopGoodsListMsg(goCatId, goProxy) +// // 构建统一格式的响应 +// var apiResponse APIResponse +// if err != nil { +// apiResponse = APIResponse{ +// Success: false, +// Message: err.Error(), +// } +// } else { +// apiResponse = APIResponse{ +// Success: true, +// Data: response, +// } +// } +// // 转换为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)) +//} +// +//// KongfzDeliveryMethodList 获取配送方式列表 +//// +////export KongfzDeliveryMethodList +//func KongfzDeliveryMethodList(appId C.int, appSecret, accessToken *C.char) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// list, err := kongfzDeliveryMethodList(goAppId, goAppSecret, goAccessToken) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(list) +//} +// +//// KongfzOrderDeliver 订单发货 +//// +////export KongfzOrderDeliver +//func KongfzOrderDeliver(appId C.int, appSecret, accessToken *C.char, +// orderId C.int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum *C.char) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// goOrderId := int(orderId) +// goShippingId := C.GoString(shippingId) +// goShippingCom := C.GoString(shippingCom) +// goShipmentNum := C.GoString(shipmentNum) +// goUserDefined := C.GoString(userDefined) +// goMoreShipmentNum := C.GoString(moreShipmentNum) +// deliver, err := kongfzOrderDeliver(goAppId, goAppSecret, goAccessToken, +// goOrderId, goShippingId, goShippingCom, goShipmentNum, goUserDefined, goMoreShipmentNum) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(deliver) +//} +// +//// KongfzOrderSynchronization 孔网订单同步 +//// +////export KongfzOrderSynchronization +//func KongfzOrderSynchronization(appId C.int, appSecret, accessToken, shippingComName *C.char, +// orderId C.int, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum *C.char) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// goShippingComName := C.GoString(shippingComName) +// goOrderId := int(orderId) +// goShippingId := C.GoString(shippingId) +// goShippingCom := C.GoString(shippingCom) +// goShipmentNum := C.GoString(shipmentNum) +// goUserDefined := C.GoString(userDefined) +// goMoreShipmentNum := C.GoString(moreShipmentNum) +// synchronization, err := kongfzOrderSynchronization(goAppId, goAppSecret, goAccessToken, goShippingComName, +// goOrderId, goShippingId, goShippingCom, goShipmentNum, goUserDefined, goMoreShipmentNum) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(synchronization) +//} +// +//// KongfzImageUpload 上传图片接口 +//// +////export KongfzImageUpload +//func KongfzImageUpload(appId C.int, appSecret, accessToken, filePath, savePath *C.char) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// goFilePath := C.GoString(filePath) +// goSavePath := C.GoString(savePath) +// upload, err := kongfzImageUpload(goAppId, goAppSecret, goAccessToken, goFilePath, goSavePath) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(upload) +//} +// +//// KongfzShopItemAdd 添加店铺商品 +//// +////export KongfzShopItemAdd +//func KongfzShopItemAdd(appId C.int, appSecret, accessToken, shopItemAddJson *C.char) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// goShopItemAddJson := C.GoString(shopItemAddJson) +// upload, err := kongfzShopItemAdd(goAppId, goAppSecret, goAccessToken, goShopItemAddJson) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(upload) +//} +// +//// KongfzOrderList 查询订单列表 +//// +////export KongfzOrderList +//func KongfzOrderList(appId C.int, appSecret, accessToken *C.char, orderListJson *C.char) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// goOrderListJson := C.GoString(orderListJson) +// list, err := kongfzOrderList(goAppId, goAppSecret, goAccessToken, goOrderListJson) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(list) +//} +// +//// KongfzOrderGet 查询单个订单 +//// +////export KongfzOrderGet +//func KongfzOrderGet(appId C.int, appSecret, accessToken *C.char, userType *C.char, orderId C.int) *C.char { +// goAppId := int(appId) +// goAppSecret := C.GoString(appSecret) +// goAccessToken := C.GoString(accessToken) +// goUserType := C.GoString(userType) +// goOrderId := int(orderId) +// orderGet, err := kongfzOrderGet(goAppId, goAppSecret, goAccessToken, goUserType, goOrderId) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(orderGet) +//} +// +//// KongfzOrderRedeliver 订单修改发货单号 +//// +////export KongfzOrderRedeliver +//func KongfzOrderRedeliver(appId C.int, appSecret, accessToken *C.char, requestJson *C.char) *C.char { +// appIdStr := int(appId) +// appSecretStr := C.GoString(appSecret) +// accessTokenStr := C.GoString(accessToken) +// requestJsonStr := C.GoString(requestJson) +// info, err := kongfzOrderRedeliver(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(info) +//} +// +//// KongfzOrderFlagAdd 订单标记添加/修改 +//// +////export KongfzOrderFlagAdd +//func KongfzOrderFlagAdd(appId C.int, appSecret, accessToken *C.char, requestJson *C.char) *C.char { +// appIdStr := int(appId) +// appSecretStr := C.GoString(appSecret) +// accessTokenStr := C.GoString(accessToken) +// requestJsonStr := C.GoString(requestJson) +// info, err := kongfzOrderFlagAdd(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(info) +//} +// +//// KongfzOrderFlagDel 订单标记删除 +//// +////export KongfzOrderFlagDel +//func KongfzOrderFlagDel(appId C.int, appSecret, accessToken *C.char, requestJson *C.char) *C.char { +// appIdStr := int(appId) +// appSecretStr := C.GoString(appSecret) +// accessTokenStr := C.GoString(accessToken) +// requestJsonStr := C.GoString(requestJson) +// info, err := kongfzOrderFlagDel(appIdStr, appSecretStr, accessTokenStr, requestJsonStr) +// if err != nil { +// return C.CString(fmt.Sprint(err)) +// } +// return C.CString(info) +//} +// +//// Initialize 初始化配置 +//// +////export Initialize +//func Initialize(configJSON *C.char) *C.char { +// configStr := C.GoString(configJSON) +// log.Printf("[DEBUG] 接收到的配置JSON: %s", configStr) +// +// var config Config +// if err := json.Unmarshal([]byte(configStr), &config); err != nil { +// return C.CString(fmt.Sprintf(`{"success":false,"message":"配置解析失败: %v"}`, err)) +// } +// initializeConfig(config) +// +// return C.CString(`{"success":true,"message":"初始化成功"}`) +//} +// +//// FreeCString 释放C字符串内存 +//// +////export FreeCString +//func FreeCString(str *C.char) { +// C.free(unsafe.Pointer(str)) +//} +// +//// KfzVersion 版本号 +//const ( +// KfzVersion = "v3" +//) +// +//// GetVersion 获取版本信息 +//// +////export GetVersion +//func GetVersion() *C.char { +// return C.CString(KfzVersion) +//} +// +//// 空main函数,编译DLL时需要 +//func main() { +//} diff --git a/listener/ceshi.go b/listener/ceshi.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/listener/ceshi.go @@ -0,0 +1 @@ +package main diff --git a/listener/listener.go b/listener/listener.go index 7ea564d..e31a789 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -1,71 +1,351 @@ package main import ( + "bytes" + "context" + "encoding/json" "fmt" - "log" + "io" "net/http" + "net/http/cookiejar" + "net/url" + "os" + "path/filepath" + "strings" "time" + + "github.com/chromedp/chromedp" ) -func main() { - // 定义路由 - http.HandleFunc("/", homeHandler) - http.HandleFunc("/api/data", dataHandler) - http.HandleFunc("/ws", websocketHandler) +//func main() { +// username := "15140030829" +// password := "long616" +// downloadDir := "./downloads" +// os.MkdirAll(downloadDir, 0755) +// +// // 1. API 登录获取 Cookie +// fmt.Println("正在登录...") +// cookieJar, err := loginAndGetCookies(username, password) +// if err != nil { +// fmt.Printf("登录失败: %v\n", err) +// return +// } +// fmt.Println("✓ 登录成功") +// +// // 2. 使用 chromedp 的远程浏览器(自动下载) +// targetURL := "https://seller.kongfz.com/management/item/unsold/?tab=onSale" +// var downloadPath string +// +// // 创建 chromedp 上下文 +// ctx, cancel := createChromeContext() +// defer cancel() +// +// // 设置超时 +// ctx, cancel = context.WithTimeout(ctx, 5*time.Minute) +// defer cancel() +// +// // 执行操作 +// err = chromedp.Run(ctx, +// // 注入 Cookie +// chromedp.ActionFunc(func(ctx context.Context) error { +// fmt.Println("正在注入 Session Cookie...") +// return injectCookies(ctx, cookieJar) +// }), +// +// // 导航到目标页面 +// chromedp.Navigate(targetURL), +// chromedp.Sleep(5*time.Second), +// +// // 等待页面加载完成 +// chromedp.WaitVisible("body", chromedp.ByQuery), +// chromedp.Sleep(2*time.Second), +// +// // 点击下载按钮 +// chromedp.ActionFunc(func(ctx context.Context) error { +// fmt.Println("查找并点击下载按钮...") +// return clickDownloadButton(ctx) +// }), +// +// // 等待下载完成 +// chromedp.ActionFunc(func(ctx context.Context) error { +// fmt.Println("等待文件下载...") +// path, err := waitForDownload(downloadDir, 90*time.Second) +// if err != nil { +// return err +// } +// downloadPath = path +// return nil +// }), +// ) +// +// if err != nil { +// fmt.Printf("错误: %v\n", err) +// //// 打印更多错误信息 +// //if chromedpErr, ok := err.(*chromedp.Err); ok { +// // fmt.Printf("Chromedp 错误详情: %v\n", chromedpErr) +// //} +// return +// } +// +// fmt.Printf("✓ 文件已保存: %s\n", downloadPath) +//} - // 中间件示例 - wrappedHandler := loggingMiddleware(http.DefaultServeMux) +// createChromeContext 创建 Chrome 上下文 +func createChromeContext() (context.Context, context.CancelFunc) { + // 设置 Chrome 选项 + opts := append(chromedp.DefaultExecAllocatorOptions[:], + chromedp.Flag("disable-blink-features", "AutomationControlled"), + chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"), + chromedp.Flag("headless", false), // 设置为 false 可以看到浏览器窗口 + chromedp.Flag("no-sandbox", true), + chromedp.Flag("disable-dev-shm-usage", true), + chromedp.Flag("disable-gpu", true), + chromedp.Flag("disable-web-security", false), + chromedp.Flag("disable-extensions", true), + chromedp.Flag("disable-plugins", true), + // 设置下载目录 + chromedp.Flag("disable-default-apps", true), + chromedp.Flag("disable-background-networking", true), + // 指定下载路径(如果需要) + // chromedp.Flag("download.default_directory", downloadDir), + ) - // 配置服务器 - server := &http.Server{ - Addr: ":8080", - Handler: wrappedHandler, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 60 * time.Second, + allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) + ctx, _ := chromedp.NewContext(allocCtx) + + return ctx, cancel +} + +// loginAndGetCookies 登录 API +func loginAndGetCookies(username, password string) (*cookiejar.Jar, error) { + loginURL := "https://login.kongfz.com/Pc/Login/account" + + reqBody := map[string]interface{}{ + "loginName": username, + "loginPass": password, + "captchaCode": "", + "autoLogin": 0, + "newUsername": "", + "returnUrl": "https://seller.kongfz.com/management/item/unsold/?tab=onSale", + "captchaId": "", } - fmt.Println("HTTP Server listening on :8080") + jsonData, _ := json.Marshal(reqBody) - // 启动服务器 - if err := server.ListenAndServe(); err != nil { - log.Fatal("Server error:", err) + jar, _ := cookiejar.New(nil) + client := &http.Client{ + Jar: jar, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return nil // 允许重定向 + }, } -} -func homeHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return + req, _ := http.NewRequest("POST", loginURL, bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01") + req.Header.Set("Referer", "https://login.kongfz.com/") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) } - fmt.Fprintf(w, "Welcome to HTTP Server!\n") -} + defer resp.Body.Close() -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) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) } + + fmt.Printf("登录响应: %s\n", string(body)) + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + if code, ok := result["code"]; ok { + switch v := code.(type) { + case float64: + if v != 0 { + return nil, fmt.Errorf("登录失败: %v", result["msg"]) + } + case int: + if v != 0 { + return nil, fmt.Errorf("登录失败: %v", result["msg"]) + } + } + } + + // 获取并打印所有 Cookie + cookies := jar.Cookies(&url.URL{Scheme: "https", Host: "kongfz.com"}) + fmt.Printf("获取到 %d 个 Cookie\n", len(cookies)) + for _, c := range cookies { + fmt.Printf("Cookie: %s=%s\n", c.Name, c.Value[:min(20, len(c.Value))]) + } + + return jar, nil } -func websocketHandler(w http.ResponseWriter, r *http.Request) { - // WebSocket处理逻辑 - fmt.Fprintf(w, "WebSocket endpoint") +func injectCookies(ctx context.Context, jar *cookiejar.Jar) error { + // 获取所有 Cookie + cookies := jar.Cookies(&url.URL{Scheme: "https", Host: "kongfz.com"}) + + // 先设置 document.cookie + for _, cookie := range cookies { + // 构建 Cookie 字符串 + cookieStr := fmt.Sprintf("%s=%s; path=/; domain=.kongfz.com", cookie.Name, cookie.Value) + if cookie.Domain != "" { + cookieStr += fmt.Sprintf("; domain=%s", cookie.Domain) + } + if cookie.Path != "" { + cookieStr += fmt.Sprintf("; path=%s", cookie.Path) + } + + jsCode := fmt.Sprintf(`document.cookie = '%s';`, cookieStr) + err := chromedp.Evaluate(jsCode, nil).Do(ctx) + if err != nil { + fmt.Printf("设置 cookie %s 失败: %v\n", cookie.Name, err) + } + } + + // 验证 Cookie 是否设置成功 + var cookiesCount int + err := chromedp.Evaluate(`document.cookie.length`, &cookiesCount).Do(ctx) + if err != nil { + return fmt.Errorf("验证 Cookie 失败: %v", err) + } + fmt.Printf("Cookie 注入完成,当前 document.cookie 长度: %d\n", cookiesCount) + + return nil } -// 中间件:记录请求日志 -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() +func clickDownloadButton(ctx context.Context) error { + // 多种方式尝试点击下载按钮 - // 调用下一个处理器 - next.ServeHTTP(w, r) + // 方法1: 通过文本查找 + var clicked bool + err := chromedp.Evaluate(` + (function() { + // 禁用确认对话框 + window.confirm = function() { return true; }; + window.alert = function() { return true; }; + + // 查找包含"下载查询结果"的按钮 + var buttons = Array.from(document.querySelectorAll('button, a, span, div')); + var downloadBtn = buttons.find(el => el.innerText && el.innerText.includes('下载查询结果')); + if (downloadBtn) { + downloadBtn.click(); + return true; + } + + // 尝试通过类名查找 + var btnByClass = document.querySelector('.download-btn, .export-btn, [class*="download"], [class*="export"]'); + if (btnByClass) { + btnByClass.click(); + return true; + } + + return false; + })() + `, &clicked).Do(ctx) - // 记录日志 - log.Printf("%s %s %s %v", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start)) - }) + if err != nil { + return fmt.Errorf("执行点击脚本失败: %v", err) + } + + if !clicked { + // 截图以便调试 + var buf []byte + err = chromedp.CaptureScreenshot(&buf).Do(ctx) + if err == nil { + os.WriteFile("debug_screenshot.png", buf, 0644) + fmt.Println("已保存调试截图: debug_screenshot.png") + } + + // 获取页面标题和URL + var title, url string + chromedp.Title(&title).Do(ctx) + chromedp.Location(&url).Do(ctx) + fmt.Printf("当前页面: %s, URL: %s\n", title, url) + + return fmt.Errorf("未找到下载按钮") + } + + fmt.Println("✓ 已点击下载按钮") + time.Sleep(3 * time.Second) + + return nil +} + +func getCookiesFromJar(jar *cookiejar.Jar, domain string) []*http.Cookie { + parsedURL, _ := url.Parse(fmt.Sprintf("https://%s/", domain)) + return jar.Cookies(parsedURL) +} + +func waitForDownload(dir string, timeout time.Duration) (string, error) { + deadline := time.Now().Add(timeout) + lastCount := 0 + + for time.Now().Before(deadline) { + entries, err := os.ReadDir(dir) + if err != nil { + return "", err + } + + downloading := false + var excels []string + + for _, entry := range entries { + name := entry.Name() + if strings.HasSuffix(name, ".crdownload") || + strings.HasSuffix(name, ".tmp") || + strings.HasSuffix(name, ".download") { + downloading = true + fmt.Printf("下载中: %s\n", name) + } else if strings.HasSuffix(name, ".xls") || + strings.HasSuffix(name, ".xlsx") || + strings.HasSuffix(name, ".csv") { + excels = append(excels, filepath.Join(dir, name)) + } + } + + if !downloading && len(excels) > 0 { + // 找到最新的文件 + var newest string + var newestTime time.Time + for _, f := range excels { + info, err := os.Stat(f) + if err == nil { + if info.ModTime().After(newestTime) { + newestTime = info.ModTime() + newest = f + } + } + } + + // 确保文件写入完成 + time.Sleep(2 * time.Second) + if newest != "" { + fmt.Printf("下载完成: %s\n", newest) + return newest, nil + } + } + + if len(excels) != lastCount { + fmt.Printf("发现 %d 个 Excel 文件\n", len(excels)) + lastCount = len(excels) + } + + time.Sleep(2 * time.Second) + } + + return "", fmt.Errorf("下载超时 (%v)", timeout) +} + +func min(a, b int) int { + if a < b { + return a + } + return b } diff --git a/md/pdd.md b/md/pdd.md index 9f502d3..977de59 100644 --- a/md/pdd.md +++ b/md/pdd.md @@ -623,6 +623,778 @@ dll.PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, request) } ``` +## 12. 商品图片上传接口(base64方式)--PddGoodsImageUpload +### 请求信息 +```gotemplate +dll.PddGoodsImageUpload(clientId, clientSecret, accessToken, fileBase) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| fileBase | string | 是 | 图片base64编码字符串 | +### 响应示例 +```json +{ + "goods_image_upload_response": { + "image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg", + "request_id": "17666480184871655" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 13. 订单基础信息列表查询接口(根据成交时间)--PddOrderBasicListGet +### 请求信息 +```gotemplate +dll.PddOrderBasicListGet(clientId, clientSecret, accessToken, orderBasicListGetJSONStr) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| orderBasicListGetJSONStr | string | 是 | 订单列表查询JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "order_status": 1, + "refund_status": 1, + "start_confirm_at": 1700000000, + "end_confirm_at": 1700100000, + "page": 1, + "page_size": 50 +} +``` +### 响应示例 +```json +{ + "order_basic_list_get_response": { + "order_list": [ + { + "order_sn": "250101-123456789", + "goods_id": 123456789, + "goods_name": "测试商品", + "goods_quantity": 1, + "order_amount": 8900, + "order_status": 1, + "confirm_time": 1700000000 + } + ], + "total_count": 100, + "request_id": "17666480184871656" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 14. 获取商品提交的商品详情--PddGoodsCommitDetailGet +### 请求信息 +```gotemplate +dll.PddGoodsCommitDetailGet(clientId, clientSecret, accessToken, goodsCommitId, goodsId) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| goodsCommitId | string | 是 | 商品提交ID | +| goodsId | string | 是 | 商品ID | +### 响应示例 +```json +{ + "goods_commit_detail_get_response": { + "goods_id": 123456789, + "goods_name": "测试商品", + "cat_id": 20111, + "goods_desc": "商品描述", + "image_url": "http://oms-imageimg.pinduoduo.com/upload/xxx.jpg", + "sku_list": [], + "request_id": "17666480184871657" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 15. 获取拼多多系统时间--PddTimeGet +### 请求信息 +```gotemplate +dll.PddTimeGet(clientId, clientSecret, accessToken) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +### 响应示例 +```json +{ + "time_get_response": { + "time": 1700000000000, + "request_id": "17666480184871658" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 16. 查询面单服务订购及面单使用情况--PddWaybillSearch +### 请求信息 +```gotemplate +dll.PddWaybillSearch(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 查询参数JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "wp_code": "STO" +} +``` +### 响应示例 +```json +{ + "waybill_search_response": { + "wp_code": "STO", + "total_quantity": 1000, + "used_quantity": 100, + "created_at": 1700000000, + "request_id": "17666480184871659" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 17. 电子面单取号--PddFdsWaybillGet +### 请求信息 +```gotemplate +dll.PddFdsWaybillGet(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 取号请求JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "wp_code": "STO", + "order_sn": "250101-123456789", + "sender": { + "name": "张三", + "mobile": "13800000000", + "province": "广东省", + "city": "深圳市", + "town": "南山区", + "address": "xx街道xx号" + }, + "receiver": { + "name": "李四", + "mobile": "13900000000", + "province": "广东省", + "city": "广州市", + "town": "天河区", + "address": "xx街道xx号" + } +} +``` +### 响应示例 +```json +{ + "fds_waybill_get_response": { + "waybill_code": "1234567890", + "print_data": "{\"content\": \"打印数据\"}", + "request_id": "17666480184871660" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 18. 商家取消获取的电子面单号--PddWaybillCancel +### 请求信息 +```gotemplate +dll.PddWaybillCancel(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 取消面单请求JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "wp_code": "STO", + "waybill_code": "1234567890" +} +``` +### 响应示例 +```json +{ + "waybill_cancel_response": { + "is_success": true, + "request_id": "17666480184871661" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 19. 商品列表接口--PddGoodsListGet +### 请求信息 +```gotemplate +dll.PddGoodsListGet(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 商品列表查询JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "is_onsale": 1, + "page": 1, + "page_size": 50 +} +``` +### 响应示例 +```json +{ + "goods_list_get_response": { + "goods_list": [ + { + "goods_id": 123456789, + "goods_name": "测试商品", + "goods_sn": "G202501200001", + "goods_status": 1, + "goods_type": 1 + } + ], + "total_count": 100, + "request_id": "17666480184871662" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 20. 电子面单云打印接口--PddWaybillGet +### 请求信息 +```gotemplate +dll.PddWaybillGet(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印请求JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "wp_code": "STO", + "waybill_code": "1234567890" +} +``` +### 响应示例 +```json +{ + "waybill_get_response": { + "print_data": "{\"content\": \"打印数据\"}", + "request_id": "17666480184871663" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 21. 通过面单号查询面单信息--PddWaybillQueryByWaybillcode +### 请求信息 +```gotemplate +dll.PddWaybillQueryByWaybillcode(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 查询面单信息JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "wp_code": "STO", + "waybill_code": "1234567890" +} +``` +### 响应示例 +```json +{ + "waybill_query_by_waybillcode_response": { + "wp_code": "STO", + "waybill_code": "1234567890", + "order_sn": "250101-123456789", + "print_data": "{\"content\": \"打印数据\"}", + "request_id": "17666480184871664" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 22. 云打印--PddCloudPrint +### 请求信息 +```gotemplate +dll.PddCloudPrint(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印请求JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "print_data": "{\"content\": \"打印数据\"}", + "printer_id": "printer_001" +} +``` +### 响应示例 +```json +{ + "cloud_print_response": { + "is_success": true, + "request_id": "17666480184871665" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 23. 云打印任务查询--PddCloudPrintTaskQuery +### 请求信息 +```gotemplate +dll.PddCloudPrintTaskQuery(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印任务查询JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "task_id": "task_001" +} +``` +### 响应示例 +```json +{ + "cloud_print_task_query_response": { + "task_id": "task_001", + "task_status": 2, + "request_id": "17666480184871666" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 24. 云打印验证码--PddCloudPrintVerifyCode +### 请求信息 +```gotemplate +dll.PddCloudPrintVerifyCode(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印验证码请求JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "printer_id": "printer_001", + "verify_code": "123456" +} +``` +### 响应示例 +```json +{ + "cloud_print_verify_code_response": { + "is_success": true, + "request_id": "17666480184871667" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 25. 云打印机绑定--PddCloudPrinterBind +### 请求信息 +```gotemplate +dll.PddCloudPrinterBind(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印机绑定JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "printer_id": "printer_001", + "printer_name": "前台打印机" +} +``` +### 响应示例 +```json +{ + "cloud_printer_bind_response": { + "is_success": true, + "printer_id": "printer_001", + "request_id": "17666480184871668" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 26. 云打印机设置--PddCloudPrinterSetting +### 请求信息 +```gotemplate +dll.PddCloudPrinterSetting(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印机设置JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "printer_id": "printer_001", + "auto_print": 1, + "print_count": 1 +} +``` +### 响应示例 +```json +{ + "cloud_printer_setting_response": { + "is_success": true, + "request_id": "17666480184871669" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 27. 云打印机状态查询--PddCloudPrinterStatusQuery +### 请求信息 +```gotemplate +dll.PddCloudPrinterStatusQuery(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 云打印机状态查询JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "printer_id": "printer_001" +} +``` +### 响应示例 +```json +{ + "cloud_printer_status_query_response": { + "printer_id": "printer_001", + "printer_status": 1, + "request_id": "17666480184871670" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 28. 商品上架状态设置--PddGoodsSaleStatusSet +### 请求信息 +```gotemplate +dll.PddGoodsSaleStatusSet(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 商品上架状态设置JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "goods_id": 123456789, + "is_onsale": 1 +} +``` +### 响应示例 +```json +{ + "goods_sale_status_set_response": { + "is_success": true, + "goods_id": 123456789, + "request_id": "17666480184871671" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + +## 29. 删除商品接口--PddDeleteGoodsCommit +### 请求信息 +```gotemplate +dll.PddDeleteGoodsCommit(clientId, clientSecret, accessToken, requestJson) +``` +### 请求参数 +| 参数名 | 类型 | 必填 | 说明 | +|--|--|--|----------| +| clientId | string | 是 | 拼多多开放平台ClientID | +| clientSecret | string | 是 | 拼多多开放平台ClientSecret | +| accessToken | string | 是 | 授权令牌 | +| requestJson | string | 是 | 删除商品请求JSON字符串 | +#### 请求JSON结构示例 +```json +{ + "goods_id": 123456789 +} +``` +### 响应示例 +```json +{ + "delete_goods_commit_response": { + "is_success": true, + "goods_id": 123456789, + "request_id": "17666480184871672" + } +} +``` +### 错误响应示例 +```json +{ + "error_response": { + "error_msg": "公共参数错误:type", + "sub_msg": "", + "sub_code": null, + "error_code": 10001, + "request_id": "15440104776643887" + } +} +``` + ## 获取商品信息接口 -- OutPddAuthGetCommitDetailt ### 请求信息 ```gotemplate diff --git a/newWalkingWithBooks.go b/newWalkingWithBooks.go new file mode 100644 index 0000000..8de7bba --- /dev/null +++ b/newWalkingWithBooks.go @@ -0,0 +1,2231 @@ +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("http://103.236.81.185:8099/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("http://103.236.81.185:8099/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) (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, dll.version) +// if err != nil { +// u.updateStatus(fmt.Sprintf("检查 %s 失败: %v", dll.name, err)) +// continue +// } +// +// // 如果需要更新 +// if dll.version != currentVersion && currentVersion != "" { +// u.updateStatus(fmt.Sprintf("更新 %s...,版本:%s", dll.name, dll.version)) +// +// // 创建带new-前缀的临时文件名 +// newFileName := "new-" + dll.name +// dllDir := filepath.Join(u.currentDir, "dll") +// newDllPath := filepath.Join(dllDir, newFileName) +// finalDllPath := filepath.Join(dllDir, dll.name) +// +// //dllPath := filepath.Join(u.currentDir, "dll", dll.name) +// err = downloadDLL("http://36.212.20.113:53300/api/downLoad", newDllPath, dll.name, dll.version) +// //if err != nil { +// // u.updateStatus(fmt.Sprintf("更新 %s 失败: %v", dll.name, err)) +// //} else { +// // u.updateStatus(fmt.Sprintf("%s 更新完成", dll.name)) +// //} +// +// if err != nil { +// u.updateStatus(fmt.Sprintf("更新 %s 失败: %v", dll.name, err)) +// } else { +// // 下载成功后,删除原文件(如果存在)并将new-文件重命名为原文件名 +// if _, err := os.Stat(finalDllPath); err == nil { +// os.Remove(finalDllPath) +// } +// if err := os.Rename(newDllPath, finalDllPath); 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("http://103.236.81.185:8099/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("http://103.236.81.185:8099/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, versionsJSON.CsvVersion) +// 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, versionsJSON.KongfzVersion) +// 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, versionsJSON.LoggerVersion) +// 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, versionsJSON.ModuleCenterBookVersion) +// 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, versionsJSON.ModuleLoginVersion) +// 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, versionsJSON.ModuleErpVersion) +// 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, versionsJSON.ModuleKongfzVersion) +// 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, versionsJSON.ModuleTaskPoolVersion) +// 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, versionsJSON.ModuleVerifyPriceVersion) +// 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, versionsJSON.PicToolVersion) +// 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, versionsJSON.ProxyVersion) +// 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 := "http://103.236.81.185:8099/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/versionNew.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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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, version 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", version) +// 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 +//} diff --git a/pdd/dll/pdd.dll b/pdd/dll/pdd.dll index 2f5a3f3..532e5c9 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 b28ecd5..dd8d9f4 100644 --- a/pdd/dll/pdd.h +++ b/pdd/dll/pdd.h @@ -87,82 +87,42 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 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); - -// 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); +extern char* PddGoodsOuterCatMappingGet(char* clientId, char* clientSecret, char* accessToken, char* outerCatId, char* outerCatName, char* outerGoodsName); +extern char* PddLogisticsCompaniesGet(char* clientId, char* clientSecret); +extern char* PddErpOrderSync(char* clientId, char* clientSecret, char* accessToken, char* logisticsId, char* orderSn, char* orderState, char* waybillNo); +extern char* PddOrderSynchronization(char* clientId, char* clientSecret, char* accessToken, char* logisticsCompany, char* logisticsOnlineSendJson); +extern char* PddGoodsImgUpload(char* clientId, char* clientSecret, char* accessToken, char* filePath); +extern char* PddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* goodsAddJson); +extern char* SelfPddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* filePath, char* goodsAddJson); +extern char* PddOpenDecryptMaskBatch(char* clientId, char* clientSecret, char* accessToken, char* reqJson); +extern char* PddGoodsSpecIdGet(char* clientId, char* clientSecret, char* accessToken, char* parentSpecId, char* specName); +extern char* PddGoodsSkuPriceUpdate(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddGoodsQuantityUpdate(char* clientId, char* clientSecret, char* accessToken, char* request); +extern char* PddGoodsImageUpload(char* clientId, char* clientSecret, char* accessToken, char* fileBase); +extern char* PddOrderBasicListGet(char* clientId, char* clientSecret, char* accessToken, char* orderBasicListGetJSON); +extern char* PddGoodsCommitDetailGet(char* clientId, char* clientSecret, char* accessToken, char* goodsCommitId, char* goodsId); +extern char* PddTimeGet(char* clientId, char* clientSecret, char* accessToken); +extern char* PddWaybillSearch(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddFdsWaybillGet(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddWaybillCancel(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddGoodsListGet(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddWaybillGet(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddWaybillQueryByWaybillcode(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddCloudPrint(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddCloudPrintTaskQuery(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddCloudPrintVerifyCode(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddCloudPrinterBind(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddCloudPrinterSetting(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddCloudPrinterStatusQuery(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddGoodsSaleStatusSet(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* PddDeleteGoodsCommit(char* clientId, char* clientSecret, char* accessToken, char* requestJson); +extern char* OutPddAuthGetCommitDetailt(char* goodsCommitId, char* goodsId, char* accessToken); +extern char* OutPddAuthGetGoodsDetail(char* goodsId, char* accessToken); +extern char* OutPddAuthSetSpec(int specTypeId, char* specName, char* accessToken); +extern char* OutPddAuthGetCats(void); +extern char* OutPddAuthUpdatePrice(char* jsonData); +extern char* OutPddAuthUpdateStock(char* jsonData); +extern void FreeCString(char* str); #ifdef __cplusplus } diff --git a/pdd/pdd.go b/pdd/pdd.go index 72e60cf..4221d05 100644 --- a/pdd/pdd.go +++ b/pdd/pdd.go @@ -423,7 +423,8 @@ func pddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompa break } } - fmt.Println(logisticsOnlineSendData["logistics_id"]) + // logistics + fmt.Println(logisticsOnlineSendData) logisticsOnlineSendStr, err := json.Marshal(logisticsOnlineSendData) if err != nil { @@ -991,62 +992,33 @@ type SkuPriceListItem struct { } // 修改商品sku价格 -func pddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken string, request PddGoodsSkuPriceUpdateRequest) (string, error) { +func pddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken string, requestJson string) (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类型:批量解密脱敏 + params := map[string]interface{}{ + "type": "pdd.goods.sku.price.update", "data_type": "JSON", "client_id": clientId, "access_token": accessToken, "timestamp": timestamp, } - // 添加非空参数 - if request.GoodsId > 0 { - params["goods_id"] = strconv.FormatInt(request.GoodsId, 10) + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") } - if request.MarketPrice > 0 { - params["market_price"] = strconv.FormatInt(request.MarketPrice, 10) + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err } - if request.MarketPriceInYuan != "" { - params["market_price_in_yuan"] = request.MarketPriceInYuan - } + sign := generateSign(toParams, clientSecret) - 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 签名错误!") } @@ -1055,7 +1027,22 @@ func pddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken string, request // 将参数编码为 application/x-www-form-urlencoded 格式 formData := url.Values{} for key, value := range params { - formData.Set(key, value) + // 将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)) + } + } } req := gorequest.New() @@ -1082,12 +1069,13 @@ func pddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken string, request // 异常处理 if response["error_response"] != nil { + fmt.Println("error_response", response) 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) + return "", fmt.Errorf("请求失败: %v, 错误码: %d, 详细错误: %v", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode, errorWrapper.ErrorResponse.SubMsg) } // 转换成json字符串 @@ -1431,7 +1419,87 @@ func pddGoodsCommitDetailGet(clientId, clientSecret, accessToken string, goodsCo } } - 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 pddTimeGet(clientId, clientSecret, accessToken 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.time.get", // API类型:商品新增接口 + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + sign := generateSign(params, clientSecret) + + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + 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). @@ -1474,6 +1542,1319 @@ func pddGoodsCommitDetailGet(clientId, clientSecret, accessToken string, goodsCo return string(responseJSON), nil } +// 查询面单服务订购及面单使用情况 +func pddWaybillSearch(clientId, clientSecret, accessToken, requestJson 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.waybill.search", // API类型:商品新增接口 + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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 pddFdsWaybillGet(clientId, clientSecret, accessToken string, requestJson 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.fds.waybill.get", // API类型:商品新增接口 + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, params) + if err != nil { + return "", err + } + + sign := generateSign(toParams, clientSecret) + + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + marshal, _ := json.Marshal(params) + fmt.Println("params:", string(marshal)) + + // 将参数编码为 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 pddWaybillCancel(clientId, clientSecret, accessToken string, requestJson 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.waybill.cancel", // API类型:商品新增接口 + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 商品列表接口 +func pddGoodsListGet(clientId, clientSecret, accessToken string, requestJson 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.list.get", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 电子面单云打印接口 +func pddWaybillGet(clientId, clientSecret, accessToken string, requestJson 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.waybill.get", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + "param_waybill_cloud_print_apply_new_request": requestJson, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + //// 将JSON参数合并到params中 + //toParams, err := addStructToParams(requestJson, params) + //if err != nil { + // return "", err + //} + + sign := generateSign(params, clientSecret) + + if sign == "" { + return "", fmt.Errorf("生成 sign 签名错误!") + } + params["sign"] = sign + + marshal, _ := json.Marshal(params) + fmt.Println("参数:", string(marshal)) + // 将参数编码为 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 通过面单号查询面单信息 +func pddWaybillQueryByWaybillcode(clientId, clientSecret, accessToken string, requestJson 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.waybill.query.by.waybillcode", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + "param_list": requestJson, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + 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)) + } + } + } + + 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 云打印 +func pddCloudPrint(clientId, clientSecret, accessToken string, requestJson 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.cloud.print", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 云打印任务查询 +func pddCloudPrintTaskQuery(clientId, clientSecret, accessToken string, requestJson 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.cloud.print.task.query", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 云打印验证码 +func pddCloudPrintVerifyCode(clientId, clientSecret, accessToken string, requestJson 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.cloud.print.verify.code", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 云打印机绑定 +func pddCloudPrinterBind(clientId, clientSecret, accessToken string, requestJson 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.cloud.printer.bind", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 云打印机设置 +func pddCloudPrinterSetting(clientId, clientSecret, accessToken string, requestJson 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.cloud.printer.setting", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 云打印机状态查询 +func pddCloudPrinterStatusQuery(clientId, clientSecret, accessToken string, requestJson 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.cloud.printer.status.query", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 商品上架状态设置 +func pddGoodsSaleStatusSet(clientId, clientSecret, accessToken string, requestJson 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.sale.status.set", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成json字符串 + responseJSON, err := json.Marshal(response) + if err != nil { + return "", fmt.Errorf("JSON序列化失败: %v", err) + } + return string(responseJSON), nil +} + +// 删除商品接口 +func pddDeleteGoodsCommit(clientId, clientSecret, accessToken string, requestJson 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.delete.goods.commit", + "data_type": "JSON", + "client_id": clientId, + "access_token": accessToken, + "timestamp": timestamp, + } + + if requestJson == "" { + return "", fmt.Errorf("requestJson 参数为空!") + } + + // 将JSON参数合并到params中 + toParams, err := addStructToParams(requestJson, 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, 错误码: %v", errorWrapper.ErrorResponse.SubMsg, errorWrapper.ErrorResponse.SubCode) + } + + // 转换成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" @@ -1869,16 +3250,12 @@ func PddGoodsSpecIdGet(clientId, clientSecret, accessToken, parentSpecId, specNa // PddGoodsSkuPriceUpdate 修改商品sku价格 // //export PddGoodsSkuPriceUpdate -func PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, request *C.char) *C.char { +func PddGoodsSkuPriceUpdate(clientId, clientSecret, accessToken, requestJson *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) + requestJsonStr := C.GoString(requestJson) + info, err := pddGoodsSkuPriceUpdate(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) if err != nil { return C.CString(err.Error()) } @@ -1919,7 +3296,7 @@ func PddGoodsImageUpload(clientId, clientSecret, accessToken, fileBase *C.char) return C.CString(info) } -// PddOrderBasicListGet 商品图片上传接口 +// PddOrderBasicListGet 订单基础信息列表查询接口(根据成交时间) // //export PddOrderBasicListGet func PddOrderBasicListGet(clientId, clientSecret, accessToken, orderBasicListGetJSON *C.char) *C.char { @@ -1950,6 +3327,253 @@ func PddGoodsCommitDetailGet(clientId, clientSecret, accessToken, goodsCommitId, return C.CString(info) } +// PddTimeGet 获取拼多多系统时间 +// +//export PddTimeGet +func PddTimeGet(clientId, clientSecret, accessToken *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + info, err := pddTimeGet(clientIdStr, clientSecretStr, accessTokenStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddWaybillSearch 查询面单服务订购及面单使用情况 +// +//export PddWaybillSearch +func PddWaybillSearch(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddWaybillSearch(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddFdsWaybillGet 电子面单取号 +// +//export PddFdsWaybillGet +func PddFdsWaybillGet(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddFdsWaybillGet(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// ResponseAPI 统一响应数据结构 +type ResponseAPI struct { + Success bool `json:"success"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +// 生成JSON响应字符串 +func jsonResponse(data interface{}, err error) string { + resp := ResponseAPI{ + Success: err == nil, + Message: getErrorMsg(err), + Data: data, + } + bytes, _ := json.Marshal(resp) + return string(bytes) +} + +// 获取错误信息 +func getErrorMsg(err error) string { + if err != nil { + return err.Error() + } + return "" +} + +// PddWaybillCancel 商家取消获取的电子面单号 +// +//export PddWaybillCancel +func PddWaybillCancel(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddWaybillCancel(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + return C.CString(jsonResponse(info, err)) +} + +// PddGoodsListGet 商品列表接口 +// +//export PddGoodsListGet +func PddGoodsListGet(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddGoodsListGet(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddWaybillGet 电子面单云打印接口 +// +//export PddWaybillGet +func PddWaybillGet(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddWaybillGet(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddWaybillQueryByWaybillcode 通过面单号查询面单信息 +// +//export PddWaybillQueryByWaybillcode +func PddWaybillQueryByWaybillcode(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddWaybillQueryByWaybillcode(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddCloudPrint 云打印 +// +//export PddCloudPrint +func PddCloudPrint(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddCloudPrint(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddCloudPrintTaskQuery 云打印任务查询 +// +//export PddCloudPrintTaskQuery +func PddCloudPrintTaskQuery(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddCloudPrintTaskQuery(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddCloudPrintVerifyCode 云打印验证码 +// +//export PddCloudPrintVerifyCode +func PddCloudPrintVerifyCode(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddCloudPrintVerifyCode(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddCloudPrinterBind 云打印机绑定 +// +//export PddCloudPrinterBind +func PddCloudPrinterBind(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddCloudPrinterBind(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddCloudPrinterSetting 云打印机设置 +// +//export PddCloudPrinterSetting +func PddCloudPrinterSetting(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddCloudPrinterSetting(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddCloudPrinterStatusQuery 云打印机状态查询 +// +//export PddCloudPrinterStatusQuery +func PddCloudPrinterStatusQuery(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddCloudPrinterStatusQuery(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddGoodsSaleStatusSet 商品上架状态设置 +// +//export PddGoodsSaleStatusSet +func PddGoodsSaleStatusSet(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddGoodsSaleStatusSet(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + +// PddDeleteGoodsCommit 删除商品接口 +// +//export PddDeleteGoodsCommit +func PddDeleteGoodsCommit(clientId, clientSecret, accessToken, requestJson *C.char) *C.char { + clientIdStr := C.GoString(clientId) + clientSecretStr := C.GoString(clientSecret) + accessTokenStr := C.GoString(accessToken) + requestJsonStr := C.GoString(requestJson) + info, err := pddDeleteGoodsCommit(clientIdStr, clientSecretStr, accessTokenStr, requestJsonStr) + if err != nil { + return C.CString(err.Error()) + } + return C.CString(info) +} + // OutPddAuthGetCommitDetailt 获取商品信息 // //export OutPddAuthGetCommitDetailt @@ -2032,4 +3656,5 @@ func FreeCString(str *C.char) { // main函数 func main() { + } diff --git a/pdd/pddDll.go b/pdd/pddDll.go index 86167cf..a24e02d 100644 --- a/pdd/pddDll.go +++ b/pdd/pddDll.go @@ -266,12 +266,32 @@ type DataList struct { } //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", +// //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 := pddTimeGet("203c5a7ba8bd4b8488d5e26f93052642", // "892ffaa86e12b7a3d8d2942b669d9aa520ad8179", -// "1177d0c36419417eba692a3fea88f611d42f0665", jsonStr) +// "1177d0c36419417eba692a3fea88f611d42f0665") // if err != nil { // fmt.Println(err.Error()) // } // fmt.Println(add) //} + +//func main() { +// 替换为你的图片路径 +//imagePath := "D:\\isbn_images\\9771005867004.jpg" + +//url := "http://103.236.68.64:19000/public-img/bbb.jpg" + +// http://36.212.7.35:19000/public-img/e5ef25ae85853f3e_s.jpg + +//fPath := "D:\\isbn_images\\9780064408561.jpg" +// +//baseUrl := "http://36.212.7.35:19000/public-img/" + filepath.Base(fPath) +////picSpaceResult, err := utils.UploadImageToSpace(imgResult.PicPath, baseUrl) +// +//space, err := UploadImageToSpace(fPath, baseUrl) +//if err != nil { +// fmt.Println(err) +//} +//fmt.Println(space) +//} diff --git a/proxy/dll/proxy.dll b/proxy/dll/proxy.dll deleted file mode 100644 index c30b648..0000000 Binary files a/proxy/dll/proxy.dll and /dev/null differ diff --git a/proxy/dll/proxy.h b/proxy/dll/proxy.h deleted file mode 100644 index 2eec6b7..0000000 --- a/proxy/dll/proxy.h +++ /dev/null @@ -1,129 +0,0 @@ -/* 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); - -// 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.dll b/proxy/proxy.dll deleted file mode 100644 index ca1a1c5..0000000 Binary files a/proxy/proxy.dll and /dev/null differ diff --git a/proxy/proxy.go b/proxy/proxy.go index 2786561..e34ced3 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -8,538 +8,168 @@ import ( "crypto/md5" "encoding/json" "fmt" - "github.com/parnurzeal/gorequest" - "log" "math/rand" - "net/url" "strings" "sync" "time" "unsafe" + + "github.com/parnurzeal/gorequest" ) -// 代理类型常量 const ( - CalfElephantProxyType = "CALF_ELEPHANT_PROXY" // 小象代理类型 - TailProxyType = "TAIL_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 // 全局随机数生成器 - // 代理健康状态管理 - // 代理健康状态映射表,key为代理主机名,value为健康状态 - proxyHealthMaps = make(map[string]*ProxyHealth) - proxyHealthMutex sync.RWMutex + globalRand *rand.Rand ) -// 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 -} - -// ProxyHealth 代理健康状态结构体,用于跟踪代理的健康状况 -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,代理类型管理器,根据代理类型构建不同的代理URL - * param proxyType[string] 代理类型 - * param username[string] 代理用户名 - * param password[string] 代理密码 - * param machineCode[string] 机器码 - * return 代理服务器IP,错误信息 - */ -func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) { +// 根据代理类型获取代理URL +func getProxyByType(proxyType, machineCode string) (string, error) { switch proxyType { - case CalfElephantProxyType: - // 小象代理:使用用户名和密码构建代理URL - return buildCalfElephantProxyURL(username, password) case TailProxyType: - // 尾巴代理:使用机器码构建代理URL - return buildTailProxyURL(machineCode) + return getTailProxyURL(machineCode) default: - // 不支持的代理类型,返回错误 return "", fmt.Errorf("不支持的代理类型: %s", proxyType) } } -func proxyTypeManagerNew(proxyType, username, password, machineCode string) (string, error) { +// 获取尾巴代理URL +func getTailProxyURL(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 -} + proxyURL := fmt.Sprintf("http://%s", proxy) -/* - * 构建小象代理URL - * param username[string] 代理用户名 - * param password[string] 代理密码 - * return 代理服务器IP,错误信息 - */ -func buildCalfElephantProxyURL(username, password string) (string, error) { - // 随机选择一个代理服务器 - server := randomServer() - // 构建代理URL格式:http://用户名:密码@服务器:端口 - proxyURL := fmt.Sprintf("http://%s:%s@%s:%d", - url.QueryEscape(username), // URL编码用户名,防止特殊字符问题 - url.QueryEscape(password), // URL编码密码,防止特殊字符问题 - 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 } -/* - * 尝试下一个小象代理服务器 - * param username[string] 代理用户名 - * param password[string] 代理密码 - * param failedServer[string] 代理服务器 - * return 代理服务器IP,错误信息 - */ -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) { - // 构建代理URL - 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 - * param machineCode[string] 机器码 - * return 代理服务器IP,错误信息 - */ -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) -} - -/* - * 过滤健康代理,返回当前健康的代理列表 - * param proxies[[]string] 内置代理服务器数组 - * return 内置代理服务器数组,错误信息 - */ -func filterHealthyProxies(proxies []string) []string { - // 获取读锁,允许多个goroutine同时读取 - 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 -} - -/* - * 查找可用的尾巴代理 - * param proxies[[]string] 内置代理服务器数组 - * return 可用的代理服务器IP,错误信息 - */ -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 // 等待组,用于等待所有检测goroutine完成 - - // 限制并发数,避免同时检测太多代理导致网络拥塞 - 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) - } - // 等待所有检测goroutine完成 - 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("所有尾巴代理都不可用") -} - -/* - * 检测代理健康状态,通过访问测试网站验证代理可用性 - * param proxyURL[string] 代理服务器 - * return 错误信息 - */ -func checkProxyHealth(proxyURL string) error { - start := time.Now() // 记录开始时间,用于计算响应时间 - // 创建HTTP请求,设置代理和超时 - req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second) - // 发送GET请求到测试网站 - 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) - } - // 检查HTTP状态码 - 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 -} - -/* - * 更新代理健康状态 - * param proxyURL[string] 代理服务器 - * param success[bool] 是否成功 - * param responseTime[time.Duration] 响应时间 - */ -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 - } - } -} - -/* - * 获取代理主机名 - * param proxyURL[string] 代理服务器 - * return 代理服务器IP - */ -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) - // 使用Fisher-Yates算法打乱顺序 - globalRand.Shuffle(len(shuffled), func(i, j int) { - shuffled[i], shuffled[j] = shuffled[j], shuffled[i] - }) - return shuffled + return slice[globalRand.Intn(len(slice))] } -/* - * 检查卡密是否过期 - * param tailCardSecret[string] 卡密 - * return 是否过期,错误信息 - */ -func checkTailCardSecretExpired(tailCardSecret string) (bool, error) { - // 获取机器码信息 - code, err := getMachineCode(tailCardSecret) - if err != nil { - return false, fmt.Errorf("请求错误: %v", err) - } - // 解析过期时间字符串 - targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime) - if err != nil { - return false, fmt.Errorf("时间格式错误: %v", err) - } - currentTime := time.Now() - // 卡密日期在当前日期之后表示有效 - if targetTime.After(currentTime) { - return true, nil - } else { - // 卡密已过期 - return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime) - } +// MachineCodeResponse 查询机器码响应结构体 +type MachineCodeResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data MachineCodeData `json:"data"` } -// 定义响应结构体 -type getMachineCodeResp struct { - Code int `json:"code"` // 状态码 - Message string `json:"message"` // 消息 - Data struct { - MachineCode string `json:"machine_code"` // 机器码 - IpExpTime string `json:"ip_exp_time"` // IP过期时间 - IpThread int `json:"ip_thread"` // IP线程数 - IpCardCode string `json:"ip_card_code"` // IP卡密 - } `json:"data"` +// MachineCodeData 机器码数据 +type MachineCodeData struct { + MachineCode string `json:"machine_code"` // 机器码 + IpExpTime string `json:"ip_exp_time"` // 使用时间 + IpThread int `json:"ip_thread"` // 线程数 + IpCardCode string `json:"ip_card_code"` // 卡密 } -/* - * 查询机器码 - * param tailCardSecret[string] 卡密 - * return 响应结构体,错误信息 - */ -func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) { - url := "http://114.66.2.223:7842/api/proxies/ip_show" - // 构建请求数据 +// 查询机器码 +func getMachineCode(tailCardSecret string) (*MachineCodeData, error) { + url := "http://36.134.51.135:7842/api/proxies/ip_show" data := map[string]interface{}{ "ip_card_code": tailCardSecret, "agent_id": 9999, } - // 发送POST请求 + _, body, errs := gorequest.New().Post(url).Send(data).End() if len(errs) > 0 { return nil, fmt.Errorf("查询机器码失败: %v", errs) } - // 解析响应 - var resp getMachineCodeResp + + var resp MachineCodeResponse if err := json.Unmarshal([]byte(body), &resp); err != nil { return nil, fmt.Errorf("解析响应失败: %v", err) } - // 处理不同的响应码 - if resp.Code == 201 { - // 需要充值卡密 + + switch resp.Code { + case 201: machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode) if err != nil { return nil, err } resp.Data.MachineCode = machineCode - return &resp, nil - } else if resp.Code == 200 { - // 成功获取机器码 - return &resp, nil - } else { - // 其他错误 + return &resp.Data, nil + case 200: + return &resp.Data, nil + default: return nil, fmt.Errorf("查询机器码失败: %s", resp.Message) } } -/* - * 充值卡密 - * param tailCardSecret[string] 卡密 - * param machineCode[string] 机器码 - * return 机器码,错误信息 - */ +// 充值卡密 func rechargeCard(tailCardSecret, machineCode string) (string, error) { - url := "http://114.66.2.223:7842/api/proxies/ip_recharge" - // 构建请求数据 + url := "http://36.134.51.135:7842/api/proxies/ip_recharge" data := map[string]interface{}{ "machine_code": machineCode, "ip_card_code": tailCardSecret, "agent_id": 9999, } - // 发送POST请求 + _, body, errs := gorequest.New().Post(url).Send(data).End() if len(errs) > 0 { return "", fmt.Errorf("充值卡密失败: %v", errs) } - // 解析响应 + var resp struct { - Code int `json:"code"` // 状态码 - Message string `json:"message"` // 返回消息 + Code int `json:"code"` + Message string `json:"message"` Data struct { - IpExpTime string `json:"ip_exp_time"` // 过期时间 - IpThread int `json:"ip_thread"` // 线程 - MachineCode string `json:"machine_code"` // 机器码 + IpExpTime string `json:"ip_exp_time"` + IpThread int `json:"ip_thread"` + MachineCode string `json:"machine_code"` } `json:"data"` } + if err := json.Unmarshal([]byte(body), &resp); err != nil { return "", fmt.Errorf("解析响应失败: %v", err) } - // 检查响应状态 if resp.Code != 200 { return "", fmt.Errorf("充值卡密失败: %s", resp.Message) } + return resp.Data.MachineCode, nil } -/* - * 获取代理服务器列表 - * param machineCode[string] 机器码 - * return 代理服务器组,错误信息 - */ +// 获取代理服务器列表 func getProxies(machineCode string) ([]string, error) { - log.Printf("[INFO] 开始获取代理列表: %s", machineCode) // 生成签名 sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode)))) // 构建请求URL - GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999", + getProxiesUrl := fmt.Sprintf("http://36.134.51.135:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999", machineCode, sign) // 发送GET请求 - req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second) + req := gorequest.New().Get(getProxiesUrl).Timeout(20 * time.Second) _, body, errs := req.End() if len(errs) > 0 { return nil, fmt.Errorf("获取代理失败: %v", errs) } - // 检查是否为JSON错误响应(响应以{开头,以}结尾) - if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") { - // 尝试解析为JSON错误响应 + // 检查是否为JSON错误响应 + trimmedBody := strings.TrimSpace(body) + if strings.HasPrefix(trimmedBody, "{") && strings.HasSuffix(trimmedBody, "}") { var errorResp struct { Code int `json:"code"` Message string `json:"message"` @@ -549,190 +179,139 @@ func getProxies(machineCode string) ([]string, error) { } } - // 解析响应(每行一个代理地址) - lines := strings.Split(strings.TrimSpace(body), "\n") + // 解析响应 + lines := strings.Split(trimmedBody, "\n") var proxies []string for _, line := range lines { line = strings.TrimSpace(line) - // 过滤空行和JSON格式的行 if line != "" && !strings.HasPrefix(line, "{") { proxies = append(proxies, line) } } - // 检查是否获取到有效代理 + if len(proxies) == 0 { return nil, fmt.Errorf("未获取到有效代理") } - log.Printf("[INFO] 获取到 %d 个代理", len(proxies)) return proxies, nil } -// 初始化代理信息 -func initProxy() { - -} - -// =================== C 导出函数 ======================= - -// GetProxyHealth 导出函数:获取代理健康状态(用于调试) -// -//export GetProxyHealth -func GetProxyHealth() *C.char { - proxyHealthMutex.RLock() - defer proxyHealthMutex.RUnlock() - // 构建健康信息映射 - healthInfo := make(map[string]interface{}) - for proxy, health := range proxyHealthMaps { - healthInfo[proxy] = map[string]interface{}{ - "success_count": health.SuccessCount, - "fail_count": health.FailCount, - "last_check": health.LastCheck.Format(time.RFC3339), - "response_time": health.ResponseTime.String(), - "is_healthy": health.IsHealthy, - } - } - // 序列化为JSON - jsonData, err := json.Marshal(healthInfo) +// 检查卡密是否过期 +func checkTailCardSecretExpired(tailCardSecret string) (bool, error) { + resp, err := getMachineCode(tailCardSecret) if err != nil { - return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err)) + return false, fmt.Errorf("请求错误: %v", err) } - return C.CString(string(jsonData)) -} - -// ProxyTypeManager 导出函数:代理类型管理器 -// -//export ProxyTypeManager -func ProxyTypeManager(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) - - log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType) - // 调用代理类型管理器 - proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode) + targetTime, err := time.Parse("2006-01-02 15:04:05", resp.IpExpTime) if err != nil { - errorMsg := fmt.Sprintf("ERROR: %v", err) - log.Printf("[ERROR] 代理类型管理器错误: %v", err) - return C.CString(errorMsg) + return false, fmt.Errorf("时间格式错误: %v", err) } - log.Printf("[DEBUG] 代理类型管理器返回: %s", proxyURL) - return C.CString(proxyURL) + if targetTime.After(time.Now()) { + return true, nil + } + + return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime) } -// 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) +// =================== C导出函数 ======================= - // 调用代理类型管理器 - proxyURL, err := proxyTypeManagerNew(goProxyType, goUsername, goPassword, goMachineCode) +// ResponseAPI 统一响应数据结构 +type ResponseAPI struct { + Success bool `json:"success"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +// 生成JSON响应字符串 +func jsonResponse(data interface{}, err error) string { + resp := ResponseAPI{ + Success: err == nil, + Message: getErrorMsg(err), + Data: data, + } + bytes, _ := json.Marshal(resp) + return string(bytes) +} + +// 获取错误信息 +func getErrorMsg(err error) string { if err != nil { - errorMsg := fmt.Sprintf("ERROR: %v", err) - return C.CString(errorMsg) + return err.Error() } - return C.CString(proxyURL) + return "" } -// GetMachineCode 导出函数:查询机器码 +// GetProxyByType 根据代理类型获取代理URL +// +//export GetProxyByType +func GetProxyByType(proxyType, machineCode *C.char) *C.char { + proxyTypeStr := C.GoString(proxyType) + machineCodeStr := C.GoString(machineCode) + + proxyURL, err := getProxyByType(proxyTypeStr, machineCodeStr) + return C.CString(jsonResponse(proxyURL, err)) +} + +// GetTailProxyURL 获取尾巴代理URL +// +//export GetTailProxyURL +func GetTailProxyURL(machineCode *C.char) *C.char { + machineCodeStr := C.GoString(machineCode) + proxyURL, err := getTailProxyURL(machineCodeStr) + return C.CString(jsonResponse(proxyURL, err)) +} + +// GetMachineCode 查询机器码 // //export GetMachineCode func GetMachineCode(tailCardSecret *C.char) *C.char { - goTailCardSecret := C.GoString(tailCardSecret) - - resp, err := getMachineCode(goTailCardSecret) - if err != nil { - errorMsg := fmt.Sprintf("ERROR: %v", err) - log.Printf("[ERROR] 查询机器码错误: %v", err) - return C.CString(errorMsg) - } - - // 将响应转换为JSON - jsonData, err := json.Marshal(resp) - if err != nil { - errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) - log.Printf("[ERROR] 序列化机器码响应失败: %v", err) - return C.CString(errorMsg) - } - - return C.CString(string(jsonData)) + tailCardSecretStr := C.GoString(tailCardSecret) + resp, err := getMachineCode(tailCardSecretStr) + return C.CString(jsonResponse(resp, err)) } -// RechargeCard 导出函数:充值卡密 +// RechargeCard 充值卡密 // //export RechargeCard func RechargeCard(tailCardSecret, machineCode *C.char) *C.char { goTailCardSecret := C.GoString(tailCardSecret) goMachineCode := C.GoString(machineCode) - log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode) newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode) if err != nil { - errorMsg := fmt.Sprintf("ERROR: %v", err) - log.Printf("[ERROR] 充值卡密错误: %v", err) - return C.CString(errorMsg) + return C.CString(jsonResponse(nil, err)) } - // 构建成功响应 response := map[string]interface{}{ - "success": true, - "machine_code": newMachineCode, - "message": "充值成功", + "machineCode": newMachineCode, + "rechargeInfo": "充值成功", } - jsonData, err := json.Marshal(response) - if err != nil { - errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) - log.Printf("[ERROR] 序列化充值响应失败: %v", err) - return C.CString(errorMsg) - } - - log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode) - return C.CString(string(jsonData)) + return C.CString(jsonResponse(response, nil)) } -// GetProxies 导出函数:获取代理服务器列表 +// GetProxies 获取代理服务器列表 // //export GetProxies func GetProxies(machineCode *C.char) *C.char { goMachineCode := C.GoString(machineCode) - log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode) proxies, err := getProxies(goMachineCode) if err != nil { - errorMsg := fmt.Sprintf("ERROR: %v", err) - log.Printf("[ERROR] 获取代理服务器列表错误: %v", err) - return C.CString(errorMsg) + return C.CString(jsonResponse(nil, err)) } - // 将代理列表转换为JSON response := map[string]interface{}{ - "success": true, "count": len(proxies), "proxies": proxies, } - jsonData, err := json.Marshal(response) - if err != nil { - errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) - log.Printf("[ERROR] 序列化代理列表失败: %v", err) - return C.CString(errorMsg) - } - - log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies)) - return C.CString(string(jsonData)) + return C.CString(jsonResponse(response, nil)) } -// CheckTailCardSecretExpired 导出函数:检查卡密是否过期 +// CheckTailCardSecretExpired 检查卡密是否过期 // //export CheckTailCardSecretExpired func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char { @@ -740,104 +319,30 @@ func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char { isValid, err := checkTailCardSecretExpired(goTailCardSecret) if err != nil { - // 如果是过期错误,返回特定格式 - if strings.Contains(err.Error(), "卡密已经过期") { - response := map[string]interface{}{ - "is_valid": false, - "message": err.Error(), - } - jsonData, _ := json.Marshal(response) - return C.CString(string(jsonData)) - } - - errorMsg := fmt.Sprintf("ERROR: %v", err) - log.Printf("[ERROR] 检查卡密过期错误: %v", err) - return C.CString(errorMsg) + return C.CString(jsonResponse(nil, err)) } response := map[string]interface{}{ - "is_valid": isValid, - "message": "卡密有效", + "isValid": isValid, + "checkInfo": "卡密有效", } - - jsonData, err := json.Marshal(response) - if err != nil { - errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) - return C.CString(errorMsg) - } - return C.CString(string(jsonData)) + return C.CString(jsonResponse(response, nil)) } -// InitProxyManager 导出函数:初始化代理管理器 -// -//export InitProxyManager -func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char { - goServersJson := C.GoString(serversJson) - goUsername := C.GoString(username) - goPassword := C.GoString(password) - goTailCardSecret := C.GoString(tailCardSecret) - goProxyType := C.GoString(proxyType) - - log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType) - - // 解析服务器列表 - var serverList []string - if goServersJson != "" { - if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil { - errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err) - log.Printf("[ERROR] 解析服务器列表失败: %v", err) - return C.CString(errorMsg) - } - } - - // 更新全局服务器列表 - if len(serverList) > 0 { - randMutex.Lock() - servers = serverList - randMutex.Unlock() - log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers)) - } - - // 创建代理管理器 - manager := ProxyManager{ - servers: servers, - username: goUsername, - password: goPassword, - tailCardSecret: goTailCardSecret, - proxyType: goProxyType, - } - - // 序列化管理器信息 - jsonData, err := json.Marshal(manager) - if err != nil { - errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err) - log.Printf("[ERROR] 序列化代理管理器失败: %v", err) - return C.CString(errorMsg) - } - - log.Printf("[DEBUG] 代理管理器初始化成功") - return C.CString(string(jsonData)) -} - -// FreeCString 导出函数:释放C字符串内存 +// FreeCString 释放C字符串内存 // //export FreeCString 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) + return C.CString("v3") } -// 空main函数,编译DLL时需要 +// 空main函数 func main() { } diff --git a/proxy/proxy.h b/proxy/proxy.h deleted file mode 100644 index 5e896fe..0000000 --- a/proxy/proxy.h +++ /dev/null @@ -1,133 +0,0 @@ -/* 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/proxyLao.go b/proxy/proxyLao.go new file mode 100644 index 0000000..4f418d9 --- /dev/null +++ b/proxy/proxyLao.go @@ -0,0 +1,848 @@ +package main + +///* +//#include +//*/ +//import "C" +//import ( +// "crypto/md5" +// "encoding/json" +// "fmt" +// "github.com/parnurzeal/gorequest" +// "log" +// "math/rand" +// "net/url" +// "strings" +// "sync" +// "time" +// "unsafe" +//) +// +//// 代理类型常量 +//const ( +// CalfElephantProxyType = "CALF_ELEPHANT_PROXY" // 小象代理类型 +// TailProxyType = "TAIL_PROXY" // 尾巴代理类型 +//) +// +//// 小象代理服务器列表 +//var ( +// servers = []string{ +// "http-dynamic.xiaoxiangdaili.com", +// "http-dynamic-S02.xiaoxiangdaili.com", +// "http-dynamic-S03.xiaoxiangdaili.com", +// "http-dynamic-S04.xiaoxiangdaili.com", +// } +// // 互斥锁,用于保护全局随机数生成器的并发访问 +// randMutex sync.Mutex +// globalRand *rand.Rand // 全局随机数生成器 +// // 代理健康状态管理 +// // 代理健康状态映射表,key为代理主机名,value为健康状态 +// 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 +//} +// +//// ProxyHealth 代理健康状态结构体,用于跟踪代理的健康状况 +//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,代理类型管理器,根据代理类型构建不同的代理URL +//* param proxyType[string] 代理类型 +//* param username[string] 代理用户名 +//* param password[string] 代理密码 +//* param machineCode[string] 机器码 +//* return 代理服务器IP,错误信息 +// */ +//func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) { +// switch proxyType { +// case CalfElephantProxyType: +// // 小象代理:使用用户名和密码构建代理URL +// return buildCalfElephantProxyURL(username, password) +// case TailProxyType: +// // 尾巴代理:使用机器码构建代理URL +// return buildTailProxyURL(machineCode) +// default: +// // 不支持的代理类型,返回错误 +// return "", fmt.Errorf("不支持的代理类型: %s", proxyType) +// } +//} +// +//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] 代理用户名 +//* param password[string] 代理密码 +//* return 代理服务器IP,错误信息 +// */ +//func buildCalfElephantProxyURL(username, password string) (string, error) { +// // 随机选择一个代理服务器 +// server := randomServer() +// // 构建代理URL格式:http://用户名:密码@服务器:端口 +// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d", +// url.QueryEscape(username), // URL编码用户名,防止特殊字符问题 +// url.QueryEscape(password), // URL编码密码,防止特殊字符问题 +// 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 +//} +// +///* +//* 尝试下一个小象代理服务器 +//* param username[string] 代理用户名 +//* param password[string] 代理密码 +//* param failedServer[string] 代理服务器 +//* return 代理服务器IP,错误信息 +// */ +//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) { +// // 构建代理URL +// 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 +//* param machineCode[string] 机器码 +//* return 代理服务器IP,错误信息 +// */ +//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) +//} +// +///* +//* 过滤健康代理,返回当前健康的代理列表 +//* param proxies[[]string] 内置代理服务器数组 +//* return 内置代理服务器数组,错误信息 +// */ +//func filterHealthyProxies(proxies []string) []string { +// // 获取读锁,允许多个goroutine同时读取 +// 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 +//} +// +///* +//* 查找可用的尾巴代理 +//* param proxies[[]string] 内置代理服务器数组 +//* return 可用的代理服务器IP,错误信息 +// */ +//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 // 等待组,用于等待所有检测goroutine完成 +// +// // 限制并发数,避免同时检测太多代理导致网络拥塞 +// 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) +// } +// // 等待所有检测goroutine完成 +// 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("所有尾巴代理都不可用") +//} +// +///* +//* 检测代理健康状态,通过访问测试网站验证代理可用性 +//* param proxyURL[string] 代理服务器 +//* return 错误信息 +// */ +//func checkProxyHealth(proxyURL string) error { +// start := time.Now() // 记录开始时间,用于计算响应时间 +// // 创建HTTP请求,设置代理和超时 +// req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second) +// // 发送GET请求到测试网站 +// 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) +// } +// // 检查HTTP状态码 +// 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 +//} +// +///* +//* 更新代理健康状态 +//* param proxyURL[string] 代理服务器 +//* param success[bool] 是否成功 +//* param responseTime[time.Duration] 响应时间 +// */ +//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 +// } +// } +//} +// +///* +//* 获取代理主机名 +//* param proxyURL[string] 代理服务器 +//* return 代理服务器IP +// */ +//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) +// // 使用Fisher-Yates算法打乱顺序 +// globalRand.Shuffle(len(shuffled), func(i, j int) { +// shuffled[i], shuffled[j] = shuffled[j], shuffled[i] +// }) +// return shuffled +//} +// +///* +//* 检查卡密是否过期 +//* param tailCardSecret[string] 卡密 +//* return 是否过期,错误信息 +// */ +//func checkTailCardSecretExpired(tailCardSecret string) (bool, error) { +// // 获取机器码信息 +// code, err := getMachineCode(tailCardSecret) +// if err != nil { +// return false, fmt.Errorf("请求错误: %v", err) +// } +// // 解析过期时间字符串 +// targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime) +// if err != nil { +// return false, fmt.Errorf("时间格式错误: %v", err) +// } +// currentTime := time.Now() +// // 卡密日期在当前日期之后表示有效 +// if targetTime.After(currentTime) { +// return true, nil +// } else { +// // 卡密已过期 +// return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime) +// } +//} +// +//// 定义响应结构体 +//type getMachineCodeResp struct { +// Code int `json:"code"` // 状态码 +// Message string `json:"message"` // 消息 +// Data struct { +// MachineCode string `json:"machine_code"` // 机器码 +// IpExpTime string `json:"ip_exp_time"` // IP过期时间 +// IpThread int `json:"ip_thread"` // IP线程数 +// IpCardCode string `json:"ip_card_code"` // IP卡密 +// } `json:"data"` +//} +// +///* +//* 查询机器码 +//* param tailCardSecret[string] 卡密 +//* return 响应结构体,错误信息 +// */ +//func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) { +// url := "http://114.66.2.223:7842/api/proxies/ip_show" +// // 构建请求数据 +// data := map[string]interface{}{ +// "ip_card_code": tailCardSecret, +// "agent_id": 9999, +// } +// // 发送POST请求 +// _, body, errs := gorequest.New().Post(url).Send(data).End() +// if len(errs) > 0 { +// return nil, fmt.Errorf("查询机器码失败: %v", errs) +// } +// // 解析响应 +// var resp getMachineCodeResp +// if err := json.Unmarshal([]byte(body), &resp); err != nil { +// return nil, fmt.Errorf("解析响应失败: %v", err) +// } +// // 处理不同的响应码 +// if resp.Code == 201 { +// // 需要充值卡密 +// machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode) +// if err != nil { +// return nil, err +// } +// resp.Data.MachineCode = machineCode +// return &resp, nil +// } else if resp.Code == 200 { +// // 成功获取机器码 +// return &resp, nil +// } else { +// // 其他错误 +// return nil, fmt.Errorf("查询机器码失败: %s", resp.Message) +// } +//} +// +///* +//* 充值卡密 +//* param tailCardSecret[string] 卡密 +//* param machineCode[string] 机器码 +//* return 机器码,错误信息 +// */ +//func rechargeCard(tailCardSecret, machineCode string) (string, error) { +// url := "http://114.66.2.223:7842/api/proxies/ip_recharge" +// // 构建请求数据 +// data := map[string]interface{}{ +// "machine_code": machineCode, +// "ip_card_code": tailCardSecret, +// "agent_id": 9999, +// } +// // 发送POST请求 +// _, body, errs := gorequest.New().Post(url).Send(data).End() +// if len(errs) > 0 { +// return "", fmt.Errorf("充值卡密失败: %v", errs) +// } +// // 解析响应 +// var resp struct { +// Code int `json:"code"` // 状态码 +// Message string `json:"message"` // 返回消息 +// Data struct { +// IpExpTime string `json:"ip_exp_time"` // 过期时间 +// IpThread int `json:"ip_thread"` // 线程 +// MachineCode string `json:"machine_code"` // 机器码 +// } `json:"data"` +// } +// if err := json.Unmarshal([]byte(body), &resp); err != nil { +// return "", fmt.Errorf("解析响应失败: %v", err) +// } +// +// // 检查响应状态 +// if resp.Code != 200 { +// return "", fmt.Errorf("充值卡密失败: %s", resp.Message) +// } +// return resp.Data.MachineCode, nil +//} +// +///* +//* 获取代理服务器列表 +//* param machineCode[string] 机器码 +//* return 代理服务器组,错误信息 +// */ +//func getProxies(machineCode string) ([]string, error) { +// log.Printf("[INFO] 开始获取代理列表: %s", machineCode) +// // 生成签名 +// sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode)))) +// +// // 构建请求URL +// GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999", +// machineCode, sign) +// +// // 发送GET请求 +// 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) +// // 过滤空行和JSON格式的行 +// if line != "" && !strings.HasPrefix(line, "{") { +// proxies = append(proxies, line) +// } +// } +// // 检查是否获取到有效代理 +// if len(proxies) == 0 { +// return nil, fmt.Errorf("未获取到有效代理") +// } +// +// log.Printf("[INFO] 获取到 %d 个代理", len(proxies)) +// return proxies, nil +//} +// +//// 初始化代理信息 +//func initProxy() { +// +//} +// +//// =================== C 导出函数 ======================= +// +//// GetProxyHealth 导出函数:获取代理健康状态(用于调试) +//// +////export GetProxyHealth +//func GetProxyHealth() *C.char { +// proxyHealthMutex.RLock() +// defer proxyHealthMutex.RUnlock() +// // 构建健康信息映射 +// healthInfo := make(map[string]interface{}) +// for proxy, health := range proxyHealthMaps { +// healthInfo[proxy] = map[string]interface{}{ +// "success_count": health.SuccessCount, +// "fail_count": health.FailCount, +// "last_check": health.LastCheck.Format(time.RFC3339), +// "response_time": health.ResponseTime.String(), +// "is_healthy": health.IsHealthy, +// } +// } +// // 序列化为JSON +// jsonData, err := json.Marshal(healthInfo) +// if err != nil { +// return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err)) +// } +// +// return C.CString(string(jsonData)) +//} +// +//// ProxyTypeManager 导出函数:代理类型管理器 +//// +////export ProxyTypeManager +//func ProxyTypeManager(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) +// +// log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType) +// // 调用代理类型管理器 +// proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: %v", err) +// log.Printf("[ERROR] 代理类型管理器错误: %v", err) +// return C.CString(errorMsg) +// } +// +// log.Printf("[DEBUG] 代理类型管理器返回: %s", proxyURL) +// return C.CString(proxyURL) +//} +// +//// 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) +// +// resp, err := getMachineCode(goTailCardSecret) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: %v", err) +// log.Printf("[ERROR] 查询机器码错误: %v", err) +// return C.CString(errorMsg) +// } +// +// // 将响应转换为JSON +// jsonData, err := json.Marshal(resp) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) +// log.Printf("[ERROR] 序列化机器码响应失败: %v", err) +// return C.CString(errorMsg) +// } +// +// return C.CString(string(jsonData)) +//} +// +//// RechargeCard 导出函数:充值卡密 +//// +////export RechargeCard +//func RechargeCard(tailCardSecret, machineCode *C.char) *C.char { +// goTailCardSecret := C.GoString(tailCardSecret) +// goMachineCode := C.GoString(machineCode) +// log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode) +// +// newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: %v", err) +// log.Printf("[ERROR] 充值卡密错误: %v", err) +// return C.CString(errorMsg) +// } +// +// // 构建成功响应 +// response := map[string]interface{}{ +// "success": true, +// "machine_code": newMachineCode, +// "message": "充值成功", +// } +// +// jsonData, err := json.Marshal(response) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) +// log.Printf("[ERROR] 序列化充值响应失败: %v", err) +// return C.CString(errorMsg) +// } +// +// log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode) +// return C.CString(string(jsonData)) +//} +// +//// GetProxies 导出函数:获取代理服务器列表 +//// +////export GetProxies +//func GetProxies(machineCode *C.char) *C.char { +// goMachineCode := C.GoString(machineCode) +// log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode) +// +// proxies, err := getProxies(goMachineCode) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: %v", err) +// log.Printf("[ERROR] 获取代理服务器列表错误: %v", err) +// return C.CString(errorMsg) +// } +// +// // 将代理列表转换为JSON +// response := map[string]interface{}{ +// "success": true, +// "count": len(proxies), +// "proxies": proxies, +// } +// +// jsonData, err := json.Marshal(response) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) +// log.Printf("[ERROR] 序列化代理列表失败: %v", err) +// return C.CString(errorMsg) +// } +// +// log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies)) +// return C.CString(string(jsonData)) +//} +// +//// CheckTailCardSecretExpired 导出函数:检查卡密是否过期 +//// +////export CheckTailCardSecretExpired +//func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char { +// goTailCardSecret := C.GoString(tailCardSecret) +// +// isValid, err := checkTailCardSecretExpired(goTailCardSecret) +// if err != nil { +// // 如果是过期错误,返回特定格式 +// if strings.Contains(err.Error(), "卡密已经过期") { +// response := map[string]interface{}{ +// "is_valid": false, +// "message": err.Error(), +// } +// jsonData, _ := json.Marshal(response) +// return C.CString(string(jsonData)) +// } +// +// errorMsg := fmt.Sprintf("ERROR: %v", err) +// log.Printf("[ERROR] 检查卡密过期错误: %v", err) +// return C.CString(errorMsg) +// } +// +// response := map[string]interface{}{ +// "is_valid": isValid, +// "message": "卡密有效", +// } +// +// jsonData, err := json.Marshal(response) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err) +// return C.CString(errorMsg) +// } +// return C.CString(string(jsonData)) +//} +// +//// InitProxyManager 导出函数:初始化代理管理器 +//// +////export InitProxyManager +//func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char { +// goServersJson := C.GoString(serversJson) +// goUsername := C.GoString(username) +// goPassword := C.GoString(password) +// goTailCardSecret := C.GoString(tailCardSecret) +// goProxyType := C.GoString(proxyType) +// +// log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType) +// +// // 解析服务器列表 +// var serverList []string +// if goServersJson != "" { +// if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil { +// errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err) +// log.Printf("[ERROR] 解析服务器列表失败: %v", err) +// return C.CString(errorMsg) +// } +// } +// +// // 更新全局服务器列表 +// if len(serverList) > 0 { +// randMutex.Lock() +// servers = serverList +// randMutex.Unlock() +// log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers)) +// } +// +// // 创建代理管理器 +// manager := ProxyManager{ +// servers: servers, +// username: goUsername, +// password: goPassword, +// tailCardSecret: goTailCardSecret, +// proxyType: goProxyType, +// } +// +// // 序列化管理器信息 +// jsonData, err := json.Marshal(manager) +// if err != nil { +// errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err) +// log.Printf("[ERROR] 序列化代理管理器失败: %v", err) +// return C.CString(errorMsg) +// } +// +// log.Printf("[DEBUG] 代理管理器初始化成功") +// return C.CString(string(jsonData)) +//} +// +//// FreeCString 导出函数:释放C字符串内存 +//// +////export FreeCString +//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() { +// managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c") +// if err != nil { +// fmt.Println(err.Error()) +// } +// fmt.Println(managerNew) +//} diff --git a/proxy/proxy_so.go b/proxy/proxy_so.go index ce00944..7c39676 100644 --- a/proxy/proxy_so.go +++ b/proxy/proxy_so.go @@ -1,7 +1,5 @@ package main -import "fmt" - ///* //#cgo LDFLAGS: -ldl // @@ -700,10 +698,10 @@ import "fmt" //func main() { //} -func main() { - managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c") - if err != nil { - fmt.Println(err) - } - fmt.Println(managerNew) -} +//func main() { +// managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c") +// if err != nil { +// fmt.Println(err) +// } +// fmt.Println(managerNew) +//} diff --git a/redis/redis.go b/redis/redis.go new file mode 100644 index 0000000..b36f5b5 --- /dev/null +++ b/redis/redis.go @@ -0,0 +1,675 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" + + "github.com/redis/go-redis/v9" +) + +// RedisClient Redis客户端结构体 +type RedisClient struct { + Client *redis.Client + Ctx context.Context +} + +// RedisConfig Redis配置结构体 +type RedisConfig struct { + Addr string // 服务器地址:端口 + Password string // 密码 + DB int // 数据库编号 + PoolSize int // 连接池大小 + MinIdleConns int // 最小空闲连接数 + DialTimeout time.Duration // 连接超时 + ReadTimeout time.Duration // 读取超时 + WriteTimeout time.Duration // 写入超时 + PoolTimeout time.Duration // 获取连接超时 +} + +// NewRedisClient 创建新的Redis客户端 +func NewRedisClient(config *RedisConfig) *RedisClient { + if config == nil { + // 默认配置 - 根据您的需求设置 + config = &RedisConfig{ + Addr: "36.212.20.113:7963", + Password: "", + DB: 2, + PoolSize: 10, + MinIdleConns: 5, + DialTimeout: 5 * time.Second, + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + PoolTimeout: 4 * time.Second, + } + } + + rdb := redis.NewClient(&redis.Options{ + Addr: config.Addr, + Password: config.Password, + DB: config.DB, + PoolSize: config.PoolSize, + MinIdleConns: config.MinIdleConns, + DialTimeout: config.DialTimeout, + ReadTimeout: config.ReadTimeout, + WriteTimeout: config.WriteTimeout, + PoolTimeout: config.PoolTimeout, + }) + + return &RedisClient{ + Client: rdb, + Ctx: context.Background(), + } +} + +// Close 关闭Redis连接 +func (r *RedisClient) Close() error { + return r.Client.Close() +} + +// Ping 测试连接 +func (r *RedisClient) Ping() error { + pong, err := r.Client.Ping(r.Ctx).Result() + if err != nil { + return fmt.Errorf("Redis连接失败: %v", err) + } + log.Printf("Redis连接成功: %s", pong) + return nil +} + +// ==================== 字符串操作 ==================== + +// Set 设置键值对 +func (r *RedisClient) Set(key string, value interface{}, expiration time.Duration) error { + err := r.Client.Set(r.Ctx, key, value, expiration).Err() + if err != nil { + return fmt.Errorf("设置键 %s 失败: %v", key, err) + } + return nil +} + +// Get 获取字符串值 +func (r *RedisClient) Get(key string) (string, error) { + val, err := r.Client.Get(r.Ctx, key).Result() + if err == redis.Nil { + return "", fmt.Errorf("键 %s 不存在", key) + } else if err != nil { + return "", fmt.Errorf("获取键 %s 失败: %v", key, err) + } + return val, nil +} + +// GetInt 获取整数值 +func (r *RedisClient) GetInt(key string) (int, error) { + val, err := r.Client.Get(r.Ctx, key).Int() + if err == redis.Nil { + return 0, fmt.Errorf("键 %s 不存在", key) + } else if err != nil { + return 0, fmt.Errorf("获取键 %s 失败: %v", key, err) + } + return val, nil +} + +// GetInt64 获取64位整数值 +func (r *RedisClient) GetInt64(key string) (int64, error) { + val, err := r.Client.Get(r.Ctx, key).Int64() + if err == redis.Nil { + return 0, fmt.Errorf("键 %s 不存在", key) + } else if err != nil { + return 0, fmt.Errorf("获取键 %s 失败: %v", key, err) + } + return val, nil +} + +// GetFloat64 获取浮点数值 +func (r *RedisClient) GetFloat64(key string) (float64, error) { + val, err := r.Client.Get(r.Ctx, key).Float64() + if err == redis.Nil { + return 0, fmt.Errorf("键 %s 不存在", key) + } else if err != nil { + return 0, fmt.Errorf("获取键 %s 失败: %v", key, err) + } + return val, nil +} + +// GetBool 获取布尔值 +func (r *RedisClient) GetBool(key string) (bool, error) { + val, err := r.Client.Get(r.Ctx, key).Bool() + if err == redis.Nil { + return false, fmt.Errorf("键 %s 不存在", key) + } else if err != nil { + return false, fmt.Errorf("获取键 %s 失败: %v", key, err) + } + return val, nil +} + +// SetNX 只有当键不存在时设置值 +func (r *RedisClient) SetNX(key string, value interface{}, expiration time.Duration) (bool, error) { + success, err := r.Client.SetNX(r.Ctx, key, value, expiration).Result() + if err != nil { + return false, fmt.Errorf("SetNX操作失败: %v", err) + } + return success, nil +} + +// SetXX 只有当键存在时设置值 +func (r *RedisClient) SetXX(key string, value interface{}, expiration time.Duration) (bool, error) { + success, err := r.Client.SetXX(r.Ctx, key, value, expiration).Result() + if err != nil { + return false, fmt.Errorf("SetXX操作失败: %v", err) + } + return success, nil +} + +// MSet 批量设置键值对 +func (r *RedisClient) MSet(values map[string]interface{}) error { + pairs := make([]interface{}, 0, len(values)*2) + for k, v := range values { + pairs = append(pairs, k, v) + } + err := r.Client.MSet(r.Ctx, pairs...).Err() + if err != nil { + return fmt.Errorf("批量设置失败: %v", err) + } + return nil +} + +// MGet 批量获取值 +func (r *RedisClient) MGet(keys ...string) ([]interface{}, error) { + vals, err := r.Client.MGet(r.Ctx, keys...).Result() + if err != nil { + return nil, fmt.Errorf("批量获取失败: %v", err) + } + return vals, nil +} + +// Incr 自增1 +func (r *RedisClient) Incr(key string) (int64, error) { + val, err := r.Client.Incr(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("自增失败: %v", err) + } + return val, nil +} + +// IncrBy 自增指定值 +func (r *RedisClient) IncrBy(key string, value int64) (int64, error) { + val, err := r.Client.IncrBy(r.Ctx, key, value).Result() + if err != nil { + return 0, fmt.Errorf("自增 %d 失败: %v", value, err) + } + return val, nil +} + +// Decr 自减1 +func (r *RedisClient) Decr(key string) (int64, error) { + val, err := r.Client.Decr(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("自减失败: %v", err) + } + return val, nil +} + +// DecrBy 自减指定值 +func (r *RedisClient) DecrBy(key string, value int64) (int64, error) { + val, err := r.Client.DecrBy(r.Ctx, key, value).Result() + if err != nil { + return 0, fmt.Errorf("自减 %d 失败: %v", value, err) + } + return val, nil +} + +// Append 追加字符串 +func (r *RedisClient) Append(key, value string) (int64, error) { + length, err := r.Client.Append(r.Ctx, key, value).Result() + if err != nil { + return 0, fmt.Errorf("追加字符串失败: %v", err) + } + return length, nil +} + +// StrLen 获取字符串长度 +func (r *RedisClient) StrLen(key string) (int64, error) { + length, err := r.Client.StrLen(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("获取字符串长度失败: %v", err) + } + return length, nil +} + +// ==================== 键操作 ==================== + +// Exists 检查键是否存在 +func (r *RedisClient) Exists(keys ...string) (int64, error) { + count, err := r.Client.Exists(r.Ctx, keys...).Result() + if err != nil { + return 0, fmt.Errorf("检查键是否存在失败: %v", err) + } + return count, nil +} + +// Del 删除键 +func (r *RedisClient) Del(keys ...string) (int64, error) { + count, err := r.Client.Del(r.Ctx, keys...).Result() + if err != nil { + return 0, fmt.Errorf("删除键失败: %v", err) + } + return count, nil +} + +// Expire 设置过期时间 +func (r *RedisClient) Expire(key string, expiration time.Duration) (bool, error) { + success, err := r.Client.Expire(r.Ctx, key, expiration).Result() + if err != nil { + return false, fmt.Errorf("设置过期时间失败: %v", err) + } + return success, nil +} + +// TTL 获取剩余过期时间 +func (r *RedisClient) TTL(key string) (time.Duration, error) { + ttl, err := r.Client.TTL(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("获取过期时间失败: %v", err) + } + return ttl, nil +} + +// Persist 移除过期时间 +func (r *RedisClient) Persist(key string) (bool, error) { + success, err := r.Client.Persist(r.Ctx, key).Result() + if err != nil { + return false, fmt.Errorf("移除过期时间失败: %v", err) + } + return success, nil +} + +// Type 获取键类型 +func (r *RedisClient) Type(key string) (string, error) { + typeStr, err := r.Client.Type(r.Ctx, key).Result() + if err != nil { + return "", fmt.Errorf("获取键类型失败: %v", err) + } + return typeStr, nil +} + +// Keys 查找所有匹配的键 +func (r *RedisClient) Keys(pattern string) ([]string, error) { + keys, err := r.Client.Keys(r.Ctx, pattern).Result() + if err != nil { + return nil, fmt.Errorf("查找键失败: %v", err) + } + return keys, nil +} + +// RandomKey 随机获取一个键 +func (r *RedisClient) RandomKey() (string, error) { + key, err := r.Client.RandomKey(r.Ctx).Result() + if err != nil { + return "", fmt.Errorf("随机获取键失败: %v", err) + } + return key, nil +} + +// Rename 重命名键 +func (r *RedisClient) Rename(key, newKey string) error { + err := r.Client.Rename(r.Ctx, key, newKey).Err() + if err != nil { + return fmt.Errorf("重命名键失败: %v", err) + } + return nil +} + +// RenameNX 只有当新键不存在时重命名 +func (r *RedisClient) RenameNX(key, newKey string) (bool, error) { + success, err := r.Client.RenameNX(r.Ctx, key, newKey).Result() + if err != nil { + return false, fmt.Errorf("重命名键失败: %v", err) + } + return success, nil +} + +// ==================== Hash操作 ==================== + +// HSet 设置Hash字段 +func (r *RedisClient) HSet(key string, values ...interface{}) error { + err := r.Client.HSet(r.Ctx, key, values...).Err() + if err != nil { + return fmt.Errorf("设置Hash失败: %v", err) + } + return nil +} + +// HGet 获取Hash字段 +func (r *RedisClient) HGet(key, field string) (string, error) { + val, err := r.Client.HGet(r.Ctx, key, field).Result() + if err == redis.Nil { + return "", fmt.Errorf("字段 %s 不存在", field) + } else if err != nil { + return "", fmt.Errorf("获取Hash字段失败: %v", err) + } + return val, nil +} + +// HGetAll 获取所有Hash字段 +func (r *RedisClient) HGetAll(key string) (map[string]string, error) { + vals, err := r.Client.HGetAll(r.Ctx, key).Result() + if err != nil { + return nil, fmt.Errorf("获取所有Hash字段失败: %v", err) + } + return vals, nil +} + +// HDel 删除Hash字段 +func (r *RedisClient) HDel(key string, fields ...string) (int64, error) { + count, err := r.Client.HDel(r.Ctx, key, fields...).Result() + if err != nil { + return 0, fmt.Errorf("删除Hash字段失败: %v", err) + } + return count, nil +} + +// HExists 检查Hash字段是否存在 +func (r *RedisClient) HExists(key, field string) (bool, error) { + exists, err := r.Client.HExists(r.Ctx, key, field).Result() + if err != nil { + return false, fmt.Errorf("检查Hash字段失败: %v", err) + } + return exists, nil +} + +// HKeys 获取所有Hash字段名 +func (r *RedisClient) HKeys(key string) ([]string, error) { + keys, err := r.Client.HKeys(r.Ctx, key).Result() + if err != nil { + return nil, fmt.Errorf("获取Hash字段名失败: %v", err) + } + return keys, nil +} + +// HVals 获取所有Hash字段值 +func (r *RedisClient) HVals(key string) ([]string, error) { + vals, err := r.Client.HVals(r.Ctx, key).Result() + if err != nil { + return nil, fmt.Errorf("获取Hash字段值失败: %v", err) + } + return vals, nil +} + +// HLen 获取Hash字段数量 +func (r *RedisClient) HLen(key string) (int64, error) { + count, err := r.Client.HLen(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("获取Hash字段数量失败: %v", err) + } + return count, nil +} + +// HIncrBy Hash字段自增 +func (r *RedisClient) HIncrBy(key, field string, incr int64) (int64, error) { + val, err := r.Client.HIncrBy(r.Ctx, key, field, incr).Result() + if err != nil { + return 0, fmt.Errorf("Hash字段自增失败: %v", err) + } + return val, nil +} + +// ==================== List操作 ==================== + +// LPush 从左侧插入列表 +func (r *RedisClient) LPush(key string, values ...interface{}) (int64, error) { + count, err := r.Client.LPush(r.Ctx, key, values...).Result() + if err != nil { + return 0, fmt.Errorf("LPush失败: %v", err) + } + return count, nil +} + +// RPush 从右侧插入列表 +func (r *RedisClient) RPush(key string, values ...interface{}) (int64, error) { + count, err := r.Client.RPush(r.Ctx, key, values...).Result() + if err != nil { + return 0, fmt.Errorf("RPush失败: %v", err) + } + return count, nil +} + +// LPop 从左侧弹出元素 +func (r *RedisClient) LPop(key string) (string, error) { + val, err := r.Client.LPop(r.Ctx, key).Result() + if err == redis.Nil { + return "", fmt.Errorf("列表为空") + } else if err != nil { + return "", fmt.Errorf("LPop失败: %v", err) + } + return val, nil +} + +// RPop 从右侧弹出元素 +func (r *RedisClient) RPop(key string) (string, error) { + val, err := r.Client.RPop(r.Ctx, key).Result() + if err == redis.Nil { + return "", fmt.Errorf("列表为空") + } else if err != nil { + return "", fmt.Errorf("RPop失败: %v", err) + } + return val, nil +} + +// LLen 获取列表长度 +func (r *RedisClient) LLen(key string) (int64, error) { + length, err := r.Client.LLen(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("获取列表长度失败: %v", err) + } + return length, nil +} + +// LRange 获取列表范围 +func (r *RedisClient) LRange(key string, start, stop int64) ([]string, error) { + vals, err := r.Client.LRange(r.Ctx, key, start, stop).Result() + if err != nil { + return nil, fmt.Errorf("获取列表范围失败: %v", err) + } + return vals, nil +} + +// ==================== Set操作 ==================== + +// SAdd 添加集合元素 +func (r *RedisClient) SAdd(key string, members ...interface{}) (int64, error) { + count, err := r.Client.SAdd(r.Ctx, key, members...).Result() + if err != nil { + return 0, fmt.Errorf("SAdd失败: %v", err) + } + return count, nil +} + +// SMembers 获取所有集合成员 +func (r *RedisClient) SMembers(key string) ([]string, error) { + members, err := r.Client.SMembers(r.Ctx, key).Result() + if err != nil { + return nil, fmt.Errorf("获取集合成员失败: %v", err) + } + return members, nil +} + +// SIsMember 检查是否是集合成员 +func (r *RedisClient) SIsMember(key string, member interface{}) (bool, error) { + exists, err := r.Client.SIsMember(r.Ctx, key, member).Result() + if err != nil { + return false, fmt.Errorf("检查集合成员失败: %v", err) + } + return exists, nil +} + +// SCard 获取集合基数 +func (r *RedisClient) SCard(key string) (int64, error) { + count, err := r.Client.SCard(r.Ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("获取集合基数失败: %v", err) + } + return count, nil +} + +// ==================== Sorted Set操作 ==================== + +// ZAdd 添加有序集合元素 +func (r *RedisClient) ZAdd(key string, members ...redis.Z) (int64, error) { + count, err := r.Client.ZAdd(r.Ctx, key, members...).Result() + if err != nil { + return 0, fmt.Errorf("ZAdd失败: %v", err) + } + return count, nil +} + +// ZRange 获取有序集合范围 +func (r *RedisClient) ZRange(key string, start, stop int64) ([]string, error) { + vals, err := r.Client.ZRange(r.Ctx, key, start, stop).Result() + if err != nil { + return nil, fmt.Errorf("ZRange失败: %v", err) + } + return vals, nil +} + +// ZRangeWithScores 获取有序集合范围和分数 +func (r *RedisClient) ZRangeWithScores(key string, start, stop int64) ([]redis.Z, error) { + vals, err := r.Client.ZRangeWithScores(r.Ctx, key, start, stop).Result() + if err != nil { + return nil, fmt.Errorf("ZRangeWithScores失败: %v", err) + } + return vals, nil +} + +// ZScore 获取元素分数 +func (r *RedisClient) ZScore(key, member string) (float64, error) { + score, err := r.Client.ZScore(r.Ctx, key, member).Result() + if err == redis.Nil { + return 0, fmt.Errorf("元素不存在") + } else if err != nil { + return 0, fmt.Errorf("获取分数失败: %v", err) + } + return score, nil +} + +// ==================== JSON序列化操作 ==================== + +// SetJSON 将结构体序列化为JSON并存储 +func (r *RedisClient) SetJSON(key string, data interface{}, expiration time.Duration) error { + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("JSON序列化失败: %v", err) + } + return r.Set(key, jsonData, expiration) +} + +// GetJSON 获取JSON并反序列化到结构体 +func (r *RedisClient) GetJSON(key string, result interface{}) error { + data, err := r.Get(key) + if err != nil { + return err + } + err = json.Unmarshal([]byte(data), result) + if err != nil { + return fmt.Errorf("JSON反序列化失败: %v", err) + } + return nil +} + +// ==================== 分布式锁 ==================== + +// TryLock 尝试获取分布式锁 +func (r *RedisClient) TryLock(key string, expiration time.Duration) (bool, error) { + success, err := r.Client.SetNX(r.Ctx, key, "locked", expiration).Result() + if err != nil { + return false, fmt.Errorf("获取锁失败: %v", err) + } + return success, nil +} + +// ReleaseLock 释放分布式锁 +func (r *RedisClient) ReleaseLock(key string) error { + // 使用Lua脚本确保原子性操作 + script := ` + if redis.call("get", KEYS[1]) == ARGV[1] then + return redis.call("del", KEYS[1]) + else + return 0 + end + ` + result, err := r.Client.Eval(r.Ctx, script, []string{key}, "locked").Result() + if err != nil { + return fmt.Errorf("释放锁失败: %v", err) + } + if result.(int64) == 0 { + return fmt.Errorf("锁已释放或不属于当前客户端") + } + return nil +} + +// ==================== 管道操作 ==================== + +// Pipeline 执行管道操作 +func (r *RedisClient) Pipeline(fn func(pipe redis.Pipeliner) error) error { + pipe := r.Client.Pipeline() + err := fn(pipe) + if err != nil { + return err + } + _, err = pipe.Exec(r.Ctx) + return err +} + +// ==================== 事务操作 ==================== + +// Watch 监视键并执行事务 +func (r *RedisClient) Watch(fn func(tx *redis.Tx) error, keys ...string) error { + err := r.Client.Watch(r.Ctx, fn, keys...) + if err != nil { + return fmt.Errorf("事务执行失败: %v", err) + } + return nil +} + +// ==================== 发布订阅 ==================== + +// Publish 发布消息 +func (r *RedisClient) Publish(channel string, message interface{}) error { + err := r.Client.Publish(r.Ctx, channel, message).Err() + if err != nil { + return fmt.Errorf("发布消息失败: %v", err) + } + return nil +} + +// Subscribe 订阅频道 +func (r *RedisClient) Subscribe(channels ...string) *redis.PubSub { + return r.Client.Subscribe(r.Ctx, channels...) +} + +// ==================== 数据库操作 ==================== + +// SelectDB 切换数据库 +// 注意:go-redis/v9的Client没有直接的Select方法,需要通过Do命令执行 +func (r *RedisClient) SelectDB(db int) error { + // 使用Do命令执行SELECT + cmd := redis.NewCmd(r.Ctx, "SELECT", db) + err := r.Client.Process(r.Ctx, cmd) + if err != nil { + return fmt.Errorf("切换数据库到 %d 失败: %v", db, err) + } + + // 检查命令执行结果 + _, err = cmd.Result() + if err != nil { + return fmt.Errorf("切换数据库到 %d 失败: %v", db, err) + } + + return nil +} + +//// 主函数 +//func main() { +//} diff --git a/redis/redisDll.go b/redis/redisDll.go new file mode 100644 index 0000000..b9d0f8c --- /dev/null +++ b/redis/redisDll.go @@ -0,0 +1,334 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "strconv" + "strings" + "time" +) + +// KfzVerifyPriceInfo 主结构体 +type KfzVerifyPriceInfo struct { + Isbn string `json:"isbn"` // ISBN + OnSaleCount string `json:"onSaleCount"` // 在售数量 + AvgPrice string `json:"avgPrice"` // 平均价格 + LastPushTime string `json:"lastPushTime"` // 最后推送时间 + Source string `json:"source"` // 来源 + Data []BookItem `json:"data"` // 商品列表 +} + +// BookItem 商品项的具体结构 +type BookItem struct { + Price string `json:"price"` // 价格 "17.01" + ShippingFee float64 `json:"shippingFee"` // 运费 0 或 5.8 + Condition string `json:"condition"` // 品相 "八五品" + ImgBigUrl string `json:"imgBigUrl"` // 图片URL + ShopAvgShippingTime string `json:"shopAvgShippingTime"` // 平均发货时间 "10" + ShopSuccessOrderRate string `json:"shopSuccessOrderRate"` // 成功率 "90.03" + GoodsUrl string `json:"goodsUrl"` // 商品URL "575550/9643470711" + Title string `json:"title"` // 标题 + GoodsImgCount int `json:"goodsImgCount"` // 图片数量 9 +} + +// ParseRedisData 解析Redis返回的数据 +func ParseRedisData(redisData interface{}) (*KfzVerifyPriceInfo, error) { + // 将redisData转换为[]interface{}类型 + data, ok := redisData.([]interface{}) + if !ok { + return nil, fmt.Errorf("redis数据格式错误: 期望数组,得到 %T", redisData) + } + + // 检查数组长度 + if len(data) < 6 { + return nil, fmt.Errorf("redis数据格式错误: 数组长度不足,期望至少6个元素,实际 %d", len(data)) + } + + // 创建结果对象 + result := &KfzVerifyPriceInfo{} + + // 解析ISBN (索引0) + if isbn, ok := data[0].(string); ok { + result.Isbn = isbn + } else { + result.Isbn = fmt.Sprintf("%v", data[0]) + } + + // 解析在售数量 (索引2) + if onSaleCount, ok := data[2].(string); ok { + result.OnSaleCount = onSaleCount + } else { + result.OnSaleCount = fmt.Sprintf("%v", data[2]) + } + + // 解析平均价格 (索引3) + if avgPrice, ok := data[3].(string); ok { + result.AvgPrice = avgPrice + } else { + result.AvgPrice = fmt.Sprintf("%v", data[3]) + } + + // 解析最后推送时间 (索引4) + if lastPushTime, ok := data[4].(string); ok { + result.LastPushTime = lastPushTime + } else { + result.LastPushTime = fmt.Sprintf("%v", data[4]) + } + + // 解析来源 (索引5) + if source, ok := data[5].(string); ok { + result.Source = source + } else { + result.Source = fmt.Sprintf("%v", data[5]) + } + + // 解析商品列表 (索引1) + bookItems, err := parseBookItems(data[1]) + if err != nil { + return nil, fmt.Errorf("解析商品列表失败: %v", err) + } + result.Data = bookItems + + return result, nil +} + +// parseBookItems 解析商品列表 +func parseBookItems(items interface{}) ([]BookItem, error) { + // 将items转换为[]interface{} + itemList, ok := items.([]interface{}) + if !ok { + return nil, fmt.Errorf("商品列表格式错误: 期望数组,得到 %T", items) + } + + bookItems := make([]BookItem, 0, len(itemList)) + + for i, item := range itemList { + // 每个商品项应该是一个包含9个元素的数组 + bookData, ok := item.([]interface{}) + if !ok { + log.Printf("第%d个商品格式错误: 期望数组,得到 %T", i, item) + continue + } + + if len(bookData) < 9 { + log.Printf("第%d个商品数据长度不足: 期望至少9个元素,实际 %d", i, len(bookData)) + continue + } + + bookItem := BookItem{} + + // 解析价格 (索引0) + if price, ok := bookData[0].(string); ok { + bookItem.Price = price + } else { + bookItem.Price = fmt.Sprintf("%v", bookData[0]) + } + + // 解析运费 (索引1) + switch v := bookData[1].(type) { + case float64: + bookItem.ShippingFee = v + case int: + bookItem.ShippingFee = float64(v) + case string: + if f, err := strconv.ParseFloat(v, 64); err == nil { + bookItem.ShippingFee = f + } + default: + bookItem.ShippingFee = 0 + } + + // 解析品相 (索引2) + if condition, ok := bookData[2].(string); ok { + bookItem.Condition = condition + } else { + bookItem.Condition = fmt.Sprintf("%v", bookData[2]) + } + + // 解析图片URL (索引3) + if imgUrl, ok := bookData[3].(string); ok { + bookItem.ImgBigUrl = imgUrl + } else { + bookItem.ImgBigUrl = fmt.Sprintf("%v", bookData[3]) + } + + // 解析平均发货时间 (索引4) + if shippingTime, ok := bookData[4].(string); ok { + bookItem.ShopAvgShippingTime = shippingTime + } else { + bookItem.ShopAvgShippingTime = fmt.Sprintf("%v", bookData[4]) + } + + // 解析成功率 (索引5) + if successRate, ok := bookData[5].(string); ok { + bookItem.ShopSuccessOrderRate = successRate + } else { + bookItem.ShopSuccessOrderRate = fmt.Sprintf("%v", bookData[5]) + } + + // 解析商品URL (索引6) + if goodsUrl, ok := bookData[6].(string); ok { + bookItem.GoodsUrl = goodsUrl + } else { + bookItem.GoodsUrl = fmt.Sprintf("%v", bookData[6]) + } + + // 解析标题 (索引7) + if title, ok := bookData[7].(string); ok { + bookItem.Title = title + } else { + bookItem.Title = fmt.Sprintf("%v", bookData[7]) + } + + // 解析图片数量 (索引8) + switch v := bookData[8].(type) { + case int: + bookItem.GoodsImgCount = v + case float64: + bookItem.GoodsImgCount = int(v) + case string: + if count, err := strconv.Atoi(v); err == nil { + bookItem.GoodsImgCount = count + } + default: + bookItem.GoodsImgCount = 0 + } + + bookItems = append(bookItems, bookItem) + } + + return bookItems, nil +} + +// GetValueFromDB1 当从DB2获取失败时,从DB1的cp85hash中查找对应的key并加1 +func GetValueFromDB1(redisClient *RedisClient, originalKey string) (string, error) { + // 提取ISBN号(去掉"cp:"前缀) + isbn := strings.TrimPrefix(originalKey, "cp:") + + // 切换到DB1 + err := redisClient.SelectDB(1) + if err != nil { + return "", fmt.Errorf("切换到DB1失败: %v", err) + } + + // 从cp85hash中获取对应的值 + hashKey := "cp85" + value, err := redisClient.HGet(hashKey, isbn) + if err != nil { + return "", fmt.Errorf("从cp85hash获取数据失败: %v", err) + } + + log.Println("cp85Isbn:", isbn, "value:", value) + // 将值转换为整数并加1 + intValue, err := strconv.Atoi(value) + if err != nil { + return "", fmt.Errorf("值转换失败: %v", err) + } + + newValue := intValue + 1 + + // 更新hash中的值 + err = redisClient.HSet(hashKey, isbn, strconv.Itoa(newValue)) + if err != nil { + return "", fmt.Errorf("更新cp85hash值失败: %v", err) + } + + log.Printf("成功更新DB1中cp85hash的键 %s,值从 %d 增加到 %d", isbn, intValue, newValue) + + // 切换回DB2(保持一致性) + err = redisClient.SelectDB(2) + if err != nil { + log.Printf("切换回DB2失败: %v", err) + } + + return "", nil +} + +// 修改您的main函数 +func main() { + // 自定义配置 + config := &RedisConfig{ + Addr: "36.212.20.113:7963", + Password: "j8nZ4jra2E", + DB: 2, + PoolSize: 20, + MinIdleConns: 10, + DialTimeout: 5 * time.Second, + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + PoolTimeout: 4 * time.Second, + } + + // 使用自定义配置 + redisClient := NewRedisClient(config) + defer redisClient.Close() + + // 字符串操作示例 + key := "cp:9787040110951" + + // 获取字符串值 + val, err := redisClient.Get(key) + if err != nil { + log.Printf("从DB2获取失败: %v", err) + + // 尝试从DB1的cp85hash中查找并加1 + newValue, db1Err := GetValueFromDB1(redisClient, key) + if db1Err != nil { + log.Printf("从DB1获取也失败: %v", db1Err) + return + } + + fmt.Printf("从DB1的cp85hash中找到并更新值: %s\n", newValue) + return + } + + fmt.Printf("键 %s 的原始值: %s\n", key, val) + + // 由于Redis返回的是JSON字符串,需要先解析 + var redisData interface{} + err = json.Unmarshal([]byte(val), &redisData) + if err != nil { + log.Printf("JSON解析失败: %v", err) + return + } + + // 解析为结构体 + result, err := ParseRedisData(redisData) + if err != nil { + log.Printf("解析Redis数据失败: %v", err) + return + } + + // 输出解析后的结构体 + fmt.Println("\n=== 解析后的数据 ===") + fmt.Printf("ISBN: %s\n", result.Isbn) + fmt.Printf("在售数量: %s\n", result.OnSaleCount) + fmt.Printf("平均价格: %s\n", result.AvgPrice) + fmt.Printf("最后推送时间: %s\n", result.LastPushTime) + fmt.Printf("来源: %s\n", result.Source) + fmt.Printf("商品数量: %d\n", len(result.Data)) + + // 输出商品列表 + for i, item := range result.Data { + fmt.Printf("\n--- 商品 %d ---\n", i+1) + fmt.Printf(" 价格: %s\n", item.Price) + fmt.Printf(" 运费: %.2f\n", item.ShippingFee) + fmt.Printf(" 品相: %s\n", item.Condition) + fmt.Printf(" 图片URL: %s\n", item.ImgBigUrl) + fmt.Printf(" 平均发货时间: %s\n", item.ShopAvgShippingTime) + fmt.Printf(" 成功率: %s\n", item.ShopSuccessOrderRate) + fmt.Printf(" 商品URL: %s\n", item.GoodsUrl) + fmt.Printf(" 标题: %s\n", item.Title) + fmt.Printf(" 图片数量: %d\n", item.GoodsImgCount) + } + + // 如果需要转换为JSON + jsonResult, err := json.MarshalIndent(result, "", " ") + if err != nil { + log.Printf("JSON序列化失败: %v", err) + } else { + fmt.Println("\n=== JSON格式输出 ===") + fmt.Println(string(jsonResult)) + } +} diff --git a/version.yaml b/version.yaml new file mode 100644 index 0000000..a635ef9 --- /dev/null +++ b/version.yaml @@ -0,0 +1 @@ +verifyPriceLatestVersion: v1.0