package handlers import ( "Vali-Tools/utils" "bytes" "context" "encoding/base64" "encoding/json" "fmt" "github.com/wailsapp/wails/v2/pkg/runtime" "io" "mime/multipart" "net/http" "os" "path/filepath" "strings" ) // ProcessResult 处理结果结构体 type ProcessResult struct { Success bool `json:"success"` Message string `json:"message"` Progress int `json:"progress"` } // ProcessCallback 处理过程回调函数类型 type ProcessCallback func(result ProcessResult) type DirectoryHandler struct { ctx context.Context } func NewDirectoryHandler(ctx context.Context) *DirectoryHandler { return &DirectoryHandler{ctx: ctx} } // HandlerDirectory 处理目录复制的主要方法 func (dh *DirectoryHandler) HandlerDirectory(sourceDir, targetDir string) error { // 检查源目录是否存在 if _, err := os.Stat(sourceDir); os.IsNotExist(err) { return fmt.Errorf("源目录不存在: %s", sourceDir) } // 检查目标目录,如果不存在则创建 if _, err := os.Stat(targetDir); os.IsNotExist(err) { // 源目录不存在时不需要创建目录,但目标目录可以创建 err := os.MkdirAll(targetDir, os.ModePerm) if err != nil { return fmt.Errorf("创建目标目录失败: %v", err) } } // 执行具体的处理逻辑 return dh.processDirectories(sourceDir, targetDir) } // processDirectories 实际处理目录的逻辑 func (dh *DirectoryHandler) processDirectories(sourceDir, targetDir string) error { // 发送初始进度 dh.sendProgress(ProcessResult{ Success: true, Message: "开始处理目录...", Progress: 0, }) // 遍历和处理逻辑 err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error { if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("访问路径出错: %v", err), Progress: 0, }) return err } fmt.Printf("正在处理: %s\n", path) // 处理 800x800 目录的逻辑 if info.IsDir() && strings.Contains(info.Name(), "800x800") { fmt.Printf("👍👍👍符合条件: %s\n", path) // 获取上级目录名称 parentDirName := filepath.Base(filepath.Dir(path)) // 在目标目录中创建以父目录命名的子目录 newTargetDir := filepath.Join(targetDir, parentDirName) // 检查目标目录,如果不存在则创建 if _, err := os.Stat(newTargetDir); os.IsNotExist(err) { // 源目录不存在时不需要创建目录,但目标目录可以创建 err := os.MkdirAll(newTargetDir, os.ModePerm) if err != nil { return fmt.Errorf("创建目标目录失败: %v", err) } } dh.sendProgress(ProcessResult{ Success: true, Message: fmt.Sprintf("找到目录: %s,将在目标目录创建子目录: %s", info.Name(), parentDirName), Progress: 50, }) // 执行复制操作到新创建的子目录 err := dh.copyFilesFromDir(path, newTargetDir) if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("复制文件到:%v 失败: %v", newTargetDir, err), Progress: 50, }) return err } } return nil }) // 发送完成进度 if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("处理失败: %v", err), Progress: 100, }) } else { dh.sendProgress(ProcessResult{ Success: true, Message: "处理完成", Progress: 100, }) } return err } // copyFilesFromDir 从指定目录复制所有文件到目标目录 func (dh *DirectoryHandler) copyFilesFromDir(sourceDir, targetDir string) error { entries, err := os.ReadDir(sourceDir) if err != nil { return fmt.Errorf("读取目录失败 %s: %v", sourceDir, err) } for _, entry := range entries { // 只处理文件,跳过子目录 if entry.IsDir() { continue // 忽略目录 } // 检查是否为有效的图片文件 if !dh.isValidImageFile(entry.Name()) { continue // 跳过非图片文件 } sourcePath := filepath.Join(sourceDir, entry.Name()) targetPath := filepath.Join(targetDir, entry.Name()) err := dh.copyFile(sourcePath, targetPath) if err != nil { return fmt.Errorf("复制文件失败 %s 到 %s: %v", sourcePath, targetPath, err) } } return nil } // copyFile 复制单个文件 func (dh *DirectoryHandler) copyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer sourceFile.Close() // 创建目标文件 destFile, err := os.Create(dst) if err != nil { return err } defer destFile.Close() // 复制文件内容 _, err = io.Copy(destFile, sourceFile) if err != nil { return err } // 同步文件内容到磁盘 return destFile.Sync() } // isValidImageFile 检查文件是否为有效的图片文件 func (dh *DirectoryHandler) isValidImageFile(filename string) bool { validExtensions := []string{".avif", ".bmp", ".png", ".jpg", ".jpeg"} ext := strings.ToLower(filepath.Ext(filename)) for _, validExt := range validExtensions { if ext == validExt { return true } } return false } type ImageResult struct { GoodsArtNo string `json:"goods_art_no"` ImagePath string `json:"image_path"` Category string `json:"category"` Description string `json:"description"` Price string `json:"price"` ImageBase64 string `json:"image_base64"` } // UploadSkuResult SKU图片上传结果 type UploadSkuResult struct { FileName string `json:"FileName"` ImageType string `json:"ImageType"` Size int64 `json:"Size"` } // UploadSkuResponse SKU图片上传响应 type UploadSkuResponse struct { Data []UploadSkuResult `json:"data"` Success bool `json:"success"` Code int `json:"code"` } // BatchFileItem 批量上传文件项 type BatchFileItem struct { FilePath string ImageType string FileName string } // ActionNamesResponse 动作名称响应 type ActionNamesResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data []string `json:"data"` } // sendProgress 发送进度更新 func (dh *DirectoryHandler) sendProgress(result ProcessResult) { runtime.EventsEmit(dh.ctx, "copy800-progress", result) } // HandlerOutPutDirectory 处理输出目录的主要方法 func (dh *DirectoryHandler) HandlerOutPutDirectory(sourceDir string) ([]ImageResult, error) { // 检查源目录是否存在 if _, err := os.Stat(sourceDir); os.IsNotExist(err) { return nil, fmt.Errorf("源目录不存在: %s", sourceDir) } var Results []ImageResult // 执行具体的处理逻辑 // 遍历和处理逻辑 err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } fmt.Printf("正在处理: %s\n", path) // 处理 800x800 目录的逻辑 if info.IsDir() && strings.Contains(info.Name(), "800x800") { fmt.Printf("👍👍👍符合条件: %s\n", path) // 获取父目录名作为 goods_art_no parentDirName := filepath.Base(filepath.Dir(path)) goodsArtNo := parentDirName // 查找该目录下的第一张有效图片 imagePath, err := dh.findFirstImageInDir(path) if err == nil { toBase64, err := dh.ImageToBase64(imagePath) if err != nil { toBase64 = "" } // 添加到结果数组 Results = append(Results, ImageResult{ GoodsArtNo: goodsArtNo, ImagePath: imagePath, Description: "", Category: "", Price: "0", ImageBase64: toBase64, }) } } return nil }) if err != nil { return nil, err } return Results, nil } // findFirstImageInDir 查找目录下的第一张有效图片 func (dh *DirectoryHandler) findFirstImageInDir(dirPath string) (string, error) { entries, err := os.ReadDir(dirPath) if err != nil { return "", fmt.Errorf("读取目录失败: %v", err) } for _, entry := range entries { if entry.IsDir() { continue } if dh.isValidImageFile(entry.Name()) { return filepath.Join(dirPath, entry.Name()), nil } } return "", fmt.Errorf("未找到有效图片文件") } // ImageToBase64 将图片文件转换为Base64编码 func (dh *DirectoryHandler) ImageToBase64(imagePath string) (string, error) { // 读取图片文件 fileData, err := os.ReadFile(imagePath) if err != nil { return "", fmt.Errorf("读取图片文件失败: %v", err) } // 获取文件的MIME类型 mimeType := getMimeType(imagePath) // 将文件数据编码为Base64 encoded := base64.StdEncoding.EncodeToString(fileData) // 返回带有数据URI方案的Base64字符串 return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded), nil } // getMimeType 根据文件扩展名获取MIME类型 func getMimeType(filePath string) string { ext := strings.ToLower(filePath[strings.LastIndex(filePath, "."):]) mimeTypes := map[string]string{ ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".webp": "image/webp", } if mime, exists := mimeTypes[ext]; exists { return mime } return "application/octet-stream" } // GetCurrentWorkingDirectory 获取当前工作目录 func (dh *DirectoryHandler) GetCurrentWorkingDirectory() (string, error) { return os.Getwd() } // GetApplicationDirectory 获取应用程序所在目录 func (dh *DirectoryHandler) GetApplicationDirectory() (string, error) { execPath, err := os.Executable() if err != nil { return "", err } return filepath.Dir(execPath), nil } // HandlerUploadSkuImages 处理SKU图片上传 func (dh *DirectoryHandler) HandlerUploadSkuImages(sourceDir string) error { // 检查源目录是否存在 if _, err := os.Stat(sourceDir); os.IsNotExist(err) { return fmt.Errorf("源目录不存在: %s", sourceDir) } // 发送初始进度 dh.sendProgress(ProcessResult{ Success: true, Message: "开始扫描目录...", Progress: 0, }) // 遍历和处理逻辑 err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error { if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("访问路径出错: %v", err), Progress: 0, }) return err } // 处理 800x800 目录的逻辑 if info.IsDir() && strings.Contains(info.Name(), "800x800") { fmt.Printf("👍👍👍找到800x800目录: %s\n", path) // 获取上级目录名称作为Key参数 parentDirName := filepath.Base(filepath.Dir(path)) // 读取800x800目录下的所有文件 files, err := os.ReadDir(path) if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("读取目录失败 %s: %v", path, err), Progress: 0, }) return err } // 尝试从接口获取动作名称列表 var actionNames []string actionNamesResponse, err := dh.getActionNamesByGoods(parentDirName) if err == nil && actionNamesResponse.Code == 0 && len(actionNamesResponse.Data) > 0 { actionNames = actionNamesResponse.Data fmt.Printf("📋 获取到动作名称列表 [%s]: %v\n", parentDirName, actionNames) } else { fmt.Printf("️ 获取动作名称失败或为空,将使用默认逻辑 [%s]\n", parentDirName) } // 收集所有有效的图片文件 var validFiles []BatchFileItem fileIndex := 0 for _, file := range files { if file.IsDir() { continue // 跳过子目录 } // 检查是否为有效的图片文件 if !dh.isValidImageFile(file.Name()) { continue // 跳过非图片文件 } filePath := filepath.Join(path, file.Name()) // 确定ImageType:如果接口返回了数据且索引有效,则使用接口返回的值 var imageType string if len(actionNames) > 0 && fileIndex < len(actionNames) { imageType = actionNames[fileIndex] fmt.Printf("️ 使用接口返回的ImageType [%s]: %s\n", file.Name(), imageType) } else { // 使用文件名(不含扩展名)作为ImageType参数 imageType = strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) fmt.Printf("🏷️ 使用默认ImageType [%s]: %s\n", file.Name(), imageType) } fileIndex++ validFiles = append(validFiles, BatchFileItem{ FilePath: filePath, ImageType: imageType, FileName: file.Name(), }) } // 如果没有有效文件,跳过 if len(validFiles) == 0 { fmt.Printf("⚠️ 目录 %s 中没有有效的图片文件\n", path) return nil } fmt.Printf("📦 开始批量上传 [%s]: 共 %d 个文件\n", parentDirName, len(validFiles)) // 批量上传该目录下的所有文件(一次性请求) var response UploadSkuResponse err = dh.uploadSkuImagesBatch(validFiles, parentDirName, &response) if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("[%s] 批量上传失败: %v", parentDirName, err), Progress: 0, }) fmt.Printf("❌ 批量上传失败: %v\n", err) } else if !response.Success { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("[%s] 批量上传失败: API返回错误 code=%d", parentDirName, response.Code), Progress: 0, }) fmt.Printf("❌ 批量上传失败: API返回错误 code=%d\n", response.Code) } else { dh.sendProgress(ProcessResult{ Success: true, Message: fmt.Sprintf("[%s] 批量上传成功: 共 %d 个文件", parentDirName, len(response.Data)), Progress: 0, }) fmt.Printf("✅ 批量上传成功: %+v\n", response) } } return nil }) // 发送完成进度 if err != nil { dh.sendProgress(ProcessResult{ Success: false, Message: fmt.Sprintf("处理失败: %v", err), Progress: 100, }) } else { dh.sendProgress(ProcessResult{ Success: true, Message: "所有文件处理完成", Progress: 100, }) } return err } // uploadSkuImagesBatch 批量上传SKU图片(同一个Key下的多个文件一次性上传) func (dh *DirectoryHandler) uploadSkuImagesBatch(files []BatchFileItem, key string, result interface{}) error { // 创建multipart表单 var buf bytes.Buffer writer := multipart.NewWriter(&buf) // 添加Key字段 err := writer.WriteField("Key", key) if err != nil { return fmt.Errorf("写入Key字段失败: %w", err) } // 添加多个ImageType和File字段对 for i, file := range files { // 添加ImageType字段 err = writer.WriteField("ImageType", file.ImageType) if err != nil { return fmt.Errorf("写入ImageType字段失败 [%d]: %w", i, err) } // 打开文件 fileHandle, err := os.Open(file.FilePath) if err != nil { return fmt.Errorf("打开文件失败 [%s]: %w", file.FilePath, err) } // 添加文件字段 fileWriter, err := writer.CreateFormFile("File", file.FileName) if err != nil { fileHandle.Close() return fmt.Errorf("创建表单文件字段失败 [%s]: %w", file.FileName, err) } // 复制文件内容到表单 _, err = io.Copy(fileWriter, fileHandle) fileHandle.Close() if err != nil { return fmt.Errorf("复制文件内容失败 [%s]: %w", file.FileName, err) } fmt.Printf("📤 已添加文件 [%d/%d]: %s (ImageType: %s)\n", i+1, len(files), file.FileName, file.ImageType) } // 关闭表单写入器 err = writer.Close() if err != nil { return fmt.Errorf("关闭表单写入器失败: %w", err) } // 创建请求 req, err := http.NewRequestWithContext(dh.ctx, "POST", utils.UploadSkuImage, &buf) if err != nil { return fmt.Errorf("创建请求失败: %w", err) } // 设置内容类型为multipart表单 req.Header.Set("Content-Type", writer.FormDataContentType()) // 创建HTTP客户端并执行请求 client := &http.Client{} resp, err := client.Do(req) if err != nil { return fmt.Errorf("请求失败: %w", err) } defer resp.Body.Close() // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("读取响应体失败: %w", err) } // 检查HTTP状态码 if resp.StatusCode >= 400 { return fmt.Errorf("HTTP请求失败 status=%d body=%s", resp.StatusCode, string(body)) } // 解析JSON响应 if err := json.Unmarshal(body, result); err != nil { return fmt.Errorf("解析JSON失败: %w, body=%s", err, string(body)) } return nil } // getActionNamesByGoods 获取动作名称列表 func (dh *DirectoryHandler) getActionNamesByGoods(goodsArtNo string) (*ActionNamesResponse, error) { // 创建请求 url := fmt.Sprintf("%s?goods_art_no=%s", utils.GetActionNames, goodsArtNo) req, err := http.NewRequestWithContext(dh.ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("创建请求失败: %w", err) } // 设置请求头 req.Header.Set("Content-Type", "application/json") // 创建HTTP客户端并执行请求 client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("请求失败: %w", err) } defer resp.Body.Close() // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应体失败: %w", err) } // 检查HTTP状态码 if resp.StatusCode >= 400 { return nil, fmt.Errorf("HTTP请求失败 status=%d body=%s", resp.StatusCode, string(body)) } // 解析JSON响应 var response ActionNamesResponse if err := json.Unmarshal(body, &response); err != nil { return nil, fmt.Errorf("解析JSON失败: %w, body=%s", err, string(body)) } return &response, nil }