using WenChuanyi.Api.Models;
namespace WenChuanyi.Api.Services;
/// 过期文件定时清理:每 N 分钟扫描 IsPermanent=false 且已过期的记录,先删记录再删 OSS 对象
public class ExpiredFileCleanerService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger _logger;
private readonly TimeSpan _interval;
public ExpiredFileCleanerService(
IServiceScopeFactory scopeFactory,
IConfiguration cfg,
ILogger logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
var minutes = cfg.GetValue("Cleaner:IntervalMinutes", 30);
_interval = TimeSpan.FromMinutes(Math.Max(1, minutes));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("过期文件清理任务启动,间隔 {Minutes} 分钟", _interval.TotalMinutes);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await CleanExpiredAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "过期文件清理任务异常");
}
await Task.Delay(_interval, stoppingToken);
}
}
private async Task CleanExpiredAsync(CancellationToken ct)
{
await using var scope = _scopeFactory.CreateAsyncScope();
var fsql = scope.ServiceProvider.GetRequiredService();
var oss = scope.ServiceProvider.GetRequiredService();
var expired = await fsql.Select()
.Where(f => !f.IsPermanent && f.ExpiresAt != null && f.ExpiresAt < DateTime.Now)
.ToListAsync(ct);
if (expired.Count == 0)
{
return;
}
_logger.LogInformation("发现 {Count} 个过期文件,开始清理", expired.Count);
foreach (var item in expired)
{
// 先删数据库记录
var deleted = await fsql.Delete().Where(f => f.Id == item.Id).ExecuteAffrowsAsync(ct) > 0;
// 再删 OSS 对象(失败仅记日志,不阻塞任务)
if (deleted)
{
oss.TryDeleteObject(item.ObjectKey);
}
}
}
}