147 lines
4.3 KiB
C#
147 lines
4.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
using MilliSim.Common.Entities;
|
||
using MilliSim.Data;
|
||
|
||
namespace MilliSim.Services.Infrastructure;
|
||
|
||
/// <summary>
|
||
/// 上传文件跟踪服务接口:管理 PendingUpload 记录,实现垃圾资源清理
|
||
/// </summary>
|
||
public interface IUploadTrackingService
|
||
{
|
||
/// <summary>
|
||
/// 注册上传文件(上传成功后调用)
|
||
/// </summary>
|
||
Task RegisterAsync(string objectKey, string folder, long? uploadedBy);
|
||
|
||
/// <summary>
|
||
/// 消费上传文件(保存到实体后调用,标记为已使用)
|
||
/// </summary>
|
||
Task ConsumeAsync(string objectKey);
|
||
|
||
/// <summary>
|
||
/// 删除待消费的上传文件(取消表单/替换文件时调用)
|
||
/// 仅删除 ConsumedAt == null 的记录,防止误删已使用的文件
|
||
/// </summary>
|
||
/// <returns>是否实际删除了文件</returns>
|
||
Task<bool> DeletePendingAsync(string objectKey);
|
||
|
||
/// <summary>
|
||
/// 清理过期的待消费上传文件(GC 定时任务调用)
|
||
/// </summary>
|
||
/// <param name="expireHours">过期时间(小时)</param>
|
||
/// <returns>清理的文件数量</returns>
|
||
Task<int> CleanupExpiredAsync(int expireHours = 24);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传文件跟踪服务实现
|
||
/// </summary>
|
||
public class UploadTrackingService : IUploadTrackingService
|
||
{
|
||
private readonly MilliSimDbContext _db;
|
||
private readonly IFileStorageService _fileStorage;
|
||
private readonly ILogger<UploadTrackingService> _logger;
|
||
|
||
public UploadTrackingService(
|
||
MilliSimDbContext db,
|
||
IFileStorageService fileStorage,
|
||
ILogger<UploadTrackingService> 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<bool> 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<int> 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;
|
||
}
|
||
}
|