310 lines
7.3 KiB
Go
310 lines
7.3 KiB
Go
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))
|
|
}
|