Files
X-Agents/server/internal/handler/upload_handler.go
DESKTOP-72TV0V4\caoxiaozhu fdd6b2c17d fix: 优化后端各模块 handler
- database_handler, knowledge_handler, model_handler
- neo4j_handler, sub_table_handler
- system_handler, upload_handler
- knowledge_service, upload_service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:26:04 +08:00

162 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}