/
afanasevn
/
RedisLab
Обзор
Документация
Войти
/
afanasevn
/
RedisLab
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/RedisLab.Infrastructure/Reminders/TaskReminderHostedService.cs
96 строк
3 KB
IBS\NAfanasev
Solution commit
24 июн 2026, 16:45
24 июн 2026, 16:45
bab80b7
Код
Авторство
О чём код?
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using RedisLab.Application.Abstractions; using RedisLab.Application.Redis; using RedisLab.Domain.Enums; using RedisLab.Infrastructure.Hosting; using RedisLab.Infrastructure.Persistence; using TaskStatus = RedisLab.Domain.Enums.TaskStatus; namespace RedisLab.Infrastructure.Reminders; /// <summary> /// Cron каждые 5 мин: scan просроченных задач под distributed lock lock:task-reminder. /// </summary> public sealed class TaskReminderHostedService( IServiceScopeFactory scopeFactory, IDistributedLockService distributedLock, InstanceIdentity instanceIdentity, ILogger<TaskReminderHostedService> logger) : BackgroundService { private static readonly TimeSpan ScanInterval = TimeSpan.FromMinutes(5); /// <inheritdoc /> protected override async Task ExecuteAsync(CancellationToken stoppingToken) { logger.LogInformation( "TaskReminderHostedService запущен, interval={Interval} мин, instanceId={InstanceId}", ScanInterval.TotalMinutes, instanceIdentity.Id); using var timer = new PeriodicTimer(ScanInterval); await RunScanCycleAsync(stoppingToken); while (await timer.WaitForNextTickAsync(stoppingToken)) { await RunScanCycleAsync(stoppingToken); } } /// <summary> /// Один цикл: попытка lock => scan PostgreSQL => лог результата. /// </summary> private async Task RunScanCycleAsync(CancellationToken ct) { var lockTtl = TimeSpan.FromSeconds(RedisKeyNames.TaskReminderLockTtlSeconds); var acquired = await distributedLock.TryAcquireAsync( RedisKeyNames.TaskReminderLock, instanceIdentity.Id, lockTtl, ct); if (!acquired) { logger.LogInformation( "Lock занят, пропуск scan просроченных задач (instanceId={InstanceId})", instanceIdentity.Id); return; } await using var scope = scopeFactory.CreateAsyncScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var now = DateTime.UtcNow; var overdueTasks = await db.Tasks .AsNoTracking() .Where(x => x.DueDate != null && x.DueDate < now && x.Status != TaskStatus.Closed) .OrderBy(x => x.DueDate) .Select(x => new { x.Id, x.Title, x.DueDate }) .ToListAsync(ct); logger.LogInformation( "Scan выполнен instanceId={InstanceId}: найдено {Count} просроченных задач", instanceIdentity.Id, overdueTasks.Count); foreach (var task in overdueTasks.Take(5)) { logger.LogInformation( " просрочена: {Title} (due {DueDate:u})", task.Title, task.DueDate); } if (overdueTasks.Count > 5) { logger.LogInformation(" ... и ещё {More} задач", overdueTasks.Count - 5); } } }