package handler import ( "io" "mime" "net/http" "path/filepath" "github.com/gin-gonic/gin" "x-agents/server/internal/repository" "x-agents/server/internal/service" ) type UploadHandler struct { uploadService *service.UploadService knowledgeRepo *repository.KnowledgeRepository } func NewUploadHandler(uploadService *service.UploadService, knowledgeRepo *repository.KnowledgeRepository) *UploadHandler { return &UploadHandler{uploadService: uploadService, knowledgeRepo: knowledgeRepo} } // @Summary 上传文件 // @Description 上传文件到服务器(本地存储或MinIO) // @Tags 文件上传 // @Accept multipart/form-data // @Produce json // @Param file formData file true "要上传的文件" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/file_upload [post] func (h *UploadHandler) Upload(c *gin.Context) { file, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "No file uploaded"}) return } // 检查文件大小(最大 100MB) if file.Size > 100*1024*1024 { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "File too large (max 100MB)"}) return } result, err := h.uploadService.Upload(file) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } if !result.Success { c.JSON(http.StatusInternalServerError, result) return } c.JSON(http.StatusOK, result) } // @Summary 删除文件 // @Description 删除指定文件 // @Tags 文件上传 // @Accept json // @Produce json // @Param filename path string true "文件名" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/file_upload/{filename} [delete] func (h *UploadHandler) Delete(c *gin.Context) { filename := c.Param("filename") if filename == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Filename is required"}) return } if err := h.uploadService.DeleteFile(filename); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "File deleted"}) } // @Summary 代理文件访问 // @Description 代理访问文件,解决 MinIO 内网和 HTTPS 问题 // @Tags 文件上传 // @Accept json // @Produce octet-stream // @Param key query string true "文件Key" // @Param kb_id query string false "知识库ID" // @Success 200 {file} binary // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/file_proxy [get] func (h *UploadHandler) ProxyFile(c *gin.Context) { fileKey := c.Query("key") kbID := c.Query("kb_id") if fileKey == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "File key is required"}) return } var content io.Reader var contentType string // 如果提供了知识库 ID,使用知识库的存储配置 if kbID != "" && h.knowledgeRepo != nil { kb, err := h.knowledgeRepo.FindByID(kbID) if err == nil && kb.StorageConfig.Type == "minio" { reader, ct, err := h.uploadService.GetFileContentWithConfig(fileKey, kb.StorageConfig) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } content = reader contentType = ct defer reader.Close() } else { // 使用全局配置 reader, ct, err := h.uploadService.GetFileContent(fileKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } content = reader contentType = ct defer reader.Close() } } else { // 使用全局配置 reader, ct, err := h.uploadService.GetFileContent(fileKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } content = reader contentType = ct defer reader.Close() } // 设置响应头 // 如果 Content-Type 是 application/octet-stream,根据文件扩展名推断 if contentType == "application/octet-stream" { ext := filepath.Ext(fileKey) if detected := mime.TypeByExtension(ext); detected != "" { contentType = detected } } if contentType != "" { c.Header("Content-Type", contentType) } else { c.Header("Content-Type", "application/octet-stream") } c.Header("Content-Disposition", "inline") // 直接流式输出文件内容 c.DataFromReader(http.StatusOK, -1, contentType, content, nil) }