using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using MilliSim.Common.Entities; using MilliSim.Data; namespace MilliSim.Services.Infrastructure; /// /// 上传文件跟踪服务接口:管理 PendingUpload 记录,实现垃圾资源清理 /// public interface IUploadTrackingService { /// /// 注册上传文件(上传成功后调用) /// Task RegisterAsync(string objectKey, string folder, long? uploadedBy); /// /// 消费上传文件(保存到实体后调用,标记为已使用) /// Task ConsumeAsync(string objectKey); /// /// 删除待消费的上传文件(取消表单/替换文件时调用) /// 仅删除 ConsumedAt == null 的记录,防止误删已使用的文件 /// /// 是否实际删除了文件 Task DeletePendingAsync(string objectKey); /// /// 清理过期的待消费上传文件(GC 定时任务调用) /// /// 过期时间(小时) /// 清理的文件数量 Task CleanupExpiredAsync(int expireHours = 24); } /// /// 上传文件跟踪服务实现 /// public class UploadTrackingService : IUploadTrackingService { private readonly MilliSimDbContext _db; private readonly IFileStorageService _fileStorage; private readonly ILogger _logger; public UploadTrackingService( MilliSimDbContext db, IFileStorageService fileStorage, ILogger logger) { _db = db; _fileStorage = fileStorage; _logger = logger; } public async Task RegisterAsync(string objectKey, string folder, long? uploadedBy) { if (string.IsNullOrEmpty(objectKey)) return; _db.PendingUploads.Add(new PendingUpload { ObjectKey = objectKey, Folder = folder ?? string.Empty, UploadedBy = uploadedBy, UploadedAt = DateTime.Now, ConsumedAt = null }); await _db.SaveChangesAsync(); } public async Task ConsumeAsync(string objectKey) { if (string.IsNullOrEmpty(objectKey)) return; var pending = await _db.PendingUploads .Where(p => p.ObjectKey == objectKey && p.ConsumedAt == null) .ToListAsync(); if (pending.Count == 0) return; var now = DateTime.Now; foreach (var item in pending) { item.ConsumedAt = now; item.UpdatedAt = now; } await _db.SaveChangesAsync(); } public async Task DeletePendingAsync(string objectKey) { if (string.IsNullOrEmpty(objectKey)) return false; var pending = await _db.PendingUploads .Where(p => p.ObjectKey == objectKey && p.ConsumedAt == null) .ToListAsync(); if (pending.Count == 0) return false; // 删除存储中的文件 try { await _fileStorage.DeleteAsync(objectKey); } catch (Exception ex) { _logger.LogWarning(ex, "删除上传文件失败:{ObjectKey}", objectKey); } // 删除 PendingUpload 记录 _db.PendingUploads.RemoveRange(pending); await _db.SaveChangesAsync(); return true; } public async Task CleanupExpiredAsync(int expireHours = 24) { var cutoff = DateTime.Now.AddHours(-expireHours); var expired = await _db.PendingUploads .Where(p => p.ConsumedAt == null && p.UploadedAt < cutoff) .ToListAsync(); if (expired.Count == 0) return 0; var deletedCount = 0; foreach (var item in expired) { try { await _fileStorage.DeleteAsync(item.ObjectKey); deletedCount++; } catch (Exception ex) { _logger.LogWarning(ex, "GC 清理删除文件失败:{ObjectKey}", item.ObjectKey); } } _db.PendingUploads.RemoveRange(expired); await _db.SaveChangesAsync(); _logger.LogInformation("垃圾资源 GC 清理:共清理 {Count} 个过期上传文件", deletedCount); return deletedCount; } }