151 lines
3.4 KiB
Go
151 lines
3.4 KiB
Go
package medias
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
uploadTempDir = "./temp/chunks" // 临时分片目录
|
|
uploadStorageDir = "./uploads" // 最终文件存储目录
|
|
)
|
|
|
|
// @provider
|
|
type Controller struct {
|
|
svc *Service
|
|
log *log.Entry `inject:"false"`
|
|
}
|
|
|
|
func (ctl *Controller) Prepare() error {
|
|
ctl.log = log.WithField("module", "medias.Controller")
|
|
return nil
|
|
}
|
|
|
|
// Upload
|
|
// @Router /api/v1/medias/upload [post]
|
|
// @Bind req body
|
|
// @Bind file file
|
|
func (ctl *Controller) Upload(ctx fiber.Ctx, file *multipart.FileHeader, req *UploadReq) (*UploadResp, error) {
|
|
// 使用MD5创建唯一的临时目录
|
|
tempDir := filepath.Join(uploadTempDir, req.FileMD5)
|
|
if err := os.MkdirAll(tempDir, 0o755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
chunkPath := filepath.Join(tempDir, fmt.Sprintf("chunk_%d", req.ChunkNumber))
|
|
if err := ctx.SaveFile(file, chunkPath); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 如果是最后一个分片
|
|
if req.ChunkNumber == req.TotalChunks-1 {
|
|
// 生成唯一的文件存储路径
|
|
ext := filepath.Ext(req.FileName)
|
|
storageDir := filepath.Join(uploadStorageDir, time.Now().Format("2006/01/02"))
|
|
if err := os.MkdirAll(storageDir, 0o755); err != nil {
|
|
os.RemoveAll(tempDir)
|
|
return nil, err
|
|
}
|
|
|
|
finalPath := filepath.Join(storageDir, req.FileMD5+ext)
|
|
|
|
// 计算所有分片的实际大小总和
|
|
totalSize, err := calculateTotalSize(tempDir, req.TotalChunks)
|
|
if err != nil {
|
|
os.RemoveAll(tempDir)
|
|
return nil, fmt.Errorf("计算文件大小失败: %w", err)
|
|
}
|
|
|
|
// 合并文件
|
|
if err := combineChunks(tempDir, finalPath, req.TotalChunks); err != nil {
|
|
os.RemoveAll(tempDir)
|
|
return nil, fmt.Errorf("合并文件失败: %w", err)
|
|
}
|
|
|
|
// 验证MD5
|
|
calculatedMD5, err := calculateFileMD5(finalPath)
|
|
if err != nil || calculatedMD5 != req.FileMD5 {
|
|
os.RemoveAll(tempDir)
|
|
os.Remove(finalPath)
|
|
return nil, errors.New("文件MD5验证失败")
|
|
}
|
|
|
|
// 清理临时目录
|
|
os.RemoveAll(tempDir)
|
|
|
|
return &UploadResp{
|
|
Files: []UploadFile{
|
|
{
|
|
HashID: calculatedMD5,
|
|
Name: req.FileName,
|
|
Path: finalPath,
|
|
Size: totalSize,
|
|
MimeType: file.Header.Get("Content-Type"),
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
return &UploadResp{}, nil
|
|
}
|
|
|
|
// 计算所有分片的实际大小总和
|
|
func calculateTotalSize(tempDir string, totalChunks int) (int64, error) {
|
|
var totalSize int64
|
|
for i := 0; i < totalChunks; i++ {
|
|
chunkPath := filepath.Join(tempDir, fmt.Sprintf("chunk_%d", i))
|
|
info, err := os.Stat(chunkPath)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
totalSize += info.Size()
|
|
}
|
|
return totalSize, nil
|
|
}
|
|
|
|
func combineChunks(tempDir, finalPath string, totalChunks int) error {
|
|
finalFile, err := os.Create(finalPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer finalFile.Close()
|
|
|
|
for i := 0; i < totalChunks; i++ {
|
|
chunkPath := fmt.Sprintf("%s/chunk_%d", tempDir, i)
|
|
chunk, err := os.ReadFile(chunkPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := finalFile.Write(chunk); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func calculateFileMD5(filePath string) (string, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
hash := md5.New()
|
|
if _, err := io.Copy(hash, file); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
|
}
|