package handler import ( "crypto/md5" "encoding/hex" "fmt" "io" "photowall/internal/config" "photowall/internal/middleware" "photowall/internal/model" "photowall/pkg/response" "path/filepath" "strings" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" ) type UploadHandler struct { db *gorm.DB } func NewUploadHandler(db *gorm.DB) *UploadHandler { return &UploadHandler{db: db} } // Upload POST /api/upload // 通用文件上传,MD5去重,返回可访问的 URL。type 参数:class(毕业照)/photo(个人照片)/avatar func (h *UploadHandler) Upload(c *gin.Context) { userID := middleware.CurrentUserID(c) uploadType := c.DefaultPostForm("type", "photo") allowedDirs := map[string]string{ "class": "classes", "photo": "photos", "avatar": "avatars", } subDir, ok := allowedDirs[uploadType] if !ok { subDir = "photos" } file, err := c.FormFile("file") if err != nil { response.BadRequest(c, "请选择要上传的文件") return } // 限制大小 10MB if file.Size > 10*1024*1024 { response.BadRequest(c, "文件大小不能超过 10MB") return } // 校验图片格式 ext := strings.ToLower(filepath.Ext(file.Filename)) allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true} if !allowedExts[ext] { response.BadRequest(c, "仅支持 JPG/PNG/GIF/WEBP 格式") return } // 打开文件计算 MD5 src, err := file.Open() if err != nil { response.Internal(c, "文件读取失败") return } defer src.Close() hash := md5.New() if _, err := io.Copy(hash, src); err != nil { response.Internal(c, "文件MD5计算失败") return } fileMd5 := hex.EncodeToString(hash.Sum(nil)) // MD5 去重:已存在则返回已有路径 var existing model.UploadFile if err := h.db.Where("md5 = ?", fileMd5).First(&existing).Error; err == nil { // 引用次数+1 h.db.Model(&existing).Update("ref_count", gorm.Expr("ref_count + 1")) response.OK(c, gin.H{ "url": existing.FileUrl, "filename": existing.FileName, "size": existing.FileSize, "md5": fileMd5, "duplicate": true, "message": "文件已存在,使用已有路径", }) return } // 新文件:保存到磁盘 src.Seek(0, io.SeekStart) // 重置文件指针 now := time.Now().Format("20060102") filename := fmt.Sprintf("%s_%d_%s%s", now, userID, uuid.New().String()[:8], ext) relPath := filepath.Join(subDir, filename) fullPath := filepath.Join(config.C.UploadDir, relPath) if err := c.SaveUploadedFile(file, fullPath); err != nil { response.Internal(c, "文件保存失败:"+err.Error()) return } // 记录到文件表 url := "/uploads/" + strings.ReplaceAll(relPath, "\\", "/") record := model.UploadFile{ Md5: fileMd5, FilePath: relPath, FileUrl: url, FileName: filename, FileSize: file.Size, FileType: uploadType, UploaderID: userID, RefCount: 1, } h.db.Create(&record) response.OK(c, gin.H{ "url": url, "filename": filename, "size": file.Size, "md5": fileMd5, "duplicate": false, }) }