From 6ffe58ffb49bd0380f9602ed87e5e49527852025 Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 8 Jan 2026 15:22:40 +0800 Subject: [PATCH] feat: align storage object keys --- backend/app/services/common.go | 53 ++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/backend/app/services/common.go b/backend/app/services/common.go index f0f5cec..a5cc0ba 100644 --- a/backend/app/services/common.go +++ b/backend/app/services/common.go @@ -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//. + 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,