// Package handlers 实现文件上传相关的API请求处理器 // 支持图片、视频、音频、文档等文件上传 package handlers import ( "excel-api/config" "excel-api/utils" "fmt" "os" "path/filepath" "strings" "time" "github.com/gin-gonic/gin" ) // allowedExtensions 允许上传的文件扩展名白名单 var allowedExtensions = map[string]bool{ // 图片格式 ".png": true, ".jpg": true, ".jpeg": true, ".gif": true, ".webp": true, ".svg": true, // 视频格式 ".mp4": true, ".webm": true, ".mov": true, // 音频格式 ".mp3": true, ".wav": true, ".ogg": true, ".flac": true, // 压缩包格式 ".zip": true, ".rar": true, ".7z": true, ".tar": true, ".gz": true, // 文档格式 ".pdf": true, ".doc": true, ".docx": true, ".xls": true, ".xlsx": true, ".ppt": true, ".pptx": true, ".txt": true, ".csv": true, } // DetectFileType 根据文件扩展名自动检测文件类型 // 返回值: image/video/audio/archive/file func DetectFileType(filename string) string { ext := strings.ToLower(filepath.Ext(filename)) switch ext { case ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg": return "image" case ".mp4", ".webm", ".mov": return "video" case ".mp3", ".wav", ".ogg", ".flac": return "audio" case ".zip", ".rar", ".7z", ".tar", ".gz": return "archive" default: return "file" } } // UploadResponse 文件上传响应结构体 type UploadResponse struct { URL string `json:"url"` // 文件访问URL Name string `json:"name"` // 原始文件名 Size int64 `json:"size"` // 文件大小(字节) MimeType string `json:"mimeType"` // 文件MIME类型 FileType string `json:"fileType"` // 自动检测的文件类型 } // Upload 处理文件上传请求 // 请求方式: POST // 路径: /api/upload // 请求体: multipart/form-data,字段名为 "file" func Upload(c *gin.Context) { // 获取上传的文件 file, err := c.FormFile("file") if err != nil { utils.Error(c, 400, "请选择要上传的文件") return } // 验证文件扩展名 ext := strings.ToLower(filepath.Ext(file.Filename)) if !allowedExtensions[ext] { utils.Error(c, 400, "不支持的文件格式: "+ext) return } // 限制文件大小(最大 100MB) const maxFileSize = 100 * 1024 * 1024 if file.Size > maxFileSize { utils.Error(c, 400, "文件大小超过限制(最大100MB)") return } // 确定存储路径 storagePath := config.App.Storage.Local.Path if storagePath == "" { storagePath = "./uploads" } // 按日期创建子目录:uploads/2024/01/ dateDir := time.Now().Format("2006/01") uploadDir := filepath.Join(storagePath, dateDir) // 创建存储目录(如果不存在) if err := os.MkdirAll(uploadDir, 0755); err != nil { utils.Error(c, 500, "创建上传目录失败") return } // 生成唯一文件名:时间戳 + 随机数 + 原始扩展名 filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext) filePath := filepath.Join(uploadDir, filename) // 保存文件到磁盘 if err := c.SaveUploadedFile(file, filePath); err != nil { utils.Error(c, 500, "保存文件失败") return } // 检测文件MIME类型 mimeType := file.Header.Get("Content-Type") if mimeType == "" { mimeType = "application/octet-stream" } // 自动检测文件类型 fileType := DetectFileType(file.Filename) // 返回文件访问URL(相对路径) url := fmt.Sprintf("/uploads/%s/%s", dateDir, filename) utils.Success(c, UploadResponse{ URL: url, Name: file.Filename, Size: file.Size, MimeType: mimeType, FileType: fileType, }) }