feat: align storage object keys
This commit is contained in:
@@ -8,9 +8,11 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
@@ -93,6 +95,16 @@ type UploadMeta struct {
|
||||
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) {
|
||||
uploadID := uuid.NewString()
|
||||
localPath := s.storage.Config.LocalPath
|
||||
@@ -177,9 +189,8 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
||||
}
|
||||
sort.Ints(parts)
|
||||
|
||||
objectKey := uuid.NewString() + "_" + meta.Filename
|
||||
dstPath := filepath.Join(localPath, objectKey)
|
||||
dst, err := os.Create(dstPath)
|
||||
mergedPath := filepath.Join(tempDir, "merged")
|
||||
dst, err := os.Create(mergedPath)
|
||||
if err != nil {
|
||||
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))
|
||||
dst.Close() // Ensure flush before potential removal
|
||||
os.RemoveAll(tempDir)
|
||||
|
||||
// Deduplication Logic (Similar to Upload)
|
||||
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
|
||||
}
|
||||
|
||||
objectKey := s.buildObjectKey(t, hash, meta.Filename)
|
||||
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
|
||||
var asset *models.MediaAsset
|
||||
|
||||
if err == nil {
|
||||
os.Remove(dstPath) // Delete duplicate
|
||||
os.Remove(mergedPath) // Delete duplicate
|
||||
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
|
||||
Where(models.MediaAssetQuery.Hash.Eq(hash), models.MediaAssetQuery.UserID.Eq(userID)).
|
||||
First()
|
||||
if err == nil {
|
||||
os.RemoveAll(tempDir)
|
||||
return s.composeUploadResult(myExisting), nil
|
||||
}
|
||||
asset = &models.MediaAsset{
|
||||
@@ -237,6 +249,13 @@ func (s *common) CompleteUpload(ctx context.Context, userID int64, form *common_
|
||||
Meta: existing.Meta,
|
||||
}
|
||||
} 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{
|
||||
TenantID: tid,
|
||||
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 {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
@@ -303,8 +323,6 @@ func (s *common) Upload(
|
||||
) (*common_dto.UploadResult, error) {
|
||||
// But this Upload endpoint accepts file. So we save it.
|
||||
|
||||
objectKey := uuid.NewString() + "_" + file.Filename
|
||||
|
||||
// Save file content to local storage
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
@@ -316,13 +334,13 @@ func (s *common) Upload(
|
||||
if localPath == "" {
|
||||
localPath = "./storage" // Fallback
|
||||
}
|
||||
dstPath := filepath.Join(localPath, objectKey)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
||||
tmpDir := filepath.Join(localPath, "temp", "uploads", uuid.NewString())
|
||||
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
||||
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 {
|
||||
return nil, errorx.ErrInternalError.WithCause(err).WithMsg("failed to create destination file")
|
||||
}
|
||||
@@ -343,13 +361,15 @@ func (s *common) Upload(
|
||||
tid = t.ID
|
||||
}
|
||||
|
||||
objectKey := s.buildObjectKey(t, hash, file.Filename)
|
||||
var asset *models.MediaAsset
|
||||
|
||||
// Deduplication Check
|
||||
existing, err := models.MediaAssetQuery.WithContext(ctx).Where(models.MediaAssetQuery.Hash.Eq(hash)).First()
|
||||
if err == nil {
|
||||
// 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)
|
||||
myExisting, err := models.MediaAssetQuery.WithContext(ctx).
|
||||
@@ -372,6 +392,15 @@ func (s *common) Upload(
|
||||
Meta: existing.Meta,
|
||||
}
|
||||
} 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
|
||||
asset = &models.MediaAsset{
|
||||
TenantID: tid,
|
||||
|
||||
Reference in New Issue
Block a user