package handlers import ( "awesomeProject/internal/common" "crypto/md5" "fmt" "github.com/gin-gonic/gin" "io" "net/http" "os" "path/filepath" "strings" "time" ) // UploadHandler 文件上传处理器 type UploadHandler struct{} // NewUploadHandler 创建文件上传处理器实例 func NewUploadHandler() *UploadHandler { return &UploadHandler{} } // UploadResponse 上传响应 type UploadResponse struct { FileName string `json:"fileName"` // 原文件名 NewFileName string `json:"newFileName"` // 新文件名 URL string `json:"url"` // 访问URL Size int64 `json:"size"` // 文件大小(字节) Type string `json:"type"` // 文件类型 } // UploadFile 上传文件 func (h *UploadHandler) UploadFile(c *gin.Context) { // 获取上传的文件 file, header, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusOK, common.Error(500, "获取上传文件失败: "+err.Error())) return } defer file.Close() // 检查文件大小限制 (10MB) maxSize := int64(10 * 1024 * 1024) // 10MB if header.Size > maxSize { c.JSON(http.StatusOK, common.Error(500, "文件大小超过限制(最大10MB)")) return } // 检查文件类型 if !isAllowedFileType(header.Filename) { c.JSON(http.StatusOK, common.Error(500, "不支持的文件类型")) return } // 创建保存目录 saveDir := "./public/file" if err := os.MkdirAll(saveDir, os.ModePerm); err != nil { c.JSON(http.StatusOK, common.Error(500, "创建保存目录失败: "+err.Error())) return } // 生成新的文件名 newFileName, err := generateFileName(header.Filename, file) if err != nil { c.JSON(http.StatusOK, common.Error(500, "生成文件名失败: "+err.Error())) return } // 完整的保存路径 savePath := filepath.Join(saveDir, newFileName) // 保存文件 if err := saveUploadedFile(file, savePath); err != nil { c.JSON(http.StatusOK, common.Error(500, "保存文件失败: "+err.Error())) return } // 构建响应 response := UploadResponse{ FileName: header.Filename, NewFileName: newFileName, URL: "/back/file/" + newFileName, Size: header.Size, Type: getFileType(header.Filename), } c.JSON(http.StatusOK, common.Success(response)) } // generateFileName 生成唯一的文件名 func generateFileName(originalName string, file io.Reader) (string, error) { // 获取文件扩展名 ext := filepath.Ext(originalName) // 重置文件指针到开头 if seeker, ok := file.(io.Seeker); ok { seeker.Seek(0, 0) } // 读取文件内容用于生成哈希 hasher := md5.New() if _, err := io.Copy(hasher, file); err != nil { return "", err } // 重置文件指针到开头 if seeker, ok := file.(io.Seeker); ok { seeker.Seek(0, 0) } // 生成文件名:时间戳 + MD5哈希前8位 + 扩展名 timestamp := time.Now().Format("20060102150405") hash := fmt.Sprintf("%x", hasher.Sum(nil))[:8] newName := fmt.Sprintf("%s_%s%s", timestamp, hash, ext) return newName, nil } // saveUploadedFile 保存上传的文件 func saveUploadedFile(src io.Reader, dst string) error { out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, src) return err } // isAllowedFileType 检查是否为允许的文件类型 func isAllowedFileType(filename string) bool { ext := strings.ToLower(filepath.Ext(filename)) // 允许的文件类型 allowedTypes := map[string]bool{ // 图片类型 ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".bmp": true, ".webp": true, ".svg": true, // 文档类型 ".pdf": true, ".doc": true, ".docx": true, ".xls": true, ".xlsx": true, ".ppt": true, ".pptx": true, ".txt": true, ".rtf": true, // 压缩包类型 ".zip": true, ".rar": true, ".7z": true, ".tar": true, ".gz": true, // 视频类型 ".mp4": true, ".avi": true, ".mov": true, ".wmv": true, ".flv": true, ".webm": true, // 音频类型 ".mp3": true, ".wav": true, ".flac": true, ".aac": true, ".ogg": true, } return allowedTypes[ext] } // getFileType 获取文件类型 func getFileType(filename string) string { ext := strings.ToLower(filepath.Ext(filename)) imageTypes := map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".bmp": true, ".webp": true, ".svg": true, } documentTypes := map[string]bool{ ".pdf": true, ".doc": true, ".docx": true, ".xls": true, ".xlsx": true, ".ppt": true, ".pptx": true, ".txt": true, ".rtf": true, } videoTypes := map[string]bool{ ".mp4": true, ".avi": true, ".mov": true, ".wmv": true, ".flv": true, ".webm": true, } audioTypes := map[string]bool{ ".mp3": true, ".wav": true, ".flac": true, ".aac": true, ".ogg": true, } archiveTypes := map[string]bool{ ".zip": true, ".rar": true, ".7z": true, ".tar": true, ".gz": true, } if imageTypes[ext] { return "image" } else if documentTypes[ext] { return "document" } else if videoTypes[ext] { return "video" } else if audioTypes[ext] { return "audio" } else if archiveTypes[ext] { return "archive" } return "other" } // BatchUploadFiles 批量上传文件 func (h *UploadHandler) BatchUploadFiles(c *gin.Context) { form, err := c.MultipartForm() if err != nil { c.JSON(http.StatusOK, common.Error(500, "获取上传文件失败: "+err.Error())) return } files := form.File["files"] if len(files) == 0 { c.JSON(http.StatusOK, common.Error(500, "没有选择文件")) return } // 限制批量上传数量 if len(files) > 10 { c.JSON(http.StatusOK, common.Error(500, "一次最多上传10个文件")) return } var responses []UploadResponse var failedFiles []string // 创建保存目录 saveDir := "./public/file" if err := os.MkdirAll(saveDir, os.ModePerm); err != nil { c.JSON(http.StatusOK, common.Error(500, "创建保存目录失败: "+err.Error())) return } for _, header := range files { // 检查文件大小 if header.Size > int64(10*1024*1024) { failedFiles = append(failedFiles, header.Filename+" (文件过大)") continue } // 检查文件类型 if !isAllowedFileType(header.Filename) { failedFiles = append(failedFiles, header.Filename+" (不支持的类型)") continue } // 打开文件 file, err := header.Open() if err != nil { failedFiles = append(failedFiles, header.Filename+" (打开失败)") continue } // 生成新文件名 newFileName, err := generateFileName(header.Filename, file) if err != nil { file.Close() failedFiles = append(failedFiles, header.Filename+" (生成文件名失败)") continue } // 保存文件 savePath := filepath.Join(saveDir, newFileName) if err := saveUploadedFile(file, savePath); err != nil { file.Close() failedFiles = append(failedFiles, header.Filename+" (保存失败)") continue } file.Close() // 添加到成功列表 responses = append(responses, UploadResponse{ FileName: header.Filename, NewFileName: newFileName, URL: "/back/file/" + newFileName, Size: header.Size, Type: getFileType(header.Filename), }) } result := map[string]interface{}{ "success": responses, "failed": failedFiles, "totalCount": len(files), "successCount": len(responses), "failedCount": len(failedFiles), } c.JSON(http.StatusOK, common.Success(result)) }