261 lines
9.2 KiB
Go
261 lines
9.2 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"kra/internal/biz"
|
|
"time"
|
|
)
|
|
|
|
type mediaPO struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
Name string
|
|
CategoryID int `gorm:"column:class_id"`
|
|
URL string
|
|
Tag string
|
|
Key string `gorm:"index"`
|
|
Size int64
|
|
Mime string
|
|
MD5 string `gorm:"index;column:md5"`
|
|
UserID uint `gorm:"index"`
|
|
}
|
|
|
|
func (mediaPO) TableName() string { return "media_file_upload_and_downloads" }
|
|
|
|
type categoryPO struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
Name string
|
|
ParentID uint `gorm:"column:pid"`
|
|
}
|
|
|
|
func (categoryPO) TableName() string { return "media_attachment_category" }
|
|
|
|
type uploadSessionPO struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
UserID uint `gorm:"index"`
|
|
FileName string
|
|
FileHash string `gorm:"index"`
|
|
FileSize int64
|
|
ChunkSize int64
|
|
ChunkTotal int
|
|
Status string
|
|
StorageKey string
|
|
MediaID uint
|
|
}
|
|
|
|
func (uploadSessionPO) TableName() string { return "media_uploads" }
|
|
|
|
type uploadChunkPO struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
UploadID uint `gorm:"uniqueIndex:idx_upload_chunk"`
|
|
ChunkIndex int `gorm:"uniqueIndex:idx_upload_chunk"`
|
|
ChunkHash string
|
|
Size int64
|
|
}
|
|
|
|
func (uploadChunkPO) TableName() string { return "media_upload_chunks" }
|
|
|
|
type mediaRepo struct{ data *Data }
|
|
|
|
func NewMediaRepo(data *Data) biz.MediaRepo { return &mediaRepo{data: data} }
|
|
func mediaFromPO(v mediaPO) *biz.MediaFile {
|
|
return &biz.MediaFile{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
|
|
}
|
|
func mediaToPO(v *biz.MediaFile) mediaPO {
|
|
return mediaPO{ID: v.ID, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
|
|
}
|
|
func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error {
|
|
po := mediaToPO(v)
|
|
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
|
return err
|
|
}
|
|
v.ID = po.ID
|
|
v.CreatedAt = po.CreatedAt
|
|
return nil
|
|
}
|
|
func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*biz.MediaFile, error) {
|
|
var po mediaPO
|
|
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return mediaFromPO(po), nil
|
|
}
|
|
func (r *mediaRepo) FindMediaByHash(ctx context.Context, userID uint, hash string) (*biz.MediaFile, error) {
|
|
var po mediaPO
|
|
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND md5 = ?", userID, hash).First(&po).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return mediaFromPO(po), nil
|
|
}
|
|
func (r *mediaRepo) ListMedia(ctx context.Context, page, size int, keyword string, categoryID int, tag string, userID uint) ([]*biz.MediaFile, int64, error) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if size < 1 {
|
|
size = 10
|
|
}
|
|
db := r.data.gormDB.WithContext(ctx).Model(&mediaPO{})
|
|
if keyword != "" {
|
|
db = db.Where("name LIKE ?", "%"+keyword+"%")
|
|
}
|
|
if categoryID > 0 {
|
|
db = db.Where("class_id = ?", categoryID)
|
|
}
|
|
if tag != "" {
|
|
db = db.Where("tag = ?", tag)
|
|
}
|
|
if userID > 0 {
|
|
db = db.Where("user_id = ?", userID)
|
|
}
|
|
var total int64
|
|
if err := db.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var pos []mediaPO
|
|
if err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&pos).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
out := make([]*biz.MediaFile, 0, len(pos))
|
|
for _, po := range pos {
|
|
out = append(out, mediaFromPO(po))
|
|
}
|
|
return out, total, nil
|
|
}
|
|
func (r *mediaRepo) UpdateMediaName(ctx context.Context, id uint, name string) error {
|
|
return r.data.gormDB.WithContext(ctx).Model(&mediaPO{}).Where("id = ?", id).Update("name", name).Error
|
|
}
|
|
func (r *mediaRepo) DeleteMedia(ctx context.Context, id uint) error {
|
|
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&mediaPO{}, id).Error
|
|
}
|
|
func (r *mediaRepo) MediaKeyReferences(ctx context.Context, key string) (int64, error) {
|
|
var count int64
|
|
err := r.data.gormDB.WithContext(ctx).Model(&mediaPO{}).Where("`key` = ?", key).Count(&count).Error
|
|
return count, err
|
|
}
|
|
func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*biz.MediaFile) error {
|
|
pos := make([]mediaPO, 0, len(items))
|
|
for _, v := range items {
|
|
pos = append(pos, mediaToPO(v))
|
|
}
|
|
if len(pos) == 0 {
|
|
return nil
|
|
}
|
|
return r.data.gormDB.WithContext(ctx).Create(&pos).Error
|
|
}
|
|
func (r *mediaRepo) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error {
|
|
db := r.data.gormDB.WithContext(ctx)
|
|
var count int64
|
|
query := db.Model(&categoryPO{}).Where("name = ? AND pid = ?", v.Name, v.ParentID)
|
|
if v.ID != 0 {
|
|
query = query.Where("id <> ?", v.ID)
|
|
}
|
|
if err := query.Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return errors.New("分类名称已存在")
|
|
}
|
|
if v.ID == 0 {
|
|
po := categoryPO{Name: v.Name, ParentID: v.ParentID}
|
|
if err := db.Create(&po).Error; err != nil {
|
|
return err
|
|
}
|
|
v.ID = po.ID
|
|
return nil
|
|
}
|
|
return db.Model(&categoryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "pid": v.ParentID}).Error
|
|
}
|
|
func (r *mediaRepo) DeleteCategory(ctx context.Context, id uint) error {
|
|
var count int64
|
|
if err := r.data.gormDB.WithContext(ctx).Model(&categoryPO{}).Where("pid = ?", id).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return errors.New("请先删除子级")
|
|
}
|
|
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&categoryPO{}, id).Error
|
|
}
|
|
func (r *mediaRepo) ListCategories(ctx context.Context) ([]*biz.AttachmentCategory, error) {
|
|
var pos []categoryPO
|
|
if err := r.data.gormDB.WithContext(ctx).Order("id").Find(&pos).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
byID := map[uint]*biz.AttachmentCategory{}
|
|
for _, po := range pos {
|
|
byID[po.ID] = &biz.AttachmentCategory{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID, Children: []*biz.AttachmentCategory{}}
|
|
}
|
|
roots := []*biz.AttachmentCategory{}
|
|
for _, po := range pos {
|
|
v := byID[po.ID]
|
|
if parent := byID[po.ParentID]; parent != nil {
|
|
parent.Children = append(parent.Children, v)
|
|
} else {
|
|
roots = append(roots, v)
|
|
}
|
|
}
|
|
return roots, nil
|
|
}
|
|
func uploadFromPO(v uploadSessionPO) *biz.UploadSession {
|
|
return &biz.UploadSession{ID: v.ID, UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status, StorageKey: v.StorageKey, MediaID: v.MediaID}
|
|
}
|
|
func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) {
|
|
var po uploadSessionPO
|
|
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "uploading").First(&po).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return uploadFromPO(po), nil
|
|
}
|
|
func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *biz.UploadSession) error {
|
|
po := uploadSessionPO{UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status}
|
|
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
|
return err
|
|
}
|
|
v.ID = po.ID
|
|
return nil
|
|
}
|
|
func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*biz.UploadSession, error) {
|
|
var po uploadSessionPO
|
|
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return uploadFromPO(po), nil
|
|
}
|
|
func (r *mediaRepo) CompleteUploadSession(ctx context.Context, id uint, key string, mediaID uint) error {
|
|
return r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}).Error
|
|
}
|
|
func (r *mediaRepo) DeleteUploadSession(ctx context.Context, id uint) error {
|
|
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&uploadSessionPO{}, id).Error
|
|
}
|
|
func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *biz.UploadChunk) error {
|
|
po := uploadChunkPO{UploadID: uploadID, ChunkIndex: v.Index, ChunkHash: v.Hash, Size: v.Size}
|
|
return r.data.gormDB.WithContext(ctx).Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "upload_id"}, {Name: "chunk_index"}}, DoUpdates: clause.AssignmentColumns([]string{"chunk_hash", "size", "updated_at"})}).Create(&po).Error
|
|
}
|
|
func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.UploadChunk, error) {
|
|
var pos []uploadChunkPO
|
|
if err := r.data.gormDB.WithContext(ctx).Where("upload_id = ?", uploadID).Order("chunk_index").Find(&pos).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]*biz.UploadChunk, 0, len(pos))
|
|
for _, po := range pos {
|
|
out = append(out, &biz.UploadChunk{Index: po.ChunkIndex, Hash: po.ChunkHash, Size: po.Size})
|
|
}
|
|
return out, nil
|
|
}
|
|
func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error {
|
|
return r.data.gormDB.WithContext(ctx).Unscoped().Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error
|
|
}
|