using MiaoJiZhang.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; namespace MiaoJiZhang.Api.Services; public class RecycleBinCleanupService( IServiceScopeFactory scopeFactory, ILogger logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Cleanup(stoppingToken); using var timer = new PeriodicTimer(TimeSpan.FromHours(24)); while (await timer.WaitForNextTickAsync(stoppingToken)) await Cleanup(stoppingToken); } private async Task Cleanup(CancellationToken ct) { try { using var scope = scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var cutoff = DateTime.UtcNow.AddDays(-30); var ids = await db.Transactions.IgnoreQueryFilters() .Where(t => t.IsDeleted && t.DeletedAt < cutoff) .Select(t => t.Id) .ToListAsync(ct); if (ids.Count == 0) return; await db.ChatMessages .Where(message => message.TransactionId.HasValue && ids.Contains(message.TransactionId.Value)) .ExecuteUpdateAsync(setters => setters .SetProperty(message => message.TransactionId, (long?)null) .SetProperty(message => message.Content, """{"deleted":true}"""), ct); await db.Transactions.IgnoreQueryFilters() .Where(t => ids.Contains(t.Id)) .ExecuteDeleteAsync(ct); logger.LogInformation( "Permanently removed {Count} transactions after recycle-bin retention", ids.Count); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { } catch (Exception exception) { logger.LogError(exception, "Recycle-bin cleanup failed"); } } }