refactor(storage): drop per-upload storage_driver, use storage_config as single source

Remove w_uploads.storage_driver and route all read/write/delete paths through
storage.Active() backed by storage_config.driver. Block direct driver switches
when uploads exist; require migration task instead. Simplify migration to
cursor-based file_path iteration without per-row driver updates.
This commit is contained in:
ryan
2026-06-18 13:56:31 +08:00
parent 1f391e9ec2
commit 410ff14795
25 changed files with 512 additions and 604 deletions
@@ -67,7 +67,6 @@ func TestServeFileByIDAccessControl(t *testing.T) {
FileSize: 5,
MimeType: "image/png",
Extension: "png",
StorageDriver: "local",
Type: "avatar",
Status: model.UploadStatusUsed,
AccessMode: 1,
@@ -80,7 +79,6 @@ func TestServeFileByIDAccessControl(t *testing.T) {
FileSize: 5,
MimeType: "application/pdf",
Extension: "pdf",
StorageDriver: "local",
Type: "attachment",
Status: model.UploadStatusUsed,
AccessMode: 1,
@@ -207,7 +205,6 @@ func TestImageCompression(t *testing.T) {
FileSize: int64(pngBuf.Len()),
MimeType: "image/png",
Extension: "png",
StorageDriver: "local",
Type: "avatar", // Whitelisted by default
Status: model.UploadStatusUsed,
AccessMode: 1,
@@ -29,7 +29,6 @@ func TestGetDistinctUploadTypes(t *testing.T) {
FileSize: 10,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Type: "custom_type_xyz",
Status: model.UploadStatusUsed,
}
+7 -8
View File
@@ -124,7 +124,6 @@ func createInstantUpload(ctx context.Context, existing model.Upload, input insta
MimeType: input.MimeType,
Extension: input.Extension,
Hash: input.FileHash,
StorageDriver: existing.StorageDriver,
Type: input.UploadType,
Status: model.UploadStatusUsed,
AccessMode: input.AccessMode,
@@ -142,9 +141,9 @@ func findReusableUpload(ctx context.Context, hash string, size int64) (model.Upl
return repository.FindReusableUploadByHash(ctx, hash, size)
}
func saveNewUploadRecord(ctx context.Context, upload *model.Upload, storageDriver, filePath string) error {
func saveNewUploadRecord(ctx context.Context, upload *model.Upload, filePath string) error {
if err := repository.CreateUpload(ctx, upload); err != nil {
backend, backendErr := storage.ForDriver(ctx, storage.Driver(storageDriver))
_, backend, backendErr := storage.Active(ctx)
if backendErr == nil {
if deleteErr := backend.Delete(ctx, filePath); deleteErr != nil {
logger.WarnF(ctx, "清理未写入数据库的上传对象失败: %v", deleteErr)
@@ -162,22 +161,22 @@ func loadUploadStats(ctx context.Context) ([]model.UploadStat, error) {
var errUploadForbidden = errors.New("upload forbidden")
func storeUploadObject(ctx context.Context, subPath string, size int64, mimeType string, buf *bytes.Buffer, meta *model.UploadMetadata) (string, string, error) {
func storeUploadObject(ctx context.Context, subPath string, size int64, mimeType string, buf *bytes.Buffer, meta *model.UploadMetadata) (string, error) {
if uploadstorage.ReadOnly(ctx) {
return "", "", errors.New(shared.ErrStorageReadOnly)
return "", errors.New(shared.ErrStorageReadOnly)
}
driver, backend, err := storage.Active(ctx)
if err != nil {
logger.ErrorF(ctx, "初始化活动存储失败: %v", err)
return "", "", errors.New(shared.ErrSaveFileFailed)
return "", errors.New(shared.ErrSaveFileFailed)
}
result, err := backend.Put(ctx, subPath, bytes.NewReader(buf.Bytes()), size, mimeType)
if err != nil {
logger.ErrorF(ctx, "写入 %s 存储失败: %v", driver, err)
return "", "", errors.New(shared.ErrSaveFileFailed)
return "", errors.New(shared.ErrSaveFileFailed)
}
meta.Bucket = result.Bucket
return string(driver), result.Key, nil
return result.Key, nil
}
func validateUploadAllowedExtension(ctx context.Context, ext string) string {
+14 -15
View File
@@ -138,29 +138,28 @@ func UploadFile(c *gin.Context) {
id := idgen.NextUint64ID()
subPath := fmt.Sprintf("uploads/%s/%d.%s", time.Now().Format("2006/01/02"), id, ext)
storageDriver, subPath, err := storeUploadObject(ctx, subPath, size, mimeType, &buf, &meta)
subPath, err = storeUploadObject(ctx, subPath, size, mimeType, &buf, &meta)
if err != nil {
response.AbortBadRequest(c, err.Error())
return
}
newUpload := model.Upload{
ID: id,
UserID: currUser.ID,
FileName: origName,
FilePath: subPath,
FileSize: size,
MimeType: mimeType,
Extension: ext,
Hash: fileHash,
StorageDriver: storageDriver,
Type: uploadType,
Status: model.UploadStatusUsed,
AccessMode: accessMode,
Metadata: meta,
ID: id,
UserID: currUser.ID,
FileName: origName,
FilePath: subPath,
FileSize: size,
MimeType: mimeType,
Extension: ext,
Hash: fileHash,
Type: uploadType,
Status: model.UploadStatusUsed,
AccessMode: accessMode,
Metadata: meta,
}
if err := saveNewUploadRecord(ctx, &newUpload, storageDriver, subPath); err != nil {
if err := saveNewUploadRecord(ctx, &newUpload, subPath); err != nil {
response.AbortBadRequest(c, shared.ErrSaveUploadRecordFailed)
return
}
@@ -193,10 +193,6 @@ func TestUploadFile(t *testing.T) {
t.Errorf("incorrect mime type detected: %s", dbRecord.MimeType)
}
if dbRecord.StorageDriver != "s3" {
t.Errorf("expected storage driver s3, got %s", dbRecord.StorageDriver)
}
if dbRecord.Metadata.Extra["source"] != "test_runner" {
t.Errorf("expected extra meta 'source' to be 'test_runner', got %v", dbRecord.Metadata.Extra)
}
@@ -325,10 +321,6 @@ func TestUploadFile(t *testing.T) {
t.Fatalf("failed to unmarshal local upload record: %v", err)
}
if localRecord.StorageDriver != "local" {
t.Errorf("expected storage driver local, got %s", localRecord.StorageDriver)
}
// Confirm file was actually written to local disk
fileContent, err := os.ReadFile(localRecord.FilePath)
if err != nil {
@@ -358,7 +350,6 @@ func TestDownloadFile(t *testing.T) {
FileSize: 12,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
}
@@ -426,7 +417,6 @@ func TestListFiles(t *testing.T) {
FileSize: 10,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
{
@@ -437,7 +427,6 @@ func TestListFiles(t *testing.T) {
FileSize: 20,
MimeType: "image/png",
Extension: "png",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
{
@@ -448,7 +437,6 @@ func TestListFiles(t *testing.T) {
FileSize: 30,
MimeType: "text/markdown",
Extension: "md",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
{
@@ -459,7 +447,6 @@ func TestListFiles(t *testing.T) {
FileSize: 40,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
}
@@ -582,7 +569,6 @@ func TestBatchDownloadFiles(t *testing.T) {
FileSize: 13,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
{
@@ -593,7 +579,6 @@ func TestBatchDownloadFiles(t *testing.T) {
FileSize: 13,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
{
@@ -604,7 +589,6 @@ func TestBatchDownloadFiles(t *testing.T) {
FileSize: 28,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
},
}
@@ -773,7 +757,6 @@ func TestGetFileStats(t *testing.T) {
FileSize: 100,
MimeType: "image/png",
Extension: "png",
StorageDriver: "local",
Type: "generic",
Status: model.UploadStatusUsed,
CreatedAt: time.Now(),
@@ -786,7 +769,6 @@ func TestGetFileStats(t *testing.T) {
FileSize: 500,
MimeType: "video/mp4",
Extension: "mp4",
StorageDriver: "local",
Type: "generic",
Status: model.UploadStatusUsed,
CreatedAt: time.Now().AddDate(0, 0, -2), // 2 days ago
@@ -799,7 +781,6 @@ func TestGetFileStats(t *testing.T) {
FileSize: 200,
MimeType: "application/pdf",
Extension: "pdf",
StorageDriver: "local",
Type: "avatar", // different type
Status: model.UploadStatusUsed,
CreatedAt: time.Now().AddDate(0, 0, -10), // older than 7 days
@@ -891,7 +872,6 @@ func TestUserUploadManagement(t *testing.T) {
FileSize: 100,
MimeType: "text/plain",
Extension: "txt",
StorageDriver: "local",
Status: model.UploadStatusUsed,
CreatedAt: time.Now(),
}
@@ -903,7 +883,6 @@ func TestUserUploadManagement(t *testing.T) {
FileSize: 200,
MimeType: "image/png",
Extension: "png",
StorageDriver: "local",
Status: model.UploadStatusUsed,
CreatedAt: time.Now(),
}
-3
View File
@@ -3,8 +3,6 @@
package shared
import "github.com/Rain-kl/Wavelet/internal/storage"
// Upload size, path, media quality, and cache constants shared across subpackages.
const (
MaxUploadSize = 32 * 1024 * 1024 // 32MB
@@ -15,7 +13,6 @@ const (
ImageQualityMedium = "medium"
ImageQualityHigh = "high"
ImageQualityOrigin = "origin"
StorageDriverLocal = string(storage.DriverLocal)
DefaultPublicUploadType = "avatar"
FileStatsTrendDays = 7
MaxS3KeyLength = 1024
+2 -38
View File
@@ -5,7 +5,6 @@ package storage
import (
"context"
"fmt"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/storage"
@@ -22,46 +21,11 @@ func ReadOnly(ctx context.Context) bool {
return state.ReadOnly
}
// OpenStoredObject opens a stored upload object from its configured backend.
// OpenStoredObject opens a stored upload object from the active storage backend.
func OpenStoredObject(ctx context.Context, upload *model.Upload) (*storage.Object, error) {
driver := storage.Driver(upload.StorageDriver)
if driver == "" {
driver = storage.DriverLocal
}
backend, err := backendForStoredDriver(ctx, driver)
_, backend, err := storage.Active(ctx)
if err != nil {
return nil, err
}
return backend.Get(ctx, upload.FilePath)
}
func backendForStoredDriver(ctx context.Context, driver storage.Driver) (storage.Backend, error) {
backend, err := storage.ForDriver(ctx, driver)
if err == nil {
return backend, nil
}
target, ok, targetErr := CurrentMigrationTargetConfig(ctx)
if targetErr != nil {
return nil, targetErr
}
if ok && target.Driver == driver {
return storage.NewBackend(ctx, target, driver)
}
return nil, fmt.Errorf("storage configuration for driver %q is unavailable", driver)
}
// CurrentMigrationTargetConfig returns the pending migration target config when available.
func CurrentMigrationTargetConfig(ctx context.Context) (storage.Config, bool, error) {
state := LoadMigrationAccessState(ctx)
if state.LoadErr != nil {
return storage.Config{}, false, state.LoadErr
}
if state.TargetErr != nil {
return storage.Config{}, false, state.TargetErr
}
if !state.HasTarget {
return storage.Config{}, false, nil
}
return state.Target, true, nil
}
+1 -5
View File
@@ -84,11 +84,7 @@ func (h *SystemCleanupHandler) Execute(ctx context.Context, _ []byte) (*task.Tas
return err
}
driver := storage.Driver(u.StorageDriver)
if driver == "" {
driver = storage.DriverLocal
}
backend, err := storage.ForDriver(ctx, driver)
_, backend, err := storage.Active(ctx)
if err != nil {
return err
}
+36 -58
View File
@@ -29,8 +29,6 @@ const (
StorageMigrationTask = uploadstorage.StorageMigrationTask
// TaskTypeStorageMigration is the task metadata type for storage migration.
TaskTypeStorageMigration = "storage_migration"
colStorageDriver = "storage_driver"
)
// StorageMigrationMeta describes the manually dispatchable migration task.
@@ -136,7 +134,7 @@ func (h *MigrationHandler) Execute(ctx context.Context, payload []byte) (*task.T
return &task.TaskResult{Message: message}, nil
}
total, err := countStorageObjects(ctx, active.Driver)
total, err := countStorageObjects(ctx)
if err != nil {
return nil, fmt.Errorf("count source objects: %w", err)
}
@@ -159,7 +157,7 @@ func (h *MigrationHandler) Execute(ctx context.Context, payload []byte) (*task.T
}
task.AppendLog(ctx, "开始存储迁移: %s -> %s,总对象数: %d", active.Driver, target.Driver, total)
migrated, err := migrateObjects(ctx, sourceBackend, targetBackend, active.Driver, target.Driver, total)
migrated, err := migrateObjects(ctx, sourceBackend, targetBackend, total)
if err != nil {
return nil, err
}
@@ -172,10 +170,10 @@ func (h *MigrationHandler) Execute(ctx context.Context, payload []byte) (*task.T
return &task.TaskResult{Message: message}, nil
}
func countStorageObjects(ctx context.Context, driver storage.Driver) (int64, error) {
func countStorageObjects(ctx context.Context) (int64, error) {
var count int64
err := db.DB(ctx).Model(&model.Upload{}).
Where("storage_driver = ? AND status != ?", driver, model.UploadStatusDeleted).
Where("status != ?", model.UploadStatusDeleted).
Distinct("file_path").
Count(&count).Error
return count, err
@@ -189,18 +187,24 @@ func hasUnresolvedMigrationTask(ctx context.Context) (bool, error) {
return execution.Status == model.TaskExecutionStatusPending || execution.Status == model.TaskExecutionStatusRunning, nil
}
type migrationObject struct {
FilePath string `gorm:"column:file_path"`
FileSize int64 `gorm:"column:file_size"`
MimeType string `gorm:"column:mime_type"`
Hash string `gorm:"column:hash"`
}
func migrateObjects(
ctx context.Context,
sourceBackend storage.Backend,
targetBackend storage.Backend,
sourceDriver storage.Driver,
targetDriver storage.Driver,
total int64,
) (int64, error) {
const batchSize = 50
const migrationConcurrency = 10
const sha256HexLength = 64
var migrated int64
var lastFilePath string
for {
if err := ctx.Err(); err != nil {
return atomic.LoadInt64(&migrated), fmt.Errorf("storage migration canceled: %w", err)
@@ -208,16 +212,14 @@ func migrateObjects(
task.AppendLog(ctx, "正在查询待迁移对象批次,当前已完成迁移: %d/%d", atomic.LoadInt64(&migrated), total)
var objects []struct {
FilePath string `gorm:"column:file_path"`
FileSize int64 `gorm:"column:file_size"`
MimeType string `gorm:"column:mime_type"`
Hash string `gorm:"column:hash"`
}
if err := db.DB(ctx).Model(&model.Upload{}).
var objects []migrationObject
query := db.DB(ctx).Model(&model.Upload{}).
Select("file_path, MAX(file_size) AS file_size, MAX(mime_type) AS mime_type, MAX(hash) AS hash").
Where("storage_driver = ? AND status != ?", sourceDriver, model.UploadStatusDeleted).
Group("file_path").
Where("status != ?", model.UploadStatusDeleted)
if lastFilePath != "" {
query = query.Where("file_path > ?", lastFilePath)
}
if err := query.Group("file_path").
Order("file_path ASC").
Limit(batchSize).
Scan(&objects).Error; err != nil {
@@ -228,6 +230,7 @@ func migrateObjects(
break
}
lastFilePath = objects[len(objects)-1].FilePath
task.AppendLog(ctx, "获取当前批次迁移对象,批次大小: %d,实际获取对象数: %d", batchSize, len(objects))
var g errgroup.Group
@@ -236,7 +239,7 @@ func migrateObjects(
for _, object := range objects {
obj := object
g.Go(func() error {
if err := migrateSingleObject(ctx, sourceBackend, targetBackend, sourceDriver, targetDriver, obj, sha256HexLength); err != nil {
if err := migrateSingleObject(ctx, sourceBackend, targetBackend, obj, sha256HexLength); err != nil {
return err
}
atomic.AddInt64(&migrated, 1)
@@ -257,25 +260,11 @@ func migrateSingleObject(
ctx context.Context,
sourceBackend storage.Backend,
targetBackend storage.Backend,
sourceDriver storage.Driver,
targetDriver storage.Driver,
obj struct {
FilePath string `gorm:"column:file_path"`
FileSize int64 `gorm:"column:file_size"`
MimeType string `gorm:"column:mime_type"`
Hash string `gorm:"column:hash"`
},
obj migrationObject,
sha256HexLength int,
) error {
if shouldSkipMigration(ctx, targetBackend, obj) {
task.AppendLog(ctx, "[跳过迁移] 目标存储已存在相同文件且校验一致: %s", obj.FilePath)
if err := db.DB(ctx).Model(&model.Upload{}).
Where("storage_driver = ? AND file_path = ?", sourceDriver, obj.FilePath).
Updates(map[string]any{
colStorageDriver: targetDriver,
}).Error; err != nil {
return fmt.Errorf("update migrated object %q: %w", obj.FilePath, err)
}
task.AppendLog(ctx, "[跳过迁移] 目标存储已存在相同文件: %s", obj.FilePath)
return nil
}
@@ -283,7 +272,7 @@ func migrateSingleObject(
source, err := sourceBackend.Get(ctx, obj.FilePath)
if err != nil {
if isNotFoundError(err) {
return markMissingMigrationObjectDeleted(ctx, sourceDriver, targetDriver, obj.FilePath, err)
return markMissingMigrationObjectDeleted(ctx, obj.FilePath, err)
}
return fmt.Errorf("open source object %q: %w", obj.FilePath, err)
}
@@ -319,28 +308,22 @@ func migrateSingleObject(
task.AppendLog(ctx, "[校验通过] 文件一致性校验成功: %s", targetResult.Key)
}
task.AppendLog(ctx, "[更新数据库] 正在更新文件 %s 的存储路径与驱动信息为: %s (%s)", obj.FilePath, targetResult.Key, targetDriver)
if err := db.DB(ctx).Model(&model.Upload{}).
Where("storage_driver = ? AND file_path = ?", sourceDriver, obj.FilePath).
Updates(map[string]any{
colStorageDriver: targetDriver,
"file_path": targetResult.Key,
}).Error; err != nil {
return fmt.Errorf("update migrated object %q: %w", obj.FilePath, err)
if targetResult.Key != obj.FilePath {
task.AppendLog(ctx, "[更新数据库] 正在更新文件路径: %s -> %s", obj.FilePath, targetResult.Key)
if err := db.DB(ctx).Model(&model.Upload{}).
Where("file_path = ? AND status != ?", obj.FilePath, model.UploadStatusDeleted).
Update("file_path", targetResult.Key).Error; err != nil {
return fmt.Errorf("update migrated object %q: %w", obj.FilePath, err)
}
}
task.AppendLog(ctx, "[迁移成功] 文件已完成迁移: %s -> %s", obj.FilePath, targetResult.Key)
task.AppendLog(ctx, "[迁移成功] 文件已完成迁移: %s", targetResult.Key)
return nil
}
func shouldSkipMigration(
ctx context.Context,
targetBackend storage.Backend,
obj struct {
FilePath string `gorm:"column:file_path"`
FileSize int64 `gorm:"column:file_size"`
MimeType string `gorm:"column:mime_type"`
Hash string `gorm:"column:hash"`
},
obj migrationObject,
) bool {
targetObj, err := targetBackend.Get(ctx, obj.FilePath)
if err != nil || targetObj == nil || targetObj.Body == nil {
@@ -355,8 +338,6 @@ func shouldSkipMigration(
func markMissingMigrationObjectDeleted(
ctx context.Context,
sourceDriver storage.Driver,
targetDriver storage.Driver,
filePath string,
sourceErr error,
) error {
@@ -364,16 +345,13 @@ func markMissingMigrationObjectDeleted(
var affectedUploads []model.Upload
if err := db.DB(ctx).
Where("storage_driver = ? AND file_path = ? AND status != ?", sourceDriver, filePath, model.UploadStatusDeleted).
Where("file_path = ? AND status != ?", filePath, model.UploadStatusDeleted).
Find(&affectedUploads).Error; err != nil {
return fmt.Errorf("load missing object uploads %q: %w", filePath, err)
}
if err := db.DB(ctx).Model(&model.Upload{}).
Where("storage_driver = ? AND file_path = ?", sourceDriver, filePath).
Updates(map[string]any{
"status": model.UploadStatusDeleted,
colStorageDriver: targetDriver,
}).Error; err != nil {
Where("file_path = ?", filePath).
Update("status", model.UploadStatusDeleted).Error; err != nil {
return fmt.Errorf("update missing object %q: %w", filePath, err)
}
for i := range affectedUploads {
@@ -68,7 +68,6 @@ func TestMigrationHandlerExecute(t *testing.T) {
MimeType: "text/plain",
Extension: "txt",
Hash: "hash",
StorageDriver: string(storage.DriverLocal),
Type: "attachment",
Status: model.UploadStatusUsed,
}
@@ -106,9 +105,6 @@ func TestMigrationHandlerExecute(t *testing.T) {
if err := dbConn.First(&migrated, upload.ID).Error; err != nil {
t.Fatalf("First(upload) returned error: %v", err)
}
if migrated.StorageDriver != string(storage.DriverS3) {
t.Errorf("StorageDriver = %q, want %q", migrated.StorageDriver, storage.DriverS3)
}
current, err := storage.LoadConfig(ctx)
if err != nil {
t.Fatalf("LoadConfig() returned error: %v", err)
@@ -169,7 +165,6 @@ func TestMigrationHandlerExecuteWithHashValidation(t *testing.T) {
MimeType: "text/plain",
Extension: "txt",
Hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // Invalid hash
StorageDriver: string(storage.DriverLocal),
Type: "attachment",
Status: model.UploadStatusUsed,
}
@@ -224,8 +219,8 @@ func TestMigrationHandlerExecuteWithHashValidation(t *testing.T) {
if err := dbConn.First(&migrated, uploadIncorrect.ID).Error; err != nil {
t.Fatalf("First(upload) returned error: %v", err)
}
if migrated.StorageDriver != string(storage.DriverS3) {
t.Errorf("StorageDriver = %q, want %q", migrated.StorageDriver, storage.DriverS3)
if migrated.FilePath != "uploads/test-hash.txt" {
t.Errorf("FilePath = %q, want %q", migrated.FilePath, "uploads/test-hash.txt")
}
}
+7 -8
View File
@@ -42,6 +42,9 @@ func TestSystemCleanupHandler_Execute(t *testing.T) {
func(ctx context.Context, key string) error { return nil },
)
defer storageMock()
storage.IsEnabledFunc = func() bool { return true }
defer func() { storage.IsEnabledFunc = func() bool { return false } }()
storage.ResetCache()
ctx := context.Background()
err := db.DB(ctx).AutoMigrate(&model.PushHistory{})
@@ -56,27 +59,27 @@ func TestSystemCleanupHandler_Execute(t *testing.T) {
{
UserID: 1001, FileName: "old_file_1.jpg", FilePath: "uploads/old_1.jpg",
FileSize: 1024, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash1",
StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusPending,
Type: "attachment", Status: model.UploadStatusPending,
CreatedAt: twoHoursAgo,
},
{
UserID: 1001, FileName: "old_file_2.png", FilePath: "uploads/old_2.png",
FileSize: 2048, MimeType: "image/png", Extension: "png", Hash: "hash2",
StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusPending,
Type: "attachment", Status: model.UploadStatusPending,
CreatedAt: twoHoursAgo,
},
// 状态为 used 的记录 —— 不应被清理
{
UserID: 1001, FileName: "used_file.jpg", FilePath: "uploads/used.jpg",
FileSize: 512, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash3",
StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusUsed,
Type: "attachment", Status: model.UploadStatusUsed,
CreatedAt: twoHoursAgo,
},
// 不到1小时的 pending 记录 —— 不应被清理
{
UserID: 1001, FileName: "recent_file.jpg", FilePath: "uploads/recent.jpg",
FileSize: 256, MimeType: "image/jpeg", Extension: "jpg", Hash: "hash4",
StorageDriver: "s3", Type: "attachment", Status: model.UploadStatusPending,
Type: "attachment", Status: model.UploadStatusPending,
CreatedAt: now.Add(-10 * time.Minute),
},
}
@@ -283,7 +286,6 @@ func TestWarmImageCacheHandlerExecute(t *testing.T) {
FilePath: firstPath,
MimeType: "image/png",
Extension: "png",
StorageDriver: shared.StorageDriverLocal,
Status: model.UploadStatusUsed,
},
{
@@ -293,7 +295,6 @@ func TestWarmImageCacheHandlerExecute(t *testing.T) {
FilePath: secondPath,
MimeType: "application/octet-stream",
Extension: "jpg",
StorageDriver: shared.StorageDriverLocal,
Status: model.UploadStatusPending,
},
{
@@ -303,7 +304,6 @@ func TestWarmImageCacheHandlerExecute(t *testing.T) {
FilePath: filepath.Join(testDir, "notes.txt"),
MimeType: "text/plain",
Extension: "txt",
StorageDriver: shared.StorageDriverLocal,
Status: model.UploadStatusUsed,
},
{
@@ -313,7 +313,6 @@ func TestWarmImageCacheHandlerExecute(t *testing.T) {
FilePath: firstPath,
MimeType: "image/png",
Extension: "png",
StorageDriver: shared.StorageDriverLocal,
Status: model.UploadStatusDeleted,
},
}