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;
}
}