1.0
This commit is contained in:
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user