package api import ( "fmt" "io" "net/http" "os" "path/filepath" "strconv" "strings" "time" "github.com/go-chi/chi/v5" "litepan/internal/domain" "litepan/internal/upload" ) type batchDeleteUploadTasksReq struct { TaskIDs []string `json:"task_ids"` DeleteUploadedFile bool `json:"delete_uploaded_file"` } func (h *Handler) createUploadTask(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(32 << 20); err != nil { writeErr(w, domain.Errorf(domain.CodeValidation, "解析上传表单失败")) return } accountID, err := strconv.ParseInt(r.FormValue("account_id"), 10, 64) if err != nil || accountID <= 0 { writeErr(w, domain.Errorf(domain.CodeValidation, "非法 account_id")) return } targetPath := r.FormValue("path") conflictPolicy := r.FormValue("conflict_policy") if conflictPolicy == "" { conflictPolicy = "overwrite" } file, header, err := r.FormFile("file") if err != nil { writeErr(w, domain.Errorf(domain.CodeValidation, "缺少上传文件")) return } defer file.Close() tempPath, total, err := saveUploadTemp(h.uploads.TempDir(), file, header.Filename) if err != nil { writeErr(w, err) return } if total < 0 { _ = os.Remove(tempPath) writeErr(w, domain.Errorf(domain.CodeValidation, "上传文件大小非法")) return } displayName := strings.TrimSpace(r.FormValue("display_name")) fileName := header.Filename if fileName == "" { fileName = filepath.Base(tempPath) } task, err := h.uploads.Create(r.Context(), upload.CreateParams{ ClientTaskID: r.FormValue("client_task_id"), AccountID: accountID, FileName: fileName, DisplayName: displayName, TargetPath: targetPath, TargetDisplayPath: r.FormValue("target_display_path"), LocalPath: tempPath, TotalBytes: total, ConflictPolicy: conflictPolicy, }) if err != nil { _ = os.Remove(tempPath) writeErr(w, err) return } writeJSON(w, http.StatusOK, Resp{Success: true, Message: "上传任务已创建", Data: task}) } func saveUploadTemp(dir string, src io.Reader, name string) (string, int64, error) { if err := os.MkdirAll(dir, 0o755); err != nil { return "", 0, domain.Wrap(domain.CodeInternal, err) } ext := filepath.Ext(name) path := filepath.Join(dir, fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)) out, err := os.Create(path) if err != nil { return "", 0, domain.Wrap(domain.CodeInternal, err) } defer out.Close() n, err := io.Copy(out, src) if err != nil { _ = os.Remove(path) return "", 0, domain.Wrap(domain.CodeInternal, err) } return path, n, nil } func (h *Handler) listUploadTasks(w http.ResponseWriter, r *http.Request) { var accountID int64 if raw := r.URL.Query().Get("account_id"); raw != "" { id, err := strconv.ParseInt(raw, 10, 64) if err != nil { writeErr(w, domain.Errorf(domain.CodeValidation, "非法 account_id")) return } accountID = id } tasks := h.uploads.List(r.Context(), accountID) writeJSON(w, http.StatusOK, Resp{Success: true, Message: "获取上传任务成功", Data: tasks}) } func (h *Handler) streamUploadTasks(w http.ResponseWriter, r *http.Request) { s, err := newSSEWriter(w) if err != nil { writeErr(w, err) return } ch := h.uploads.Subscribe() defer h.uploads.Unsubscribe(ch) streamSSEByteMessages(r, s, "tasks", h.uploads.SnapshotPayload(), ch) } func (h *Handler) getUploadTask(w http.ResponseWriter, r *http.Request) { taskID := chi.URLParam(r, "taskID") task, ok := h.uploads.Get(r.Context(), taskID) if !ok { writeErr(w, domain.Errorf(domain.CodeNotFound, "上传任务不存在")) return } writeJSON(w, http.StatusOK, Resp{Success: true, Message: "获取上传任务成功", Data: task}) } func (h *Handler) pauseUploadTask(w http.ResponseWriter, r *http.Request) { taskID := chi.URLParam(r, "taskID") task, ok := h.uploads.Pause(r.Context(), taskID) if !ok { writeErr(w, domain.Errorf(domain.CodeNotFound, "上传任务不存在")) return } writeJSON(w, http.StatusOK, Resp{Success: true, Message: "上传任务已暂停", Data: task}) } func (h *Handler) resumeUploadTask(w http.ResponseWriter, r *http.Request) { taskID := chi.URLParam(r, "taskID") task, ok := h.uploads.Resume(r.Context(), taskID) if !ok { writeErr(w, domain.Errorf(domain.CodeNotFound, "上传任务不存在")) return } writeJSON(w, http.StatusOK, Resp{Success: true, Message: "上传任务已继续", Data: task}) } func (h *Handler) deleteUploadTask(w http.ResponseWriter, r *http.Request) { taskID := chi.URLParam(r, "taskID") deleteFile := r.URL.Query().Get("delete_uploaded_file") == "true" found, err := h.uploads.Delete(r.Context(), taskID, deleteFile) if !found { writeErr(w, domain.Errorf(domain.CodeNotFound, "上传任务不存在")) return } if err != nil { writeErr(w, err) return } writeJSON(w, http.StatusOK, Resp{Success: true, Message: "上传任务已删除"}) } func (h *Handler) batchDeleteUploadTasks(w http.ResponseWriter, r *http.Request) { var req batchDeleteUploadTasksReq if err := decodeJSON(r, &req); err != nil { writeErr(w, err) return } result := h.uploads.BatchDelete(r.Context(), req.TaskIDs, req.DeleteUploadedFile) writeJSON(w, http.StatusOK, Resp{Success: true, Message: "批量删除上传任务成功", Data: result}) }