147 lines
4.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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