kra-new/internal/biz/media_upload.go

136 lines
4.5 KiB
Go

package biz
import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"path/filepath"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*UploadSession, *MediaFile, []int, error) {
if err := validateMediaName(name); err != nil {
return nil, nil, nil, err
}
if size <= 0 || chunkSize <= 0 || total <= 0 {
return nil, nil, nil, errors.New("上传参数不合法")
}
if media, err := uc.FindMediaByHash(ctx, userID, hash); err == nil {
copy := *media
copy.ID = 0
copy.Name = name
if err = uc.CreateMedia(ctx, &copy); err == nil {
return nil, &copy, nil, nil
}
}
session, err := uc.FindUploadingSession(ctx, userID, hash)
if err != nil {
session = &UploadSession{UserID: userID, FileName: name, FileHash: hash, FileSize: size, ChunkSize: chunkSize, ChunkTotal: total, Status: "uploading"}
if err = uc.CreateUploadSession(ctx, session); err != nil {
return nil, nil, nil, err
}
}
chunks, err := uc.ListChunks(ctx, session.ID)
if err != nil {
return nil, nil, nil, err
}
indices := make([]int, 0, len(chunks))
for _, v := range chunks {
indices = append(indices, v.Index)
}
sort.Ints(indices)
return session, nil, indices, nil
}
func (uc *MediaUsecase) SaveChunk(ctx context.Context, userID, uploadID uint, index int, expected string, reader io.Reader) error {
session, err := uc.FindUploadSession(ctx, uploadID)
if err != nil || session.UserID != userID {
return errors.New("上传会话不存在或无权操作")
}
if session.Status != "uploading" || index < 0 || index >= session.ChunkTotal {
return errors.New("上传会话状态或分片序号不合法")
}
hash := md5.New()
key := fmt.Sprintf(".chunks/%d/%08d", uploadID, index)
stored, err := uc.files.Put(ctx, key, io.TeeReader(reader, hash))
if err != nil {
return err
}
actual := hex.EncodeToString(hash.Sum(nil))
if !strings.EqualFold(actual, expected) {
_ = uc.files.Delete(ctx, key)
return errors.New("分片校验失败")
}
return uc.UpsertChunk(ctx, uploadID, &UploadChunk{Index: index, Hash: actual, Size: stored.Size})
}
func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uint, mime string) (*MediaFile, error) {
session, err := uc.FindUploadSession(ctx, uploadID)
if err != nil || session.UserID != userID {
return nil, errors.New("上传会话不存在或无权操作")
}
chunks, err := uc.ListChunks(ctx, uploadID)
if err != nil || len(chunks) != session.ChunkTotal {
return nil, errors.New("分片不完整")
}
sort.Slice(chunks, func(i, j int) bool { return chunks[i].Index < chunks[j].Index })
names := make([]string, 0, len(chunks))
for index, chunk := range chunks {
if chunk.Index != index {
return nil, errors.New("分片序号不连续")
}
names = append(names, fmt.Sprintf(".chunks/%d/%08d", uploadID, index))
}
ext := strings.ToLower(filepath.Ext(session.FileName))
key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext
stored, hash, err := uc.files.Compose(ctx, names, key)
if err != nil {
return nil, err
}
if !strings.EqualFold(hash, session.FileHash) {
_ = uc.files.Delete(ctx, key)
return nil, errors.New("整文件校验失败")
}
media := &MediaFile{Name: session.FileName, URL: stored.URL, Tag: strings.TrimPrefix(ext, "."), Key: key, Size: stored.Size, Mime: mime, MD5: hash, UserID: userID}
if err = uc.CreateMedia(ctx, media); err != nil {
_ = uc.files.Delete(ctx, key)
return nil, err
}
_ = uc.CompleteUploadSession(ctx, uploadID, key, media.ID)
_ = uc.DeleteChunks(ctx, uploadID)
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
return media, nil
}
func (uc *MediaUsecase) CancelUpload(ctx context.Context, userID, uploadID uint) error {
session, err := uc.FindUploadSession(ctx, uploadID)
if err != nil || session.UserID != userID {
return errors.New("上传会话不存在或无权操作")
}
_ = uc.DeleteChunks(ctx, uploadID)
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
return uc.DeleteUploadSession(ctx, uploadID)
}
func (uc *MediaUsecase) CleanupStale(ctx context.Context, ttlHours int) error {
if ttlHours <= 0 {
ttlHours = 24
}
ids, err := uc.StaleUploadSessionIDs(ctx, time.Now().Add(-time.Duration(ttlHours)*time.Hour))
if err != nil {
return err
}
for _, id := range ids {
if err = uc.DeleteUploadData(ctx, id); err != nil {
return err
}
if err = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", id)); err != nil {
return err
}
}
return nil
}