更新修改功能
This commit is contained in:
parent
bd241447c6
commit
d41bfcb4ca
557
csv/csv.go
557
csv/csv.go
@ -181,7 +181,7 @@ func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取总行数
|
// 获取总行数
|
||||||
rows, err := csvHandle.GetTotalRows()
|
rows, err := csvHandle.getTotalRows()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bo
|
|||||||
// 读取表头(如果需要)
|
// 读取表头(如果需要)
|
||||||
if hasHeader {
|
if hasHeader {
|
||||||
if err := mgr.readHeader(csvHandle); err != nil {
|
if err := mgr.readHeader(csvHandle); err != nil {
|
||||||
csvHandle.Close()
|
csvHandle.close()
|
||||||
return -1, fmt.Errorf("读取表头失败: %w", err)
|
return -1, fmt.Errorf("读取表头失败: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@ func (mgr *CSVManager) OpenCSVFile(filename string, delimiter rune, hasHeader bo
|
|||||||
return handleID, nil
|
return handleID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFile 打开文件
|
// 打开文件
|
||||||
func (mgr *CSVManager) openFile(handle *CSVHandle) error {
|
func (mgr *CSVManager) openFile(handle *CSVHandle) error {
|
||||||
handle.mu.Lock()
|
handle.mu.Lock()
|
||||||
defer handle.mu.Unlock()
|
defer handle.mu.Unlock()
|
||||||
@ -222,6 +222,7 @@ func (mgr *CSVManager) openFile(handle *CSVHandle) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileSize := fileInfo.Size()
|
fileSize := fileInfo.Size()
|
||||||
|
fmt.Println("文件大小:", fileSize)
|
||||||
|
|
||||||
// 根据文件大小选择打开策略
|
// 根据文件大小选择打开策略
|
||||||
if mgr.config.UseMMap && fileSize > mgr.config.MMapThreshold {
|
if mgr.config.UseMMap && fileSize > mgr.config.MMapThreshold {
|
||||||
@ -231,7 +232,7 @@ func (mgr *CSVManager) openFile(handle *CSVHandle) error {
|
|||||||
return mgr.openFileNormal(handle)
|
return mgr.openFileNormal(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFileNormal 正常打开文件
|
// 正常打开文件
|
||||||
func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error {
|
func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error {
|
||||||
file, err := os.Open(handle.Filename)
|
file, err := os.Open(handle.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -251,7 +252,7 @@ func (mgr *CSVManager) openFileNormal(handle *CSVHandle) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFileWithMMap 使用内存映射打开大文件
|
// 使用内存映射打开大文件
|
||||||
func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error {
|
func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error {
|
||||||
file, err := os.Open(handle.Filename)
|
file, err := os.Open(handle.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -271,7 +272,7 @@ func (mgr *CSVManager) openFileWithMMap(handle *CSVHandle, fileSize int64) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readHeader 读取CSV表头
|
// 读取CSV表头
|
||||||
func (mgr *CSVManager) readHeader(handle *CSVHandle) error {
|
func (mgr *CSVManager) readHeader(handle *CSVHandle) error {
|
||||||
handle.mu.Lock()
|
handle.mu.Lock()
|
||||||
defer handle.mu.Unlock()
|
defer handle.mu.Unlock()
|
||||||
@ -299,8 +300,8 @@ func (mgr *CSVManager) readHeader(handle *CSVHandle) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHandle 获取句柄对象
|
// 获取句柄对象
|
||||||
func (mgr *CSVManager) GetHandle(handleID int64) (*CSVHandle, error) {
|
func (mgr *CSVManager) getHandle(handleID int64) (*CSVHandle, error) {
|
||||||
value, ok := mgr.handles.Load(handleID)
|
value, ok := mgr.handles.Load(handleID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("句柄不存在: %d", handleID)
|
return nil, fmt.Errorf("句柄不存在: %d", handleID)
|
||||||
@ -326,10 +327,114 @@ func (mgr *CSVManager) GetHandle(handleID int64) (*CSVHandle, error) {
|
|||||||
return handle, nil
|
return handle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================== 读取操作 ==========================
|
// 启动自动关闭计时器
|
||||||
|
func (h *CSVHandle) startAutoClose(timeout time.Duration, mgr *CSVManager) {
|
||||||
|
h.autoCloseTimer = time.AfterFunc(timeout, func() {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
// ReadRow 读取一行数据
|
// 检查是否长时间未访问
|
||||||
func (h *CSVHandle) ReadRow() ([]string, error) {
|
if h.IsOpen && time.Since(h.AccessTime) > timeout {
|
||||||
|
h.close()
|
||||||
|
// 从管理器移除
|
||||||
|
mgr.handles.Delete(h.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取表头
|
||||||
|
func (h *CSVHandle) getHeader() []string {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
return h.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
//// 获取句柄信息
|
||||||
|
//func (h *CSVHandle) getInfo() map[string]interface{} {
|
||||||
|
// h.mu.RLock()
|
||||||
|
// defer h.mu.RUnlock()
|
||||||
|
//
|
||||||
|
// return map[string]interface{}{
|
||||||
|
// "id": h.ID,
|
||||||
|
// "filename": h.Filename,
|
||||||
|
// "delimiter": string(h.Delimiter),
|
||||||
|
// "has_header": h.HasHeader,
|
||||||
|
// "is_open": h.IsOpen,
|
||||||
|
// "open_time": h.OpenTime,
|
||||||
|
// "access_time": h.AccessTime,
|
||||||
|
// "access_count": h.AccessCount,
|
||||||
|
// "total_rows": h.TotalRows,
|
||||||
|
// "header": h.Header,
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
// ========================== 句柄管理 ==========================
|
||||||
|
|
||||||
|
// 关闭CSV句柄
|
||||||
|
func (h *CSVHandle) close() error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
h.writeMu.Lock()
|
||||||
|
defer h.writeMu.Unlock()
|
||||||
|
|
||||||
|
if !h.IsOpen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止自动关闭计时器
|
||||||
|
if h.autoCloseTimer != nil {
|
||||||
|
h.autoCloseTimer.Stop()
|
||||||
|
h.autoCloseTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭文件
|
||||||
|
if h.File != nil {
|
||||||
|
if err := h.File.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.File = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h.CSVReader = nil
|
||||||
|
h.IsOpen = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭指定句柄
|
||||||
|
func (mgr *CSVManager) closeHandle(handleID int64) error {
|
||||||
|
value, ok := mgr.handles.Load(handleID)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("句柄不存在: %d", handleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
handle := value.(*CSVHandle)
|
||||||
|
if err := handle.close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理文件锁
|
||||||
|
mgr.fileLocks.Delete(handle.Filename)
|
||||||
|
|
||||||
|
mgr.handles.Delete(handleID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭所有句柄
|
||||||
|
func (mgr *CSVManager) closeAllHandles() {
|
||||||
|
mgr.handles.Range(func(key, value interface{}) bool {
|
||||||
|
handle := value.(*CSVHandle)
|
||||||
|
handle.close()
|
||||||
|
mgr.fileLocks.Delete(handle.Filename)
|
||||||
|
mgr.handles.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================== 读取操作 ==========================
|
||||||
|
// 读取一行数据
|
||||||
|
func (h *CSVHandle) readRow() ([]string, error) {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
defer h.mu.RUnlock()
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
@ -344,8 +449,8 @@ func (h *CSVHandle) ReadRow() ([]string, error) {
|
|||||||
return h.CSVReader.Read()
|
return h.CSVReader.Read()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAllRows 读取所有行
|
// 读取所有行
|
||||||
func (h *CSVHandle) ReadAllRows() ([][]string, error) {
|
func (h *CSVHandle) readAllRows() ([][]string, error) {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
@ -373,8 +478,8 @@ func (h *CSVHandle) ReadAllRows() ([][]string, error) {
|
|||||||
return h.CSVReader.ReadAll()
|
return h.CSVReader.ReadAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRows 读取指定数量的行
|
// 读取指定数量的行
|
||||||
func (h *CSVHandle) ReadRows(count int) ([][]string, error) {
|
func (h *CSVHandle) readRows(count int) ([][]string, error) {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
@ -398,8 +503,8 @@ func (h *CSVHandle) ReadRows(count int) ([][]string, error) {
|
|||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalRows 获取总行数
|
// 获取总行数
|
||||||
func (h *CSVHandle) GetTotalRows() (int64, error) {
|
func (h *CSVHandle) getTotalRows() (int64, error) {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
@ -450,8 +555,53 @@ func (h *CSVHandle) GetTotalRows() (int64, error) {
|
|||||||
|
|
||||||
// ========================== 修改操作 ==========================
|
// ========================== 修改操作 ==========================
|
||||||
|
|
||||||
// ModifyRow 修改指定行
|
// 写入和覆盖CSV文件
|
||||||
func (mgr *CSVManager) ModifyRow(handleID int64, rowNumber int64, newRow []string) (*ModifyResult, error) {
|
func (mgr *CSVManager) writeCSVFile(handleID int64, filename string, data [][]string) (int64, error) {
|
||||||
|
// 1. 获取句柄
|
||||||
|
handle, err := mgr.getHandle(handleID)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
return -1, fmt.Errorf("写入的数据不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileLock := mgr.getFileLock(handle.Filename)
|
||||||
|
fileLock.Lock()
|
||||||
|
defer fileLock.Unlock()
|
||||||
|
|
||||||
|
var file *os.File
|
||||||
|
var fileMode int
|
||||||
|
|
||||||
|
// 检查文件是否存在和有内容
|
||||||
|
if info, err := os.Stat(handle.Filename); err == nil && info.Size() > 0 {
|
||||||
|
// 文件存在且有内容,追加模式
|
||||||
|
fileMode = os.O_APPEND | os.O_WRONLY
|
||||||
|
} else {
|
||||||
|
// 文件不存在或为空,创建模式(会清空已有内容)
|
||||||
|
fileMode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开或创建文件
|
||||||
|
file, err = os.OpenFile(filename, fileMode, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 写入CSV数据
|
||||||
|
writer := csv.NewWriter(file)
|
||||||
|
if err := writer.WriteAll(data); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
|
||||||
|
return -1, writer.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改指定行
|
||||||
|
func (mgr *CSVManager) modifyRow(handleID int64, rowNumber int64, newRow []string) (*ModifyResult, error) {
|
||||||
req := &ModifyRequest{
|
req := &ModifyRequest{
|
||||||
HandleID: handleID,
|
HandleID: handleID,
|
||||||
RowNumber: rowNumber,
|
RowNumber: rowNumber,
|
||||||
@ -459,26 +609,13 @@ func (mgr *CSVManager) ModifyRow(handleID int64, rowNumber int64, newRow []strin
|
|||||||
NewRow: newRow,
|
NewRow: newRow,
|
||||||
}
|
}
|
||||||
|
|
||||||
return mgr.ModifyCSV(req)
|
return mgr.modifyCSV(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModifyCell 修改指定单元格
|
// 通用的CSV修改函数
|
||||||
func (mgr *CSVManager) ModifyCell(handleID int64, rowNumber int64, columnIndex int, newValue string) (*ModifyResult, error) {
|
func (mgr *CSVManager) modifyCSV(req *ModifyRequest) (*ModifyResult, error) {
|
||||||
req := &ModifyRequest{
|
|
||||||
HandleID: handleID,
|
|
||||||
RowNumber: rowNumber,
|
|
||||||
ModifyType: ModifyCell,
|
|
||||||
ColumnIndex: columnIndex,
|
|
||||||
NewCellValue: newValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
return mgr.ModifyCSV(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyCSV 通用的CSV修改函数
|
|
||||||
func (mgr *CSVManager) ModifyCSV(req *ModifyRequest) (*ModifyResult, error) {
|
|
||||||
// 1. 获取句柄
|
// 1. 获取句柄
|
||||||
handle, err := mgr.GetHandle(req.HandleID)
|
handle, err := mgr.getHandle(req.HandleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ModifyResult{
|
return &ModifyResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@ -507,7 +644,7 @@ func (mgr *CSVManager) ModifyCSV(req *ModifyRequest) (*ModifyResult, error) {
|
|||||||
defer handle.writeMu.Unlock()
|
defer handle.writeMu.Unlock()
|
||||||
|
|
||||||
// 6. 执行修改操作
|
// 6. 执行修改操作
|
||||||
result, err := mgr.executeModifyWithRetry(handle, req, operationID)
|
result, err := mgr.executeModifyOnce(handle, req, operationID)
|
||||||
if result != nil {
|
if result != nil {
|
||||||
result.OperationID = operationID
|
result.OperationID = operationID
|
||||||
}
|
}
|
||||||
@ -533,13 +670,6 @@ func (mgr *CSVManager) validateModifyRequestBeforeLock(handle *CSVHandle, req *M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ModifyType == ModifyCell {
|
|
||||||
expectedCols := len(header)
|
|
||||||
if req.ColumnIndex < 0 || req.ColumnIndex >= expectedCols {
|
|
||||||
return fmt.Errorf("列索引超出范围,有效范围: 0-%d", expectedCols-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,15 +722,6 @@ func (mgr *CSVManager) executeModifyOnce(handle *CSVHandle, req *ModifyRequest,
|
|||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//// 2. 获取总行数用于验证
|
|
||||||
//totalRows, err := mgr.calculateTotalRowsWithLock(handle)
|
|
||||||
//if err != nil {
|
|
||||||
// return &ModifyResult{
|
|
||||||
// Success: false,
|
|
||||||
// Message: fmt.Sprintf("获取总行数失败: %v", err),
|
|
||||||
// }, err
|
|
||||||
//}
|
|
||||||
|
|
||||||
// 3. 验证行号范围
|
// 3. 验证行号范围
|
||||||
if err := mgr.validateRowNumber(handle, req, totalRows); err != nil {
|
if err := mgr.validateRowNumber(handle, req, totalRows); err != nil {
|
||||||
return &ModifyResult{
|
return &ModifyResult{
|
||||||
@ -800,6 +921,7 @@ func (mgr *CSVManager) createBackup(handle *CSVHandle) (string, error) {
|
|||||||
}
|
}
|
||||||
handle.tempDir = tempDir
|
handle.tempDir = tempDir
|
||||||
}
|
}
|
||||||
|
fmt.Println("handle.tempDir", handle.tempDir)
|
||||||
|
|
||||||
// 生成备份文件名
|
// 生成备份文件名
|
||||||
backupFile := filepath.Join(handle.tempDir,
|
backupFile := filepath.Join(handle.tempDir,
|
||||||
@ -1305,7 +1427,7 @@ func (mgr *CSVManager) restoreFromBackup(handle *CSVHandle) error {
|
|||||||
|
|
||||||
// GetRow 获取指定行数据
|
// GetRow 获取指定行数据
|
||||||
func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) {
|
func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error) {
|
||||||
handle, err := mgr.GetHandle(handleID)
|
handle, err := mgr.getHandle(handleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1320,7 +1442,7 @@ func (mgr *CSVManager) GetRow(handleID int64, rowNumber int64) ([]string, error)
|
|||||||
|
|
||||||
// UndoLastModify 撤销最后一次修改
|
// UndoLastModify 撤销最后一次修改
|
||||||
func (mgr *CSVManager) UndoLastModify(handleID int64) (*ModifyResult, error) {
|
func (mgr *CSVManager) UndoLastModify(handleID int64) (*ModifyResult, error) {
|
||||||
handle, err := mgr.GetHandle(handleID)
|
handle, err := mgr.getHandle(handleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1383,7 +1505,7 @@ func (mgr *CSVManager) UndoLastModify(handleID int64) (*ModifyResult, error) {
|
|||||||
|
|
||||||
// CleanupBackups 清理备份文件
|
// CleanupBackups 清理备份文件
|
||||||
func (mgr *CSVManager) CleanupBackups(handleID int64) error {
|
func (mgr *CSVManager) CleanupBackups(handleID int64) error {
|
||||||
handle, err := mgr.GetHandle(handleID)
|
handle, err := mgr.getHandle(handleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1406,156 +1528,9 @@ func (mgr *CSVManager) CleanupBackups(handleID int64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================== 句柄管理 ==========================
|
|
||||||
|
|
||||||
// Close 关闭CSV句柄
|
|
||||||
func (h *CSVHandle) Close() error {
|
|
||||||
h.mu.Lock()
|
|
||||||
defer h.mu.Unlock()
|
|
||||||
|
|
||||||
h.writeMu.Lock()
|
|
||||||
defer h.writeMu.Unlock()
|
|
||||||
|
|
||||||
if !h.IsOpen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止自动关闭计时器
|
|
||||||
if h.autoCloseTimer != nil {
|
|
||||||
h.autoCloseTimer.Stop()
|
|
||||||
h.autoCloseTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭文件
|
|
||||||
if h.File != nil {
|
|
||||||
if err := h.File.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.File = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
h.CSVReader = nil
|
|
||||||
h.IsOpen = false
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// startAutoClose 启动自动关闭计时器
|
|
||||||
func (h *CSVHandle) startAutoClose(timeout time.Duration, mgr *CSVManager) {
|
|
||||||
h.autoCloseTimer = time.AfterFunc(timeout, func() {
|
|
||||||
h.mu.Lock()
|
|
||||||
defer h.mu.Unlock()
|
|
||||||
|
|
||||||
// 检查是否长时间未访问
|
|
||||||
if h.IsOpen && time.Since(h.AccessTime) > timeout {
|
|
||||||
h.Close()
|
|
||||||
// 从管理器移除
|
|
||||||
mgr.handles.Delete(h.ID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeader 获取表头
|
|
||||||
func (h *CSVHandle) GetHeader() []string {
|
|
||||||
h.mu.RLock()
|
|
||||||
defer h.mu.RUnlock()
|
|
||||||
return h.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInfo 获取句柄信息
|
|
||||||
func (h *CSVHandle) GetInfo() map[string]interface{} {
|
|
||||||
h.mu.RLock()
|
|
||||||
defer h.mu.RUnlock()
|
|
||||||
|
|
||||||
return map[string]interface{}{
|
|
||||||
"id": h.ID,
|
|
||||||
"filename": h.Filename,
|
|
||||||
"delimiter": string(h.Delimiter),
|
|
||||||
"has_header": h.HasHeader,
|
|
||||||
"is_open": h.IsOpen,
|
|
||||||
"open_time": h.OpenTime,
|
|
||||||
"access_time": h.AccessTime,
|
|
||||||
"access_count": h.AccessCount,
|
|
||||||
"total_rows": h.TotalRows,
|
|
||||||
"header": h.Header,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseHandle 关闭指定句柄
|
|
||||||
func (mgr *CSVManager) CloseHandle(handleID int64) error {
|
|
||||||
value, ok := mgr.handles.Load(handleID)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("句柄不存在: %d", handleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
handle := value.(*CSVHandle)
|
|
||||||
if err := handle.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理文件锁
|
|
||||||
mgr.fileLocks.Delete(handle.Filename)
|
|
||||||
|
|
||||||
mgr.handles.Delete(handleID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseAllHandles 关闭所有句柄
|
|
||||||
func (mgr *CSVManager) CloseAllHandles() {
|
|
||||||
mgr.handles.Range(func(key, value interface{}) bool {
|
|
||||||
handle := value.(*CSVHandle)
|
|
||||||
handle.Close()
|
|
||||||
mgr.fileLocks.Delete(handle.Filename)
|
|
||||||
mgr.handles.Delete(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================== 简便函数 ==========================
|
|
||||||
|
|
||||||
//// OpenCSVFile 打开CSV文件
|
|
||||||
//func OpenCSVFile(filename string, delimiter rune, hasHeader bool) (int64, error) {
|
|
||||||
// return GetManager().OpenCSVFile(filename, delimiter, hasHeader, "utf-8")
|
|
||||||
//}
|
|
||||||
|
|
||||||
// GetCSVHandle 获取CSV句柄
|
|
||||||
func GetCSVHandle(handleID int64) (*CSVHandle, error) {
|
|
||||||
return GetManager().GetHandle(handleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseCSVHandle 关闭CSV句柄
|
|
||||||
func CloseCSVHandle(handleID int64) error {
|
|
||||||
return GetManager().CloseHandle(handleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyCSVRow 修改CSV行
|
|
||||||
func ModifyCSVRow(handleID int64, rowNumber int64, newRow []string) (*ModifyResult, error) {
|
|
||||||
return GetManager().ModifyRow(handleID, rowNumber, newRow)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyCSVCell 修改CSV单元格
|
|
||||||
func ModifyCSVCell(handleID int64, rowNumber int64, columnIndex int, newValue string) (*ModifyResult, error) {
|
|
||||||
return GetManager().ModifyCell(handleID, rowNumber, columnIndex, newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCSVRow 获取CSV行
|
|
||||||
func GetCSVRow(handleID int64, rowNumber int64) ([]string, error) {
|
|
||||||
return GetManager().GetRow(handleID, rowNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UndoLastCSVModify 撤销最后修改
|
|
||||||
func UndoLastCSVModify(handleID int64) (*ModifyResult, error) {
|
|
||||||
return GetManager().UndoLastModify(handleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanupCSVBackups 清理CSV备份
|
|
||||||
func CleanupCSVBackups(handleID int64) error {
|
|
||||||
return GetManager().CleanupBackups(handleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateCSVRowSafe 修改csv文件行数据
|
// UpdateCSVRowSafe 修改csv文件行数据
|
||||||
func (mgr *CSVManager) UpdateCSVRowSafe(handleID int64, rowNum int, newRow []string) error {
|
func (mgr *CSVManager) UpdateCSVRowSafe(handleID int64, rowNum int, newRow []string) error {
|
||||||
getHandle, err := mgr.GetHandle(handleID)
|
getHandle, err := mgr.getHandle(handleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("获取句柄信息失败: %v", err)
|
return fmt.Errorf("获取句柄信息失败: %v", err)
|
||||||
}
|
}
|
||||||
@ -1764,44 +1739,52 @@ func UpdateCSVRowSafe(handleID C.longlong, rowNum C.int, newRow *C.char) *C.char
|
|||||||
|
|
||||||
// 主函数
|
// 主函数
|
||||||
func main() {
|
func main() {
|
||||||
//fmt.Println("=== CSV句柄管理器测试 ===")
|
fmt.Println("=== CSV句柄管理器测试 ===")
|
||||||
//
|
|
||||||
//filename := "csv/taskLog.csv"
|
filename := "csv/taskLog1.csv"
|
||||||
//fmt.Printf("1. 创建测试文件: %s\n", filename)
|
fmt.Printf("1. 创建测试文件: %s\n", filename)
|
||||||
//
|
|
||||||
//// 2. 打开CSV文件
|
// 2. 打开CSV文件
|
||||||
//handleID, err := OpenCSVFile(filename, ',', true)
|
handleID, err := GetManager().OpenCSVFile(filename, ',', true)
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
// fmt.Printf("打开文件失败: %v\n", err)
|
fmt.Printf("打开文件失败: %v\n", err)
|
||||||
// return
|
return
|
||||||
//}
|
}
|
||||||
//defer CloseCSVHandle(handleID)
|
defer GetManager().closeHandle(handleID)
|
||||||
//
|
|
||||||
|
newStr := []string{"9787115524539", "20.00", "2", "上传成功", ""}
|
||||||
|
|
||||||
|
row, err := GetManager().modifyRow(handleID, 3, newStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
fmt.Println(row)
|
||||||
|
|
||||||
//fmt.Printf("2. 打开文件成功,句柄ID: %d\n", handleID)
|
//fmt.Printf("2. 打开文件成功,句柄ID: %d\n", handleID)
|
||||||
//
|
//
|
||||||
////// 3. 获取句柄信息
|
//// 3. 获取句柄信息
|
||||||
////handle, err := GetCSVHandle(handleID)
|
//handle, err := GetCSVHandle(handleID)
|
||||||
////if err != nil {
|
//if err != nil {
|
||||||
//// fmt.Printf("获取句柄失败: %v\n", err)
|
// fmt.Printf("获取句柄失败: %v\n", err)
|
||||||
//// return
|
// return
|
||||||
////}
|
//}
|
||||||
////
|
|
||||||
////info := handle.GetInfo()
|
|
||||||
////fmt.Printf("3. 文件信息:\n")
|
|
||||||
////fmt.Printf(" - 文件名: %s\n", info["filename"])
|
|
||||||
////fmt.Printf(" - 总行数: %v\n", info["total_rows"])
|
|
||||||
////fmt.Printf(" - 表头: %v\n", info["header"])
|
|
||||||
//
|
//
|
||||||
////// 准备新的行数据
|
//info := handle.GetInfo()
|
||||||
////newData := []string{"9787115524539", "20.00", "20", "上传成功", ""}
|
//fmt.Printf("3. 文件信息:\n")
|
||||||
////err = UpdateCSVRowSafes(handleID, 6, newData)
|
//fmt.Printf(" - 文件名: %s\n", info["filename"])
|
||||||
////if err != nil {
|
//fmt.Printf(" - 总行数: %v\n", info["total_rows"])
|
||||||
//// fmt.Printf("错误: %v\n", err)
|
//fmt.Printf(" - 表头: %v\n", info["header"])
|
||||||
////} else {
|
|
||||||
//// fmt.Println("CSV文件更新成功!")
|
|
||||||
////}
|
|
||||||
//
|
//
|
||||||
//// 4. 读取数据
|
//// 准备新的行数据
|
||||||
|
//newData := []string{"9787115524539", "20.00", "20", "上传成功", ""}
|
||||||
|
//err = UpdateCSVRowSafes(handleID, 6, newData)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Printf("错误: %v\n", err)
|
||||||
|
//} else {
|
||||||
|
// fmt.Println("CSV文件更新成功!")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
////4. 读取数据
|
||||||
//fmt.Println("4. 读取测试:")
|
//fmt.Println("4. 读取测试:")
|
||||||
//row3, err := GetCSVRow(handleID, 100000)
|
//row3, err := GetCSVRow(handleID, 100000)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
@ -1810,72 +1793,36 @@ func main() {
|
|||||||
// fmt.Printf(" 第100000行数据: %v\n", row3)
|
// fmt.Printf(" 第100000行数据: %v\n", row3)
|
||||||
//}
|
//}
|
||||||
//
|
//
|
||||||
////// 5. 修改测试
|
//// 5. 修改测试
|
||||||
////fmt.Println("5. 修改测试:")
|
//fmt.Println("5. 修改测试:")
|
||||||
////newRow := []string{"9787115524539", "20.00", "1", "上传成功", ""}
|
//newRow := []string{"9787115524539", "20.00", "1", "上传成功", ""}
|
||||||
////result, err := ModifyCSVRow(handleID, 3, newRow)
|
//result, err := ModifyCSVRow(handleID, 3, newRow)
|
||||||
////if err != nil {
|
//if err != nil {
|
||||||
//// fmt.Printf(" 修改失败: %v\n", err)
|
// fmt.Printf(" 修改失败: %v\n", err)
|
||||||
////} else {
|
//} else {
|
||||||
//// fmt.Printf(" 修改结果: %s (影响行数: %d)\n", result.Message, result.RowsAffected)
|
// fmt.Printf(" 修改结果: %s (影响行数: %d)\n", result.Message, result.RowsAffected)
|
||||||
////
|
|
||||||
//// // 验证修改
|
|
||||||
//// modifiedRow, _ := GetCSVRow(handleID, 3)
|
|
||||||
//// fmt.Printf(" 修改后的第3行: %v\n", modifiedRow)
|
|
||||||
////}
|
|
||||||
//
|
//
|
||||||
////// 6. 单元格修改测试
|
// // 验证修改
|
||||||
////fmt.Println("6. 单元格修改测试:")
|
// modifiedRow, _ := GetCSVRow(handleID, 3)
|
||||||
////cellResult, err := ModifyCSVCell(handleID, 5, 1, "35")
|
// fmt.Printf(" 修改后的第3行: %v\n", modifiedRow)
|
||||||
////if err != nil {
|
//}
|
||||||
//// fmt.Printf(" 单元格修改失败: %v\n", err)
|
//
|
||||||
////} else {
|
//// 6. 单元格修改测试
|
||||||
//// fmt.Printf(" 单元格修改结果: %s\n", cellResult.Message)
|
//fmt.Println("6. 单元格修改测试:")
|
||||||
////
|
//cellResult, err := ModifyCSVCell(handleID, 5, 1, "35")
|
||||||
//// // 验证修改
|
//if err != nil {
|
||||||
//// cellModifiedRow, _ := GetCSVRow(handleID, 5)
|
// fmt.Printf(" 单元格修改失败: %v\n", err)
|
||||||
//// fmt.Printf(" 修改后的第5行: %v\n", cellModifiedRow)
|
//} else {
|
||||||
////}
|
// fmt.Printf(" 单元格修改结果: %s\n", cellResult.Message)
|
||||||
////
|
//
|
||||||
////// 7. 并发测试
|
// // 验证修改
|
||||||
////fmt.Println("7. 并发测试:")
|
// cellModifiedRow, _ := GetCSVRow(handleID, 5)
|
||||||
////TestConcurrentOperations(handleID)
|
// fmt.Printf(" 修改后的第5行: %v\n", cellModifiedRow)
|
||||||
////
|
//}
|
||||||
////fmt.Println("=== 测试完成 ===")
|
//
|
||||||
}
|
//// 7. 并发测试
|
||||||
|
//fmt.Println("7. 并发测试:")
|
||||||
// 下面是一个更简单的版本,根据你的具体需求可以选择使用
|
//TestConcurrentOperations(handleID)
|
||||||
func WriteCSVSimple(filename string, data [][]string) error {
|
//
|
||||||
if len(data) == 0 {
|
//fmt.Println("=== 测试完成 ===")
|
||||||
return fmt.Errorf("写入的数据不能为空")
|
|
||||||
}
|
|
||||||
|
|
||||||
var file *os.File
|
|
||||||
var err error
|
|
||||||
var fileMode int
|
|
||||||
|
|
||||||
// 检查文件是否存在和有内容
|
|
||||||
if info, err := os.Stat(filename); err == nil && info.Size() > 0 {
|
|
||||||
// 文件存在且有内容,追加模式
|
|
||||||
fileMode = os.O_APPEND | os.O_WRONLY
|
|
||||||
} else {
|
|
||||||
// 文件不存在或为空,创建模式(会清空已有内容)
|
|
||||||
fileMode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开或创建文件
|
|
||||||
file, err = os.OpenFile(filename, fileMode, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// 写入CSV数据
|
|
||||||
writer := csv.NewWriter(file)
|
|
||||||
if err := writer.WriteAll(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
writer.Flush()
|
|
||||||
|
|
||||||
return writer.Error()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -320,58 +320,6 @@ func handleOpenCSVFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
|
||||||
dll, err := InitCsvDLL()
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
}
|
|
||||||
file, err := dll.CreateOpenCSVFile("csv/taskLog.csv", ',', true)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
}
|
|
||||||
newRow := []string{"9787115524539", "100.00", "1", "上传成功", ""}
|
|
||||||
safe, err := dll.UpdateCSVRowSafe(file.Data.HandleID, 9, newRow)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
}
|
|
||||||
marshal, _ := json.Marshal(safe)
|
|
||||||
fmt.Println(string(marshal))
|
|
||||||
|
|
||||||
//http.HandleFunc("/csv/openCSVFile", handleOpenCSVFile)
|
|
||||||
//port := "8080"
|
|
||||||
//server := &http.Server{
|
|
||||||
// Addr: ":" + port,
|
|
||||||
// Handler: nil,
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// 4. 优雅关闭设置
|
|
||||||
//done := make(chan bool, 1)
|
|
||||||
//quit := make(chan os.Signal, 1)
|
|
||||||
//signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
//
|
|
||||||
//// 5. 优雅关闭协程
|
|
||||||
//go func() {
|
|
||||||
// <-quit
|
|
||||||
// fmt.Println("\n服务器正在关闭...")
|
|
||||||
//
|
|
||||||
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
// defer cancel()
|
|
||||||
//
|
|
||||||
// if err := server.Shutdown(ctx); err != nil {
|
|
||||||
// fmt.Printf("强制关闭服务器: %v\n", err)
|
|
||||||
// }
|
|
||||||
// close(done)
|
|
||||||
//}()
|
|
||||||
//
|
|
||||||
//// 启动服务器
|
|
||||||
//if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
// fmt.Printf("服务器启动失败: %s\n", err)
|
|
||||||
//}
|
|
||||||
//// 7. 等待关闭完成
|
|
||||||
//<-done
|
|
||||||
//fmt.Println("服务器已关闭")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解码从DLL读取的数据
|
// 解码从DLL读取的数据
|
||||||
func decodeRowData(buffer []byte, maxBytes int) [][]string {
|
func decodeRowData(buffer []byte, maxBytes int) [][]string {
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
@ -411,3 +359,91 @@ func decodeRowData(buffer []byte, maxBytes int) [][]string {
|
|||||||
|
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//func main() {
|
||||||
|
// 使用dll文件
|
||||||
|
//dll, err := InitCsvDLL()
|
||||||
|
//if err != nil {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//file, err := dll.CreateOpenCSVFile("csv/taskLog.csv", ',', true)
|
||||||
|
//if err != nil {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//newRow := []string{"9787115524539", "100.00", "1", "上传成功", ""}
|
||||||
|
//safe, err := dll.UpdateCSVRowSafe(file.Data.HandleID, 9, newRow)
|
||||||
|
//if err != nil {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//marshal, _ := json.Marshal(safe)
|
||||||
|
//fmt.Println(string(marshal))
|
||||||
|
|
||||||
|
//file, err := GetManager().OpenCSVFile("csv/taskLog1.csv", ',', true)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(file)
|
||||||
|
|
||||||
|
//handle, err := GetManager().getHandle(2)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
// 获取指定数量的行
|
||||||
|
//row, err := handle.readRows(100)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//for _, i := range row {
|
||||||
|
// fmt.Println(i)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// 获取总行数
|
||||||
|
//row, err := handle.getTotalRows()
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(row)
|
||||||
|
//newRow := []string{
|
||||||
|
// "9787107267505", "20.00", "10", "上传成功", "877133619369",
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//row, err := GetManager().modifyRow(file, 1, newRow)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(row)
|
||||||
|
|
||||||
|
//http.HandleFunc("/csv/openCSVFile", handleOpenCSVFile)
|
||||||
|
//port := "8080"
|
||||||
|
//server := &http.Server{
|
||||||
|
// Addr: ":" + port,
|
||||||
|
// Handler: nil,
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 4. 优雅关闭设置
|
||||||
|
//done := make(chan bool, 1)
|
||||||
|
//quit := make(chan os.Signal, 1)
|
||||||
|
//signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
//
|
||||||
|
//// 5. 优雅关闭协程
|
||||||
|
//go func() {
|
||||||
|
// <-quit
|
||||||
|
// fmt.Println("\n服务器正在关闭...")
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// if err := server.Shutdown(ctx); err != nil {
|
||||||
|
// fmt.Printf("强制关闭服务器: %v\n", err)
|
||||||
|
// }
|
||||||
|
// close(done)
|
||||||
|
//}()
|
||||||
|
//
|
||||||
|
//// 启动服务器
|
||||||
|
//if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
// fmt.Printf("服务器启动失败: %s\n", err)
|
||||||
|
//}
|
||||||
|
//// 7. 等待关闭完成
|
||||||
|
//<-done
|
||||||
|
//fmt.Println("服务器已关闭")
|
||||||
|
//}
|
||||||
|
|||||||
1050
csv/csvTool.go
1050
csv/csvTool.go
File diff suppressed because it is too large
Load Diff
@ -657,6 +657,7 @@ package main
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// fmt.Printf("✅ 成功更新第 %d 行,文件已直接更新\n", rowNum)
|
// fmt.Printf("✅ 成功更新第 %d 行,文件已直接更新\n", rowNum)
|
||||||
|
//
|
||||||
// return nil
|
// return nil
|
||||||
//}
|
//}
|
||||||
//
|
//
|
||||||
@ -1220,6 +1221,6 @@ package main
|
|||||||
// C.free(unsafe.Pointer(str))
|
// C.free(unsafe.Pointer(str))
|
||||||
//}
|
//}
|
||||||
//
|
//
|
||||||
////// main 函数是必需的,即使为空
|
//// main 函数是必需的,即使为空
|
||||||
////func main() {
|
//func main() {
|
||||||
////}
|
//}
|
||||||
|
|||||||
54
csv/test.go
54
csv/test.go
@ -1,54 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
//func main() {
|
|
||||||
//filename := filepath.Join("csv", "test1.csv")
|
|
||||||
//handle := openCSVFile(filename, ',', true)
|
|
||||||
//fmt.Println("handle:", handle)
|
|
||||||
//
|
|
||||||
//// 测试1:基础写入
|
|
||||||
//fmt.Println("\n=== 测试1:基础写入 ===")
|
|
||||||
//// 清空并写入新数据
|
|
||||||
//newRow := make([][]string, 0)
|
|
||||||
//newRow = append(newRow, []string{"基础测试行5", "基础值5", "基础数据5", "基础测试5"})
|
|
||||||
//
|
|
||||||
//for i := 1; i <= 10; i++ {
|
|
||||||
// row := []string{
|
|
||||||
// "基础行" + strconv.Itoa(i),
|
|
||||||
// "基础值" + strconv.Itoa(i),
|
|
||||||
// "基础数据" + strconv.Itoa(i),
|
|
||||||
// fmt.Sprintf("基础测试%d", i),
|
|
||||||
// }
|
|
||||||
// newRow = append(newRow, row)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//start := time.Now()
|
|
||||||
//rows := writeRows(handle, newRow, 0)
|
|
||||||
//
|
|
||||||
//elapsed := time.Since(start)
|
|
||||||
//fmt.Printf("基础写入完成,影响行数: %d,耗时: %v\n", rows, elapsed)
|
|
||||||
//
|
|
||||||
//buffer := make([]byte, 4096)
|
|
||||||
//rowss := readRows(handle, 0, rows, buffer)
|
|
||||||
//fmt.Printf("读取到 %d 行数据\n", rowss)
|
|
||||||
//
|
|
||||||
//decodedRows := decodeRowData(buffer, 4096)
|
|
||||||
//fmt.Println("合并文件前几行数据:")
|
|
||||||
//for i, row := range decodedRows {
|
|
||||||
// if i >= int(rowss) {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// fmt.Printf(" 行 %d: %v\n", i, row)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// 测试3:读写混合
|
|
||||||
//fmt.Println("\n=== 测试3:读写混合 ===")
|
|
||||||
//testMixedOperations(handle)
|
|
||||||
|
|
||||||
//file, err := closeCSVFile(handle)
|
|
||||||
//if err != nil {
|
|
||||||
// fmt.Println(err)
|
|
||||||
//}
|
|
||||||
//if file == 0 {
|
|
||||||
// fmt.Println("\n文件保存成功")
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
BIN
es/dll/es.dll
Normal file
BIN
es/dll/es.dll
Normal file
Binary file not shown.
141
es/dll/es.h
Normal file
141
es/dll/es.h
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||||
|
|
||||||
|
/* package command-line-arguments */
|
||||||
|
|
||||||
|
|
||||||
|
#line 1 "cgo-builtin-export-prolog"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||||
|
extern size_t _GoStringLen(_GoString_ s);
|
||||||
|
extern const char *_GoStringPtr(_GoString_ s);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Start of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
#line 3 "es.go"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#line 1 "cgo-generated-wrapper"
|
||||||
|
|
||||||
|
|
||||||
|
/* End of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
/* Start of boilerplate cgo prologue. */
|
||||||
|
#line 1 "cgo-gcc-export-header-prolog"
|
||||||
|
|
||||||
|
#ifndef GO_CGO_PROLOGUE_H
|
||||||
|
#define GO_CGO_PROLOGUE_H
|
||||||
|
|
||||||
|
typedef signed char GoInt8;
|
||||||
|
typedef unsigned char GoUint8;
|
||||||
|
typedef short GoInt16;
|
||||||
|
typedef unsigned short GoUint16;
|
||||||
|
typedef int GoInt32;
|
||||||
|
typedef unsigned int GoUint32;
|
||||||
|
typedef long long GoInt64;
|
||||||
|
typedef unsigned long long GoUint64;
|
||||||
|
typedef GoInt64 GoInt;
|
||||||
|
typedef GoUint64 GoUint;
|
||||||
|
typedef size_t GoUintptr;
|
||||||
|
typedef float GoFloat32;
|
||||||
|
typedef double GoFloat64;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||||
|
#include <complex.h>
|
||||||
|
typedef _Fcomplex GoComplex64;
|
||||||
|
typedef _Dcomplex GoComplex128;
|
||||||
|
#else
|
||||||
|
#include <complex>
|
||||||
|
typedef std::complex<float> GoComplex64;
|
||||||
|
typedef std::complex<double> GoComplex128;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
typedef float _Complex GoComplex64;
|
||||||
|
typedef double _Complex GoComplex128;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
static assertion to make sure the file is being used on architecture
|
||||||
|
at least with matching size of GoInt.
|
||||||
|
*/
|
||||||
|
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef _GoString_ GoString;
|
||||||
|
#endif
|
||||||
|
typedef void *GoMap;
|
||||||
|
typedef void *GoChan;
|
||||||
|
typedef struct { void *t; void *v; } GoInterface;
|
||||||
|
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* End of boilerplate cgo prologue. */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// ListAllIndices 查询所有索引
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* ListAllIndices(void);
|
||||||
|
|
||||||
|
// GetIndicesInfo 获取所有索引的详细信息
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* GetIndicesInfo(void);
|
||||||
|
|
||||||
|
// GetIndexDetail 获取单个索引的详细信息
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* GetIndexDetail(char* indexName);
|
||||||
|
|
||||||
|
// CreateIndex 创建索引(如果不存在)
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* CreateIndex(char* indexName, char* mapping);
|
||||||
|
|
||||||
|
// DeleteIndex 删除索引
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* DeleteIndex(char* indexName);
|
||||||
|
|
||||||
|
// GetDocumentCount 获取索引文档数量
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* GetDocumentCount(char* indexName);
|
||||||
|
|
||||||
|
// CreateDocument 创建文档
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* CreateDocument(char* indexName, char* id, char* doc);
|
||||||
|
|
||||||
|
// GetDocument 根据ID获取文档
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* GetDocument(char* indexName, char* id);
|
||||||
|
|
||||||
|
// UpdateDocument 更新文档
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* UpdateDocument(char* indexName, char* id, char* updateData);
|
||||||
|
|
||||||
|
// DeleteDocument 删除文档
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* DeleteDocument(char* indexName, char* id);
|
||||||
|
|
||||||
|
// SearchDocuments 搜索文档
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* SearchDocuments(char* indexName, char* query);
|
||||||
|
|
||||||
|
// 释放C字符串内存
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) void FreeCString(char* str);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
133
es/esDll.go
Normal file
133
es/esDll.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EsDLL Elasticsearch工具DLL结构
|
||||||
|
type esDLL struct {
|
||||||
|
dll *syscall.DLL
|
||||||
|
listAllIndices *syscall.Proc // 查询所有索引
|
||||||
|
getIndicesInfo *syscall.Proc // 获取所有索引的详细信息
|
||||||
|
getIndexDetail *syscall.Proc // 获取单个索引的详细信息
|
||||||
|
createIndex *syscall.Proc // 创建索引
|
||||||
|
deleteIndex *syscall.Proc // 删除索引
|
||||||
|
getDocumentCount *syscall.Proc // 获取索引文档数量
|
||||||
|
createDocument *syscall.Proc // 创建文档
|
||||||
|
getDocument *syscall.Proc // 根据ID获取文档
|
||||||
|
updateDocument *syscall.Proc // 更新文档
|
||||||
|
deleteDocument *syscall.Proc // 删除文档
|
||||||
|
searchDocuments *syscall.Proc // 搜索文档
|
||||||
|
freeCString *syscall.Proc // 释放C字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化esDLL
|
||||||
|
func InitEsDLL() (*esDLL, error) {
|
||||||
|
dllPath := filepath.Join("es", "dll", "es.dll")
|
||||||
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("es DLL 不存在: %s", dllPath)
|
||||||
|
}
|
||||||
|
if dll, err := syscall.LoadDLL(dllPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("加载es DLL 失败: %s", err)
|
||||||
|
} else {
|
||||||
|
return &esDLL{
|
||||||
|
dll: dll,
|
||||||
|
listAllIndices: dll.MustFindProc("ListAllIndices"),
|
||||||
|
getIndicesInfo: dll.MustFindProc("GetIndicesInfo"),
|
||||||
|
getIndexDetail: dll.MustFindProc("GetIndexDetail"),
|
||||||
|
createIndex: dll.MustFindProc("CreateIndex"),
|
||||||
|
deleteIndex: dll.MustFindProc("DeleteIndex"),
|
||||||
|
getDocumentCount: dll.MustFindProc("GetDocumentCount"),
|
||||||
|
createDocument: dll.MustFindProc("CreateDocument"),
|
||||||
|
getDocument: dll.MustFindProc("GetDocument"),
|
||||||
|
updateDocument: dll.MustFindProc("UpdateDocument"),
|
||||||
|
deleteDocument: dll.MustFindProc("DeleteDocument"),
|
||||||
|
searchDocuments: dll.MustFindProc("SearchDocuments"),
|
||||||
|
freeCString: dll.MustFindProc("FreeCString"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cStr 获取C字符串
|
||||||
|
func (m *esDLL) cStr(p uintptr) string {
|
||||||
|
if p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
for i := uintptr(0); ; i++ {
|
||||||
|
c := *(*byte)(unsafe.Pointer(p + i))
|
||||||
|
if c == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if m.freeCString != nil {
|
||||||
|
m.freeCString.Call(p)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有索引
|
||||||
|
func (m *esDLL) ListAllIndices() (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("ListAllIndices")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 ListAllIndices: %v", err)
|
||||||
|
}
|
||||||
|
resultPtr, _, _ := proc.Call()
|
||||||
|
result := m.cStr(resultPtr)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *esDLL) CreateDocument(indexName string, id string, doc interface{}) (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("CreateDocument")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 CreateDocument: %v", err)
|
||||||
|
}
|
||||||
|
docJson, err := json.Marshal(doc)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
indexNamePtr, _ := syscall.BytePtrFromString(indexName)
|
||||||
|
idPtr, _ := syscall.BytePtrFromString(id)
|
||||||
|
docJsonPtr, _ := syscall.BytePtrFromString(string(docJson))
|
||||||
|
|
||||||
|
resultPtr, _, _ := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(indexNamePtr)),
|
||||||
|
uintptr(unsafe.Pointer(idPtr)),
|
||||||
|
uintptr(unsafe.Pointer(docJsonPtr)),
|
||||||
|
)
|
||||||
|
result := m.cStr(resultPtr)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//func main() {
|
||||||
|
// dll, err := InitEsDLL()
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// //indices, err := dll.ListAllIndices()
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err)
|
||||||
|
// //}
|
||||||
|
// //fmt.Println(string(indices))
|
||||||
|
//
|
||||||
|
// doc := Document{
|
||||||
|
// ID: "10003",
|
||||||
|
// Title: "测试文档3",
|
||||||
|
// Content: "这是一个测试文档3",
|
||||||
|
// Author: "测试员3",
|
||||||
|
// CreatedAt: time.Now(),
|
||||||
|
// Tags: []string{"测试3", "文档3"},
|
||||||
|
// }
|
||||||
|
// document, err := dll.CreateDocument("test-cc", doc.ID, doc)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// fmt.Println(document)
|
||||||
|
//}
|
||||||
4172
es/main.go
4172
es/main.go
File diff suppressed because it is too large
Load Diff
207
es/test.go
Normal file
207
es/test.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//func main() {
|
||||||
|
|
||||||
|
//client, err := newESClient([]string{esAddress}, esUsername, esPassword)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
//}
|
||||||
|
//fmt.Println(client)
|
||||||
|
//
|
||||||
|
//// 获取所有索引
|
||||||
|
//indices, err := client.listAllIndices()
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
//}
|
||||||
|
//for _, i := range indices {
|
||||||
|
// fmt.Println("索引:", i)
|
||||||
|
//}
|
||||||
|
//fmt.Println("所有索引:", indices)
|
||||||
|
|
||||||
|
// // 获取所有索引的详细信息
|
||||||
|
// //infos, err := client.GetIndicesInfo()
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
// //infosJson, _ := json.Marshal(infos)
|
||||||
|
// //fmt.Println("所有索引的详细信息:", string(infosJson))
|
||||||
|
//
|
||||||
|
// // 获取索引设置
|
||||||
|
// //settings, err := client.GetIndexSettings("test-go-index")
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
// //marshal, _ := json.Marshal(settings)
|
||||||
|
// //fmt.Println("marshal:", string(marshal))
|
||||||
|
//
|
||||||
|
// // 创建索引
|
||||||
|
// // 创建索引映射
|
||||||
|
// //mapping := `{
|
||||||
|
// // "settings": {
|
||||||
|
// // "number_of_shards": 1,
|
||||||
|
// // "number_of_replicas": 1,
|
||||||
|
// // "analysis": {
|
||||||
|
// // "analyzer": {
|
||||||
|
// // "default": {
|
||||||
|
// // "type": "standard"
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // },
|
||||||
|
// // "mappings": {
|
||||||
|
// // "properties": {
|
||||||
|
// // "id":{
|
||||||
|
// // "type":"integer"
|
||||||
|
// // },
|
||||||
|
// // "title": {
|
||||||
|
// // "type": "text",
|
||||||
|
// // "analyzer": "standard",
|
||||||
|
// // "search_analyzer": "standard"
|
||||||
|
// // },
|
||||||
|
// // "content": {
|
||||||
|
// // "type": "text",
|
||||||
|
// // "analyzer": "standard",
|
||||||
|
// // "search_analyzer": "standard"
|
||||||
|
// // },
|
||||||
|
// // "author": {
|
||||||
|
// // "type": "keyword"
|
||||||
|
// // },
|
||||||
|
// // "created_at": {
|
||||||
|
// // "type": "date"
|
||||||
|
// // },
|
||||||
|
// // "tags": {
|
||||||
|
// // "type": "keyword"
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// //}`
|
||||||
|
// //index, err := client.CreateIndex("test-cc", mapping)
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
// //marshal, _ := json.Marshal(index)
|
||||||
|
// //fmt.Println("marshal:", string(marshal))
|
||||||
|
//
|
||||||
|
// 删除索引
|
||||||
|
//err = client.deleteIndex("test-go-index")
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
// // 修改索引设置
|
||||||
|
// //newSettings := map[string]interface{}{
|
||||||
|
// // "index": map[string]interface{}{
|
||||||
|
// // "refresh_interval": "5s",
|
||||||
|
// // "max_result_window": 10000,
|
||||||
|
// // },
|
||||||
|
// //}
|
||||||
|
// //err = client.UpdateIndexSettings("test-cc", newSettings)
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // 更新映射
|
||||||
|
// //newMappings := map[string]interface{}{
|
||||||
|
// // "views": map[string]interface{}{
|
||||||
|
// // "type": "integer",
|
||||||
|
// // },
|
||||||
|
// //}
|
||||||
|
// //err = client.UpdateIndexMappings("test-cc", newMappings)
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // 关闭索引
|
||||||
|
// //err = client.CloseIndex("test-cc")
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// //// 打开索引
|
||||||
|
// //err = client.OpenIndex("test-cc")
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // 获取文档数量
|
||||||
|
// //count, err := client.GetDocumentCount("test-cc")
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
// //fmt.Println("文档数量: ", count)
|
||||||
|
//
|
||||||
|
// // 创建文档
|
||||||
|
// //doc := Document{
|
||||||
|
// // ID: "10002",
|
||||||
|
// // Title: "测试文档2",
|
||||||
|
// // Content: "这是一个测试文档2",
|
||||||
|
// // Author: "测试员2",
|
||||||
|
// // CreatedAt: time.Now(),
|
||||||
|
// // Tags: []string{"测试2", "文档2"},
|
||||||
|
// //}
|
||||||
|
// //err = client.CreateDocument("test-cc", doc.ID, doc)
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // 更新文档
|
||||||
|
// //err = client.UpdateDocument("test-cc", "10002", map[string]interface{}{
|
||||||
|
// // "title": "测试文档2",
|
||||||
|
// // "content": "这是一个测试文档2",
|
||||||
|
// //})
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // 删除文档
|
||||||
|
// //err = client.DeleteDocument("test-cc", "10001")
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // 搜索文档
|
||||||
|
// //query := map[string]interface{}{
|
||||||
|
// // "query": map[string]interface{}{
|
||||||
|
// // "match": map[string]interface{}{
|
||||||
|
// // "id": "10000",
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// // "size": 100,
|
||||||
|
// //}
|
||||||
|
// //documents, err := client.SearchDocuments("test-cc", query)
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
// //documentsJson, _ := json.Marshal(documents)
|
||||||
|
// //fmt.Println(string(documentsJson))
|
||||||
|
//
|
||||||
|
// // 获取文档信息
|
||||||
|
// //document, err := client.GetDocument("test-cc", "10000")
|
||||||
|
// //if err != nil {
|
||||||
|
// // fmt.Println(err.Error())
|
||||||
|
// //}
|
||||||
|
// //bytes, _ := json.Marshal(document)
|
||||||
|
// //fmt.Println("获取文档信息", string(bytes))
|
||||||
|
//
|
||||||
|
// // 获取单个索引
|
||||||
|
// detail, err := client.getIndexDetail("test-cc")
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
// }
|
||||||
|
// detailJson, _ := json.Marshal(detail)
|
||||||
|
// fmt.Println("单个索引的所有信息:", string(detailJson))
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Document 示例文档结构
|
||||||
|
type Document struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
29
gin/gin.go
Normal file
29
gin/gin.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
//"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 创建 Gin 实例
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// 定义路由
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"message": "pong",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// XML 响应
|
||||||
|
r.GET("/xml", func(c *gin.Context) {
|
||||||
|
c.XML(200, gin.H{
|
||||||
|
"message": "hello",
|
||||||
|
"status": "success",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 启动服务
|
||||||
|
r.Run(":8080") // 默认监听 8080 端口
|
||||||
|
}
|
||||||
31
go.mod
31
go.mod
@ -7,9 +7,12 @@ require (
|
|||||||
github.com/chromedp/chromedp v0.14.2
|
github.com/chromedp/chromedp v0.14.2
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/elastic/go-elasticsearch/v8 v8.19.0
|
github.com/elastic/go-elasticsearch/v8 v8.19.0
|
||||||
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/go-sql-driver/mysql v1.9.3
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
|
github.com/makiuchi-d/gozxing v0.1.1
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/parnurzeal/gorequest v0.3.0
|
github.com/parnurzeal/gorequest v0.3.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/xuri/excelize/v2 v2.10.0
|
github.com/xuri/excelize/v2 v2.10.0
|
||||||
golang.org/x/image v0.25.0
|
golang.org/x/image v0.25.0
|
||||||
golang.org/x/sys v0.37.0
|
golang.org/x/sys v0.37.0
|
||||||
@ -18,28 +21,56 @@ require (
|
|||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect
|
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect
|
||||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
|
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
|
||||||
github.com/elazarl/goproxy v1.7.2 // indirect
|
github.com/elazarl/goproxy v1.7.2 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
|
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.4.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/moul/http2curl v1.0.0 // indirect
|
github.com/moul/http2curl v1.0.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
github.com/xuri/efp v0.0.1 // indirect
|
github.com/xuri/efp v0.0.1 // indirect
|
||||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/crypto v0.43.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
|
golang.org/x/mod v0.28.0 // indirect
|
||||||
golang.org/x/net v0.46.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
|
golang.org/x/tools v0.37.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
78
go.sum
78
go.sum
@ -4,12 +4,19 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU
|
|||||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E=
|
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E=
|
||||||
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||||
github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM=
|
github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM=
|
||||||
github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
|
github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
|
||||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
@ -20,6 +27,12 @@ github.com/elastic/go-elasticsearch/v8 v8.19.0 h1:VmfBLNRORY7RZL+9hTxBD97ehl9H8N
|
|||||||
github.com/elastic/go-elasticsearch/v8 v8.19.0/go.mod h1:F3j9e+BubmKvzvLjNui/1++nJuJxbkhHefbaT0kFKGY=
|
github.com/elastic/go-elasticsearch/v8 v8.19.0/go.mod h1:F3j9e+BubmKvzvLjNui/1++nJuJxbkhHefbaT0kFKGY=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
|
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@ -27,6 +40,14 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
@ -35,14 +56,34 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
|
||||||
|
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
@ -51,23 +92,42 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
|
|||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
|
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
|
||||||
github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
||||||
@ -83,6 +143,10 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ
|
|||||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||||
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
@ -99,6 +163,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
@ -117,6 +183,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -157,6 +225,14 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||||
|
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
666
image/image.go
666
image/image.go
@ -6,6 +6,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/makiuchi-d/gozxing"
|
||||||
|
"github.com/makiuchi-d/gozxing/qrcode"
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
"image"
|
"image"
|
||||||
@ -14,6 +16,7 @@ import (
|
|||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -22,16 +25,18 @@ import (
|
|||||||
|
|
||||||
// Config 配置结构体
|
// Config 配置结构体
|
||||||
type Config struct {
|
type Config struct {
|
||||||
OutputDir string // 输出目录路径
|
OutputDir string // 输出目录路径
|
||||||
FileName string // 文件名
|
FileName string // 文件名
|
||||||
MatchDir string // 满足条件的图片目录名
|
MatchDir string // 满足条件的图片目录名
|
||||||
UnmatchDir string // 不满足条件的图片目录名
|
UnmatchDir string // 不满足条件的图片目录名
|
||||||
EqualHeightDir string // 等高的图片目录名
|
EqualHeightDir string // 等高的图片目录名
|
||||||
WhiteDir string // 白色底图的图片目录名
|
WhiteDir string // 白色底图的图片目录名
|
||||||
WhiteBorderPngDir string // 去白边转PNG的图片目录名
|
WhiteBorderPngDir string // 去白边转PNG的图片目录名
|
||||||
MinWhitePct float64 // 纯白占比下限(0-1)
|
WhiteHeightZoomDir string // 缩放的图片目录
|
||||||
MaxWhitePct float64 // 纯白占比上限(0-1)
|
CropDir string // 裁切的图片目录
|
||||||
Extensions []string // 支持的图片扩展名
|
MinWhitePct float64 // 纯白占比下限(0-1)
|
||||||
|
MaxWhitePct float64 // 纯白占比上限(0-1)
|
||||||
|
Extensions []string // 支持的图片扩展名
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查图片
|
// 检查图片
|
||||||
@ -52,6 +57,7 @@ func validateConfig(config *Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建目录功能
|
||||||
func createDirs(config *Config) error {
|
func createDirs(config *Config) error {
|
||||||
// 创建输出根目录
|
// 创建输出根目录
|
||||||
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
|
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
|
||||||
@ -84,6 +90,16 @@ func createDirs(config *Config) error {
|
|||||||
if err := os.MkdirAll(whiteBorderPngPath, 0755); err != nil {
|
if err := os.MkdirAll(whiteBorderPngPath, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
whiteHeightZoomPath := filepath.Join(config.OutputDir, config.WhiteHeightZoomDir)
|
||||||
|
if err := os.MkdirAll(whiteHeightZoomPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cropPath := filepath.Join(config.OutputDir, config.CropDir)
|
||||||
|
if err := os.MkdirAll(cropPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,6 +519,596 @@ func (c *ImageToPNGConverter) isBackgroundColor(pixel color.Color, hasAlpha bool
|
|||||||
b8 >= threshold8
|
b8 >= threshold8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 图片缩放
|
||||||
|
func resizeWTToHeightQuality(config *Config, dsWidth, dsHeight int) (string, error) {
|
||||||
|
// 创建输出目录
|
||||||
|
if err := createDirs(config); err != nil {
|
||||||
|
fmt.Printf("创建目录失败: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// 打开图片
|
||||||
|
file, err := os.Open(config.FileName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("打开图片失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
// 解码原始图片
|
||||||
|
img, format, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("图片解码失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Lanczos3算法缩放图片
|
||||||
|
optimized := resizeImageOptimized(img, dsWidth, dsHeight)
|
||||||
|
|
||||||
|
// 保存图片到指定目录下
|
||||||
|
filename := filepath.Base(config.FileName)
|
||||||
|
destPath := filepath.Join(config.OutputDir, config.WhiteHeightZoomDir, filename)
|
||||||
|
err = saveImage(destPath, optimized, format)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("保存图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return destPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Lanczos3算法缩放图片
|
||||||
|
func resizeImageOptimized(src image.Image, dstWidth, dstHeight int) image.Image {
|
||||||
|
srcBounds := src.Bounds()
|
||||||
|
srcWidth := srcBounds.Dx()
|
||||||
|
srcHeight := srcBounds.Dy()
|
||||||
|
|
||||||
|
// 创建目标图片
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight))
|
||||||
|
|
||||||
|
// 计算缩放比例
|
||||||
|
xScale := float64(srcWidth) / float64(dstWidth)
|
||||||
|
yScale := float64(srcHeight) / float64(dstHeight)
|
||||||
|
|
||||||
|
// Lanczos3算法半径
|
||||||
|
radius := 3.0
|
||||||
|
|
||||||
|
// 预计算x方向的权重
|
||||||
|
xWeights := make([][]float64, dstWidth)
|
||||||
|
xIndices := make([][]int, dstWidth)
|
||||||
|
|
||||||
|
for x := 0; x < dstWidth; x++ {
|
||||||
|
srcX := float64(x) * xScale
|
||||||
|
startX := int(math.Max(0, math.Floor(srcX-radius+0.5)))
|
||||||
|
endX := int(math.Min(float64(srcWidth-1), math.Floor(srcX+radius)))
|
||||||
|
|
||||||
|
weights := make([]float64, 0, endX-startX+1)
|
||||||
|
indices := make([]int, 0, endX-startX+1)
|
||||||
|
|
||||||
|
for sx := startX; sx <= endX; sx++ {
|
||||||
|
xDist := float64(sx) + 0.5 - srcX
|
||||||
|
weight := lanczos3(xDist)
|
||||||
|
if weight != 0 {
|
||||||
|
weights = append(weights, weight)
|
||||||
|
indices = append(indices, sx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xWeights[x] = weights
|
||||||
|
xIndices[x] = indices
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理每一行
|
||||||
|
for y := 0; y < dstHeight; y++ {
|
||||||
|
srcY := float64(y) * yScale
|
||||||
|
startY := int(math.Max(0, math.Floor(srcY-radius+0.5)))
|
||||||
|
endY := int(math.Min(float64(srcHeight-1), math.Floor(srcY+radius)))
|
||||||
|
|
||||||
|
// 预计算y方向的权重
|
||||||
|
yWeights := make([]float64, 0, endY-startY+1)
|
||||||
|
yIndices := make([]int, 0, endY-startY+1)
|
||||||
|
|
||||||
|
for sy := startY; sy <= endY; sy++ {
|
||||||
|
yDist := float64(sy) + 0.5 - srcY
|
||||||
|
weight := lanczos3(yDist)
|
||||||
|
if weight != 0 {
|
||||||
|
yWeights = append(yWeights, weight)
|
||||||
|
yIndices = append(yIndices, sy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理每一列
|
||||||
|
for x := 0; x < dstWidth; x++ {
|
||||||
|
var rSum, gSum, bSum, aSum, weightSum float64
|
||||||
|
|
||||||
|
// 应用预计算的权重
|
||||||
|
for i, sy := range yIndices {
|
||||||
|
yWeight := yWeights[i]
|
||||||
|
|
||||||
|
for j, sx := range xIndices[x] {
|
||||||
|
xWeight := xWeights[x][j]
|
||||||
|
weight := xWeight * yWeight
|
||||||
|
|
||||||
|
// 获取源像素颜色
|
||||||
|
srcColor := src.At(sx+srcBounds.Min.X, sy+srcBounds.Min.Y)
|
||||||
|
r, g, b, a := srcColor.RGBA()
|
||||||
|
|
||||||
|
// 累加加权颜色值
|
||||||
|
rSum += float64(r>>8) * weight
|
||||||
|
gSum += float64(g>>8) * weight
|
||||||
|
bSum += float64(b>>8) * weight
|
||||||
|
aSum += float64(a>>8) * weight
|
||||||
|
weightSum += weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止除以零
|
||||||
|
if weightSum == 0 {
|
||||||
|
weightSum = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算最终颜色值
|
||||||
|
r := clamp(rSum / weightSum)
|
||||||
|
g := clamp(gSum / weightSum)
|
||||||
|
b := clamp(bSum / weightSum)
|
||||||
|
a := clamp(aSum / weightSum)
|
||||||
|
|
||||||
|
// 设置目标像素
|
||||||
|
dst.SetRGBA(x, y, color.RGBA{r, g, b, a})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// 函数计算Lanczos3核函数值
|
||||||
|
func lanczos3(x float64) float64 {
|
||||||
|
if x == 0 {
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
if x < -3 || x > 3 {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return (3 * math.Sin(math.Pi*x) * math.Sin(math.Pi*x/3)) / (math.Pi * math.Pi * x * x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将值限制在0-255范围内
|
||||||
|
func clamp(v float64) uint8 {
|
||||||
|
if v < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if v > 255 {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
return uint8(v + 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片裁切
|
||||||
|
func cropImage(config *Config, x, y, width, height int) (string, error) {
|
||||||
|
// 创建输出目录
|
||||||
|
if err := createDirs(config); err != nil {
|
||||||
|
fmt.Printf("创建目录失败: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开图片
|
||||||
|
file, err := os.Open(config.FileName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("打开图片失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
// 解码原始图片
|
||||||
|
img, format, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("图片解码失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("输入图片: %s (%dx%d, 格式: %s)\n",
|
||||||
|
config.FileName, img.Bounds().Dx(), img.Bounds().Dy(), format)
|
||||||
|
|
||||||
|
imgFile, err := basicCrop(img, x, y, width, height)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
//// 执行裁切
|
||||||
|
//var cropped image.Image
|
||||||
|
//if width > 0 && height > 0 {
|
||||||
|
// cropped, err = SmartCrop(img, x, y, width, height int)
|
||||||
|
//} else {
|
||||||
|
// // 如果未指定尺寸,使用中心裁切
|
||||||
|
// cropped, err = CropCenter(img, config.Width, config.Height)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//if err != nil {
|
||||||
|
// return "", err
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
|
||||||
|
// 保存图片到指定目录下
|
||||||
|
filename := filepath.Base(config.FileName)
|
||||||
|
destPath := filepath.Join(config.OutputDir, config.CropDir, filename)
|
||||||
|
err = saveImage(destPath, imgFile, format)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("保存图片失败: %v", err)
|
||||||
|
}
|
||||||
|
return destPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// basicCrop 基础裁切功能
|
||||||
|
func basicCrop(src image.Image, x, y, width, height int) (image.Image, error) {
|
||||||
|
srcBounds := src.Bounds()
|
||||||
|
srcWidth := srcBounds.Dx()
|
||||||
|
srcHeight := srcBounds.Dy()
|
||||||
|
|
||||||
|
// 验证裁切参数
|
||||||
|
if x < 0 || y < 0 || width <= 0 || height <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid crop parameters: x=%d, y=%d, width=%d, height=%d",
|
||||||
|
x, y, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
if x >= srcWidth || y >= srcHeight {
|
||||||
|
return nil, fmt.Errorf("crop start point (%d, %d) is outside image bounds (%dx%d)",
|
||||||
|
x, y, srcWidth, srcHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调整裁切尺寸以避免超出边界
|
||||||
|
if x+width > srcWidth {
|
||||||
|
width = srcWidth - x
|
||||||
|
}
|
||||||
|
if y+height > srcHeight {
|
||||||
|
height = srcHeight - y
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源图像中裁切
|
||||||
|
cropped := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
for cy := 0; cy < height; cy++ {
|
||||||
|
for cx := 0; cx < width; cx++ {
|
||||||
|
cropped.Set(cx, cy, src.At(x+cx, y+cy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cropped, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//// smartCrop 智能裁切,处理边界和自动调整 -- 暂时没用
|
||||||
|
//func smartCrop(src image.Image, x, y, width, height int) (image.Image, error) {
|
||||||
|
// srcBounds := src.Bounds()
|
||||||
|
// srcWidth := srcBounds.Dx()
|
||||||
|
// srcHeight := srcBounds.Dy()
|
||||||
|
//
|
||||||
|
// // 如果宽度或高度为0,使用图像的最大可能尺寸
|
||||||
|
// if width == 0 {
|
||||||
|
// width = srcWidth - x
|
||||||
|
// }
|
||||||
|
// if height == 0 {
|
||||||
|
// height = srcHeight - y
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 调整裁切区域以确保在边界内
|
||||||
|
// newX := max(0, min(x, srcWidth-1))
|
||||||
|
// newY := max(0, min(x, srcHeight-1))
|
||||||
|
// width = max(1, min(width, srcWidth-x))
|
||||||
|
// height = max(1, min(height, srcHeight-y))
|
||||||
|
//
|
||||||
|
// // 如果需要保持宽高比,调整裁切区域
|
||||||
|
// if width > 0 && height > 0 {
|
||||||
|
// currentRatio := float64(width) / float64(height)
|
||||||
|
// originalRatio := float64(srcWidth) / float64(srcHeight)
|
||||||
|
//
|
||||||
|
// if math.Abs(currentRatio-originalRatio) > 0.01 {
|
||||||
|
// // 调整宽度以匹配原始宽高比
|
||||||
|
// newWidth := int(float64(height) * originalRatio)
|
||||||
|
// if newWidth <= (srcWidth - newX) {
|
||||||
|
// width = newWidth
|
||||||
|
// } else {
|
||||||
|
// // 调整高度以匹配原始宽高比
|
||||||
|
// newHeight := int(float64(width) / originalRatio)
|
||||||
|
// if newHeight <= (srcHeight - newY) {
|
||||||
|
// height = newHeight
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 执行裁切
|
||||||
|
// cropped, err := basicCrop(src, newX, newY, width, height)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return cropped, nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// CropCenter 中心裁切 -- 暂时没用
|
||||||
|
//func CropCenter(src image.Image, width, height int) (image.Image, error) {
|
||||||
|
// srcBounds := src.Bounds()
|
||||||
|
// srcWidth := srcBounds.Dx()
|
||||||
|
// srcHeight := srcBounds.Dy()
|
||||||
|
//
|
||||||
|
// // 如果裁切尺寸大于原图,返回原图或调整裁切尺寸
|
||||||
|
// if width >= srcWidth && height >= srcHeight {
|
||||||
|
// return src, nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 计算中心裁切的起始点
|
||||||
|
// x := (srcWidth - width) / 2
|
||||||
|
// y := (srcHeight - height) / 2
|
||||||
|
//
|
||||||
|
// // 确保不超出边界
|
||||||
|
// x = max(0, min(x, srcWidth-width))
|
||||||
|
// y = max(0, min(y, srcHeight-height))
|
||||||
|
//
|
||||||
|
// return BasicCrop(src, x, y, width, height)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 识别二维码
|
||||||
|
func scanQRCode(fileName string) (bool, string, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("打开图片失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 解码图像
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("解码图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建二维码读取器
|
||||||
|
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("创建位图失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码二维码
|
||||||
|
reader := qrcode.NewQRCodeReader()
|
||||||
|
|
||||||
|
result, err := reader.Decode(bmp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", analyzeQRCodeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 打印二维码内容
|
||||||
|
fmt.Printf("二维码内容: %s\n", result.GetText())
|
||||||
|
|
||||||
|
// 7. 获取二维码位置点
|
||||||
|
points := result.GetResultPoints()
|
||||||
|
if len(points) < 3 {
|
||||||
|
fmt.Println("未找到足够的定位点")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, result.GetText(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 识别二维码
|
||||||
|
func scanQRCodeNew(fileName string) (bool, string, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("打开图片失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 解码图像
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("解码图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建二维码读取器
|
||||||
|
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("创建位图失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码二维码
|
||||||
|
reader := qrcode.NewQRCodeReader()
|
||||||
|
|
||||||
|
result, err := reader.Decode(bmp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", analyzeQRCodeError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 打印二维码内容
|
||||||
|
fmt.Printf("二维码内容: %s\n", result.GetText())
|
||||||
|
|
||||||
|
// 7. 获取二维码位置点
|
||||||
|
points := result.GetResultPoints()
|
||||||
|
if len(points) < 3 {
|
||||||
|
fmt.Println("未找到足够的定位点")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 计算二维码边界框
|
||||||
|
minX, minY := int(points[0].GetX()), int(points[0].GetY())
|
||||||
|
maxX, maxY := minX, minY
|
||||||
|
|
||||||
|
for _, point := range points {
|
||||||
|
x, y := int(point.GetX()), int(point.GetY())
|
||||||
|
if x < minX {
|
||||||
|
minX = x
|
||||||
|
}
|
||||||
|
if x > maxX {
|
||||||
|
maxX = x
|
||||||
|
}
|
||||||
|
if y < minY {
|
||||||
|
minY = y
|
||||||
|
}
|
||||||
|
if y > maxY {
|
||||||
|
maxY = y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 添加一些边距(可选)
|
||||||
|
margin := 10
|
||||||
|
minX -= margin
|
||||||
|
minY -= margin
|
||||||
|
maxX += margin
|
||||||
|
maxY += margin
|
||||||
|
|
||||||
|
// 确保坐标不超出图片范围
|
||||||
|
bounds := img.Bounds()
|
||||||
|
if minX < bounds.Min.X {
|
||||||
|
minX = bounds.Min.X
|
||||||
|
}
|
||||||
|
if minY < bounds.Min.Y {
|
||||||
|
minY = bounds.Min.Y
|
||||||
|
}
|
||||||
|
if maxX > bounds.Max.X {
|
||||||
|
maxX = bounds.Max.X
|
||||||
|
}
|
||||||
|
if maxY > bounds.Max.Y {
|
||||||
|
maxY = bounds.Max.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. 创建裁剪区域
|
||||||
|
qrRect := image.Rect(minX, minY, maxX, maxY)
|
||||||
|
fmt.Printf("二维码位置: %v\n", qrRect)
|
||||||
|
|
||||||
|
// 11. 创建新图片并复制二维码区域
|
||||||
|
qrImg := image.NewRGBA(qrRect)
|
||||||
|
draw.Draw(qrImg, qrImg.Bounds(), img, qrRect.Min, draw.Src)
|
||||||
|
|
||||||
|
// 12. 保存二维码图片
|
||||||
|
outputFile, err := os.Create("image/qrcode.png")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
|
||||||
|
err = png.Encode(outputFile, qrImg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return true, result.GetText(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图像预处理
|
||||||
|
func preprocessImage(img image.Image) image.Image {
|
||||||
|
// 转为灰度图
|
||||||
|
gray := imaging.Grayscale(img)
|
||||||
|
|
||||||
|
// 增强对比度
|
||||||
|
enhanced := imaging.AdjustContrast(gray, 20)
|
||||||
|
|
||||||
|
// 高斯模糊去噪
|
||||||
|
blurred := imaging.Blur(enhanced, 1.0)
|
||||||
|
|
||||||
|
// 自适应二值化
|
||||||
|
binary := adaptiveThreshold(blurred)
|
||||||
|
|
||||||
|
return binary
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二值化处理
|
||||||
|
func adaptiveThreshold(img image.Image) image.Image {
|
||||||
|
bounds := img.Bounds()
|
||||||
|
dst := image.NewGray(bounds)
|
||||||
|
|
||||||
|
// 局部自适应阈值
|
||||||
|
blockSize := 15
|
||||||
|
c := 2.0
|
||||||
|
|
||||||
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
|
// 计算局部均值
|
||||||
|
sum := 0.0
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
for dy := -blockSize / 2; dy <= blockSize/2; dy++ {
|
||||||
|
for dx := -blockSize / 2; dx <= blockSize/2; dx++ {
|
||||||
|
nx, ny := x+dx, y+dy
|
||||||
|
if nx >= bounds.Min.X && nx < bounds.Max.X &&
|
||||||
|
ny >= bounds.Min.Y && ny < bounds.Max.Y {
|
||||||
|
r, _, _, _ := img.At(nx, ny).RGBA()
|
||||||
|
sum += float64(r >> 8)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localMean := sum / float64(count)
|
||||||
|
r, _, _, _ := img.At(x, y).RGBA()
|
||||||
|
gray := float64(r >> 8)
|
||||||
|
|
||||||
|
if gray > localMean-c {
|
||||||
|
dst.SetGray(x, y, color.Gray{Y: 255})
|
||||||
|
} else {
|
||||||
|
dst.SetGray(x, y, color.Gray{Y: 0})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分析二维码错误类型
|
||||||
|
func analyzeQRCodeError(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 检查不同类型的错误
|
||||||
|
switch e := err.(type) {
|
||||||
|
case gozxing.ChecksumException:
|
||||||
|
return fmt.Errorf("二维码校验失败(可能部分损坏或被遮挡): %v", e)
|
||||||
|
case gozxing.FormatException:
|
||||||
|
return fmt.Errorf("二维码格式错误(可能不是有效的二维码): %v", e)
|
||||||
|
case gozxing.NotFoundException:
|
||||||
|
return fmt.Errorf("二维码格式错误: %v", e)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("解码失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
func generateQRCode(content string, width int, height int, fileName string) (string, error) {
|
||||||
|
// 创建 QR 码写入器
|
||||||
|
writer := qrcode.NewQRCodeWriter()
|
||||||
|
|
||||||
|
// 设置编码参数
|
||||||
|
hints := make(map[gozxing.EncodeHintType]interface{})
|
||||||
|
|
||||||
|
// 纠错级别设置
|
||||||
|
hints[gozxing.EncodeHintType_ERROR_CORRECTION] = "H"
|
||||||
|
|
||||||
|
// 字符集
|
||||||
|
hints[gozxing.EncodeHintType_CHARACTER_SET] = "UTF-8"
|
||||||
|
|
||||||
|
// 边距
|
||||||
|
hints[gozxing.EncodeHintType_MARGIN] = 4
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
bitMatrix, err := writer.Encode(content, gozxing.BarcodeFormat_QR_CODE, width, height, hints)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("生成二维码失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建图像
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
|
||||||
|
// 白色背景
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
img.Set(x, y, color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 黑色二维码点
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
if bitMatrix.Get(x, y) {
|
||||||
|
img.Set(x, y, color.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
file, err := os.Create(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("保存二维码失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
png.Encode(file, img)
|
||||||
|
return fmt.Sprintf("生成二维码成功: %v", fileName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== 辅助函数 =======================
|
||||||
|
|
||||||
// 辅助函数
|
// 辅助函数
|
||||||
func absDiff(a, b uint8) uint8 {
|
func absDiff(a, b uint8) uint8 {
|
||||||
if a > b {
|
if a > b {
|
||||||
@ -578,6 +1184,44 @@ func RemoveWhiteBorderAndPNG(jsonConfig *C.char) *C.char {
|
|||||||
return C.CString(fileName)
|
return C.CString(fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResizeWTToHeightQuality 图片缩放
|
||||||
|
//
|
||||||
|
//export ResizeWTToHeightQuality
|
||||||
|
func ResizeWTToHeightQuality(jsonConfig *C.char, dsWidth, dsHeight C.int) *C.char {
|
||||||
|
configStr := C.GoString(jsonConfig)
|
||||||
|
dsWidthStr := int(dsWidth)
|
||||||
|
dsHeightStr := int(dsHeight)
|
||||||
|
var config *Config
|
||||||
|
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
|
||||||
|
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
|
||||||
|
}
|
||||||
|
fileName, err := resizeWTToHeightQuality(config, dsWidthStr, dsHeightStr)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
return C.CString(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CropImage 图片裁切
|
||||||
|
//
|
||||||
|
//export CropImage
|
||||||
|
func CropImage(jsonConfig *C.char, x, y, width, height C.int) *C.char {
|
||||||
|
configStr := C.GoString(jsonConfig)
|
||||||
|
xStr := int(x)
|
||||||
|
yStr := int(y)
|
||||||
|
widthStr := int(width)
|
||||||
|
heightStr := int(height)
|
||||||
|
var config *Config
|
||||||
|
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
|
||||||
|
return C.CString(fmt.Sprintf("解析 config 失败: %v", err))
|
||||||
|
}
|
||||||
|
fileName, err := cropImage(config, xStr, yStr, widthStr, heightStr)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
return C.CString(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
// 导出函数:释放C字符串内存
|
// 导出函数:释放C字符串内存
|
||||||
//
|
//
|
||||||
//export FreeCString
|
//export FreeCString
|
||||||
@ -585,6 +1229,6 @@ func FreeCString(str *C.char) {
|
|||||||
C.free(unsafe.Pointer(str))
|
C.free(unsafe.Pointer(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// main 函数是必需的,即使为空
|
||||||
//func main() {
|
//func main() {
|
||||||
//
|
|
||||||
//}
|
//}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -80,30 +81,75 @@ func cStr(ptr uintptr) string {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
config := &Config{
|
//config := &Config{
|
||||||
OutputDir: "D:\\isbn_images\\result", // 输出根目录
|
// OutputDir: "D:\\isbn_images\\result", // 输出根目录
|
||||||
FileName: "D:\\isbn_images\\result\\9771671688095.jpg",
|
// FileName: "D:\\isbn_images\\result\\97800079351851.jpg",
|
||||||
MatchDir: "matched", // 满足条件的图片目录
|
// //MatchDir: "matched", // 满足条件的图片目录
|
||||||
UnmatchDir: "unmatched", // 不满足条件的图片目录
|
// //UnmatchDir: "unmatched", // 不满足条件的图片目录
|
||||||
WhiteDir: "white",
|
// //WhiteDir: "white",
|
||||||
EqualHeightDir: "equalHeight",
|
// //EqualHeightDir: "equalHeight",
|
||||||
MinWhitePct: 0.1, // 纯白占比下限 10%
|
// //WhiteHeightZoomDir: "whiteHeightZoom",
|
||||||
MaxWhitePct: 0.65, // 纯白占比上限 90%
|
// CropDir: "crop",
|
||||||
Extensions: []string{"jpg", "jpeg", "png", "gif", "bmp", "webp"},
|
// MinWhitePct: 0.1, // 纯白占比下限 10%
|
||||||
}
|
// MaxWhitePct: 0.65, // 纯白占比上限 90%
|
||||||
|
// Extensions: []string{"jpg", "jpeg", "png", "gif", "bmp", "webp"},
|
||||||
|
//}
|
||||||
|
|
||||||
dll, err := InitImageDll()
|
//dll, err := InitImageDll()
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
//err = dll.ProcessImage(config)
|
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// fmt.Println(err)
|
// fmt.Println(err)
|
||||||
//}
|
//}
|
||||||
|
////err = dll.ProcessImage(config)
|
||||||
|
////if err != nil {
|
||||||
|
//// fmt.Println(err)
|
||||||
|
////}
|
||||||
|
//
|
||||||
|
//image, err := dll.CreateWhiteBottomCenteredImage(config, 800, 800)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(image)
|
||||||
|
|
||||||
image, err := dll.CreateWhiteBottomCenteredImage(config, 800, 800)
|
// 图片缩放
|
||||||
|
//quality, err := resizeWTToHeightQuality(config, 800, 500)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(quality)
|
||||||
|
|
||||||
|
//// 图片裁切
|
||||||
|
//image, err := cropImage(config, 100, 0, 300, 300)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(image)
|
||||||
|
|
||||||
|
//file := "D:\\isbn_images\\result\\123\\qrcode.jpg"
|
||||||
|
//
|
||||||
|
//code, s, err := scanQRCodeNew(file)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//if code {
|
||||||
|
// fmt.Println(s)
|
||||||
|
//}
|
||||||
|
|
||||||
|
code, err := generateQRCode("你好", 500, 500, "D:\\isbn_images\\result\\123\\qrcode.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
fmt.Println(image)
|
fmt.Println(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
func generateQRCode1() {
|
||||||
|
// 一行代码生成二维码
|
||||||
|
err := qrcode.WriteFile("https://example.com", qrcode.Medium, 256, "qrcode.png")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//// 更多控制
|
||||||
|
//q, _ := qrcode.New("https://github.com", qrcode.High)
|
||||||
|
//q.WriteFile(256, "custom_qr.png")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,644 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
//import (
|
|
||||||
// "fmt"
|
|
||||||
// "image"
|
|
||||||
// "image/color"
|
|
||||||
// "image/draw"
|
|
||||||
// "image/png"
|
|
||||||
// "io/ioutil"
|
|
||||||
// "os"
|
|
||||||
// "path/filepath"
|
|
||||||
// "strings"
|
|
||||||
// "sync"
|
|
||||||
// "time"
|
|
||||||
//
|
|
||||||
// "github.com/disintegration/imaging"
|
|
||||||
//)
|
|
||||||
//
|
|
||||||
////// ImageToPNGConverter 图片去白边并转为PNG
|
|
||||||
////type ImageToPNGConverter struct {
|
|
||||||
//// Threshold int
|
|
||||||
//// Margin int
|
|
||||||
//// BgColor color.RGBA
|
|
||||||
//// DetectColor *color.RGBA
|
|
||||||
//// KeepTransparent bool
|
|
||||||
//// PNGCompressLevel png.CompressionLevel
|
|
||||||
//// Quality int
|
|
||||||
////}
|
|
||||||
//
|
|
||||||
//// NewImageToPNGConverter 创建新的转换器
|
|
||||||
//func NewImageToPNGConverter(threshold, margin int, bgColor, detectColor *color.RGBA,
|
|
||||||
// keepTransparent bool, compressLevel png.CompressionLevel, quality int) *ImageToPNGConverter {
|
|
||||||
//
|
|
||||||
// // 默认背景色为白色
|
|
||||||
// bg := color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
|
||||||
// if bgColor != nil {
|
|
||||||
// bg = *bgColor
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return &ImageToPNGConverter{
|
|
||||||
// Threshold: threshold,
|
|
||||||
// Margin: margin,
|
|
||||||
// BgColor: bg,
|
|
||||||
// DetectColor: detectColor,
|
|
||||||
// KeepTransparent: keepTransparent,
|
|
||||||
// PNGCompressLevel: compressLevel,
|
|
||||||
// Quality: quality,
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// IsBackgroundColor 判断像素是否为背景色
|
|
||||||
//func (c *ImageToPNGConverter) IsBackgroundColor(pixel color.Color, hasAlpha bool) bool {
|
|
||||||
// r, g, b, a := pixel.RGBA()
|
|
||||||
//
|
|
||||||
// // 转换为8位值
|
|
||||||
// r8 := uint8(r >> 8)
|
|
||||||
// g8 := uint8(g >> 8)
|
|
||||||
// b8 := uint8(b >> 8)
|
|
||||||
// a8 := uint8(a >> 8)
|
|
||||||
//
|
|
||||||
// // 检查透明度
|
|
||||||
// if hasAlpha && a8 < 25 { // 透明度 > 90%
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 如果指定了检测颜色
|
|
||||||
// if c.DetectColor != nil {
|
|
||||||
// dr, dg, db, _ := c.DetectColor.RGBA()
|
|
||||||
// dr8 := uint8(dr >> 8)
|
|
||||||
// dg8 := uint8(dg >> 8)
|
|
||||||
// db8 := uint8(db >> 8)
|
|
||||||
//
|
|
||||||
// // threshold 是 int 类型,需要转换为 uint8 比较
|
|
||||||
// threshold8 := uint8(255 - c.Threshold)
|
|
||||||
// return absDiff(r8, dr8) <= threshold8 &&
|
|
||||||
// absDiff(g8, dg8) <= threshold8 &&
|
|
||||||
// absDiff(b8, db8) <= threshold8
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 自动检测白色/浅色背景
|
|
||||||
// // 注意:这里的 c.Threshold 是 int,需要转换为 uint8
|
|
||||||
// threshold8 := uint8(c.Threshold)
|
|
||||||
// return r8 >= threshold8 &&
|
|
||||||
// g8 >= threshold8 &&
|
|
||||||
// b8 >= threshold8
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// FindBorders 查找图片的有效边界
|
|
||||||
//func (c *ImageToPNGConverter) FindBorders(img image.Image) image.Rectangle {
|
|
||||||
// bounds := img.Bounds()
|
|
||||||
// width := bounds.Dx()
|
|
||||||
// height := bounds.Dy()
|
|
||||||
//
|
|
||||||
// // 检查图像是否有alpha通道
|
|
||||||
// _, hasAlpha := img.(*image.NRGBA)
|
|
||||||
// if !hasAlpha {
|
|
||||||
// _, hasAlpha = img.(*image.RGBA)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 初始化边界
|
|
||||||
// left := width
|
|
||||||
// top := height
|
|
||||||
// right := 0
|
|
||||||
// bottom := 0
|
|
||||||
//
|
|
||||||
// // 查找非背景区域
|
|
||||||
// for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
// for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
// pixel := img.At(x, y)
|
|
||||||
// if !c.IsBackgroundColor(pixel, hasAlpha) {
|
|
||||||
// if x < left {
|
|
||||||
// left = x
|
|
||||||
// }
|
|
||||||
// if x > right {
|
|
||||||
// right = x
|
|
||||||
// }
|
|
||||||
// if y < top {
|
|
||||||
// top = y
|
|
||||||
// }
|
|
||||||
// if y > bottom {
|
|
||||||
// bottom = y
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 如果没有找到非背景区域,返回整个图像
|
|
||||||
// if left > right || top > bottom {
|
|
||||||
// return bounds
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 添加边距
|
|
||||||
// left = max(bounds.Min.X, left-c.Margin)
|
|
||||||
// top = max(bounds.Min.Y, top-c.Margin)
|
|
||||||
// right = min(bounds.Max.X, right+c.Margin+1)
|
|
||||||
// bottom = min(bounds.Max.Y, bottom+c.Margin+1)
|
|
||||||
//
|
|
||||||
// return image.Rect(left, top, right, bottom)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// TrimImage 裁剪图片白边
|
|
||||||
//func (c *ImageToPNGConverter) TrimImage(img image.Image) image.Image {
|
|
||||||
// borders := c.FindBorders(img)
|
|
||||||
//
|
|
||||||
// // 创建一个新的图像并裁剪
|
|
||||||
// trimmed := imaging.Crop(img, borders)
|
|
||||||
// return trimmed
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// ConvertToPNG 转换图片为PNG格式
|
|
||||||
//func (c *ImageToPNGConverter) ConvertToPNG(img image.Image, addBackground bool) image.Image {
|
|
||||||
// // 先裁剪白边
|
|
||||||
// trimmed := c.TrimImage(img)
|
|
||||||
//
|
|
||||||
// // 检查是否有alpha通道
|
|
||||||
// _, hasAlpha := trimmed.(*image.NRGBA)
|
|
||||||
// if !hasAlpha {
|
|
||||||
// _, hasAlpha = trimmed.(*image.RGBA)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if hasAlpha {
|
|
||||||
// if c.KeepTransparent {
|
|
||||||
// // 保持透明
|
|
||||||
// return trimmed
|
|
||||||
// } else if addBackground {
|
|
||||||
// // 添加背景色
|
|
||||||
// bg := image.NewRGBA(trimmed.Bounds())
|
|
||||||
// draw.Draw(bg, bg.Bounds(), &image.Uniform{C: c.BgColor}, image.Point{}, draw.Src)
|
|
||||||
// draw.Draw(bg, bg.Bounds(), trimmed, trimmed.Bounds().Min, draw.Over)
|
|
||||||
// return bg
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // 非透明图像
|
|
||||||
// if c.KeepTransparent {
|
|
||||||
// // 转换为RGBA
|
|
||||||
// rgba := image.NewRGBA(trimmed.Bounds())
|
|
||||||
// draw.Draw(rgba, rgba.Bounds(), trimmed, trimmed.Bounds().Min, draw.Src)
|
|
||||||
// return rgba
|
|
||||||
// }
|
|
||||||
// return trimmed
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return trimmed
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// ProcessImageFile 处理单个图片文件
|
|
||||||
//func (c *ImageToPNGConverter) ProcessImageFile(inputPath, outputPath string) map[string]interface{} {
|
|
||||||
// result := map[string]interface{}{
|
|
||||||
// "success": false,
|
|
||||||
// "input_path": inputPath,
|
|
||||||
// "output_path": outputPath,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 打开图片文件
|
|
||||||
// file, err := os.Open(inputPath)
|
|
||||||
// if err != nil {
|
|
||||||
// result["error"] = err.Error()
|
|
||||||
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
|
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
// defer file.Close()
|
|
||||||
//
|
|
||||||
// // 解码图像
|
|
||||||
// img, format, err := image.Decode(file)
|
|
||||||
// if err != nil {
|
|
||||||
// result["error"] = err.Error()
|
|
||||||
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
|
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 获取原始信息
|
|
||||||
// origBounds := img.Bounds()
|
|
||||||
// origSize := origBounds.Size()
|
|
||||||
// origArea := origSize.X * origSize.Y
|
|
||||||
//
|
|
||||||
// // 转换为PNG
|
|
||||||
// resultImg := c.ConvertToPNG(img, true)
|
|
||||||
//
|
|
||||||
// // 获取处理后的信息
|
|
||||||
// newBounds := resultImg.Bounds()
|
|
||||||
// newSize := newBounds.Size()
|
|
||||||
// newArea := newSize.X * newSize.Y
|
|
||||||
//
|
|
||||||
// // 计算尺寸减少比例
|
|
||||||
// sizeReduction := 0.0
|
|
||||||
// if origArea > 0 {
|
|
||||||
// sizeReduction = 1 - float64(newArea)/float64(origArea)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 获取原始文件大小
|
|
||||||
// fileInfo, _ := os.Stat(inputPath)
|
|
||||||
// origFileSize := fileInfo.Size()
|
|
||||||
//
|
|
||||||
// // 保存为PNG
|
|
||||||
// outputFile, err := os.Create(outputPath)
|
|
||||||
// if err != nil {
|
|
||||||
// result["error"] = err.Error()
|
|
||||||
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
|
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
// defer outputFile.Close()
|
|
||||||
//
|
|
||||||
// encoder := png.Encoder{CompressionLevel: c.PNGCompressLevel}
|
|
||||||
// err = encoder.Encode(outputFile, resultImg)
|
|
||||||
// if err != nil {
|
|
||||||
// result["error"] = err.Error()
|
|
||||||
// result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err)
|
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 获取新文件大小
|
|
||||||
// newFileInfo, _ := os.Stat(outputPath)
|
|
||||||
// newFileSize := newFileInfo.Size()
|
|
||||||
//
|
|
||||||
// // 计算文件大小变化
|
|
||||||
// fileSizeChange := 0.0
|
|
||||||
// if origFileSize > 0 {
|
|
||||||
// fileSizeChange = float64(newFileSize) / float64(origFileSize)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// result["success"] = true
|
|
||||||
// result["orig_format"] = format
|
|
||||||
// result["orig_size"] = origSize
|
|
||||||
// result["new_size"] = newSize
|
|
||||||
// result["size_reduction"] = sizeReduction
|
|
||||||
// result["orig_file_size"] = origFileSize
|
|
||||||
// result["new_file_size"] = newFileSize
|
|
||||||
// result["file_size_change"] = fileSizeChange
|
|
||||||
// result["message"] = fmt.Sprintf("成功: %s (%s→PNG, %dx%d→%dx%d)",
|
|
||||||
// filepath.Base(inputPath), format, origSize.X, origSize.Y, newSize.X, newSize.Y)
|
|
||||||
//
|
|
||||||
// return result
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// BatchPNGConverter 批量PNG转换器
|
|
||||||
//type BatchPNGConverter struct {
|
|
||||||
// converter *ImageToPNGConverter
|
|
||||||
// outputDir string
|
|
||||||
// statistics map[string]interface{}
|
|
||||||
// mu sync.Mutex
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// NewBatchPNGConverter 创建批量转换器
|
|
||||||
//func NewBatchPNGConverter(converter *ImageToPNGConverter, outputDir string) *BatchPNGConverter {
|
|
||||||
// os.MkdirAll(outputDir, 0755)
|
|
||||||
// return &BatchPNGConverter{
|
|
||||||
// converter: converter,
|
|
||||||
// outputDir: outputDir,
|
|
||||||
// statistics: make(map[string]interface{}),
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// GetOutputPath 生成输出路径
|
|
||||||
//func (b *BatchPNGConverter) GetOutputPath(inputPath, suffix string) string {
|
|
||||||
// baseName := filepath.Base(inputPath)
|
|
||||||
// ext := filepath.Ext(baseName)
|
|
||||||
// nameWithoutExt := strings.TrimSuffix(baseName, ext)
|
|
||||||
//
|
|
||||||
// outputFilename := nameWithoutExt + suffix + ".png"
|
|
||||||
// return filepath.Join(b.outputDir, outputFilename)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// ProcessSingle 处理单张图片
|
|
||||||
//func (b *BatchPNGConverter) ProcessSingle(inputPath, outputPath, suffix string) map[string]interface{} {
|
|
||||||
// if outputPath == "" {
|
|
||||||
// outputPath = b.GetOutputPath(inputPath, suffix)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 确保输出目录存在
|
|
||||||
// os.MkdirAll(filepath.Dir(outputPath), 0755)
|
|
||||||
//
|
|
||||||
// return b.converter.ProcessImageFile(inputPath, outputPath)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// ProcessBatch 批量处理图片
|
|
||||||
//func (b *BatchPNGConverter) ProcessBatch(inputPaths []string, suffix string, maxWorkers int) map[string]interface{} {
|
|
||||||
// startTime := time.Now()
|
|
||||||
//
|
|
||||||
// stats := map[string]interface{}{
|
|
||||||
// "total": len(inputPaths),
|
|
||||||
// "success": 0,
|
|
||||||
// "failed": 0,
|
|
||||||
// "total_size_reduction": 0.0,
|
|
||||||
// "total_file_size_orig": int64(0),
|
|
||||||
// "total_file_size_new": int64(0),
|
|
||||||
// "results": []map[string]interface{}{},
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 使用工作池
|
|
||||||
// var wg sync.WaitGroup
|
|
||||||
// semaphore := make(chan struct{}, maxWorkers)
|
|
||||||
// resultsChan := make(chan map[string]interface{}, len(inputPaths))
|
|
||||||
//
|
|
||||||
// for _, inputPath := range inputPaths {
|
|
||||||
// wg.Add(1)
|
|
||||||
// go func(path string) {
|
|
||||||
// defer wg.Done()
|
|
||||||
// semaphore <- struct{}{}
|
|
||||||
// defer func() { <-semaphore }()
|
|
||||||
//
|
|
||||||
// outputPath := b.GetOutputPath(path, suffix)
|
|
||||||
// result := b.ProcessSingle(path, outputPath, suffix)
|
|
||||||
// resultsChan <- result
|
|
||||||
// }(inputPath)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 收集结果
|
|
||||||
// go func() {
|
|
||||||
// wg.Wait()
|
|
||||||
// close(resultsChan)
|
|
||||||
// }()
|
|
||||||
//
|
|
||||||
// completed := 0
|
|
||||||
// for result := range resultsChan {
|
|
||||||
// completed++
|
|
||||||
// b.mu.Lock()
|
|
||||||
// stats["results"] = append(stats["results"].([]map[string]interface{}), result)
|
|
||||||
//
|
|
||||||
// if result["success"].(bool) {
|
|
||||||
// stats["success"] = stats["success"].(int) + 1
|
|
||||||
// stats["total_size_reduction"] = stats["total_size_reduction"].(float64) + result["size_reduction"].(float64)
|
|
||||||
// stats["total_file_size_orig"] = stats["total_file_size_orig"].(int64) + result["orig_file_size"].(int64)
|
|
||||||
// stats["total_file_size_new"] = stats["total_file_size_new"].(int64) + result["new_file_size"].(int64)
|
|
||||||
// } else {
|
|
||||||
// stats["failed"] = stats["failed"].(int) + 1
|
|
||||||
// }
|
|
||||||
// b.mu.Unlock()
|
|
||||||
//
|
|
||||||
// fmt.Printf("[%d/%d] %s\n", completed, len(inputPaths), result["message"])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 计算统计信息
|
|
||||||
// stats["elapsed_time"] = time.Since(startTime).Seconds()
|
|
||||||
// if stats["success"].(int) > 0 {
|
|
||||||
// stats["avg_size_reduction"] = stats["total_size_reduction"].(float64) / float64(stats["success"].(int))
|
|
||||||
// if stats["total_file_size_orig"].(int64) > 0 {
|
|
||||||
// stats["total_file_size_change"] = float64(stats["total_file_size_new"].(int64)) / float64(stats["total_file_size_orig"].(int64))
|
|
||||||
// } else {
|
|
||||||
// stats["total_file_size_change"] = 1.0
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return stats
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// FindImageFiles 查找目录中的图片文件
|
|
||||||
//func FindImageFiles(directory string, recursive bool) []string {
|
|
||||||
// supportedExtensions := map[string]bool{
|
|
||||||
// ".jpg": true,
|
|
||||||
// ".jpeg": true,
|
|
||||||
// ".png": true,
|
|
||||||
// ".gif": true,
|
|
||||||
// ".bmp": true,
|
|
||||||
// ".tif": true,
|
|
||||||
// ".tiff": true,
|
|
||||||
// ".webp": true,
|
|
||||||
// ".jfif": true,
|
|
||||||
// ".ico": true,
|
|
||||||
// ".ppm": true,
|
|
||||||
// ".pgm": true,
|
|
||||||
// ".pbm": true,
|
|
||||||
// ".pnm": true,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var imagePaths []string
|
|
||||||
//
|
|
||||||
// if recursive {
|
|
||||||
// filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// if !info.IsDir() {
|
|
||||||
// ext := strings.ToLower(filepath.Ext(path))
|
|
||||||
// if supportedExtensions[ext] {
|
|
||||||
// imagePaths = append(imagePaths, path)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// files, err := ioutil.ReadDir(directory)
|
|
||||||
// if err != nil {
|
|
||||||
// return imagePaths
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for _, file := range files {
|
|
||||||
// if !file.IsDir() {
|
|
||||||
// ext := strings.ToLower(filepath.Ext(file.Name()))
|
|
||||||
// if supportedExtensions[ext] {
|
|
||||||
// imagePaths = append(imagePaths, filepath.Join(directory, file.Name()))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return imagePaths
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// PrintBanner 打印程序标题
|
|
||||||
//func PrintBanner() {
|
|
||||||
// banner := `
|
|
||||||
//╔══════════════════════════════════════════════════╗
|
|
||||||
//║ 图片去白边转PNG工具 v1.0 ║
|
|
||||||
//║ Image White Border Removal & PNG Converter ║
|
|
||||||
//╚══════════════════════════════════════════════════╝
|
|
||||||
//`
|
|
||||||
// fmt.Println(banner)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// PrintSummary 打印处理总结
|
|
||||||
//func PrintSummary(stats map[string]interface{}) {
|
|
||||||
// fmt.Println("\n" + strings.Repeat("=", 60))
|
|
||||||
// fmt.Println("📊 处理总结")
|
|
||||||
// fmt.Println(strings.Repeat("=", 60))
|
|
||||||
// fmt.Printf("📁 总共处理: %d 张图片\n", stats["total"])
|
|
||||||
// fmt.Printf("✅ 成功: %d 张\n", stats["success"])
|
|
||||||
// fmt.Printf("❌ 失败: %d 张\n", stats["failed"])
|
|
||||||
//
|
|
||||||
// if stats["success"].(int) > 0 {
|
|
||||||
// fmt.Printf("⏱️ 耗时: %.2f 秒\n", stats["elapsed_time"].(float64))
|
|
||||||
//
|
|
||||||
// if avgReduction, ok := stats["avg_size_reduction"]; ok {
|
|
||||||
// fmt.Printf("📐 平均尺寸减少: %.1f%%\n", avgReduction.(float64)*100)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if change, ok := stats["total_file_size_change"]; ok {
|
|
||||||
// changeVal := change.(float64)
|
|
||||||
// if changeVal < 1 {
|
|
||||||
// fmt.Printf("💾 总文件大小减少: %.1f%%\n", (1-changeVal)*100)
|
|
||||||
// } else if changeVal > 1 {
|
|
||||||
// fmt.Printf("💾 总文件大小增加: %.1f%%\n", (changeVal-1)*100)
|
|
||||||
// } else {
|
|
||||||
// fmt.Println("💾 总文件大小基本不变")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// fmt.Println(strings.Repeat("=", 60))
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// 辅助函数
|
|
||||||
//func absDiff(a, b uint8) uint8 {
|
|
||||||
// if a > b {
|
|
||||||
// return a - b
|
|
||||||
// }
|
|
||||||
// return b - a
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func max(a, b int) int {
|
|
||||||
// if a > b {
|
|
||||||
// return a
|
|
||||||
// }
|
|
||||||
// return b
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func min(a, b int) int {
|
|
||||||
// if a < b {
|
|
||||||
// return a
|
|
||||||
// }
|
|
||||||
// return b
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func main() {
|
|
||||||
// // 直接设置参数值,不需要命令行输入
|
|
||||||
//
|
|
||||||
// // ============ 参数配置区 ============
|
|
||||||
// // 基础参数
|
|
||||||
// inputPath := "D:\\isbn_images\\result\\matched\\9771671688095.jpg" // 输入文件或目录路径
|
|
||||||
// outputPath := "D:\\isbn_images\\result\\matched\\output.png" // 输出文件路径(单文件模式)
|
|
||||||
// outputDir := "D:\\isbn_images\\result\\matched\\" // 输出目录路径(批量模式)
|
|
||||||
// suffix := "_trimmed" // 输出文件名后缀
|
|
||||||
//
|
|
||||||
// // 处理参数
|
|
||||||
// threshold := 240 // 背景检测阈值 (0-255)
|
|
||||||
// margin := 0 // 保留边距像素
|
|
||||||
// transparent := false // 保持透明背景
|
|
||||||
// compressLevel := 6 // PNG压缩级别 (0-9)
|
|
||||||
//
|
|
||||||
// // 批量处理参数
|
|
||||||
// recursive := false // 递归处理子目录
|
|
||||||
// jobs := 4 // 并行处理数
|
|
||||||
// force := true // 覆盖已存在的输出文件(设置为true不询问)
|
|
||||||
// verbose := true // 显示详细处理信息
|
|
||||||
// showBanner := true // 显示标题横幅
|
|
||||||
// // ============ 参数配置结束 ============
|
|
||||||
//
|
|
||||||
// if showBanner {
|
|
||||||
// PrintBanner()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 创建转换器
|
|
||||||
// compressionLevel := png.DefaultCompression
|
|
||||||
// switch {
|
|
||||||
// case compressLevel <= 0:
|
|
||||||
// compressionLevel = png.NoCompression
|
|
||||||
// case compressLevel >= 9:
|
|
||||||
// compressionLevel = png.BestCompression
|
|
||||||
// default:
|
|
||||||
// // 使用默认压缩级别
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// converter := NewImageToPNGConverter(
|
|
||||||
// threshold,
|
|
||||||
// margin,
|
|
||||||
// &color.RGBA{R: 255, G: 255, B: 255, A: 255},
|
|
||||||
// nil,
|
|
||||||
// transparent,
|
|
||||||
// compressionLevel,
|
|
||||||
// 95,
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// batchConverter := NewBatchPNGConverter(converter, outputDir)
|
|
||||||
//
|
|
||||||
// // 检查输入路径
|
|
||||||
// info, err := os.Stat(inputPath)
|
|
||||||
// if err != nil {
|
|
||||||
// fmt.Printf("❌ 错误: 路径不存在 - %s\n", inputPath)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if !info.IsDir() {
|
|
||||||
// // 单文件模式
|
|
||||||
// fmt.Printf("📄 处理单文件: %s\n", inputPath)
|
|
||||||
//
|
|
||||||
// // 如果outputPath为空,则生成默认输出路径
|
|
||||||
// if outputPath == "" {
|
|
||||||
// outputPath = batchConverter.GetOutputPath(inputPath, suffix)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 检查输出文件是否存在,如果force为false则询问
|
|
||||||
// if _, err := os.Stat(outputPath); err == nil && !force {
|
|
||||||
// fmt.Printf("⚠️ 警告: 输出文件已存在 - %s\n", outputPath)
|
|
||||||
// fmt.Println("已设置force=true,直接覆盖")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// result := batchConverter.ProcessSingle(inputPath, outputPath, suffix)
|
|
||||||
//
|
|
||||||
// if result["success"].(bool) {
|
|
||||||
// fmt.Printf("\n✅ %s\n", result["message"])
|
|
||||||
// fmt.Printf("💾 输出文件: %s\n", result["output_path"])
|
|
||||||
//
|
|
||||||
// if reduction, ok := result["size_reduction"]; ok {
|
|
||||||
// reductionVal := reduction.(float64)
|
|
||||||
// if reductionVal > 0 {
|
|
||||||
// fmt.Printf("📐 尺寸减少: %.1f%%\n", reductionVal*100)
|
|
||||||
// } else if reductionVal < 0 {
|
|
||||||
// fmt.Printf("📐 尺寸增加: %.1f%%\n", -reductionVal*100)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if change, ok := result["file_size_change"]; ok {
|
|
||||||
// changeVal := change.(float64)
|
|
||||||
// if changeVal < 1 {
|
|
||||||
// fmt.Printf("💿 文件大小减少: %.1f%%\n", (1-changeVal)*100)
|
|
||||||
// } else if changeVal > 1 {
|
|
||||||
// fmt.Printf("💿 文件大小增加: %.1f%%\n", (changeVal-1)*100)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// fmt.Printf("\n❌ %s\n", result["message"])
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // 批量模式
|
|
||||||
// fmt.Printf("📁 扫描目录: %s\n", inputPath)
|
|
||||||
// imageFiles := FindImageFiles(inputPath, recursive)
|
|
||||||
//
|
|
||||||
// if len(imageFiles) == 0 {
|
|
||||||
// fmt.Println("未找到支持的图片文件")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fmt.Printf("找到 %d 张图片\n", len(imageFiles))
|
|
||||||
//
|
|
||||||
// // 检查输出目录,如果force为false则询问
|
|
||||||
// if _, err := os.Stat(outputDir); err == nil && !force {
|
|
||||||
// files, _ := ioutil.ReadDir(outputDir)
|
|
||||||
// if len(files) > 0 {
|
|
||||||
// fmt.Printf("⚠️ 警告: 输出目录不为空 - %s\n", outputDir)
|
|
||||||
// fmt.Println("已设置force=true,直接继续处理")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fmt.Printf("📂 输出目录: %s\n", outputDir)
|
|
||||||
// fmt.Printf("⚡ 并行处理: %d 个线程\n", jobs)
|
|
||||||
// fmt.Println(strings.Repeat("-", 60))
|
|
||||||
//
|
|
||||||
// // 批量处理
|
|
||||||
// stats := batchConverter.ProcessBatch(imageFiles, suffix, jobs)
|
|
||||||
//
|
|
||||||
// // 打印总结
|
|
||||||
// PrintSummary(stats)
|
|
||||||
//
|
|
||||||
// // 显示失败详情
|
|
||||||
// if stats["failed"].(int) > 0 && verbose {
|
|
||||||
// fmt.Println("\n❌ 失败详情:")
|
|
||||||
// for _, result := range stats["results"].([]map[string]interface{}) {
|
|
||||||
// if !result["success"].(bool) {
|
|
||||||
// fmt.Printf(" %s: %s\n",
|
|
||||||
// filepath.Base(result["input_path"].(string)),
|
|
||||||
// result["error"])
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
Binary file not shown.
@ -88,55 +88,67 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// 孔网登录
|
// OutLogin 孔网登录
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutLogin(char* username, char* password);
|
extern __declspec(dllexport) char* OutLogin(char* username, char* password);
|
||||||
|
|
||||||
// 获取孔网用户信息
|
// OutGetUserMsg 获取孔网用户信息
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetUserMsg(char* token);
|
extern __declspec(dllexport) char* OutGetUserMsg(char* token);
|
||||||
|
|
||||||
// 获取商品模版--已登的店铺
|
// OutGetGoodsTplMsg 获取商品模版--已登的店铺
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
|
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
|
||||||
|
|
||||||
// 获取商品列表-已登的店铺
|
// OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
||||||
|
|
||||||
// 新增商品-已登的店铺(带有Out的都非官方标准接口)
|
// OutAddGoods 新增商品-已登的店铺
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
|
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
|
||||||
|
|
||||||
// 删除商品-已登的店铺
|
// OutDelGoodsFromSelfShop 删除商品-已登的店铺
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
|
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
|
||||||
|
|
||||||
// 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
|
// OutGetImageFilterShopId 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
|
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
|
||||||
|
|
||||||
// 获取孔网商品图片和信息(官图和拍图)
|
// OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图)
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* isbn, char* proxy, int isLiveImage, int isReturnMsg);
|
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg);
|
||||||
|
|
||||||
// 获取商品列表通过店铺ID
|
// OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
|
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
|
||||||
|
|
||||||
// 获取商品信息通过商品详情链接
|
// OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
|
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
|
||||||
|
|
||||||
// 获取销量榜商品列表
|
// OutGetTopGoodsListMsg 获取销量榜商品列表
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
|
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
|
||||||
|
|
||||||
// 初始化配置
|
// KongfzDeliveryMethodList 获取配送方式列表
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* KongfzDeliveryMethodList(int appId, char* appSecret, char* accessToken);
|
||||||
|
|
||||||
|
// KongfzOrderDeliver 订单发货
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* KongfzOrderDeliver(int appId, char* appSecret, char* accessToken, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||||
|
|
||||||
|
// KongfzOrderSynchronization 孔网订单同步
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* KongfzOrderSynchronization(int appId, char* appSecret, char* accessToken, char* shippingComName, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||||
|
|
||||||
|
// Initialize 初始化配置
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* Initialize(char* configJSON);
|
extern __declspec(dllexport) char* Initialize(char* configJSON);
|
||||||
|
|
||||||
// 释放C字符串内存
|
// FreeCString 释放C字符串内存
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) void FreeCString(char* str);
|
extern __declspec(dllexport) void FreeCString(char* str);
|
||||||
|
|
||||||
|
|||||||
764
kongfz/kongfz.go
764
kongfz/kongfz.go
File diff suppressed because it is too large
Load Diff
346
md/es.md
Normal file
346
md/es.md
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
# es.dll 使用教程
|
||||||
|
## 1.创建DLL工具实例
|
||||||
|
### 加载DLL文件~~~~
|
||||||
|
```gotemplate
|
||||||
|
// EsDLL Elasticsearch工具DLL结构
|
||||||
|
type esDLL struct {
|
||||||
|
dll *syscall.DLL
|
||||||
|
listAllIndices *syscall.Proc // 查询所有索引
|
||||||
|
getIndicesInfo *syscall.Proc // 获取所有索引的详细信息
|
||||||
|
getIndexDetail *syscall.Proc // 获取单个索引的详细信息
|
||||||
|
createIndex *syscall.Proc // 创建索引
|
||||||
|
deleteIndex *syscall.Proc // 删除索引
|
||||||
|
getDocumentCount *syscall.Proc // 获取索引文档数量
|
||||||
|
createDocument *syscall.Proc // 创建文档
|
||||||
|
getDocument *syscall.Proc // 根据ID获取文档
|
||||||
|
updateDocument *syscall.Proc // 更新文档
|
||||||
|
deleteDocument *syscall.Proc // 删除文档
|
||||||
|
searchDocuments *syscall.Proc // 搜索文档
|
||||||
|
freeCString *syscall.Proc // 释放C字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化esDLL
|
||||||
|
func InitEsDLL() (*esDLL, error) {
|
||||||
|
dllPath := filepath.Join("dll", "es.dll")
|
||||||
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("es DLL 不存在: %s", dllPath)
|
||||||
|
}
|
||||||
|
if dll, err := syscall.LoadDLL(dllPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("加载es DLL 失败: %s", err)
|
||||||
|
} else {
|
||||||
|
return &esDLL{
|
||||||
|
dll: dll,
|
||||||
|
listAllIndices: dll.MustFindProc("ListAllIndices"),
|
||||||
|
getIndicesInfo: dll.MustFindProc("GetIndicesInfo"),
|
||||||
|
getIndexDetail: dll.MustFindProc("GetIndexDetail"),
|
||||||
|
createIndex: dll.MustFindProc("CreateIndex"),
|
||||||
|
deleteIndex: dll.MustFindProc("DeleteIndex"),
|
||||||
|
getDocumentCount: dll.MustFindProc("GetDocumentCount"),
|
||||||
|
createDocument: dll.MustFindProc("CreateDocument"),
|
||||||
|
getDocument: dll.MustFindProc("GetDocument"),
|
||||||
|
updateDocument: dll.MustFindProc("UpdateDocument"),
|
||||||
|
deleteDocument: dll.MustFindProc("DeleteDocument"),
|
||||||
|
searchDocuments: dll.MustFindProc("SearchDocuments"),
|
||||||
|
freeCString: dll.MustFindProc("FreeCString"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dll, err := InitEsDLL()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取C字符串
|
||||||
|
```gotemplate
|
||||||
|
// cStr 获取C字符串
|
||||||
|
func (m *esDLL) cStr(p uintptr) string {
|
||||||
|
if p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
for i := uintptr(0); ; i++ {
|
||||||
|
c := *(*byte)(unsafe.Pointer(p + i))
|
||||||
|
if c == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if m.freeCString != nil {
|
||||||
|
m.freeCString.Call(p)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 使用dll函数示例
|
||||||
|
```gotemplate
|
||||||
|
// 查询所有索引
|
||||||
|
func (m *esDLL) ListAllIndices() (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("ListAllIndices")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 ListAllIndices: %v", err)
|
||||||
|
}
|
||||||
|
resultPtr, _, _ := proc.Call()
|
||||||
|
result := m.cStr(resultPtr)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 接口详情
|
||||||
|
## 1.查询所有索引--ListAllIndices
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.ListAllIndices()
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
无
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": ["index1", "index2", "index3"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 获取所有索引的详细信息--GetIndicesInfo
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.GetIndicesInfo()
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
无
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"index": "index1",
|
||||||
|
"health": "green",
|
||||||
|
"status": "open",
|
||||||
|
"uuid": "abc123",
|
||||||
|
"pri": "1",
|
||||||
|
"rep": "1",
|
||||||
|
"docs.count": "100",
|
||||||
|
"docs.deleted": "0",
|
||||||
|
"store.size": "1.2kb",
|
||||||
|
"pri.store.size": "600b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 获取单个索引的详细信息--GetIndexDetail
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.GetIndexDetail(indexName)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": {
|
||||||
|
"index1": {
|
||||||
|
"aliases": {},
|
||||||
|
"mappings": {},
|
||||||
|
"settings": {
|
||||||
|
"index": {
|
||||||
|
"creation_date": "1234567890000",
|
||||||
|
"number_of_shards": "1",
|
||||||
|
"number_of_replicas": "1",
|
||||||
|
"uuid": "abc123",
|
||||||
|
"version": {
|
||||||
|
"created": "7080099"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 创建索引--CreateIndex
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.CreateIndex(indexName, mapping)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
| mapping | string | 是 | 索引映射的JSON字符串 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 删除索引--DeleteIndex
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.DeleteIndex(indexName)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 获取索引文档数量--GetDocumentCount
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.GetDocumentCount(indexName)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 创建文档--CreateDocument
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.CreateDocument(indexName, id, doc)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
| id | string | 是 | 文档ID |
|
||||||
|
| doc | string | 是 | 文档内容的JSON字符串 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 根据ID获取文档--GetDocument
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.GetDocument(indexName, id)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
| id | string | 是 | 文档ID |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": {
|
||||||
|
"_id": "1",
|
||||||
|
"_index": "index1",
|
||||||
|
"_source": {
|
||||||
|
"title": "示例文档",
|
||||||
|
"content": "这是文档内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 更新文档--UpdateDocument
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.UpdateDocument(indexName, id, updateData)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
| id | string | 是 | 文档ID |
|
||||||
|
| updateData | string | 是 | 更新数据的JSON字符串 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 删除文档--DeleteDocument
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.DeleteDocument(indexName, id)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|---------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
| id | string | 是 | 文档ID |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. 搜索文档--SearchDocuments
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.SearchDocuments(indexName, query)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|--------------|
|
||||||
|
| indexName | string | 是 | 索引名称 |
|
||||||
|
| query | string | 是 | 查询条件的JSON字符串|
|
||||||
|
#### query示例
|
||||||
|
```gotemplate
|
||||||
|
query := map[string]interface{}{
|
||||||
|
"query": map[string]interface{}{
|
||||||
|
"term": map[string]interface{}{
|
||||||
|
fieldName: fieldValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"size": size,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"_id": "1",
|
||||||
|
"title": "示例文档",
|
||||||
|
"content": "这是文档内容"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "2",
|
||||||
|
"title": "另一个文档",
|
||||||
|
"content": "更多内容"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
152
md/kongfz.md
152
md/kongfz.md
@ -1567,17 +1567,165 @@ dll.OutGetTopGoodsListMsg(catId,proxy)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 12.获取配送方式列表--KongfzDeliveryMethodList
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.KongfzDeliveryMethodList(appId C.int, appSecret, accessToken *C.char)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--------|----|----------------------------|
|
||||||
|
| appId | int | 是 | 开放平台分配给应用的AppId |
|
||||||
|
| appSecret | string | 是 | App密钥 |
|
||||||
|
| accessToken | string | 否 | 用户登录授权成功后,开放平台颁发给应用的授权信息。 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "EGLJzOwutBCR9RDE",
|
||||||
|
"requestMethod": "kongfz.delivery.method.list",
|
||||||
|
"successResponse": [
|
||||||
|
{
|
||||||
|
"shippingId": "registerPost",
|
||||||
|
"shippingName": "挂号印刷品",
|
||||||
|
"isDefault": false,
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"shippingCom": "registeredPrint",
|
||||||
|
"shippingComName": "邮局",
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shippingId": "express",
|
||||||
|
"shippingName": "快递",
|
||||||
|
"isDefault": false,
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"shippingCom": "huitongkuaidi",
|
||||||
|
"shippingComName": "百世快递",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shippingCom": "yunda",
|
||||||
|
"shippingComName": "韵达快递",
|
||||||
|
"isDefault": false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shippingId": "generalParcel",
|
||||||
|
"shippingName": "普通包裹",
|
||||||
|
"isDefault": false,
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"shippingCom": "generalParcel",
|
||||||
|
"shippingComName": "邮局",
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shippingId": "ems",
|
||||||
|
"shippingName": "EMS",
|
||||||
|
"isDefault": false,
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"shippingCom": "ems",
|
||||||
|
"shippingComName": "邮局",
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shippingId": "noLogistics",
|
||||||
|
"shippingName": "无需物流",
|
||||||
|
"isDefault": false,
|
||||||
|
"companies": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errorResponse": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13.订单发货--KongfzOrderDeliver
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.KongfzOrderDeliver(appId, appSecret, accessToken ,
|
||||||
|
orderId , shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum )
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--------|----|-------------|
|
||||||
|
| appId | int | 是 | 开放平台分配给应用的AppId |
|
||||||
|
| appSecret | string | 是 | App密钥 |
|
||||||
|
| accessToken | string | 否 | 用户登录授权成功后,开放平台颁发给应用的授权信息 |
|
||||||
|
| orderId | int | 是 | 订单编号 |
|
||||||
|
| shippingId | string | 是 | 配送方式 |
|
||||||
|
| shippingCom | string | 否 | 快递公司。当shippingId!=noLogistics时,此参数为必填。取值参考kongfz.delivery.method.list接口的返回值 |
|
||||||
|
| shipmentNum | string | 否 | 快递单号。当shippingId!=noLogistics时,此参数为必填。 |
|
||||||
|
| userDefined | string | 否 | 用户自定义物流公司。当shippingCom=other时,此参数为必填。 |
|
||||||
|
| moreShipmentNum | string | 否 | 填写更多的快递单号,以逗号分隔。 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "bbXrGb2dOBRRDRBL",
|
||||||
|
"requestMethod": "kongfz.order.deliver",
|
||||||
|
"successResponse": {
|
||||||
|
"order": {
|
||||||
|
"orderId": 73609014,
|
||||||
|
"remark": "订单发货成功",
|
||||||
|
"updateTime": "2019-05-22 16:32:08"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errorResponse": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 14.孔网订单同步--KongfzOrderSynchronization
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
result, err := dll.KongfzOrderSynchronization(appId, appSecret, accessToken, shippingComName,
|
||||||
|
orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|----|-----------------------------------------------------------------------------|
|
||||||
|
| appId | int | 是 | 开放平台分配给应用的AppId |
|
||||||
|
| appSecret | string | 是 | App密钥 |
|
||||||
|
| accessToken | string | 否 | 用户登录授权成功后,开放平台颁发给应用的授权信息 |
|
||||||
|
| shippingComName | string | 是 | 快递名称 |
|
||||||
|
| orderId | int | 是 | 订单编号 |
|
||||||
|
| shippingId | string | 是 | 配送方式 |
|
||||||
|
| shippingCom | string | 否 | 快递公司。当shippingId!=noLogistics时,此参数为必填。取值参考kongfz.delivery.method.list接口的返回值 |
|
||||||
|
| shipmentNum | string | 否 | 快递单号。当shippingId!=noLogistics时,此参数为必填。 |
|
||||||
|
| userDefined | string | 否 | 用户自定义物流公司。当shippingCom=other时,此参数为必填。 |
|
||||||
|
| moreShipmentNum | string | 否 | 填写更多的快递单号,以逗号分隔。 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requestId": "bbXrGb2dOBRRDRBL",
|
||||||
|
"requestMethod": "kongfz.order.deliver",
|
||||||
|
"successResponse": {
|
||||||
|
"order": {
|
||||||
|
"orderId": 73609014,
|
||||||
|
"remark": "订单发货成功",
|
||||||
|
"updateTime": "2019-05-22 16:32:08"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errorResponse": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 12.初始化--Initialize(可以不调用)
|
## 12.初始化--Initialize(可以不调用)
|
||||||
### 请求信息
|
### 请求信息
|
||||||
```gotemplate
|
```gotemplate
|
||||||
result, err := dll.Initialize(configJSON)
|
result, err := dll.Initialize(configJSON)
|
||||||
```
|
```
|
||||||
### 请求参数
|
### 请求参数
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 |
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|--|--|--|----------------|
|
|--|--|--|----------------|
|
||||||
| configJSON | string | 是 | 配置信息的 JSON 字符串 |
|
| configJSON | string | 是 | 配置信息的 JSON 字符串 |
|
||||||
|
|
||||||
### 响应示例
|
### 响应示例
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
495
md/pdd.md
Normal file
495
md/pdd.md
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
# pdd.dll 使用教程
|
||||||
|
## 1.创建DLL工具实例
|
||||||
|
### 加载DLL文件
|
||||||
|
```gotemplate
|
||||||
|
// PddDLL 拼多多工具DLL结构
|
||||||
|
type pddDLL struct {
|
||||||
|
dll *syscall.DLL
|
||||||
|
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
|
||||||
|
freeCString *syscall.Proc // 释放C字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化pddDLL
|
||||||
|
func InitPddDLL() (*pddDLL, error) {
|
||||||
|
dllPath := filepath.Join("dll", "pdd.dll")
|
||||||
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
|
||||||
|
}
|
||||||
|
if dll, err := syscall.LoadDLL(dllPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
|
||||||
|
} else {
|
||||||
|
return &pddDLL{
|
||||||
|
dll: dll,
|
||||||
|
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
|
||||||
|
freeCString: dll.MustFindProc("FreeCString"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dll, err := InitPddDLL()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取C字符串
|
||||||
|
```gotemplate
|
||||||
|
// cStr 获取C字符串
|
||||||
|
func (m *pddDLL) cStr(p uintptr) string {
|
||||||
|
if p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
for i := uintptr(0); ; i++ {
|
||||||
|
c := *(*byte)(unsafe.Pointer(p + i))
|
||||||
|
if c == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if m.freeCString != nil {
|
||||||
|
m.freeCString.Call(p)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 使用dll函数示例
|
||||||
|
```gotemplate
|
||||||
|
// 类目预测
|
||||||
|
func (m *pddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
|
||||||
|
outerCatId, outerCatName, outerGoodsName string) (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("PddGoodsOuterCatMappingGet")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
|
||||||
|
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
|
||||||
|
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
|
||||||
|
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
|
||||||
|
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
|
||||||
|
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
|
||||||
|
|
||||||
|
resultPtr, _, _ := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(clientIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(clientSecretPtr)),
|
||||||
|
uintptr(unsafe.Pointer(accessTokenPtr)),
|
||||||
|
uintptr(unsafe.Pointer(outerCatIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(outerCatNamePtr)),
|
||||||
|
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
|
||||||
|
)
|
||||||
|
|
||||||
|
result := m.cStr(resultPtr)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 接口详情
|
||||||
|
## 1. 类目预测--PddGoodsOuterCatMappingGet
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
|
||||||
|
outerCatId, outerCatName, outerGoodsName)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| outerCatId | string | 是 | 外部平台类目ID |
|
||||||
|
| outerCatName | string | 是 | 外部平台类目名称 |
|
||||||
|
| outerGoodsName | string | 是 | 外部商品名称 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"outer_cat_mapping_get_response": {
|
||||||
|
"cat_id2": 16028,
|
||||||
|
"cat_id3": 16031,
|
||||||
|
"cat_id1": 15543,
|
||||||
|
"request_id": "17666480184871649",
|
||||||
|
"cat_id4": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 快递公司查看--PddLogisticsCompaniesGet
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddLogisticsCompaniesGet(clientId, clientSecret)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"logistics_companies_get_response": {
|
||||||
|
"logistics_companies": [
|
||||||
|
{
|
||||||
|
"available": 1,
|
||||||
|
"code": "SF",
|
||||||
|
"id": 1,
|
||||||
|
"logistics_company": "顺丰速运"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"available": 1,
|
||||||
|
"code": "STO",
|
||||||
|
"id": 2,
|
||||||
|
"logistics_company": "申通快递"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. erp打单信息同步--PddErpOrderSync
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| logisticsId | string | 是 | 物流公司ID |
|
||||||
|
| orderSn | string | 是 | 拼多多订单号 |
|
||||||
|
| orderState | string | 是 | 订单状态 |
|
||||||
|
| waybillNo | string | 是 | 运单号 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"erp_order_sync_response": {
|
||||||
|
"is_success": true,
|
||||||
|
"request_id": "17666480184871650"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 拼多多订单同步--PddOrderSynchronization
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| logisticsCompany | string | 是 | 物流公司名称 |
|
||||||
|
| logisticsId | string | 是 | 物流公司ID |
|
||||||
|
| orderSn | string | 是 | 拼多多订单号 |
|
||||||
|
| orderState | string | 是 | 订单状态 |
|
||||||
|
| waybillNo | string | 是 | 运单号 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"erp_order_sync_response": {
|
||||||
|
"is_success": true,
|
||||||
|
"request_id": "17666480184871651"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 商品图片上传接口--PddGoodsImgUpload
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddGoodsImgUpload(clientId, clientSecret, accessToken, filePath)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| filePath | string | 是 | 图片文件路径 |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"goods_img_upload_response": {
|
||||||
|
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
|
||||||
|
"request_id": "17666480184871652"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 商品新增接口--PddGoodsAdd
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| goodsAddJson | string | 是 | 商品信息JSON字符串 |
|
||||||
|
#### 商品信息JSON结构示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"goods_name": "测试商品",
|
||||||
|
"goods_desc": "商品描述",
|
||||||
|
"cat_id": 20111,
|
||||||
|
"goods_type": 1,
|
||||||
|
"market_price": 9900,
|
||||||
|
"is_folt": false,
|
||||||
|
"is_pre_sale": false,
|
||||||
|
"is_refundable": true,
|
||||||
|
"shipment_limit_second": 86400,
|
||||||
|
"cost_template_id": 10001,
|
||||||
|
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
|
||||||
|
"carousel_gallery": [
|
||||||
|
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
|
||||||
|
],
|
||||||
|
"detail_gallery": [
|
||||||
|
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
|
||||||
|
],
|
||||||
|
"sku_list": [
|
||||||
|
{
|
||||||
|
"out_sku_sn": "SKU001",
|
||||||
|
"price": 8900,
|
||||||
|
"quantity": 100,
|
||||||
|
"spec_id_list": "1001:10001",
|
||||||
|
"sku_properties": [
|
||||||
|
{
|
||||||
|
"ref_pid": 1001,
|
||||||
|
"value": "红色",
|
||||||
|
"vid": 10001,
|
||||||
|
"punit": "个"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_onsale": 1,
|
||||||
|
"limit_quantity": 10,
|
||||||
|
"multi_price": 8500,
|
||||||
|
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
|
||||||
|
"weight": 500
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"goods_add_response": {
|
||||||
|
"goods_id": 123456789,
|
||||||
|
"goods_name": "测试商品",
|
||||||
|
"goods_sn": "G202501200001",
|
||||||
|
"request_id": "17666480184871653"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 联合拼多多图片上传的商品新增--SelfPddGoodsAdd
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.SelfPddGoodsAdd(clientId, clientSecret, accessToken, filePath, goodsAddJson)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| filePath | string | 是 | 图片文件路径 |
|
||||||
|
| goodsAddJson | string | 是 | 商品信息JSON字符串(不需包含image_url)|
|
||||||
|
#### 接口说明
|
||||||
|
此接口为组合接口,内部执行以下步骤:
|
||||||
|
1.上传商品主图文件到拼多多服务器
|
||||||
|
2.获取图片URL并自动填充到商品信息中
|
||||||
|
3.调用商品新增接口创建商品
|
||||||
|
#### 商品信息JSON结构示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"goods_name": "测试商品",
|
||||||
|
"goods_desc": "商品描述",
|
||||||
|
"cat_id": 20111,
|
||||||
|
"goods_type": 1,
|
||||||
|
"market_price": 9900,
|
||||||
|
"is_folt": false,
|
||||||
|
"is_pre_sale": false,
|
||||||
|
"is_refundable": true,
|
||||||
|
"shipment_limit_second": 86400,
|
||||||
|
"cost_template_id": 10001,
|
||||||
|
"image_url": "",
|
||||||
|
"carousel_gallery": [
|
||||||
|
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
|
||||||
|
],
|
||||||
|
"detail_gallery": [
|
||||||
|
"http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg"
|
||||||
|
],
|
||||||
|
"sku_list": [
|
||||||
|
{
|
||||||
|
"out_sku_sn": "SKU001",
|
||||||
|
"price": 8900,
|
||||||
|
"quantity": 100,
|
||||||
|
"spec_id_list": "1001:10001",
|
||||||
|
"sku_properties": [
|
||||||
|
{
|
||||||
|
"ref_pid": 1001,
|
||||||
|
"value": "红色",
|
||||||
|
"vid": 10001,
|
||||||
|
"punit": "个"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_onsale": 1,
|
||||||
|
"limit_quantity": 10,
|
||||||
|
"multi_price": 8500,
|
||||||
|
"thumb_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
|
||||||
|
"weight": 500
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"goods_add_response": {
|
||||||
|
"goods_id": 123456790,
|
||||||
|
"goods_name": "测试商品",
|
||||||
|
"goods_sn": "G202501200002",
|
||||||
|
"request_id": "17666480184871654"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 批量数据解密脱敏接口--PddOpenDecryptMaskBatch
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|--|--|----------|
|
||||||
|
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||||
|
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||||
|
| accessToken | string | 是 | 授权令牌 |
|
||||||
|
| reqJson | string | 是 | 信息JSON字符串 |
|
||||||
|
#### 信息JSON结构示例
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"data_tag": "251229-272441044622514",
|
||||||
|
"encrypted_data": "~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"open_decrypt_mask_batch_response": {
|
||||||
|
"data_decrypt_list": [
|
||||||
|
{
|
||||||
|
"data_tag": "str",
|
||||||
|
"data_type": 0,
|
||||||
|
"decrypted_data": "str",
|
||||||
|
"encrypted_data": "str",
|
||||||
|
"error_code": 0,
|
||||||
|
"error_msg": "str"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_response": {
|
||||||
|
"error_msg": "公共参数错误:type",
|
||||||
|
"sub_msg": "",
|
||||||
|
"sub_code": null,
|
||||||
|
"error_code": 10001,
|
||||||
|
"request_id": "15440104776643887"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
655
md/xy.md
Normal file
655
md/xy.md
Normal file
@ -0,0 +1,655 @@
|
|||||||
|
# pdd.dll 使用教程
|
||||||
|
## 1.创建DLL工具实例
|
||||||
|
### 加载DLL文件
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 接口详情
|
||||||
|
## 1. 查询快递公司--ExecuteOpenExpressCompanies
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.ExecuteOpenExpressCompanies(appid, timestamp, sign)
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|---------|----|----------|
|
||||||
|
| appid | string | 是 | 开放平台的AppKey |
|
||||||
|
| timestamp | integer | 否 | 当前时间戳(单位秒,5分钟内有效) |
|
||||||
|
| sign | string | 是 | 签名MD5值(参考签名说明) |
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "OK",
|
||||||
|
"data": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"code": "shentong",
|
||||||
|
"express_alias": "申通",
|
||||||
|
"express_name": "申通快递",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yunda",
|
||||||
|
"express_alias": "韵达",
|
||||||
|
"express_name": "韵达快递",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "htky",
|
||||||
|
"express_alias": "极兔",
|
||||||
|
"express_name": "极兔-原百世快递",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "youzhengguonei",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "邮政快递包裹",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yuantong",
|
||||||
|
"express_alias": "圆通",
|
||||||
|
"express_name": "圆通速递",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "zhongtong",
|
||||||
|
"express_alias": "中通",
|
||||||
|
"express_name": "中通快递",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "zhaijisong",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "宅急送",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "tiantian",
|
||||||
|
"express_alias": "天天",
|
||||||
|
"express_name": "天天快递",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "shunfeng",
|
||||||
|
"express_alias": "顺丰",
|
||||||
|
"express_name": "顺丰速运",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ems",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "EMS",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "other",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "其他",
|
||||||
|
"is_hot": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "baishikuaidi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "百世快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "sxjdfreight",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "顺心捷达",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "wanjiawuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "万家物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "taijin",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "泰进物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "tcat",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "黑猫宅急便",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "tiandihuayu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "天地华宇",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "usps",
|
||||||
|
"express_alias": "USPS",
|
||||||
|
"express_name": "美国邮政",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yafengsudi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "亚风速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "sut56",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "速通物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "suning",
|
||||||
|
"express_alias": "苏宁",
|
||||||
|
"express_name": "苏宁快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "suer",
|
||||||
|
"express_alias": "速尔",
|
||||||
|
"express_name": "速尔快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "shenweizhaipei",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "神威宅配",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "shenghuiwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "盛辉物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "shangqiao56",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "商桥物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "rufengda",
|
||||||
|
"express_alias": "如风达",
|
||||||
|
"express_name": "如风达配送",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "rrs",
|
||||||
|
"express_alias": "日日顺",
|
||||||
|
"express_name": "日日顺物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "youzhengbk",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "邮政标准快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ztky",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "中铁快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "zhongtongkuaiyun",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "中通快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "zhongtiewuliu",
|
||||||
|
"express_alias": "中铁飞豹",
|
||||||
|
"express_name": "中铁物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "zengyisudi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "增益速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yundatongcheng",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "韵达同城",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yundakuaiyun",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "韵达快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yujiawl",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "山东宇佳物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yuantongcainiancang",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "圆通菜鸟仓",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yuanshuochengnuoda",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "圆硕承诺达特快",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "wanxiangwuliu",
|
||||||
|
"express_alias": "万象物流",
|
||||||
|
"express_name": "A1万象物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "youxinwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "优信物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "youshuwuliu",
|
||||||
|
"express_alias": "优速",
|
||||||
|
"express_name": "优速快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "yimidida",
|
||||||
|
"express_alias": "壹米滴答",
|
||||||
|
"express_name": "壹米滴答快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ycgky",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "远成快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "post",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "中国邮政",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "xinzebangwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "鑫泽邦物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "xinfengwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "信丰物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "xinbangwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "新邦物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "debangwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "德邦物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "huayuwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "重庆华宇物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "haoyaoshizijian",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "好药师自建物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "guotongkuaidi",
|
||||||
|
"express_alias": "国通",
|
||||||
|
"express_name": "国通快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ganzhongnengda",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "能达速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "fushisudi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "服饰速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "fengwang",
|
||||||
|
"express_alias": "丰网",
|
||||||
|
"express_name": "丰网速运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "exfresh",
|
||||||
|
"express_alias": "安鲜达",
|
||||||
|
"express_name": "安鲜达快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "esb",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "E速宝",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "dsukuaidi",
|
||||||
|
"express_alias": "D速快递",
|
||||||
|
"express_name": "D速物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "diandiansong",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "点点送",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jd",
|
||||||
|
"express_alias": "京东",
|
||||||
|
"express_name": "京东物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "debangkuaidi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "德邦快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "cszx",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "城市之星",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "canpostfr",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "加拿大邮政",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "cainiaodj-woaijia",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "菜鸟大件-沃埃家",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "baishiyp",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "百世云配",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "baishikuaiyun",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "百世快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "astexpress",
|
||||||
|
"express_alias": "安世通",
|
||||||
|
"express_name": "安世通国际快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "anxl",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "安迅物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "annto",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "安得物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiuyescm",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "九曳鲜配",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "quanfengkuaidi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "全峰快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "annengwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "安能物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "pingandatengfei",
|
||||||
|
"express_alias": "平安达腾飞",
|
||||||
|
"express_name": "平安达腾飞快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "menduimen",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "门对门",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "linshiwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "林氏物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "lianhaowuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "联昊通",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "lianbangkuaidi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "联邦快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "kuayue",
|
||||||
|
"express_alias": "跨越",
|
||||||
|
"express_name": "跨越速运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "kahangtianxia",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "卡行天下",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jtexpress",
|
||||||
|
"express_alias": "极兔",
|
||||||
|
"express_name": "极兔速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "quanyikuaidi",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "全一快递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jinguangsudikuaijian",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "京广速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jindouyunjiaoche",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "筋斗云轿车物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiazhuang-zhengjia",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "家装-正佳",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiazhuang-zhelian",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "家装-浙联",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiazhuang-sfc",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "家装-顺风车",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiayunmeiwuliu",
|
||||||
|
"express_alias": "加运美",
|
||||||
|
"express_name": "加运美速递",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiayiwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "佳怡物流",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jiajiwuliu",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "佳吉快运",
|
||||||
|
"is_hot": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "jgwl",
|
||||||
|
"express_alias": "",
|
||||||
|
"express_name": "景光物流",
|
||||||
|
"is_hot": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 订单物流发货--ExecuteOpenOrderShip
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.ExecuteOpenOrderShip(json)
|
||||||
|
```
|
||||||
|
### json样式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"order_no": "1339920336328048683",
|
||||||
|
"ship_name": "张三",
|
||||||
|
"ship_mobile": "13800138000",
|
||||||
|
"ship_district_id": 440305,
|
||||||
|
"ship_prov_name": "广东省",
|
||||||
|
"ship_city_name": "深圳市",
|
||||||
|
"ship_area_name": "南山区",
|
||||||
|
"ship_address": "侨香路西丽街道丰泽园仓储中心",
|
||||||
|
"waybill_no": "25051016899982",
|
||||||
|
"express_name": "其他",
|
||||||
|
"express_code": "qita"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|---------|----|-------------------|
|
||||||
|
| appid | string | 是 | 开放平台的AppKey |
|
||||||
|
| timestamp | integer | 否 | 当前时间戳(单位秒,5分钟内有效) |
|
||||||
|
| seller_id | integer | 否 | 商家ID |
|
||||||
|
| sign | string | 是 | 签名MD5值(参考签名说明) |
|
||||||
|
| order_no | string | 是 | 闲鱼订单号 |
|
||||||
|
| ship_name | string | 否 | 寄件方姓名 |
|
||||||
|
| ship_mobile | string | 否 | 寄件方号码 |
|
||||||
|
| ship_district_id | int | 否 | 寄件方所在地区ID |
|
||||||
|
| ship_prov_name | string | 否 | 寄件方所在省份 |
|
||||||
|
| ship_city_name | string | 否 | 寄件方所在城市 |
|
||||||
|
| ship_area_name | string | 否 | 寄件方所在地区 |
|
||||||
|
| ship_address | string | 否 | 寄件方详细地址 |
|
||||||
|
| waybill_no | string | 否 | 快递单号 |
|
||||||
|
| express_code | string | 否 | 快递公司代码 |
|
||||||
|
| express_name | string | 否 | 快递公司名称 |
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 闲鱼订单同步--ExecuteXyOrderSynchronization
|
||||||
|
### 请求信息
|
||||||
|
```gotemplate
|
||||||
|
dll.ExecuteXyOrderSynchronization(json)
|
||||||
|
```
|
||||||
|
### json样式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"order_no": "1339920336328048683",
|
||||||
|
"ship_name": "张三",
|
||||||
|
"ship_mobile": "13800138000",
|
||||||
|
"ship_district_id": 440305,
|
||||||
|
"ship_prov_name": "广东省",
|
||||||
|
"ship_city_name": "深圳市",
|
||||||
|
"ship_area_name": "南山区",
|
||||||
|
"ship_address": "侨香路西丽街道丰泽园仓储中心",
|
||||||
|
"waybill_no": "25051016899982",
|
||||||
|
"express_name": "其他",
|
||||||
|
"express_code": "qita"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 请求参数
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--|---------|----|-------------------|
|
||||||
|
| appid | string | 是 | 开放平台的AppKey |
|
||||||
|
| timestamp | integer | 否 | 当前时间戳(单位秒,5分钟内有效) |
|
||||||
|
| seller_id | integer | 否 | 商家ID |
|
||||||
|
| sign | string | 是 | 签名MD5值(参考签名说明) |
|
||||||
|
| order_no | string | 是 | 闲鱼订单号 |
|
||||||
|
| ship_name | string | 否 | 寄件方姓名 |
|
||||||
|
| ship_mobile | string | 否 | 寄件方号码 |
|
||||||
|
| ship_district_id | int | 否 | 寄件方所在地区ID |
|
||||||
|
| ship_prov_name | string | 否 | 寄件方所在省份 |
|
||||||
|
| ship_city_name | string | 否 | 寄件方所在城市 |
|
||||||
|
| ship_area_name | string | 否 | 寄件方所在地区 |
|
||||||
|
| ship_address | string | 否 | 寄件方详细地址 |
|
||||||
|
| waybill_no | string | 否 | 快递单号 |
|
||||||
|
| express_code | string | 否 | 快递公司代码 |
|
||||||
|
| express_name | string | 否 | 快递公司名称 |
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
pdd/dll/pdd.dll
Normal file
BIN
pdd/dll/pdd.dll
Normal file
Binary file not shown.
125
pdd/dll/pdd.h
Normal file
125
pdd/dll/pdd.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||||
|
|
||||||
|
/* package command-line-arguments */
|
||||||
|
|
||||||
|
|
||||||
|
#line 1 "cgo-builtin-export-prolog"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||||
|
extern size_t _GoStringLen(_GoString_ s);
|
||||||
|
extern const char *_GoStringPtr(_GoString_ s);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Start of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
#line 3 "pdd.go"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#line 1 "cgo-generated-wrapper"
|
||||||
|
|
||||||
|
|
||||||
|
/* End of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
/* Start of boilerplate cgo prologue. */
|
||||||
|
#line 1 "cgo-gcc-export-header-prolog"
|
||||||
|
|
||||||
|
#ifndef GO_CGO_PROLOGUE_H
|
||||||
|
#define GO_CGO_PROLOGUE_H
|
||||||
|
|
||||||
|
typedef signed char GoInt8;
|
||||||
|
typedef unsigned char GoUint8;
|
||||||
|
typedef short GoInt16;
|
||||||
|
typedef unsigned short GoUint16;
|
||||||
|
typedef int GoInt32;
|
||||||
|
typedef unsigned int GoUint32;
|
||||||
|
typedef long long GoInt64;
|
||||||
|
typedef unsigned long long GoUint64;
|
||||||
|
typedef GoInt64 GoInt;
|
||||||
|
typedef GoUint64 GoUint;
|
||||||
|
typedef size_t GoUintptr;
|
||||||
|
typedef float GoFloat32;
|
||||||
|
typedef double GoFloat64;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||||
|
#include <complex.h>
|
||||||
|
typedef _Fcomplex GoComplex64;
|
||||||
|
typedef _Dcomplex GoComplex128;
|
||||||
|
#else
|
||||||
|
#include <complex>
|
||||||
|
typedef std::complex<float> GoComplex64;
|
||||||
|
typedef std::complex<double> GoComplex128;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
typedef float _Complex GoComplex64;
|
||||||
|
typedef double _Complex GoComplex128;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
static assertion to make sure the file is being used on architecture
|
||||||
|
at least with matching size of GoInt.
|
||||||
|
*/
|
||||||
|
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef _GoString_ GoString;
|
||||||
|
#endif
|
||||||
|
typedef void *GoMap;
|
||||||
|
typedef void *GoChan;
|
||||||
|
typedef struct { void *t; void *v; } GoInterface;
|
||||||
|
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* End of boilerplate cgo prologue. */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// PddGoodsOuterCatMappingGet 类目预测
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* PddGoodsOuterCatMappingGet(char* clientId, char* clientSecret, char* accessToken, char* outerCatId, char* outerCatName, char* outerGoodsName);
|
||||||
|
|
||||||
|
// PddLogisticsCompaniesGet 快递公司查看
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* PddLogisticsCompaniesGet(char* clientId, char* clientSecret);
|
||||||
|
|
||||||
|
// PddErpOrderSync erp打单信息同步
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* PddErpOrderSync(char* clientId, char* clientSecret, char* accessToken, char* logisticsId, char* orderSn, char* orderState, char* waybillNo);
|
||||||
|
|
||||||
|
// PddOrderSynchronization 拼多多订单同步
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* PddOrderSynchronization(char* clientId, char* clientSecret, char* accessToken, char* logisticsCompany, char* logisticsId, char* orderSn, char* orderState, char* waybillNo);
|
||||||
|
|
||||||
|
// PddGoodsImgUpload 商品图片上传接口
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* PddGoodsImgUpload(char* clientId, char* clientSecret, char* accessToken, char* filePath);
|
||||||
|
|
||||||
|
// PddGoodsAdd 商品新增接口
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* PddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* goodsAddJson);
|
||||||
|
|
||||||
|
// SelfPddGoodsAdd 联合拼多多图片上传的商品新增
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) char* SelfPddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* filePath, char* goodsAddJson);
|
||||||
|
|
||||||
|
// 释放C字符串内存
|
||||||
|
//
|
||||||
|
extern __declspec(dllexport) void FreeCString(char* str);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
886
pdd/pdd.go
Normal file
886
pdd/pdd.go
Normal file
@ -0,0 +1,886 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdlib.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/parnurzeal/gorequest"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorResponse 错误响应结构
|
||||||
|
type ErrorResponse struct {
|
||||||
|
ErrorMsg string `json:"error_msg"` // 错误信息
|
||||||
|
SubMsg string `json:"sub_msg"` // 子错误信息
|
||||||
|
SubCode interface{} `json:"sub_code"` // 使用json.RawMessage处理null和不同类型
|
||||||
|
ErrorCode int `json:"error_code"` // 错误代码
|
||||||
|
RequestID string `json:"request_id"` // 请求ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorWrapper 最外层错误响应包装
|
||||||
|
type ErrorWrapper struct {
|
||||||
|
ErrorResponse ErrorResponse `json:"error_response"` // 错误响应
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateSign 生成签名
|
||||||
|
func generateSign(params map[string]interface{}, clientSecret string) string {
|
||||||
|
// 获取所有键并排序
|
||||||
|
keys := make([]string, 0, len(params))
|
||||||
|
for k := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
// 按键名升序排序
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
// 拼接签名字符串
|
||||||
|
var signStr strings.Builder
|
||||||
|
for _, k := range keys {
|
||||||
|
// 跳过 sign 参数(签名本身不参与签名)
|
||||||
|
if strings.ToLower(k) == "sign" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value := ""
|
||||||
|
if params[k] != nil {
|
||||||
|
value = fmt.Sprintf("%v", params[k]) // 将任意值转为字符串
|
||||||
|
}
|
||||||
|
// 按照文档格式:参数名+参数值
|
||||||
|
signStr.WriteString(k + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
signString := signStr.String()
|
||||||
|
|
||||||
|
// 拼接client_secret: client_secret + 参数串 + client_secret
|
||||||
|
data := clientSecret + signString + clientSecret
|
||||||
|
|
||||||
|
// MD5加密并转大写
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write([]byte(data))
|
||||||
|
return strings.ToUpper(hex.EncodeToString(hasher.Sum(nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类目预测
|
||||||
|
func pddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
|
||||||
|
outerCatId, outerCatName, outerGoodsName string) (string, error) {
|
||||||
|
// API地址
|
||||||
|
url := fmt.Sprint("https://gw-api.pinduoduo.com/api/router")
|
||||||
|
// 当前时间戳
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
|
||||||
|
// 生成签名参数
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"type": "pdd.goods.outer.cat.mapping.get", // API类型
|
||||||
|
"data_type": "JSON", // 数据类型
|
||||||
|
"client_id": clientId, // 客户端ID
|
||||||
|
"access_token": accessToken, // 访问令牌
|
||||||
|
"outer_cat_id": outerCatId, // 外部类目ID
|
||||||
|
"outer_cat_name": outerCatName, // 外部类目名称
|
||||||
|
"outer_goods_name": outerGoodsName, // 外部商品名称
|
||||||
|
"timestamp": timestamp, // 时间戳
|
||||||
|
}
|
||||||
|
// 生成签名
|
||||||
|
sign := generateSign(params, clientSecret)
|
||||||
|
// 请求体
|
||||||
|
formData := map[string]interface{}{
|
||||||
|
"type": "pdd.goods.outer.cat.mapping.get",
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"access_token": accessToken,
|
||||||
|
"outer_cat_id": outerCatId,
|
||||||
|
"outer_cat_name": outerCatName,
|
||||||
|
"outer_goods_name": outerGoodsName,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"sign": sign, // 签名
|
||||||
|
}
|
||||||
|
request := gorequest.New()
|
||||||
|
resp, body, errs := request.Get(url).
|
||||||
|
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
Set("Accept", "application/json, text/plain, */*").
|
||||||
|
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
Timeout(30 * time.Second). // 30秒超时
|
||||||
|
Send(formData). // 发送表单数据
|
||||||
|
End() // 结束请求
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return "", fmt.Errorf("请求失败: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(body), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
// 转换成json字符串
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("JSON序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
return string(responseJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快递公司查看
|
||||||
|
func pddLogisticsCompaniesGet(clientId, clientSecret string) (string, error) {
|
||||||
|
url := fmt.Sprint("https://gw-api.pinduoduo.com/api/router")
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
// 生成签名参数
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"type": "pdd.logistics.companies.get", // API类型:获取物流公司
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
sign := generateSign(params, clientSecret)
|
||||||
|
// 请求体
|
||||||
|
formData := map[string]interface{}{
|
||||||
|
"type": "pdd.logistics.companies.get",
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"sign": sign,
|
||||||
|
}
|
||||||
|
request := gorequest.New()
|
||||||
|
resp, body, errs := request.Get(url).
|
||||||
|
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
Set("Accept", "application/json, text/plain, */*").
|
||||||
|
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
Timeout(30 * time.Second).
|
||||||
|
Send(formData).
|
||||||
|
End()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return "", fmt.Errorf("请求失败: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(body), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常处理:检查是否有错误响应
|
||||||
|
if response["error_response"] != nil {
|
||||||
|
var errorWrapper ErrorWrapper
|
||||||
|
// 解析响应
|
||||||
|
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
|
||||||
|
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换成json字符串
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("JSON序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(responseJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// erp打单信息同步
|
||||||
|
func pddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo string) (string, error) {
|
||||||
|
url := fmt.Sprint("https://gw-api.pinduoduo.com/api/router")
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
// 生成签名
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"type": "pdd.erp.order.sync", // API类型:ERP订单同步
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"access_token": accessToken,
|
||||||
|
"logistics_id": logisticsId, // 物流公司ID
|
||||||
|
"order_sn": orderSn, // 订单号
|
||||||
|
"order_state": orderState, // 订单状态
|
||||||
|
"waybill_no": waybillNo, // 运单号
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
sign := generateSign(params, clientSecret)
|
||||||
|
// 请求体
|
||||||
|
formData := map[string]interface{}{
|
||||||
|
"type": "pdd.erp.order.sync",
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"access_token": accessToken,
|
||||||
|
"logistics_id": logisticsId,
|
||||||
|
"order_sn": orderSn,
|
||||||
|
"order_state": orderState,
|
||||||
|
"waybill_no": waybillNo,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"sign": sign,
|
||||||
|
}
|
||||||
|
request := gorequest.New()
|
||||||
|
resp, body, errs := request.Get(url).
|
||||||
|
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
Set("Accept", "application/json, text/plain, */*").
|
||||||
|
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
Timeout(30 * time.Second).
|
||||||
|
Send(formData).
|
||||||
|
End()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return "", fmt.Errorf("请求失败: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(body), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常处理
|
||||||
|
if response["error_response"] != nil {
|
||||||
|
var errorWrapper ErrorWrapper
|
||||||
|
// 解析响应
|
||||||
|
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
|
||||||
|
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换成json字符串
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("JSON序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
return string(responseJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogisticsCompany 物流公司信息结构体
|
||||||
|
type LogisticsCompany struct {
|
||||||
|
Available int `json:"available"` // 是否可用
|
||||||
|
Code string `json:"code"` // 物流公司代码
|
||||||
|
ID int `json:"id"` // 物流公司ID
|
||||||
|
LogisticsCompany string `json:"logistics_company"` // 物流公司名称
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogisticsCompaniesGetResponse 物流公司列表响应结构体
|
||||||
|
type LogisticsCompaniesGetResponse struct {
|
||||||
|
LogisticsCompanies []LogisticsCompany `json:"logistics_companies"` // 物流公司列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogisticsResponse 最外层响应结构体
|
||||||
|
type LogisticsResponse struct {
|
||||||
|
LogisticsCompaniesGetResponse LogisticsCompaniesGetResponse `json:"logistics_companies_get_response"` // 物流公司响应
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼多多订单同步(组合接口:先查物流公司,再同步订单)
|
||||||
|
func pddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo string) (string, error) {
|
||||||
|
// 1. 获取物流公司列表
|
||||||
|
logisticsCompaniesGet, err := pddLogisticsCompaniesGet(clientId, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// 2. 解析物流公司响应
|
||||||
|
var response LogisticsResponse
|
||||||
|
if err := json.Unmarshal([]byte(logisticsCompaniesGet), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析JSON失败: %v", err)
|
||||||
|
}
|
||||||
|
// 3. 根据物流公司名称查找对应的物流公司ID
|
||||||
|
for _, lc := range response.LogisticsCompaniesGetResponse.LogisticsCompanies {
|
||||||
|
if lc.LogisticsCompany == logisticsCompany {
|
||||||
|
logisticsId = fmt.Sprintf("%d", lc.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 4. 执行ERP订单同步
|
||||||
|
erpOrderSync, err := pddErpOrderSync(clientId, clientSecret, accessToken,
|
||||||
|
logisticsId, orderSn, orderState, waybillNo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return erpOrderSync, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品图片上传接口
|
||||||
|
func pddGoodsImgUpload(clientId, clientSecret, accessToken string, filePath string) (string, error) {
|
||||||
|
url := fmt.Sprint("https://gw-upload.pinduoduo.com/api/upload") // 上传专用地址
|
||||||
|
|
||||||
|
// 1. 打开文件
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("打开文件失败: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close() // 确保文件关闭
|
||||||
|
|
||||||
|
// 当前时间戳
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
// 生成签名参数
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"type": "pdd.goods.img.upload", // API类型:商品图片上传
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"access_token": accessToken,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := generateSign(params, clientSecret)
|
||||||
|
|
||||||
|
// 创建multipart/form-data请求体
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
// 读取文件内容
|
||||||
|
fileData, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("读取文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文件字段
|
||||||
|
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("创建表单文件失败: %v", err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(part, bytes.NewReader(fileData))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("复制文件内容失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加其他参数到multipart表单
|
||||||
|
for key, value := range params {
|
||||||
|
writer.WriteField(key, fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
writer.WriteField("sign", sign) // 添加签名
|
||||||
|
writer.Close() // 关闭writer,完成表单构建
|
||||||
|
|
||||||
|
// 发送POST请求
|
||||||
|
request := gorequest.New()
|
||||||
|
resp, respBody, errs := request.Post(url).
|
||||||
|
Timeout(30*time.Second).
|
||||||
|
Set("Content-Type", writer.FormDataContentType()). // 设置Content-Type
|
||||||
|
Send(body.String()). // 发送请求体
|
||||||
|
End()
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return "", fmt.Errorf("请求失败: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(respBody), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常处理
|
||||||
|
if response["error_response"] != nil {
|
||||||
|
var errorWrapper ErrorWrapper
|
||||||
|
// 解析响应
|
||||||
|
if err := json.Unmarshal([]byte(respBody), &errorWrapper); err != nil {
|
||||||
|
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换成json字符串
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("JSON序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
return string(responseJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoodsAddRequest 商品添加/编辑请求结构体
|
||||||
|
type GoodsAddRequest struct {
|
||||||
|
AutoFillSpuProperty bool `json:"auto_fill_spu_property,omitempty"` // 是否自动补充标品属性
|
||||||
|
BadFruitClaim int `json:"bad_fruit_claim,omitempty"` // 坏果包赔
|
||||||
|
BuyLimit int64 `json:"buy_limit,omitempty"` // 限购次数
|
||||||
|
CarouselGallery []string `json:"carousel_gallery"` // 商品轮播图
|
||||||
|
CarouselVideo []CarouselVideo `json:"carousel_video,omitempty"` // 商品视频
|
||||||
|
CarouselVideoURL string `json:"carousel_video_url,omitempty"` // 轮播视频
|
||||||
|
CatID int64 `json:"cat_id"` // 叶子类目ID
|
||||||
|
CostTemplateID int64 `json:"cost_template_id"` // 物流运费模板ID
|
||||||
|
CountryID int `json:"country_id"` // 地区/国家ID
|
||||||
|
CustomerNum int64 `json:"customer_num,omitempty"` // 团购人数
|
||||||
|
Customs string `json:"customs,omitempty"` // 海关名称
|
||||||
|
DeliveryOneDay int `json:"delivery_one_day,omitempty"` // 是否当日发货
|
||||||
|
DeliveryType int `json:"delivery_type,omitempty"` // 发货方式
|
||||||
|
DetailGallery []string `json:"detail_gallery"` // 商品详情图
|
||||||
|
ElecGoodsAttributes ElecGoodsAttributes `json:"elec_goods_attributes,omitempty"` // 卡券类商品属性
|
||||||
|
GoodsDesc string `json:"goods_desc,omitempty"` // 商品描述
|
||||||
|
GoodsName string `json:"goods_name"` // 商品标题
|
||||||
|
GoodsProperties []GoodsProperty `json:"goods_properties,omitempty"` // 商品属性列表
|
||||||
|
GoodsTradeAttr GoodsTradeAttr `json:"goods_trade_attr,omitempty"` // 日历商品交易相关信息
|
||||||
|
GoodsTravelAttr GoodsTravelAttr `json:"goods_travel_attr,omitempty"` // 商品出行信息
|
||||||
|
GoodsType int `json:"goods_type"` // 商品类型
|
||||||
|
IgnoreEditWarn bool `json:"ignore_edit_warn,omitempty"` // 是否获取商品发布警告信息
|
||||||
|
ImageURL string `json:"image_url,omitempty"` // 商品主图
|
||||||
|
InvoiceStatus bool `json:"invoice_status,omitempty"` // 是否支持开票
|
||||||
|
IsCustoms bool `json:"is_customs,omitempty"` // 是否需要上报海关
|
||||||
|
IsFolt bool `json:"is_folt"` // 是否支持假一赔十
|
||||||
|
IsGroupPreSale int `json:"is_group_pre_sale,omitempty"` // 是否成团预售
|
||||||
|
IsPreSale bool `json:"is_pre_sale"` // 是否预售
|
||||||
|
IsRefundable bool `json:"is_refundable"` // 是否7天无理由退换货
|
||||||
|
IsSkuPreSale int `json:"is_sku_pre_sale,omitempty"` // 是否sku预售
|
||||||
|
LackOfWeightClaim int `json:"lack_of_weight_claim,omitempty"` // 缺重包退
|
||||||
|
LocalServiceIDList []int `json:"local_service_id_list,omitempty"` // 本地服务id
|
||||||
|
MaiJiaZiTi string `json:"mai_jia_zi_ti,omitempty"` // 买家自提模版id
|
||||||
|
MarketPrice int64 `json:"market_price"` // 参考价格(分)
|
||||||
|
OrderLimit int `json:"order_limit,omitempty"` // 单次限量
|
||||||
|
OriginCountryID int `json:"origin_country_id,omitempty"` // 原产地id
|
||||||
|
OutGoodsID string `json:"out_goods_id,omitempty"` // 商品外部编码
|
||||||
|
OutSourceGoodsID string `json:"out_source_goods_id,omitempty"` // 第三方商品Id
|
||||||
|
OutSourceType int `json:"out_source_type,omitempty"` // 第三方商品来源
|
||||||
|
OverseaGoods OverseaGoods `json:"oversea_goods,omitempty"` // 海淘商品信息
|
||||||
|
OverseaType int `json:"oversea_type,omitempty"` // oversea_type
|
||||||
|
PreSaleTime int64 `json:"pre_sale_time,omitempty"` // 预售时间
|
||||||
|
PrivacyDelivery int `json:"privacy_delivery,omitempty"` // 保密发货
|
||||||
|
QuanGuoLianBao int `json:"quan_guo_lian_bao,omitempty"` // 是否支持全国联保
|
||||||
|
SecondHand bool `json:"second_hand"` // 是否二手商品
|
||||||
|
ShangMenAnZhuang string `json:"shang_men_an_zhuang,omitempty"` // 上门安装模版id
|
||||||
|
ShipmentLimitSecond int64 `json:"shipment_limit_second"` // 承诺发货时间(秒)
|
||||||
|
ShopGroupID int64 `json:"shop_group_id,omitempty"` // 门店组id
|
||||||
|
SizeSpecID int64 `json:"size_spec_id,omitempty"` // 尺码表id
|
||||||
|
SkuList []Sku `json:"sku_list"` // sku对象列表
|
||||||
|
SkuType int `json:"sku_type,omitempty"` // 库存方式
|
||||||
|
SongHuoAnZhuang string `json:"song_huo_an_zhuang,omitempty"` // 送货入户并安装模版id
|
||||||
|
SongHuoRuHu string `json:"song_huo_ru_hu,omitempty"` // 送货入户模版id
|
||||||
|
TinyName string `json:"tiny_name,omitempty"` // 短标题
|
||||||
|
TwoPiecesDiscount int `json:"two_pieces_discount,omitempty"` // 满2件折扣
|
||||||
|
Warehouse string `json:"warehouse,omitempty"` // 保税仓
|
||||||
|
WarmTips string `json:"warm_tips,omitempty"` // 水果类目温馨提示
|
||||||
|
ZhiHuanBuXiu int `json:"zhi_huan_bu_xiu,omitempty"` // 只换不修的天数
|
||||||
|
}
|
||||||
|
|
||||||
|
// CarouselVideo 轮播视频
|
||||||
|
type CarouselVideo struct {
|
||||||
|
FileID int64 `json:"file_id,omitempty"` // 商品视频id
|
||||||
|
VideoURL string `json:"video_url,omitempty"` // 商品视频url
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElecGoodsAttributes 卡券类商品属性
|
||||||
|
type ElecGoodsAttributes struct {
|
||||||
|
BeginTime int64 `json:"begin_time,omitempty"` // 开始时间
|
||||||
|
DaysTime int `json:"days_time,omitempty"` // 天数内有效
|
||||||
|
EndTime int64 `json:"end_time,omitempty"` // 截止时间
|
||||||
|
TimeType int `json:"time_type,omitempty"` // 卡券核销类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoodsProperty 商品属性
|
||||||
|
type GoodsProperty struct {
|
||||||
|
GroupID int `json:"group_id,omitempty"` // 属性值分组ID
|
||||||
|
ImgURL string `json:"img_url,omitempty"` // 图片url
|
||||||
|
Note string `json:"note,omitempty"` // 备注
|
||||||
|
ParentSpecID int64 `json:"parent_spec_id,omitempty"` // 父规格ID
|
||||||
|
RefPid int64 `json:"ref_pid,omitempty"` // 引用属性id
|
||||||
|
SpecID int64 `json:"spec_id,omitempty"` // 规格ID
|
||||||
|
TemplatePid int64 `json:"template_pid,omitempty"` // 模板属性id
|
||||||
|
Value string `json:"value,omitempty"` // 属性值
|
||||||
|
ValueUnit string `json:"value_unit,omitempty"` // 属性单位
|
||||||
|
Vid int64 `json:"vid,omitempty"` // 属性值id
|
||||||
|
//TableInfo []TableValueList `json:"table_info,omitempty"` // 成分表表单信息
|
||||||
|
}
|
||||||
|
|
||||||
|
//// TableValueList 表单内容列表
|
||||||
|
//type TableValueList struct {
|
||||||
|
// ColumnType int `json:"column_type,omitempty"` // 列类型 1-材质成分百分比
|
||||||
|
// Unit string `json:"unit,omitempty"` // 表单单位,材质成分表时为:%
|
||||||
|
// Value string `json:"value,omitempty"` // 表单值,材质成分表时为:占比百分值
|
||||||
|
//}
|
||||||
|
|
||||||
|
// GoodsTradeAttr 日历商品交易相关信息
|
||||||
|
type GoodsTradeAttr struct {
|
||||||
|
AdvancesDays int `json:"advances_days,omitempty"` // 提前预定天数
|
||||||
|
BookingNotes BookingNotes `json:"booking_notes,omitempty"` // 预订须知
|
||||||
|
LifeSpan int `json:"life_span,omitempty"` // 卡券有效期
|
||||||
|
}
|
||||||
|
|
||||||
|
// BookingNotes 预订须知
|
||||||
|
type BookingNotes struct {
|
||||||
|
URL *string `json:"url,omitempty"` // 预定须知图片地址
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoodsTravelAttr 商品出行信息
|
||||||
|
type GoodsTravelAttr struct {
|
||||||
|
NeedTourist bool `json:"need_tourist,omitempty"` // 出行人是否必填
|
||||||
|
Type int `json:"type,omitempty"` // 日历商品类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverseaGoods 海淘商品信息
|
||||||
|
type OverseaGoods struct {
|
||||||
|
BondedWarehouseKey string `json:"bonded_warehouse_key"` // 保税仓唯一标识
|
||||||
|
ConsumptionTaxRate int `json:"consumption_tax_rate,omitempty"` // 消费税率
|
||||||
|
CustomsBroker string `json:"customs_broker,omitempty"` // 清关服务商
|
||||||
|
HsCode string `json:"hs_code,omitempty"` // 海关编号
|
||||||
|
ValueAddedTaxRate int `json:"value_added_tax_rate,omitempty"` // 增值税率
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sku SKU信息
|
||||||
|
type Sku struct {
|
||||||
|
IsOnsale int `json:"is_onsale"` // sku上架状态
|
||||||
|
Length int64 `json:"length,omitempty"` // sku送装参数:长度
|
||||||
|
LimitQuantity int64 `json:"limit_quantity"` // sku购买限制
|
||||||
|
MultiPrice int64 `json:"multi_price"` // 商品团购价格
|
||||||
|
OutSkuSn string `json:"out_sku_sn,omitempty"` // 商品sku外部编码
|
||||||
|
OutSourceSkuID string `json:"out_source_sku_id,omitempty"` // 第三方sku Id
|
||||||
|
OverseaSku OverseaSku `json:"oversea_sku,omitempty"` // 海淘sku信息
|
||||||
|
Price int64 `json:"price"` // 商品单买价格
|
||||||
|
Quantity int64 `json:"quantity"` // 商品sku库存数量
|
||||||
|
SkuPreSaleTime int `json:"sku_pre_sale_time,omitempty"` // sku预售时间戳
|
||||||
|
SkuProperties []SkuProperty `json:"sku_properties"` // sku属性
|
||||||
|
SpecIDList string `json:"spec_id_list"` // 商品规格列表
|
||||||
|
ThumbURL string `json:"thumb_url"` // sku缩略图
|
||||||
|
Weight int64 `json:"weight"` // 重量(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverseaSku 海淘SKU信息
|
||||||
|
type OverseaSku struct {
|
||||||
|
MeasurementCode string `json:"measurement_code"` // 计量单位编码
|
||||||
|
Specifications string `json:"specifications"` // 规格
|
||||||
|
Taxation int `json:"taxation"` // 税费
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkuProperty SKU属性
|
||||||
|
type SkuProperty struct {
|
||||||
|
Punit string `json:"punit"` // 属性单位
|
||||||
|
RefPid int64 `json:"ref_pid"` // 属性id
|
||||||
|
Value string `json:"value"` // 属性值
|
||||||
|
Vid int64 `json:"vid"` // 属性值id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品新增接口
|
||||||
|
func pddGoodsAdd(clientId, clientSecret, accessToken string, goodsAddJson string) (string, error) {
|
||||||
|
url := fmt.Sprint("http://gw-api.pinduoduo.com/api/router")
|
||||||
|
|
||||||
|
// 当前时间戳
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
// 生成签名参数
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"type": "pdd.goods.add", // API类型:商品新增接口
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"access_token": accessToken,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
if goodsAddJson == "" {
|
||||||
|
return "", fmt.Errorf("goodsAddJson 参数为空!")
|
||||||
|
}
|
||||||
|
// 将JSON参数合并到params中
|
||||||
|
toParams, err := addStructToParams(goodsAddJson, params)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := generateSign(toParams, clientSecret)
|
||||||
|
|
||||||
|
if sign == "" {
|
||||||
|
return "", fmt.Errorf("生成 sign 签名错误!")
|
||||||
|
}
|
||||||
|
params["sign"] = sign
|
||||||
|
|
||||||
|
request := gorequest.New()
|
||||||
|
resp, body, errs := request.Get(url).
|
||||||
|
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
Set("Accept", "application/json, text/plain, */*").
|
||||||
|
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
Timeout(30 * time.Second).
|
||||||
|
Send(params).
|
||||||
|
End()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return "", fmt.Errorf("请求失败: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(body), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常处理
|
||||||
|
if response["error_response"] != nil {
|
||||||
|
var errorWrapper ErrorWrapper
|
||||||
|
// 解析响应
|
||||||
|
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
|
||||||
|
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换成json字符串
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("JSON序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
return string(responseJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 联合拼多多图片上传的商品新增(组合接口)
|
||||||
|
func selfPddGoodsAdd(clientId, clientSecret, accessToken string, filePath string, goodsAddJson string) (string, error) {
|
||||||
|
// 1. 上传商品图片
|
||||||
|
upload, err := pddGoodsImgUpload(clientId, clientSecret, accessToken, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// 2. 解析商品添加请求JSON
|
||||||
|
var goodsAddRequest GoodsAddRequest
|
||||||
|
if err := json.Unmarshal([]byte(goodsAddJson), &goodsAddRequest); err != nil {
|
||||||
|
return "", fmt.Errorf("解析 goodsAddJson 失败: %v", err)
|
||||||
|
}
|
||||||
|
// 3. 设置图片URL
|
||||||
|
goodsAddRequest.ImageURL = upload
|
||||||
|
// 4. 重新序列化为JSON
|
||||||
|
goodsAddRequestStr, err := json.Marshal(goodsAddRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("序列化 goodsAddRequest 失败:%v", err)
|
||||||
|
}
|
||||||
|
// 5. 调用商品添加接口
|
||||||
|
add, err := pddGoodsAdd(clientId, clientSecret, accessToken, string(goodsAddRequestStr))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return add, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量数据解密脱敏接口
|
||||||
|
func pddOpenDecryptMaskBatch(clientId, clientSecret, accessToken string, reqJson string) (string, error) {
|
||||||
|
url := fmt.Sprint("http://gw-api.pinduoduo.com/api/router")
|
||||||
|
// 当前时间戳
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
// 生成签名参数
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"type": "pdd.open.decrypt.mask.batch", // API类型:批量解密脱敏
|
||||||
|
"data_type": "JSON",
|
||||||
|
"client_id": clientId,
|
||||||
|
"access_token": accessToken,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
params["data_list"] = reqJson // 添加数据列表
|
||||||
|
|
||||||
|
sign := generateSign(params, clientSecret)
|
||||||
|
|
||||||
|
if sign == "" {
|
||||||
|
return "", fmt.Errorf("生成 sign 签名错误!")
|
||||||
|
}
|
||||||
|
params["sign"] = sign
|
||||||
|
request := gorequest.New()
|
||||||
|
resp, body, errs := request.Get(url).
|
||||||
|
Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36").
|
||||||
|
Set("Accept", "application/json, text/plain, */*").
|
||||||
|
Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8").
|
||||||
|
Timeout(30 * time.Second).
|
||||||
|
Send(params).
|
||||||
|
End()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return "", fmt.Errorf("请求失败: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP状态码异常: %d, 响应: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(body), &response); err != nil {
|
||||||
|
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常处理
|
||||||
|
if response["error_response"] != nil {
|
||||||
|
var errorWrapper ErrorWrapper
|
||||||
|
// 解析响应
|
||||||
|
if err := json.Unmarshal([]byte(body), &errorWrapper); err != nil {
|
||||||
|
return "", fmt.Errorf("解析 errorWrapper 失败: %v, 响应内容: %s", err, body)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("请求失败: %v, 错误码: %d", errorWrapper.ErrorResponse.ErrorMsg, errorWrapper.ErrorResponse.ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换成json字符串
|
||||||
|
responseJSON, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("JSON序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
return string(responseJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== 辅助函数 ============================
|
||||||
|
// 将结构体的字段添加到 params 映射中
|
||||||
|
func addStructToParams(req string, params map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// 将JSON字符串解析为map
|
||||||
|
var tempMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(req), &tempMap); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析 req json失败:%v ", err)
|
||||||
|
}
|
||||||
|
// 合并到params
|
||||||
|
for k, v := range tempMap {
|
||||||
|
// 只添加非nil的值
|
||||||
|
if v != nil {
|
||||||
|
params[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================== C 导入函数 ===================
|
||||||
|
|
||||||
|
// PddGoodsOuterCatMappingGet 类目预测
|
||||||
|
//
|
||||||
|
//export PddGoodsOuterCatMappingGet
|
||||||
|
func PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
|
||||||
|
outerCatId, outerCatName, outerGoodsName *C.char) *C.char {
|
||||||
|
// 将C字符串转换为Go字符串
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goOuterCatId := C.GoString(outerCatId)
|
||||||
|
goOuterCatName := C.GoString(outerCatName)
|
||||||
|
goOuterGoodsName := C.GoString(outerGoodsName)
|
||||||
|
// 调用Go函数
|
||||||
|
info, err := pddGoodsOuterCatMappingGet(goClientId, goClientSecret, goAccessToken, goOuterCatId, goOuterCatName, goOuterGoodsName)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error()) // 返回错误信息
|
||||||
|
}
|
||||||
|
return C.CString(info) // 返回成功信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddLogisticsCompaniesGet 快递公司查看
|
||||||
|
//
|
||||||
|
//export PddLogisticsCompaniesGet
|
||||||
|
func PddLogisticsCompaniesGet(clientId, clientSecret *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
info, err := pddLogisticsCompaniesGet(goClientId, goClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddErpOrderSync erp打单信息同步
|
||||||
|
//
|
||||||
|
//export PddErpOrderSync
|
||||||
|
func PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goLogisticsId := C.GoString(logisticsId)
|
||||||
|
goOrderSn := C.GoString(orderSn)
|
||||||
|
goOrderState := C.GoString(orderState)
|
||||||
|
goWaybillNo := C.GoString(waybillNo)
|
||||||
|
info, err := pddErpOrderSync(goClientId, goClientSecret, goAccessToken, goLogisticsId, goOrderSn, goOrderState, goWaybillNo)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddOrderSynchronization 拼多多订单同步
|
||||||
|
//
|
||||||
|
//export PddOrderSynchronization
|
||||||
|
func PddOrderSynchronization(clientId, clientSecret, accessToken, logisticsCompany, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goLogisticsCompany := C.GoString(logisticsCompany)
|
||||||
|
goLogisticsId := C.GoString(logisticsId)
|
||||||
|
goOrderSn := C.GoString(orderSn)
|
||||||
|
goOrderState := C.GoString(orderState)
|
||||||
|
goWaybillNo := C.GoString(waybillNo)
|
||||||
|
info, err := pddOrderSynchronization(goClientId, goClientSecret, goAccessToken, goLogisticsCompany, goLogisticsId, goOrderSn, goOrderState, goWaybillNo)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddGoodsImgUpload 商品图片上传接口
|
||||||
|
//
|
||||||
|
//export PddGoodsImgUpload
|
||||||
|
func PddGoodsImgUpload(clientId, clientSecret, accessToken, filePath *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goFilePath := C.GoString(filePath)
|
||||||
|
info, err := pddGoodsImgUpload(goClientId, goClientSecret, goAccessToken, goFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddGoodsAdd 商品新增接口
|
||||||
|
//
|
||||||
|
//export PddGoodsAdd
|
||||||
|
func PddGoodsAdd(clientId, clientSecret, accessToken, goodsAddJson *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goGoodsAddJson := C.GoString(goodsAddJson)
|
||||||
|
info, err := pddGoodsAdd(goClientId, goClientSecret, goAccessToken, goGoodsAddJson)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelfPddGoodsAdd 联合拼多多图片上传的商品新增
|
||||||
|
//
|
||||||
|
//export SelfPddGoodsAdd
|
||||||
|
func SelfPddGoodsAdd(clientId, clientSecret, accessToken, filePath, goodsAddJson *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goFilePath := C.GoString(filePath)
|
||||||
|
goGoodsAddJson := C.GoString(goodsAddJson)
|
||||||
|
info, err := selfPddGoodsAdd(goClientId, goClientSecret, goAccessToken, goFilePath, goGoodsAddJson)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddOpenDecryptMaskBatch 批量数据解密脱敏接口
|
||||||
|
//
|
||||||
|
//export PddOpenDecryptMaskBatch
|
||||||
|
func PddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, reqJson *C.char) *C.char {
|
||||||
|
goClientId := C.GoString(clientId)
|
||||||
|
goClientSecret := C.GoString(clientSecret)
|
||||||
|
goAccessToken := C.GoString(accessToken)
|
||||||
|
goReqJson := C.GoString(reqJson)
|
||||||
|
info, err := pddOpenDecryptMaskBatch(goClientId, goClientSecret, goAccessToken, goReqJson)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(err.Error())
|
||||||
|
}
|
||||||
|
return C.CString(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeCString 释放C字符串内存
|
||||||
|
//
|
||||||
|
//export FreeCString
|
||||||
|
func FreeCString(str *C.char) {
|
||||||
|
C.free(unsafe.Pointer(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// main函数
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
232
pdd/pddDll.go
Normal file
232
pdd/pddDll.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pddDLL 拼多多API调用结构体
|
||||||
|
type pddDLL struct {
|
||||||
|
dll *syscall.DLL
|
||||||
|
pddGoodsOuterCatMappingGet *syscall.Proc // 类目预测
|
||||||
|
pddLogisticsCompaniesGet *syscall.Proc // 快递公司查看
|
||||||
|
pddErpOrderSync *syscall.Proc // erp打单信息同步
|
||||||
|
freeCString *syscall.Proc // 释放C字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitPddDLL 初始化pddDLL
|
||||||
|
func InitPddDLL() (*pddDLL, error) {
|
||||||
|
dllPath := filepath.Join("pdd", "dll", "pdd.dll")
|
||||||
|
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("pdd DLL 不存在: %s", dllPath)
|
||||||
|
}
|
||||||
|
if dll, err := syscall.LoadDLL(dllPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("加载pdd DLL 失败: %s", err)
|
||||||
|
} else {
|
||||||
|
return &pddDLL{
|
||||||
|
dll: dll,
|
||||||
|
pddGoodsOuterCatMappingGet: dll.MustFindProc("PddGoodsOuterCatMappingGet"),
|
||||||
|
pddLogisticsCompaniesGet: dll.MustFindProc("PddLogisticsCompaniesGet"),
|
||||||
|
pddErpOrderSync: dll.MustFindProc("PddErpOrderSync"),
|
||||||
|
freeCString: dll.MustFindProc("FreeCString"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cStr 获取C字符串
|
||||||
|
func (m *pddDLL) cStr(p uintptr) string {
|
||||||
|
if p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
for i := uintptr(0); ; i++ {
|
||||||
|
c := *(*byte)(unsafe.Pointer(p + i))
|
||||||
|
if c == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
if m.freeCString != nil {
|
||||||
|
m.freeCString.Call(p)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddGoodsOuterCatMappingGet 类目预测
|
||||||
|
func (m *pddDLL) PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken,
|
||||||
|
outerCatId, outerCatName, outerGoodsName string) (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("PddGoodsOuterCatMappingGet")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 PddGoodsOuterCatMappingGet 函数: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
|
||||||
|
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
|
||||||
|
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
|
||||||
|
outerCatIdPtr, _ := syscall.BytePtrFromString(outerCatId)
|
||||||
|
outerCatNamePtr, _ := syscall.BytePtrFromString(outerCatName)
|
||||||
|
outerGoodsNamePtr, _ := syscall.BytePtrFromString(outerGoodsName)
|
||||||
|
info, _, err := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(clientIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(clientSecretPtr)),
|
||||||
|
uintptr(unsafe.Pointer(accessTokenPtr)),
|
||||||
|
uintptr(unsafe.Pointer(outerCatIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(outerCatNamePtr)),
|
||||||
|
uintptr(unsafe.Pointer(outerGoodsNamePtr)),
|
||||||
|
)
|
||||||
|
if err != nil && err.Error() != "The operation completed successfully." {
|
||||||
|
return "", fmt.Errorf("调用函数 PddGoodsOuterCatMappingGet 失败: %v", err)
|
||||||
|
}
|
||||||
|
return m.cStr(info), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddLogisticsCompaniesGet 快递公司查看
|
||||||
|
func (m *pddDLL) PddLogisticsCompaniesGet(clientId, clientSecret string) (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("PddLogisticsCompaniesGet")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 PddLogisticsCompaniesGet 函数: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
|
||||||
|
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
|
||||||
|
info, _, err := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(clientIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(clientSecretPtr)),
|
||||||
|
)
|
||||||
|
if err != nil && err.Error() != "The operation completed successfully." {
|
||||||
|
return "", fmt.Errorf("调用函数 PddGoodsOuterCatMappingGet 失败: %v", err)
|
||||||
|
}
|
||||||
|
return m.cStr(info), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PddErpOrderSync erp打单信息同步
|
||||||
|
func (m *pddDLL) PddErpOrderSync(clientId, clientSecret, accessToken, logisticsId,
|
||||||
|
orderSn, orderState, waybillNo string) (string, error) {
|
||||||
|
proc, err := m.dll.FindProc("PddErpOrderSync")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("找不到函数 PddErpOrderSync 函数: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIdPtr, _ := syscall.BytePtrFromString(clientId)
|
||||||
|
clientSecretPtr, _ := syscall.BytePtrFromString(clientSecret)
|
||||||
|
accessTokenPtr, _ := syscall.BytePtrFromString(accessToken)
|
||||||
|
logisticsIdPtr, _ := syscall.BytePtrFromString(logisticsId)
|
||||||
|
orderSnPtr, _ := syscall.BytePtrFromString(orderSn)
|
||||||
|
orderStatePtr, _ := syscall.BytePtrFromString(orderState)
|
||||||
|
waybillNoPtr, _ := syscall.BytePtrFromString(waybillNo)
|
||||||
|
info, _, err := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(clientIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(clientSecretPtr)),
|
||||||
|
uintptr(unsafe.Pointer(accessTokenPtr)),
|
||||||
|
uintptr(unsafe.Pointer(logisticsIdPtr)),
|
||||||
|
uintptr(unsafe.Pointer(orderSnPtr)),
|
||||||
|
uintptr(unsafe.Pointer(orderStatePtr)),
|
||||||
|
uintptr(unsafe.Pointer(waybillNoPtr)),
|
||||||
|
)
|
||||||
|
if err != nil && err.Error() != "The operation completed successfully." {
|
||||||
|
return "", fmt.Errorf("调用函数 PddGoodsOuterCatMappingGet 失败: %v", err)
|
||||||
|
}
|
||||||
|
return m.cStr(info), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
clientId := "203c5a7ba8bd4b8488d5e26f93052642"
|
||||||
|
clientSecret := "892ffaa86e12b7a3d8d2942b669d9aa520ad8179"
|
||||||
|
accessToken := "bd96218bb2a146779701506dc1e5e5c478692539"
|
||||||
|
//outerCatId := "15543"
|
||||||
|
//outerCatName := "书籍/杂志/报纸"
|
||||||
|
//outerGoodsName := "书籍医家金鉴 妇产科学卷"
|
||||||
|
//logisticsId := 0
|
||||||
|
//orderSn := ""
|
||||||
|
//orderState := ""
|
||||||
|
//waybillNo := ""
|
||||||
|
//logisticsCompany := "德邦"
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
//dll, err := InitPddDLL()
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 类目预测
|
||||||
|
//info, err := dll.PddGoodsOuterCatMappingGet(clientId, clientSecret, accessToken, outerCatId, outerCatName, outerGoodsName)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(info)
|
||||||
|
|
||||||
|
// 快递公司查看
|
||||||
|
//get, err := dll.PddLogisticsCompaniesGet(clientId, clientSecret)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(get)
|
||||||
|
//var logisticsResponse LogisticsResponse
|
||||||
|
//if err := json.Unmarshal([]byte(get), &logisticsResponse); err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//var company string
|
||||||
|
//var available int
|
||||||
|
//var code string
|
||||||
|
//for _, logisticsCompanies := range logisticsResponse.LogisticsCompaniesGetResponse.LogisticsCompanies {
|
||||||
|
// if strings.Contains(logisticsCompanies.LogisticsCompany, logisticsCompany) {
|
||||||
|
// company = logisticsCompanies.LogisticsCompany
|
||||||
|
// logisticsId = logisticsCompanies.ID
|
||||||
|
// available = logisticsCompanies.Available
|
||||||
|
// code = logisticsCompanies.Code
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//fmt.Println("快递公司名称: ", company)
|
||||||
|
//fmt.Println("快递公司编码: ", logisticsId)
|
||||||
|
//fmt.Println("是否有效: ", available)
|
||||||
|
//fmt.Println("物流公司代码: ", code)
|
||||||
|
|
||||||
|
//file := "D:\\isbn_images\\result\\9780007935192.jpg"
|
||||||
|
//open, err := os.Open(file)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//defer open.Close()
|
||||||
|
////base := filepath.Base(file)
|
||||||
|
//// 商品图片上传接口
|
||||||
|
//upload, err := pddGoodsImgUpload(clientId, clientSecret, accessToken, file)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(upload)
|
||||||
|
|
||||||
|
//get, err := pddLogisticsCompaniesGet(clientId, clientSecret)
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
//}
|
||||||
|
//fmt.Println(get)
|
||||||
|
|
||||||
|
// 脱敏
|
||||||
|
|
||||||
|
jsonStr := `[{"data_tag":"251229-272441044622514","encrypted_data":"~AgAAAAPlscEH0psOJAEXpTdsLOWvDJ9bB7IEjIoqNfiDhhJR9NHOxsdZ+PEFluSSCngCikoDU+CP/sSXZJ92ic7+PdNlJNLA7g/6VUMDWF6RvjW9IeRN+lKNarsjWDQR~0~"}]`
|
||||||
|
|
||||||
|
//var records []DataList
|
||||||
|
//err := json.Unmarshal([]byte(jsonStr), &records)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Fatal("解析JSON失败:", err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
batch, err := pddOpenDecryptMaskBatch(clientId, clientSecret, accessToken, jsonStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
fmt.Println(batch)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataList struct {
|
||||||
|
DataTag string `json:"data_tag"`
|
||||||
|
EncryptedData string `json:"encrypted_data"`
|
||||||
|
}
|
||||||
Binary file not shown.
@ -22,7 +22,8 @@ extern const char *_GoStringPtr(_GoString_ s);
|
|||||||
|
|
||||||
|
|
||||||
#line 3 "proxy.go"
|
#line 3 "proxy.go"
|
||||||
#include <stdlib.h>
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#line 1 "cgo-generated-wrapper"
|
#line 1 "cgo-generated-wrapper"
|
||||||
|
|
||||||
@ -87,35 +88,35 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// 导出函数:获取代理健康状态(用于调试)
|
// GetProxyHealth 导出函数:获取代理健康状态(用于调试)
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* GetProxyHealth(void);
|
extern __declspec(dllexport) char* GetProxyHealth(void);
|
||||||
|
|
||||||
// 导出函数:代理类型管理器
|
// ProxyTypeManager 导出函数:代理类型管理器
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
||||||
|
|
||||||
// 导出函数:查询机器码
|
// GetMachineCode 导出函数:查询机器码
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
|
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
|
||||||
|
|
||||||
// 导出函数:充值卡密
|
// RechargeCard 导出函数:充值卡密
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
|
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
|
||||||
|
|
||||||
// 导出函数:获取代理服务器列表
|
// GetProxies 导出函数:获取代理服务器列表
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* GetProxies(char* machineCode);
|
extern __declspec(dllexport) char* GetProxies(char* machineCode);
|
||||||
|
|
||||||
// 导出函数:检查卡密是否过期
|
// CheckTailCardSecretExpired 导出函数:检查卡密是否过期
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
|
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
|
||||||
|
|
||||||
// 导出函数:初始化代理管理器
|
// InitProxyManager 导出函数:初始化代理管理器
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
|
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
|
||||||
|
|
||||||
// 导出函数:释放C字符串内存
|
// FreeCString 导出函数:释放C字符串内存
|
||||||
//
|
//
|
||||||
extern __declspec(dllexport) void FreeCString(char* str);
|
extern __declspec(dllexport) void FreeCString(char* str);
|
||||||
|
|
||||||
|
|||||||
193
proxy/proxy.go
193
proxy/proxy.go
@ -20,11 +20,11 @@ import (
|
|||||||
|
|
||||||
// 代理类型常量
|
// 代理类型常量
|
||||||
const (
|
const (
|
||||||
CalfElephantProxyType = "CALF_ELEPHANT_PROXY"
|
CalfElephantProxyType = "CALF_ELEPHANT_PROXY" // 小象代理类型
|
||||||
TailProxyType = "TAIL_PROXY"
|
TailProxyType = "TAIL_PROXY" // 尾巴代理类型
|
||||||
)
|
)
|
||||||
|
|
||||||
// 代理服务器列表
|
// 小象代理服务器列表
|
||||||
var (
|
var (
|
||||||
servers = []string{
|
servers = []string{
|
||||||
"http-dynamic.xiaoxiangdaili.com",
|
"http-dynamic.xiaoxiangdaili.com",
|
||||||
@ -32,15 +32,16 @@ var (
|
|||||||
"http-dynamic-S03.xiaoxiangdaili.com",
|
"http-dynamic-S03.xiaoxiangdaili.com",
|
||||||
"http-dynamic-S04.xiaoxiangdaili.com",
|
"http-dynamic-S04.xiaoxiangdaili.com",
|
||||||
}
|
}
|
||||||
|
// 互斥锁,用于保护全局随机数生成器的并发访问
|
||||||
randMutex sync.Mutex
|
randMutex sync.Mutex
|
||||||
globalRand *rand.Rand
|
globalRand *rand.Rand // 全局随机数生成器
|
||||||
|
|
||||||
// 代理健康状态管理
|
// 代理健康状态管理
|
||||||
|
// 代理健康状态映射表,key为代理主机名,value为健康状态
|
||||||
proxyHealthMaps = make(map[string]*ProxyHealth)
|
proxyHealthMaps = make(map[string]*ProxyHealth)
|
||||||
proxyHealthMutex sync.RWMutex
|
proxyHealthMutex sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProxyManager 代理管理器结构体
|
// ProxyManager 代理管理器结构体,用于管理代理配置信息
|
||||||
type ProxyManager struct {
|
type ProxyManager struct {
|
||||||
servers []string `json:"servers"` // 代理服务器列表
|
servers []string `json:"servers"` // 代理服务器列表
|
||||||
username string `json:"username"` // 代理账号
|
username string `json:"username"` // 代理账号
|
||||||
@ -49,7 +50,7 @@ type ProxyManager struct {
|
|||||||
proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
|
proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理健康状态
|
// 代理健康状态结构体,用于跟踪代理的健康状况
|
||||||
type ProxyHealth struct {
|
type ProxyHealth struct {
|
||||||
SuccessCount int // 成功次数
|
SuccessCount int // 成功次数
|
||||||
FailCount int // 失败次数
|
FailCount int // 失败次数
|
||||||
@ -58,39 +59,46 @@ type ProxyHealth struct {
|
|||||||
IsHealthy bool // 是否健康
|
IsHealthy bool // 是否健康
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化函数,在程序启动时自动执行
|
||||||
func init() {
|
func init() {
|
||||||
// 创建全局的随机数生成器
|
// 创建全局的随机数生成器,使用当前时间作为种子
|
||||||
globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取代理URL
|
// 获取代理URL,代理类型管理器,根据代理类型构建不同的代理URL
|
||||||
func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
|
func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case CalfElephantProxyType:
|
case CalfElephantProxyType:
|
||||||
|
// 小象代理:使用用户名和密码构建代理URL
|
||||||
return buildCalfElephantProxyURL(username, password)
|
return buildCalfElephantProxyURL(username, password)
|
||||||
case TailProxyType:
|
case TailProxyType:
|
||||||
|
// 尾巴代理:使用机器码构建代理URL
|
||||||
return buildTailProxyURL(machineCode)
|
return buildTailProxyURL(machineCode)
|
||||||
default:
|
default:
|
||||||
|
// 不支持的代理类型,返回错误
|
||||||
return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
|
return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建小象代理URL
|
// 构建小象代理URL
|
||||||
func buildCalfElephantProxyURL(username, password string) (string, error) {
|
func buildCalfElephantProxyURL(username, password string) (string, error) {
|
||||||
|
// 随机选择一个代理服务器
|
||||||
server := randomServer()
|
server := randomServer()
|
||||||
|
// 构建代理URL格式:http://用户名:密码@服务器:端口
|
||||||
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||||
url.QueryEscape(username),
|
url.QueryEscape(username), // URL编码用户名,防止特殊字符问题
|
||||||
url.QueryEscape(password),
|
url.QueryEscape(password), // URL编码密码,防止特殊字符问题
|
||||||
server,
|
server, // 服务器地址
|
||||||
10030)
|
10030) // 固定端口号
|
||||||
|
|
||||||
// 检测代理可用性
|
// 检测代理可用性
|
||||||
if err := checkProxyHealth(proxyURL); err != nil {
|
if err := checkProxyHealth(proxyURL); err != nil {
|
||||||
|
// 代理检测失败,记录警告日志
|
||||||
log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
|
log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
|
||||||
// 尝试下一个代理服务器
|
// 尝试下一个代理服务器
|
||||||
return tryNextCalfElephantProxy(username, password, server)
|
return tryNextCalfElephantProxy(username, password, server)
|
||||||
}
|
}
|
||||||
|
// 代理检测成功,记录信息日志
|
||||||
log.Printf("[INFO] 使用小象代理: %s", server)
|
log.Printf("[INFO] 使用小象代理: %s", server)
|
||||||
return proxyURL, nil
|
return proxyURL, nil
|
||||||
}
|
}
|
||||||
@ -104,35 +112,39 @@ func tryNextCalfElephantProxy(username, password, failedServer string) (string,
|
|||||||
availableServers = append(availableServers, server)
|
availableServers = append(availableServers, server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 如果没有可用的服务器,返回错误
|
||||||
if len(availableServers) == 0 {
|
if len(availableServers) == 0 {
|
||||||
return "", fmt.Errorf("所有小象代理服务器都不可用")
|
return "", fmt.Errorf("所有小象代理服务器都不可用")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 随机尝试可用服务器
|
// 随机尝试可用服务器
|
||||||
for _, server := range shuffleServers(availableServers) {
|
for _, server := range shuffleServers(availableServers) {
|
||||||
|
// 构建代理URL
|
||||||
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||||
url.QueryEscape(username),
|
url.QueryEscape(username),
|
||||||
url.QueryEscape(password),
|
url.QueryEscape(password),
|
||||||
server,
|
server,
|
||||||
10030)
|
10030)
|
||||||
|
// 检测代理可用性
|
||||||
if err := checkProxyHealth(proxyURL); err == nil {
|
if err := checkProxyHealth(proxyURL); err == nil {
|
||||||
log.Printf("[INFO] 切换到可用代理: %s", server)
|
log.Printf("[INFO] 切换到可用代理: %s", server)
|
||||||
return proxyURL, nil
|
return proxyURL, nil
|
||||||
}
|
}
|
||||||
|
// 代理不可用,记录警告日志
|
||||||
log.Printf("[WARN] 代理 %s 检测失败", server)
|
log.Printf("[WARN] 代理 %s 检测失败", server)
|
||||||
}
|
}
|
||||||
|
// 所有服务器都检测失败,返回错误
|
||||||
return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
|
return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建内置代理URL
|
// 构建内置代理URL
|
||||||
func buildTailProxyURL(machineCode string) (string, error) {
|
func buildTailProxyURL(machineCode string) (string, error) {
|
||||||
|
// 获取代理列表
|
||||||
proxies, err := getProxies(machineCode)
|
proxies, err := getProxies(machineCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
// 检查是否获取到有效代理
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return "", fmt.Errorf("未获取到有效代理")
|
return "", fmt.Errorf("未获取到有效代理")
|
||||||
}
|
}
|
||||||
@ -140,6 +152,7 @@ func buildTailProxyURL(machineCode string) (string, error) {
|
|||||||
// 过滤并选择健康的代理
|
// 过滤并选择健康的代理
|
||||||
healthyProxies := filterHealthyProxies(proxies)
|
healthyProxies := filterHealthyProxies(proxies)
|
||||||
if len(healthyProxies) > 0 {
|
if len(healthyProxies) > 0 {
|
||||||
|
// 从健康代理中随机选择一个
|
||||||
proxyURL := "http://" + randomElement(healthyProxies)
|
proxyURL := "http://" + randomElement(healthyProxies)
|
||||||
log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
|
log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
|
||||||
return proxyURL, nil
|
return proxyURL, nil
|
||||||
@ -150,15 +163,16 @@ func buildTailProxyURL(machineCode string) (string, error) {
|
|||||||
return findWorkingTailProxy(proxies)
|
return findWorkingTailProxy(proxies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤健康代理
|
// 过滤健康代理,返回当前健康的代理列表
|
||||||
func filterHealthyProxies(proxies []string) []string {
|
func filterHealthyProxies(proxies []string) []string {
|
||||||
|
// 获取读锁,允许多个goroutine同时读取
|
||||||
proxyHealthMutex.RLock()
|
proxyHealthMutex.RLock()
|
||||||
defer proxyHealthMutex.RUnlock()
|
defer proxyHealthMutex.RUnlock() // 确保函数结束时释放锁
|
||||||
|
|
||||||
var healthy []string
|
var healthy []string
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
|
if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
|
||||||
// 检查是否在最近检查过(5分钟内)
|
// 检查是否在最近检查过(5分钟内),避免使用过时的健康状态
|
||||||
if time.Since(health.LastCheck) < 5*time.Minute {
|
if time.Since(health.LastCheck) < 5*time.Minute {
|
||||||
healthy = append(healthy, proxy)
|
healthy = append(healthy, proxy)
|
||||||
}
|
}
|
||||||
@ -177,69 +191,73 @@ func findWorkingTailProxy(proxies []string) (string, error) {
|
|||||||
proxy string
|
proxy string
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
// 创建带缓冲的通道,用于收集检测结果
|
||||||
ch := make(chan proxyResult, len(shuffledProxies))
|
ch := make(chan proxyResult, len(shuffledProxies))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup // 等待组,用于等待所有检测goroutine完成
|
||||||
|
|
||||||
// 限制并发数
|
// 限制并发数,避免同时检测太多代理导致网络拥塞
|
||||||
sem := make(chan struct{}, 5)
|
sem := make(chan struct{}, 5)
|
||||||
|
// 并发检测每个代理
|
||||||
for _, proxy := range shuffledProxies {
|
for _, proxy := range shuffledProxies {
|
||||||
wg.Add(1)
|
wg.Add(1) // 增加等待组计数
|
||||||
go func(p string) {
|
go func(p string) {
|
||||||
defer wg.Done()
|
defer wg.Done() // 函数结束时减少等待组计数
|
||||||
sem <- struct{}{}
|
sem <- struct{}{} // 获取信号量,限制并发数
|
||||||
defer func() { <-sem }()
|
defer func() { <-sem }() // 释放信号量
|
||||||
|
|
||||||
proxyURL := "http://" + p
|
proxyURL := "http://" + p
|
||||||
err := checkProxyHealth(proxyURL)
|
err := checkProxyHealth(proxyURL)
|
||||||
|
// 将检测结果发送到通道
|
||||||
ch <- proxyResult{proxy: p, err: err}
|
ch <- proxyResult{proxy: p, err: err}
|
||||||
}(proxy)
|
}(proxy)
|
||||||
}
|
}
|
||||||
|
// 等待所有检测goroutine完成
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(ch)
|
close(ch) // 关闭通道,表示不会再有数据发送
|
||||||
|
|
||||||
// 收集结果
|
// 收集结果,查找可用的代理
|
||||||
var workingProxies []string
|
var workingProxies []string
|
||||||
for result := range ch {
|
for result := range ch {
|
||||||
if result.err == nil {
|
if result.err == nil {
|
||||||
|
// 代理可用,添加到工作代理列表
|
||||||
workingProxies = append(workingProxies, result.proxy)
|
workingProxies = append(workingProxies, result.proxy)
|
||||||
// 更新健康状态
|
// 更新健康状态
|
||||||
updateProxyHealth(result.proxy, true, 0)
|
updateProxyHealth(result.proxy, true, 0)
|
||||||
} else {
|
} else {
|
||||||
|
// 代理不可用,更新健康状态
|
||||||
updateProxyHealth(result.proxy, false, 0)
|
updateProxyHealth(result.proxy, false, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 如果有可用的代理,随机选择一个
|
||||||
if len(workingProxies) > 0 {
|
if len(workingProxies) > 0 {
|
||||||
selected := randomElement(workingProxies)
|
selected := randomElement(workingProxies)
|
||||||
log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
|
log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
|
||||||
return "http://" + selected, nil
|
return "http://" + selected, nil
|
||||||
}
|
}
|
||||||
|
// 所有代理都不可用,返回错误
|
||||||
return "", fmt.Errorf("所有尾巴代理都不可用")
|
return "", fmt.Errorf("所有尾巴代理都不可用")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测代理健康状态
|
// 检测代理健康状态,通过访问测试网站验证代理可用性
|
||||||
func checkProxyHealth(proxyURL string) error {
|
func checkProxyHealth(proxyURL string) error {
|
||||||
start := time.Now()
|
start := time.Now() // 记录开始时间,用于计算响应时间
|
||||||
|
// 创建HTTP请求,设置代理和超时
|
||||||
req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
|
req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
|
||||||
|
// 发送GET请求到测试网站
|
||||||
resp, _, errs := req.Get("https://shop.kongfz.com/").End()
|
resp, _, errs := req.Get("https://shop.kongfz.com/").End()
|
||||||
|
|
||||||
responseTime := time.Since(start)
|
responseTime := time.Since(start) // 计算响应时间
|
||||||
|
// 检查请求错误
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
updateProxyHealth(proxyURL, false, responseTime)
|
updateProxyHealth(proxyURL, false, responseTime)
|
||||||
return fmt.Errorf("代理连接失败: %v", errs)
|
return fmt.Errorf("代理连接失败: %v", errs)
|
||||||
}
|
}
|
||||||
|
// 检查HTTP状态码
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
updateProxyHealth(proxyURL, false, responseTime)
|
updateProxyHealth(proxyURL, false, responseTime)
|
||||||
return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
|
return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
// 代理检测成功
|
||||||
updateProxyHealth(proxyURL, true, responseTime)
|
updateProxyHealth(proxyURL, true, responseTime)
|
||||||
log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
|
log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
|
||||||
return nil
|
return nil
|
||||||
@ -247,25 +265,29 @@ func checkProxyHealth(proxyURL string) error {
|
|||||||
|
|
||||||
// 更新代理健康状态
|
// 更新代理健康状态
|
||||||
func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
|
func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
|
||||||
|
// 获取写锁,确保更新操作的互斥性
|
||||||
proxyHealthMutex.Lock()
|
proxyHealthMutex.Lock()
|
||||||
defer proxyHealthMutex.Unlock()
|
defer proxyHealthMutex.Unlock() // 确保函数结束时释放锁
|
||||||
|
// 获取代理主机名作为键
|
||||||
host := getProxyHost(proxyURL)
|
host := getProxyHost(proxyURL)
|
||||||
|
// 如果代理健康状态不存在,创建新的
|
||||||
if _, exists := proxyHealthMaps[host]; !exists {
|
if _, exists := proxyHealthMaps[host]; !exists {
|
||||||
proxyHealthMaps[host] = &ProxyHealth{}
|
proxyHealthMaps[host] = &ProxyHealth{}
|
||||||
}
|
}
|
||||||
|
|
||||||
health := proxyHealthMaps[host]
|
health := proxyHealthMaps[host]
|
||||||
health.LastCheck = time.Now()
|
health.LastCheck = time.Now() // 更新最后检查时间
|
||||||
|
|
||||||
if success {
|
if success {
|
||||||
health.SuccessCount++
|
// 成功的情况
|
||||||
health.FailCount = 0
|
health.SuccessCount++ // 增加成功次数
|
||||||
health.ResponseTime = responseTime
|
health.FailCount = 0 // 重置失败次数
|
||||||
health.IsHealthy = true
|
health.ResponseTime = responseTime // 记录响应时间
|
||||||
|
health.IsHealthy = true // 标记为健康
|
||||||
} else {
|
} else {
|
||||||
health.FailCount++
|
// 失败的情况
|
||||||
health.SuccessCount = 0
|
health.FailCount++ // 增加失败次数
|
||||||
|
health.SuccessCount = 0 // 重置成功次数
|
||||||
// 连续失败3次标记为不健康
|
// 连续失败3次标记为不健康
|
||||||
if health.FailCount >= 3 {
|
if health.FailCount >= 3 {
|
||||||
health.IsHealthy = false
|
health.IsHealthy = false
|
||||||
@ -275,10 +297,11 @@ func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration
|
|||||||
|
|
||||||
// 获取代理主机名
|
// 获取代理主机名
|
||||||
func getProxyHost(proxyURL string) string {
|
func getProxyHost(proxyURL string) string {
|
||||||
|
// 去除协议前缀
|
||||||
if strings.HasPrefix(proxyURL, "http://") {
|
if strings.HasPrefix(proxyURL, "http://") {
|
||||||
proxyURL = proxyURL[7:]
|
proxyURL = proxyURL[7:]
|
||||||
}
|
}
|
||||||
// 去除认证信息
|
// 去除认证信息(用户名:密码@部分)
|
||||||
if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
|
if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
|
||||||
proxyURL = proxyURL[atIndex+1:]
|
proxyURL = proxyURL[atIndex+1:]
|
||||||
}
|
}
|
||||||
@ -296,18 +319,19 @@ func randomServer() string {
|
|||||||
|
|
||||||
// 线程安全的随机元素选择
|
// 线程安全的随机元素选择
|
||||||
func randomElement(slice []string) string {
|
func randomElement(slice []string) string {
|
||||||
randMutex.Lock()
|
randMutex.Lock() // 获取互斥锁
|
||||||
defer randMutex.Unlock()
|
defer randMutex.Unlock() // 确保函数结束时释放锁
|
||||||
return slice[globalRand.Intn(len(slice))]
|
return slice[globalRand.Intn(len(slice))] // 随机选择一个元素
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打乱服务器顺序
|
// 打乱服务器顺序
|
||||||
func shuffleServers(servers []string) []string {
|
func shuffleServers(servers []string) []string {
|
||||||
randMutex.Lock()
|
randMutex.Lock()
|
||||||
defer randMutex.Unlock()
|
defer randMutex.Unlock()
|
||||||
|
// 创建服务器副本,避免修改原始切片
|
||||||
shuffled := make([]string, len(servers))
|
shuffled := make([]string, len(servers))
|
||||||
copy(shuffled, servers)
|
copy(shuffled, servers)
|
||||||
|
// 使用Fisher-Yates算法打乱顺序
|
||||||
globalRand.Shuffle(len(shuffled), func(i, j int) {
|
globalRand.Shuffle(len(shuffled), func(i, j int) {
|
||||||
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
||||||
})
|
})
|
||||||
@ -316,52 +340,59 @@ func shuffleServers(servers []string) []string {
|
|||||||
|
|
||||||
// 检查卡密是否过期
|
// 检查卡密是否过期
|
||||||
func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
|
func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
|
||||||
|
// 获取机器码信息
|
||||||
code, err := getMachineCode(tailCardSecret)
|
code, err := getMachineCode(tailCardSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("请求错误: %v", err)
|
return false, fmt.Errorf("请求错误: %v", err)
|
||||||
}
|
}
|
||||||
|
// 解析过期时间字符串
|
||||||
targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime)
|
targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("时间格式错误: %v", err)
|
return false, fmt.Errorf("时间格式错误: %v", err)
|
||||||
}
|
}
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
// 卡密日期在当前日期之后
|
// 卡密日期在当前日期之后表示有效
|
||||||
if targetTime.After(currentTime) {
|
if targetTime.After(currentTime) {
|
||||||
return true, nil
|
return true, nil
|
||||||
} else {
|
} else {
|
||||||
|
// 卡密已过期
|
||||||
return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
|
return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义响应结构体
|
// 定义响应结构体
|
||||||
type getMachineCodeResp struct {
|
type getMachineCodeResp struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"` // 状态码
|
||||||
Message string `json:"message"`
|
Message string `json:"message"` // 消息
|
||||||
Data struct {
|
Data struct {
|
||||||
MachineCode string `json:"machine_code"`
|
MachineCode string `json:"machine_code"` // 机器码
|
||||||
IpExpTime string `json:"ip_exp_time"`
|
IpExpTime string `json:"ip_exp_time"` // IP过期时间
|
||||||
IpThread int `json:"ip_thread"`
|
IpThread int `json:"ip_thread"` // IP线程数
|
||||||
IpCardCode string `json:"ip_card_code"`
|
IpCardCode string `json:"ip_card_code"` // IP卡密
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询机器码
|
// 查询机器码
|
||||||
func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
|
func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
|
||||||
url := "http://114.66.2.223:7842/api/proxies/ip_show"
|
url := "http://114.66.2.223:7842/api/proxies/ip_show"
|
||||||
|
// 构建请求数据
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"ip_card_code": tailCardSecret,
|
"ip_card_code": tailCardSecret,
|
||||||
"agent_id": 9999,
|
"agent_id": 9999,
|
||||||
}
|
}
|
||||||
|
// 发送POST请求
|
||||||
_, body, errs := gorequest.New().Post(url).Send(data).End()
|
_, body, errs := gorequest.New().Post(url).Send(data).End()
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return nil, fmt.Errorf("查询机器码失败: %v", errs)
|
return nil, fmt.Errorf("查询机器码失败: %v", errs)
|
||||||
}
|
}
|
||||||
|
// 解析响应
|
||||||
var resp getMachineCodeResp
|
var resp getMachineCodeResp
|
||||||
if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
||||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||||
}
|
}
|
||||||
|
// 处理不同的响应码
|
||||||
if resp.Code == 201 {
|
if resp.Code == 201 {
|
||||||
|
// 需要充值卡密
|
||||||
machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode)
|
machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -369,8 +400,10 @@ func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
|
|||||||
resp.Data.MachineCode = machineCode
|
resp.Data.MachineCode = machineCode
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
} else if resp.Code == 200 {
|
} else if resp.Code == 200 {
|
||||||
|
// 成功获取机器码
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
} else {
|
} else {
|
||||||
|
// 其他错误
|
||||||
return nil, fmt.Errorf("查询机器码失败: %s", resp.Message)
|
return nil, fmt.Errorf("查询机器码失败: %s", resp.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,15 +411,18 @@ func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
|
|||||||
// 充值卡密
|
// 充值卡密
|
||||||
func rechargeCard(tailCardSecret, machineCode string) (string, error) {
|
func rechargeCard(tailCardSecret, machineCode string) (string, error) {
|
||||||
url := "http://114.66.2.223:7842/api/proxies/ip_recharge"
|
url := "http://114.66.2.223:7842/api/proxies/ip_recharge"
|
||||||
|
// 构建请求数据
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"machine_code": machineCode,
|
"machine_code": machineCode,
|
||||||
"ip_card_code": tailCardSecret,
|
"ip_card_code": tailCardSecret,
|
||||||
"agent_id": 9999,
|
"agent_id": 9999,
|
||||||
}
|
}
|
||||||
|
// 发送POST请求
|
||||||
_, body, errs := gorequest.New().Post(url).Send(data).End()
|
_, body, errs := gorequest.New().Post(url).Send(data).End()
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return "", fmt.Errorf("充值卡密失败: %v", errs)
|
return "", fmt.Errorf("充值卡密失败: %v", errs)
|
||||||
}
|
}
|
||||||
|
// 解析响应
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Code int `json:"code"` // 状态码
|
Code int `json:"code"` // 状态码
|
||||||
Message string `json:"message"` // 返回消息
|
Message string `json:"message"` // 返回消息
|
||||||
@ -399,6 +435,8 @@ func rechargeCard(tailCardSecret, machineCode string) (string, error) {
|
|||||||
if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
||||||
return "", fmt.Errorf("解析响应失败: %v", err)
|
return "", fmt.Errorf("解析响应失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查响应状态
|
||||||
if resp.Code != 200 {
|
if resp.Code != 200 {
|
||||||
return "", fmt.Errorf("充值卡密失败: %s", resp.Message)
|
return "", fmt.Errorf("充值卡密失败: %s", resp.Message)
|
||||||
}
|
}
|
||||||
@ -411,16 +449,18 @@ func getProxies(machineCode string) ([]string, error) {
|
|||||||
// 生成签名
|
// 生成签名
|
||||||
sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
|
sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
|
||||||
|
|
||||||
|
// 构建请求URL
|
||||||
GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
|
GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
|
||||||
machineCode, sign)
|
machineCode, sign)
|
||||||
|
|
||||||
|
// 发送GET请求
|
||||||
req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
|
req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
|
||||||
_, body, errs := req.End()
|
_, body, errs := req.End()
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return nil, fmt.Errorf("获取代理失败: %v", errs)
|
return nil, fmt.Errorf("获取代理失败: %v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为JSON错误响应
|
// 检查是否为JSON错误响应(响应以{开头,以}结尾)
|
||||||
if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
|
if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
|
||||||
// 尝试解析为JSON错误响应
|
// 尝试解析为JSON错误响应
|
||||||
var errorResp struct {
|
var errorResp struct {
|
||||||
@ -432,15 +472,17 @@ func getProxies(machineCode string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析响应
|
// 解析响应(每行一个代理地址)
|
||||||
lines := strings.Split(strings.TrimSpace(body), "\n")
|
lines := strings.Split(strings.TrimSpace(body), "\n")
|
||||||
var proxies []string
|
var proxies []string
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
|
// 过滤空行和JSON格式的行
|
||||||
if line != "" && !strings.HasPrefix(line, "{") {
|
if line != "" && !strings.HasPrefix(line, "{") {
|
||||||
proxies = append(proxies, line)
|
proxies = append(proxies, line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 检查是否获取到有效代理
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return nil, fmt.Errorf("未获取到有效代理")
|
return nil, fmt.Errorf("未获取到有效代理")
|
||||||
}
|
}
|
||||||
@ -456,13 +498,13 @@ func initProxy() {
|
|||||||
|
|
||||||
// =================== C 导出函数 =======================
|
// =================== C 导出函数 =======================
|
||||||
|
|
||||||
// 导出函数:获取代理健康状态(用于调试)
|
// GetProxyHealth 导出函数:获取代理健康状态(用于调试)
|
||||||
//
|
//
|
||||||
//export GetProxyHealth
|
//export GetProxyHealth
|
||||||
func GetProxyHealth() *C.char {
|
func GetProxyHealth() *C.char {
|
||||||
proxyHealthMutex.RLock()
|
proxyHealthMutex.RLock()
|
||||||
defer proxyHealthMutex.RUnlock()
|
defer proxyHealthMutex.RUnlock()
|
||||||
|
// 构建健康信息映射
|
||||||
healthInfo := make(map[string]interface{})
|
healthInfo := make(map[string]interface{})
|
||||||
for proxy, health := range proxyHealthMaps {
|
for proxy, health := range proxyHealthMaps {
|
||||||
healthInfo[proxy] = map[string]interface{}{
|
healthInfo[proxy] = map[string]interface{}{
|
||||||
@ -473,7 +515,7 @@ func GetProxyHealth() *C.char {
|
|||||||
"is_healthy": health.IsHealthy,
|
"is_healthy": health.IsHealthy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 序列化为JSON
|
||||||
jsonData, err := json.Marshal(healthInfo)
|
jsonData, err := json.Marshal(healthInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err))
|
return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err))
|
||||||
@ -482,17 +524,18 @@ func GetProxyHealth() *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:代理类型管理器
|
// ProxyTypeManager 导出函数:代理类型管理器
|
||||||
//
|
//
|
||||||
//export ProxyTypeManager
|
//export ProxyTypeManager
|
||||||
func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char {
|
func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char {
|
||||||
|
// C字符串转换为Go字符串
|
||||||
goProxyType := C.GoString(proxyType)
|
goProxyType := C.GoString(proxyType)
|
||||||
goUsername := C.GoString(username)
|
goUsername := C.GoString(username)
|
||||||
goPassword := C.GoString(password)
|
goPassword := C.GoString(password)
|
||||||
goMachineCode := C.GoString(machineCode)
|
goMachineCode := C.GoString(machineCode)
|
||||||
|
|
||||||
log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType)
|
log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType)
|
||||||
|
// 调用代理类型管理器
|
||||||
proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode)
|
proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||||
@ -504,7 +547,7 @@ func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.cha
|
|||||||
return C.CString(proxyURL)
|
return C.CString(proxyURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:查询机器码
|
// GetMachineCode 导出函数:查询机器码
|
||||||
//
|
//
|
||||||
//export GetMachineCode
|
//export GetMachineCode
|
||||||
func GetMachineCode(tailCardSecret *C.char) *C.char {
|
func GetMachineCode(tailCardSecret *C.char) *C.char {
|
||||||
@ -530,7 +573,7 @@ func GetMachineCode(tailCardSecret *C.char) *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:充值卡密
|
// RechargeCard 导出函数:充值卡密
|
||||||
//
|
//
|
||||||
//export RechargeCard
|
//export RechargeCard
|
||||||
func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
|
func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
|
||||||
@ -563,7 +606,7 @@ func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:获取代理服务器列表
|
// GetProxies 导出函数:获取代理服务器列表
|
||||||
//
|
//
|
||||||
//export GetProxies
|
//export GetProxies
|
||||||
func GetProxies(machineCode *C.char) *C.char {
|
func GetProxies(machineCode *C.char) *C.char {
|
||||||
@ -595,7 +638,7 @@ func GetProxies(machineCode *C.char) *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:检查卡密是否过期
|
// CheckTailCardSecretExpired 导出函数:检查卡密是否过期
|
||||||
//
|
//
|
||||||
//export CheckTailCardSecretExpired
|
//export CheckTailCardSecretExpired
|
||||||
func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
||||||
@ -634,7 +677,7 @@ func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:初始化代理管理器
|
// InitProxyManager 导出函数:初始化代理管理器
|
||||||
//
|
//
|
||||||
//export InitProxyManager
|
//export InitProxyManager
|
||||||
func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
|
func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
|
||||||
@ -685,7 +728,7 @@ func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType
|
|||||||
return C.CString(string(jsonData))
|
return C.CString(string(jsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出函数:释放C字符串内存
|
// FreeCString 导出函数:释放C字符串内存
|
||||||
//
|
//
|
||||||
//export FreeCString
|
//export FreeCString
|
||||||
func FreeCString(str *C.char) {
|
func FreeCString(str *C.char) {
|
||||||
|
|||||||
BIN
xy/dll/xy.dll
Normal file
BIN
xy/dll/xy.dll
Normal file
Binary file not shown.
114
xy/dll/xy.h
Normal file
114
xy/dll/xy.h
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||||
|
|
||||||
|
/* package command-line-arguments */
|
||||||
|
|
||||||
|
|
||||||
|
#line 1 "cgo-builtin-export-prolog"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||||
|
extern size_t _GoStringLen(_GoString_ s);
|
||||||
|
extern const char *_GoStringPtr(_GoString_ s);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Start of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
#line 3 "main.go"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#line 1 "cgo-generated-wrapper"
|
||||||
|
|
||||||
|
|
||||||
|
/* End of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
/* Start of boilerplate cgo prologue. */
|
||||||
|
#line 1 "cgo-gcc-export-header-prolog"
|
||||||
|
|
||||||
|
#ifndef GO_CGO_PROLOGUE_H
|
||||||
|
#define GO_CGO_PROLOGUE_H
|
||||||
|
|
||||||
|
typedef signed char GoInt8;
|
||||||
|
typedef unsigned char GoUint8;
|
||||||
|
typedef short GoInt16;
|
||||||
|
typedef unsigned short GoUint16;
|
||||||
|
typedef int GoInt32;
|
||||||
|
typedef unsigned int GoUint32;
|
||||||
|
typedef long long GoInt64;
|
||||||
|
typedef unsigned long long GoUint64;
|
||||||
|
typedef GoInt64 GoInt;
|
||||||
|
typedef GoUint64 GoUint;
|
||||||
|
typedef size_t GoUintptr;
|
||||||
|
typedef float GoFloat32;
|
||||||
|
typedef double GoFloat64;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||||
|
#include <complex.h>
|
||||||
|
typedef _Fcomplex GoComplex64;
|
||||||
|
typedef _Dcomplex GoComplex128;
|
||||||
|
#else
|
||||||
|
#include <complex>
|
||||||
|
typedef std::complex<float> GoComplex64;
|
||||||
|
typedef std::complex<double> GoComplex128;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
typedef float _Complex GoComplex64;
|
||||||
|
typedef double _Complex GoComplex128;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
static assertion to make sure the file is being used on architecture
|
||||||
|
at least with matching size of GoInt.
|
||||||
|
*/
|
||||||
|
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef _GoString_ GoString;
|
||||||
|
#endif
|
||||||
|
typedef void *GoMap;
|
||||||
|
typedef void *GoChan;
|
||||||
|
typedef struct { void *t; void *v; } GoInterface;
|
||||||
|
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* End of boilerplate cgo prologue. */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern __declspec(dllexport) void FreeCString(char* str);
|
||||||
|
extern __declspec(dllexport) char* StartServer(char* configFile);
|
||||||
|
extern __declspec(dllexport) char* StopServer(void);
|
||||||
|
extern __declspec(dllexport) char* GetServerStatus(void);
|
||||||
|
extern __declspec(dllexport) char* GetServerAddress(void);
|
||||||
|
extern __declspec(dllexport) char* ReloadConfig(char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGoodsCreat(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteOtherTypeGoods(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGoodsPublish(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGoodsDownShelf(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGoodsFlash(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGoodsEditPrice(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGoodsEditStock(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteSelectGoodsListPrice(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteSelectShopListPrice(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteOpenExpressCompanies(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteOpenOrderShip(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteXyOrderSynchronization(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteGetGoodsDetail(char* bodyJson, char* configFile);
|
||||||
|
extern __declspec(dllexport) char* ExecuteCountOuterId(char* bodyJson, char* configFile);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user