using Microsoft.EntityFrameworkCore; using MilliSim.Common.Models; using MilliSim.Data; namespace MilliSim.Services; /// /// 后台定时任务:每天检查即将到期(7 天内)的授权,给被授权用户发送提醒通知。 /// 使用 EventId = "auth-expiring-{authId}-{expiryDate}" 进行幂等,避免重复发送。 /// public class AuthorizationNotificationJob : IHostedService, IDisposable { private readonly IServiceProvider _serviceProvider; private Timer? _timer; // 检查间隔:6 小时(既能在到期日及时触发,又避免频繁扫描) private static readonly TimeSpan CheckInterval = TimeSpan.FromHours(6); // 提前提醒天数 private const int ExpiringSoonDays = 7; public AuthorizationNotificationJob(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public Task StartAsync(CancellationToken cancellationToken) { // 启动后 30 秒执行第一次,之后每 6 小时一次 _timer = new Timer(DoWork, null, TimeSpan.FromSeconds(30), CheckInterval); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } private async void DoWork(object? state) { try { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var notification = scope.ServiceProvider.GetRequiredService(); var now = DateTime.Now; var deadline = now.AddDays(ExpiringSoonDays); // 查询 7 天内即将到期、且尚未过期的有效授权 var expiringSoon = await db.Authorizations .Where(a => a.Status == AuthorizationStatus.Active && a.EndTime > now && a.EndTime <= deadline) .ToListAsync(); foreach (var auth in expiringSoon) { var daysRemaining = Math.Max(1, (int)Math.Ceiling((auth.EndTime - now).TotalDays)); // 按到期日期去重:同一天内只发一次 var eventId = $"auth-expiring-{auth.Id}-{auth.EndTime:yyyyMMdd}"; try { await notification.CreateNotificationAsync( NotificationType.Authorization, "授权即将到期", $"您的客户级授权将在 {daysRemaining} 天后到期({auth.EndTime:yyyy-MM-dd}),请及时联系销售续期。", userId: auth.UserId, eventId: eventId); } catch { // 单条失败不影响其他通知 } } } catch { // 后台任务异常不应影响应用运行 } } }