feat(core,dal,domain,snapshotWorker): Воркер по управлению снапшотами. Создание сгапшотов для таблицы RobotConfigurations

This commit is contained in:
Mikhail Trubnikov
2026-07-13 15:29:48 +10:00
parent 9194d127d2
commit 50556ef605
28 changed files with 4953 additions and 26 deletions

View File

@@ -0,0 +1,94 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.RobotRepositories;
using PARR.Core.Services.Snapshots.Interfaces;
using PARR.Domain.Entities.RobotEntities;
using PARR.Domain.Exceptions;
namespace PARR.Core.Services.Snapshots.Implementations
{
/// <summary>
/// Снапшоты для таблицы RobotConfiguration
/// </summary>
internal class RobotConfigurationSnapshotService : ISnapshotProvider
{
private readonly IRobotConfigurationRepository _robotConfigurationRepository;
private readonly IRobotConfigurationSnapshotRepository _robotConfigurationSnapshotRepository;
private readonly ILogger<RobotConfigurationSnapshotService> _logger;
private readonly ISnapshotSettings _snapshotSettings;
public TimeSpan Interval => _snapshotSettings.RobotConfigurationSnapshotInterval;// TimeSpan.FromMinutes(2);
public TimeSpan RetentionPeriod => _snapshotSettings.RobotConfigurationSnapshotRetentionPeriod;//TimeSpan.FromDays(60);
public RobotConfigurationSnapshotService(
IRobotConfigurationRepository robotConfigurationRepository,
IRobotConfigurationSnapshotRepository robotConfigurationSnapshotRepository,
ILogger<RobotConfigurationSnapshotService> logger,
ISnapshotSettings snapshotSettings
)
{
_robotConfigurationRepository = robotConfigurationRepository;
_robotConfigurationSnapshotRepository = robotConfigurationSnapshotRepository;
_logger = logger;
_snapshotSettings = snapshotSettings;
}
public async Task TakeSnapshotAsync(CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
_logger.LogDebug("Сбор метрик для снапшота RobotConfigurations...");
var stats = await _robotConfigurationRepository.Get()
.GroupBy(t => new { t.RobotCode, t.RobotStatusCode, t.TaskStatusCode })
.Select(g => new
{
g.Key.RobotCode,
g.Key.RobotStatusCode,
g.Key.TaskStatusCode,
Count = g.Count()
})
.ToListAsync(cancellationToken);
if (!stats.Any())
{
_logger.LogDebug("Нет активных заданий роботов для создания снапшота.");
return;
}
var snapshots = stats.Select(s => new RobotConfigurationSnapshot
{
Id = Guid.NewGuid(),
DateCreated = now,
RobotCode = s.RobotCode,
RobotStatusCode = s.RobotStatusCode,
TaskStatusCode = s.TaskStatusCode,
Count = s.Count
}).ToList();
if (!await _robotConfigurationSnapshotRepository.AddRangeAsync(snapshots) || !await _robotConfigurationSnapshotRepository.CommitAsync())
throw new DbErrorException("Ошибка при сохранении в БД");
_logger.LogInformation("Успешно сохранен снапшот RobotConfigurations. Записано строк: {Count}. Периодичность: {Interval}", snapshots.Count, Interval);
}
public async Task CleanUpOldSnapshotsAsync(CancellationToken cancellationToken)
{
// Вычисляем граничную дату (всё, что было ДО нее — удаляем)
var thresholdDate = DateTimeOffset.UtcNow.Subtract(RetentionPeriod);
_logger.LogInformation("[{ServiceName}] Запуск очистки старых снапшотов. Удаление данных старше {ThresholdDate}", GetType().Name, thresholdDate);
// Фильтруем старые записи и вызываем ExecuteDeleteAsync для удаления прямо в базе данных
var deletedCount = await _robotConfigurationSnapshotRepository.Get()
.Where(s => s.DateCreated < thresholdDate)
.ExecuteDeleteAsync(cancellationToken);
_logger.LogInformation("[{ServiceName}] Очистка завершена. Удалено устаревших строк снапшотов: {Count}", GetType().Name, deletedCount);
}
}
}