Files
X-Agents/server/internal/handler/upload_handler.go
DESKTOP-72TV0V4\caoxiaozhu 4017c0e1df feat: 优化文件上传服务
- 添加文件大小和类型验证
- 支持更多文件格式
- 优化文件存储逻辑

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 23:02:15 +08:00

134 lines
3.6 KiB
Go
Raw 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}
}
// Upload 上传文件
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)
}
// 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"})
}
// ProxyFile 代理文件访问(解决 MinIO 内网地址和 HTTPS 问题)
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)
}