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