feat: align storage object keys
This commit is contained in:
@@ -8,9 +8,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"quyun/v2/app/errorx"
|
"quyun/v2/app/errorx"
|
||||||
@@ -93,6 +95,16 @@ type UploadMeta struct {
|
|||||||
MimeType string
|
MimeType string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *common) buildObjectKey(tenant *models.Tenant, hash, filename string) string {
|
||||||
|
// 按租户维度组织对象路径:quyun/<tenant_uuid>/<hash>.<ext>
|
||||||
|
tenantUUID := "public"
|
||||||
|
if tenant != nil && tenant.UUID.String() != "" {
|
||||||
|
tenantUUID = tenant.UUID.String()
|
||||||
|
}
|
||||||
|
ext := strings.ToLower(filepath.Ext(filename))
|
||||||
|
return path.Join("quyun", tenantUUID, hash+ext)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *common) InitUpload(ctx context.Context, userID int64, form *common_dto.UploadInitForm) (*common_dto.UploadInitResponse, error) {
|
func (s *common) InitUpload(ctx context.Context, userID int64, form *common_dto.UploadInitForm) (*common_dto.UploadInitResponse, error) {
|
||||||
uploadID := uuid.NewString()
|
uploadID := uuid.NewString()
|
||||||
localPath := s.storage.Config.LocalPath
|
localPath := s.storage.Config.LocalPath
|
||||||
@@ -177,9 +189,8 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
|||||||
}
|
}
|
||||||
sort.Ints(parts)
|
sort.Ints(parts)
|
||||||
|
|
||||||
objectKey := uuid.NewString() + "_" + meta.Filename
|
mergedPath := filepath.Join(tempDir, "merged")
|
||||||
dstPath := filepath.Join(localPath, objectKey)
|
dst, err := os.Create(mergedPath)
|
||||||
dst, err := os.Create(dstPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrInternalError.WithCause(err)
|
return nil, errorx.ErrInternalError.WithCause(err)
|
||||||
}
|
}
|
||||||
@@ -205,7 +216,6 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
|||||||
|
|
||||||
hash := hex.EncodeToString(hasher.Sum(nil))
|
hash := hex.EncodeToString(hasher.Sum(nil))
|
||||||
dst.Close() // Ensure flush before potential removal
|
dst.Close() // Ensure flush before potential removal
|
||||||
os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
// Deduplication Logic (Similar to Upload)
|
// Deduplication Logic (Similar to Upload)
|
||||||
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(userID)).First()
|
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(userID)).First()
|
||||||
@@ -214,15 +224,17 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
|||||||
tid = t.ID
|
tid = t.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objectKey := s.buildObjectKey(t, hash, meta.Filename)
|
||||||
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
|
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
|
||||||
var asset *models.MediaAsset
|
var asset *models.MediaAsset
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
os.Remove(dstPath) // Delete duplicate
|
os.Remove(mergedPath) // Delete duplicate
|
||||||
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
|
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
|
||||||
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID)).
|
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID)).
|
||||||
First()
|
First()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
os.RemoveAll(tempDir)
|
||||||
return s.composeUploadResult(myExisting), nil
|
return s.composeUploadResult(myExisting), nil
|
||||||
}
|
}
|
||||||
asset = &models.MediaAsset{
|
asset = &models.MediaAsset{
|
||||||
@@ -237,6 +249,13 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
|||||||
Meta: existing.Meta,
|
Meta: existing.Meta,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
finalPath := filepath.Join(localPath, objectKey)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(finalPath), 0o755); err != nil {
|
||||||
|
return nil, errorx.ErrInternalError.WithCause(err)
|
||||||
|
}
|
||||||
|
if err := os.Rename(mergedPath, finalPath); err != nil {
|
||||||
|
return nil, errorx.ErrInternalError.WithCause(err)
|
||||||
|
}
|
||||||
asset = &models.MediaAsset{
|
asset = &models.MediaAsset{
|
||||||
TenantID: tid,
|
TenantID: tid,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@@ -253,6 +272,7 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.RemoveAll(tempDir)
|
||||||
if err := models.MediaAssetQuery.WithContext(ctx).Create(asset); err != nil {
|
if err := models.MediaAssetQuery.WithContext(ctx).Create(asset); err != nil {
|
||||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
@@ -303,8 +323,6 @@ func (s *common) Upload(
|
|||||||
) (*common_dto.UploadResult, error) {
|
) (*common_dto.UploadResult, error) {
|
||||||
// But this Upload endpoint accepts file. So we save it.
|
// But this Upload endpoint accepts file. So we save it.
|
||||||
|
|
||||||
objectKey := uuid.NewString() + "_" + file.Filename
|
|
||||||
|
|
||||||
// Save file content to local storage
|
// Save file content to local storage
|
||||||
src, err := file.Open()
|
src, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -316,13 +334,13 @@ func (s *common) Upload(
|
|||||||
if localPath == "" {
|
if localPath == "" {
|
||||||
localPath = "./storage" // Fallback
|
localPath = "./storage" // Fallback
|
||||||
}
|
}
|
||||||
dstPath := filepath.Join(localPath, objectKey)
|
tmpDir := filepath.Join(localPath, "temp", "uploads", uuid.NewString())
|
||||||
|
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
|
||||||
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create storage directory")
|
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create storage directory")
|
||||||
}
|
}
|
||||||
|
tmpPath := filepath.Join(tmpDir, "file")
|
||||||
|
|
||||||
dst, err := os.Create(dstPath)
|
dst, err := os.Create(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create destination file")
|
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create destination file")
|
||||||
}
|
}
|
||||||
@@ -343,13 +361,15 @@ func (s *common) Upload(
|
|||||||
tid = t.ID
|
tid = t.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objectKey := s.buildObjectKey(t, hash, file.Filename)
|
||||||
var asset *models.MediaAsset
|
var asset *models.MediaAsset
|
||||||
|
|
||||||
// Deduplication Check
|
// Deduplication Check
|
||||||
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
|
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Found existing file (Storage Deduplication)
|
// Found existing file (Storage Deduplication)
|
||||||
os.Remove(dstPath) // Delete the duplicate we just wrote
|
os.Remove(tmpPath) // Delete the duplicate we just wrote
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
// Check if user already has it (Logic Deduplication)
|
// Check if user already has it (Logic Deduplication)
|
||||||
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
|
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
|
||||||
@@ -372,6 +392,15 @@ func (s *common) Upload(
|
|||||||
Meta: existing.Meta,
|
Meta: existing.Meta,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
dstPath := filepath.Join(localPath, objectKey)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
||||||
|
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create storage directory")
|
||||||
|
}
|
||||||
|
if err := os.Rename(tmpPath, dstPath); err != nil {
|
||||||
|
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to finalize file")
|
||||||
|
}
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
// New unique file
|
// New unique file
|
||||||
asset = &models.MediaAsset{
|
asset = &models.MediaAsset{
|
||||||
TenantID: tid,
|
TenantID: tid,
|
||||||
|
|||||||
Reference in New Issue
Block a user