package main import ( "encoding/json" "fmt" "net/http" "os" "path/filepath" "strings" "time" ) // 请求结构 type SaveImagesRequest struct { Title string `json:"title"` Imgs map[string]string `json:"imgs"` } // 下载详情 type DownloadDetail struct { Key string `json:"key"` URL string `json:"url"` Status string `json:"status"` // success, skipped, failed Message string `json:"message"` SavedAs string `json:"saved_as,omitempty"` } // 响应结构 type SaveImagesResponse struct { Success bool `json:"success"` Message string `json:"message"` Title string `json:"title"` Folder string `json:"folder"` JsonPath string `json:"json_path"` Total int `json:"total"` Saved int `json:"saved"` Skipped int `json:"skipped"` Failed int `json:"failed"` Details []DownloadDetail `json:"details"` } func saveImagesHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "只支持POST请求", http.StatusMethodNotAllowed) return } // 解析请求 var req SaveImagesRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "请求格式错误: "+err.Error(), http.StatusBadRequest) return } // 验证参数 if req.Title == "" { http.Error(w, "标题不能为空", http.StatusBadRequest) return } if len(req.Imgs) == 0 { http.Error(w, "图片列表不能为空", http.StatusBadRequest) return } // 处理下载 resp, err := processDownload(&req) if err != nil { http.Error(w, "处理失败: "+err.Error(), http.StatusInternalServerError) return } // 返回响应 w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) } func processDownload(req *SaveImagesRequest) (*SaveImagesResponse, error) { // 清理标题(移除非法字符) cleanTitle := cleanFilename(req.Title) // 创建目录 comicDir := filepath.Join(*downloadDir, cleanTitle) if err := os.MkdirAll(comicDir, 0755); err != nil { return nil, fmt.Errorf("创建目录失败: %v", err) } // JSON文件路径 jsonFilename := cleanTitle + ".json" jsonPath := filepath.Join(comicDir, jsonFilename) // 检查现有文件 existingFiles := make(map[string]bool) files, _ := os.ReadDir(comicDir) for _, file := range files { if !file.IsDir() { filename := file.Name() if strings.HasSuffix(filename, ".json") { continue } // 提取序号(去掉扩展名) ext := filepath.Ext(filename) key := strings.TrimSuffix(filename, ext) existingFiles[key] = true } } // 准备下载 details := make([]DownloadDetail, 0, len(req.Imgs)) needDownload := make(map[string]string) for key, url := range req.Imgs { // 获取文件扩展名 ext := getFileExtension(url) filename := key + ext if existingFiles[key] { // 文件已存在,跳过 details = append(details, DownloadDetail{ Key: key, URL: url, Status: "skipped", Message: "文件已存在", SavedAs: filename, }) continue } // 需要下载 needDownload[key] = url details = append(details, DownloadDetail{ Key: key, URL: url, Status: "pending", Message: "等待下载", }) } // 下载图片 var saved, failed int if len(needDownload) > 0 { successMap, errors := downloadImages(needDownload, comicDir) // 使用 successMap 来检查哪些下载成功 // 虽然我们不直接使用 successMap,但它用于 downloadImages 函数的返回值 _ = successMap // 明确标记为使用(避免未使用警告) // 更新下载详情 for i, detail := range details { if detail.Status == "pending" { if err, ok := errors[detail.Key]; ok { details[i].Status = "failed" details[i].Message = err.Error() failed++ } else { ext := getFileExtension(detail.URL) filename := detail.Key + ext details[i].Status = "success" details[i].Message = "下载成功" details[i].SavedAs = filename saved++ } } } } // 保存JSON文件 skipped := len(req.Imgs) - saved - failed finalImgs := make(map[string]string) for key, url := range req.Imgs { finalImgs[key] = url } saveJSON(comicDir, jsonFilename, req.Title, finalImgs) // 构建响应 return &SaveImagesResponse{ Success: failed == 0, Message: fmt.Sprintf("下载完成: 新增%d张, 跳过%d张, 失败%d张", saved, skipped, failed), Title: req.Title, Folder: comicDir, JsonPath: jsonPath, Total: len(req.Imgs), Saved: saved, Skipped: skipped, Failed: failed, Details: details, }, nil } func saveJSON(dir, filename, title string, imgs map[string]string) error { data := map[string]interface{}{ "title": title, "imgs": imgs, "created_at": time.Now().Format("2006-01-02 15:04:05"), "updated_at": time.Now().Format("2006-01-02 15:04:05"), } jsonData, err := json.MarshalIndent(data, "", " ") if err != nil { return err } path := filepath.Join(dir, filename) return os.WriteFile(path, jsonData, 0644) } func cleanFilename(filename string) string { illegalChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|", "\n", "\r", "\t"} result := filename for _, char := range illegalChars { result = strings.ReplaceAll(result, char, "_") } if len(result) > 200 { result = result[:200] } return strings.TrimSpace(result) } func getFileExtension(url string) string { // 默认扩展名 ext := ".jpg" // 从URL提取扩展名 if idx := strings.LastIndex(url, "."); idx != -1 { fileExt := strings.ToLower(url[idx:]) // 检查是否为常见图片格式 validExts := []string{".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff"} for _, validExt := range validExts { if strings.HasPrefix(fileExt, validExt) { ext = validExt break } } } return ext }