package controller import ( "net/http" "strconv" "strings" "github.com/go-chi/chi" httputil "github.com/goodrain/rainbond/util/http" "github.com/sirupsen/logrus" ) // ChunkUploadController 分片上传控制器 type ChunkUploadController struct{} // InitUpload 初始化上传会话 // POST /package_build/component/events/{eventID}/upload/init func (c *ChunkUploadController) InitUpload(w http.ResponseWriter, r *http.Request) { SetPackageBuildCORSHeaders(w, r) eventID := strings.TrimSpace(chi.URLParam(r, "eventID")) if eventID == "" { httputil.ReturnError(r, w, 400, "eventID is required") return } // 解析请求参数 var req struct { FileName string `json:"file_name" validate:"required"` FileSize int64 `json:"file_size" validate:"required"` FileMD5 string `json:"file_md5"` ChunkSize int `json:"chunk_size"` } if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil) { return } // 初始化上传会话 manager := GetChunkUploadManager() session, err := manager.InitUploadSession(eventID, req.FileName, req.FileSize, req.FileMD5, req.ChunkSize) if err != nil { logrus.Errorf("Failed to init upload session: %v", err) httputil.ReturnError(r, w, 500, "Failed to initialize upload: "+err.Error()) return } // 解析已上传的分片 uploadedChunks := manager.parseUploadedChunks(session.UploadedChunks) response := map[string]interface{}{ "session_id": session.ID, "chunk_size": session.ChunkSize, "total_chunks": session.TotalChunks, "uploaded_chunks": uploadedChunks, "status": session.Status, } logrus.Infof("Initialized upload session: %s for event: %s, file: %s", session.ID, eventID, req.FileName) httputil.ReturnSuccess(r, w, response) } // UploadChunk 上传分片 // POST /package_build/component/events/{eventID}/upload/chunk func (c *ChunkUploadController) UploadChunk(w http.ResponseWriter, r *http.Request) { SetPackageBuildCORSHeaders(w, r) eventID := strings.TrimSpace(chi.URLParam(r, "eventID")) if eventID == "" { httputil.ReturnError(r, w, 400, "eventID is required") return } // 解析表单参数 sessionID := r.FormValue("session_id") chunkIndexStr := r.FormValue("chunk_index") if sessionID == "" || chunkIndexStr == "" { httputil.ReturnError(r, w, 400, "session_id and chunk_index are required") return } chunkIndex, err := strconv.Atoi(chunkIndexStr) if err != nil { httputil.ReturnError(r, w, 400, "invalid chunk_index") return } // 获取上传的文件 reader, header, err := r.FormFile("file") if err != nil { logrus.Errorf("Failed to parse chunk file: %v", err) httputil.ReturnError(r, w, 400, "Failed to parse chunk file") return } defer reader.Close() logrus.Debugf("Receiving chunk %d for session %s, size: %d", chunkIndex, sessionID, header.Size) // 保存分片 manager := GetChunkUploadManager() if err := manager.SaveChunk(sessionID, chunkIndex, reader); err != nil { logrus.Errorf("Failed to save chunk: %v", err) httputil.ReturnError(r, w, 500, "Failed to save chunk: "+err.Error()) return } // 获取最新状态 status, err := manager.GetUploadStatus(sessionID) if err != nil { logrus.Errorf("Failed to get upload status: %v", err) httputil.ReturnError(r, w, 500, "Failed to get status") return } response := map[string]interface{}{ "session_id": sessionID, "chunk_index": chunkIndex, "received_size": header.Size, "uploaded_chunks": status.UploadedChunks, "progress": status.Progress, } httputil.ReturnSuccess(r, w, response) } // CompleteUpload 完成上传,合并分片 // POST /package_build/component/events/{eventID}/upload/complete func (c *ChunkUploadController) CompleteUpload(w http.ResponseWriter, r *http.Request) { SetPackageBuildCORSHeaders(w, r) eventID := strings.TrimSpace(chi.URLParam(r, "eventID")) if eventID == "" { httputil.ReturnError(r, w, 400, "eventID is required") return } // 解析请求参数 var req struct { SessionID string `json:"session_id" validate:"required"` } if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &req, nil) { return } // 完成上传 manager := GetChunkUploadManager() filePath, err := manager.CompleteUpload(req.SessionID) if err != nil { logrus.Errorf("Failed to complete upload: %v", err) httputil.ReturnError(r, w, 500, "Failed to complete upload: "+err.Error()) return } response := map[string]interface{}{ "file_path": filePath, "status": "completed", } logrus.Infof("Upload completed for session: %s, file: %s", req.SessionID, filePath) httputil.ReturnSuccess(r, w, response) } // GetUploadStatus 查询上传状态 // GET /package_build/component/events/{eventID}/upload/status/{sessionID} func (c *ChunkUploadController) GetUploadStatus(w http.ResponseWriter, r *http.Request) { SetPackageBuildCORSHeaders(w, r) sessionID := strings.TrimSpace(chi.URLParam(r, "sessionID")) if sessionID == "" { httputil.ReturnError(r, w, 400, "sessionID is required") return } manager := GetChunkUploadManager() status, err := manager.GetUploadStatus(sessionID) if err != nil { logrus.Errorf("Failed to get upload status: %v", err) httputil.ReturnError(r, w, 404, "Session not found") return } httputil.ReturnSuccess(r, w, status) } // CancelUpload 取消上传 // DELETE /package_build/component/events/{eventID}/upload/{sessionID} func (c *ChunkUploadController) CancelUpload(w http.ResponseWriter, r *http.Request) { SetPackageBuildCORSHeaders(w, r) sessionID := strings.TrimSpace(chi.URLParam(r, "sessionID")) if sessionID == "" { httputil.ReturnError(r, w, 400, "sessionID is required") return } manager := GetChunkUploadManager() if err := manager.CancelUpload(sessionID); err != nil { logrus.Errorf("Failed to cancel upload: %v", err) httputil.ReturnError(r, w, 500, "Failed to cancel upload: "+err.Error()) return } logrus.Infof("Cancelled upload session: %s", sessionID) httputil.ReturnSuccess(r, w, map[string]string{"message": "Upload cancelled"}) } // HandleOptions 处理 CORS 预检请求 func (c *ChunkUploadController) HandleOptions(w http.ResponseWriter, r *http.Request) { SetPackageBuildCORSHeaders(w, r) w.WriteHeader(http.StatusOK) }