Files
kra-oa/internal/biz/media.go
T

114 lines
3.3 KiB
Go

package biz
import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"io"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
)
type MediaFile struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
CategoryID int
URL, Tag, Key string
Size int64
Mime, MD5 string
UserID uint
}
type AttachmentCategory struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name string
ParentID uint
Children []*AttachmentCategory
}
type MediaMetadataRepo interface {
CreateMedia(context.Context, *MediaFile) error
FindMedia(context.Context, uint) (*MediaFile, error)
FindMediaByHash(context.Context, uint, string) (*MediaFile, error)
ListMedia(context.Context, int, int, string, int, string, uint) ([]*MediaFile, int64, error)
UpdateMediaName(context.Context, uint, string) error
DeleteMedia(context.Context, uint) error
MediaKeyReferences(context.Context, string) (int64, error)
CreateMediaBatch(context.Context, []*MediaFile) error
SaveCategory(context.Context, *AttachmentCategory) error
DeleteCategory(context.Context, uint) error
ListCategories(context.Context) ([]*AttachmentCategory, error)
}
type MediaRepo interface {
MediaMetadataRepo
UploadRepo
}
type MediaUsecase struct {
repo MediaRepo
files FileStorage
}
func NewMediaUsecase(repo MediaRepo, files FileStorage) *MediaUsecase {
return &MediaUsecase{repo: repo, files: files}
}
func (uc *MediaUsecase) Repo() MediaRepo { return uc.repo }
var blockedExtensions = map[string]bool{".exe": true, ".dll": true, ".bat": true, ".cmd": true, ".sh": true, ".php": true, ".jsp": true, ".asp": true, ".aspx": true, ".html": true, ".htm": true, ".svg": true}
func validateMediaName(name string) error {
if name == "" || filepath.Base(name) != name {
return errors.New("文件名不合法")
}
if blockedExtensions[strings.ToLower(filepath.Ext(name))] {
return errors.New("文件类型不允许上传")
}
return nil
}
func (uc *MediaUsecase) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (*MediaFile, error) {
if err := validateMediaName(name); err != nil {
return nil, err
}
ext := strings.ToLower(filepath.Ext(name))
key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext
hash := md5.New()
stored, err := uc.files.Put(ctx, key, io.TeeReader(reader, hash))
if err != nil {
return nil, err
}
media := &MediaFile{Name: name, CategoryID: categoryID, URL: stored.URL, Tag: strings.TrimPrefix(ext, "."), Key: key, Size: stored.Size, Mime: mime, MD5: hex.EncodeToString(hash.Sum(nil)), UserID: userID}
if save {
if err = uc.repo.CreateMedia(ctx, media); err != nil {
_ = uc.files.Delete(ctx, key)
return nil, err
}
}
return media, nil
}
func (uc *MediaUsecase) Delete(ctx context.Context, id uint) error {
media, err := uc.repo.FindMedia(ctx, id)
if err != nil {
return err
}
count, err := uc.repo.MediaKeyReferences(ctx, media.Key)
if err != nil {
return err
}
if count <= 1 {
if err = uc.files.Delete(ctx, media.Key); err != nil {
return err
}
}
return uc.repo.DeleteMedia(ctx, id)
}
func (uc *MediaUsecase) ListStorage(ctx context.Context, prefix, cursor string, limit int) ([]*StoredFile, string, bool, error) {
return uc.files.List(ctx, prefix, cursor, limit)
}