This commit is contained in:
solosw
2026-01-05 14:11:34 +08:00
parent 35ef825371
commit 99b11b04e4
658 changed files with 99266 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
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))
}