- SimpleTemplateSynchronizer и GroupedTemplateSynchronizer переведены на паттерн Pipeline с разделением на Read/Write этапы - Выделены контракты этапов (ISimpleSyncStage, IGroupedSyncStage) и контексты (SimpleSyncContext, GroupedSyncContext) - Read-этапы безопасны для тестов (не пишут в БД/MQ), Write-этапы изолированы через отдельные интерфейсы - Добавлено [Perf]-логирование каждого этапа с метриками времени выполнения - Логи приведены к человекочитаемому формату 'Имя' (ID) для Job, JobGroup и Unit - Устранено дублирование данных в контекстах (FilteredUnits перезаписывается, TemplateGroups строго типизирован) - Константы неиспользуемых шаблонов вынесены в UnusedTemplateConstants - Структура проекта реорганизована: SimpleSync, GroupedSync, Implementations, Interfaces
120 lines
5.4 KiB
C#
120 lines
5.4 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using PARR.Core.Services.MatchingStatusService;
|
|
using PARR.Domain.Cache.Models;
|
|
using PARR.Domain.Entities.Base.History;
|
|
using PARR.Domain.Enums;
|
|
using PARR.TemplateMatcher.Services.Implementations.GroupedSync;
|
|
using PARR.TemplateMatcher.Services.Interfaces;
|
|
using System.Diagnostics;
|
|
|
|
namespace PARR.TemplateMatcher.Services.GroupedSync;
|
|
|
|
internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|
{
|
|
private readonly IEnumerable<IGroupedSyncStage> _readStages;
|
|
private readonly IEnumerable<IGroupedSyncWriteStage> _writeStages;
|
|
private readonly IMatchingStatusService _matchingStatusService;
|
|
private readonly ILogger<GroupedTemplateSynchronizer> _logger;
|
|
|
|
public GroupedTemplateSynchronizer(
|
|
IEnumerable<IGroupedSyncStage> readStages,
|
|
IEnumerable<IGroupedSyncWriteStage> writeStages,
|
|
IMatchingStatusService matchingStatusService,
|
|
ILogger<GroupedTemplateSynchronizer> logger)
|
|
{
|
|
_readStages = readStages;
|
|
_writeStages = writeStages;
|
|
_matchingStatusService = matchingStatusService;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
|
{
|
|
_logger.LogInformation("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
|
|
|
var existingStatus = await _matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|
if (existingStatus.DetailsJobGroups?.Any() == true)
|
|
{
|
|
_logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId);
|
|
return;
|
|
}
|
|
|
|
await SetStatusAsync(jobGroupId, "Начало синхронизации");
|
|
|
|
var totalSw = Stopwatch.StartNew();
|
|
|
|
try
|
|
{
|
|
var context = new GroupedSyncContext { JobGroupId = jobGroupId, Initiator = initiator };
|
|
|
|
foreach (var stage in _readStages)
|
|
{
|
|
var stageSw = Stopwatch.StartNew();
|
|
await stage.ExecuteAsync(context);
|
|
stageSw.Stop();
|
|
_logger.LogDebug("[Perf] JobGroup '{JobGroupName}' ({JobGroupId}) | Этап: {Stage} | Время: {Ms} мс",
|
|
context.JobGroupName, jobGroupId, stage.StageName, stageSw.ElapsedMilliseconds);
|
|
}
|
|
|
|
foreach (var stage in _writeStages)
|
|
{
|
|
var stageSw = Stopwatch.StartNew();
|
|
await stage.ExecuteAsync(context);
|
|
stageSw.Stop();
|
|
_logger.LogDebug("[Perf] JobGroup '{JobGroupName}' ({JobGroupId}) | Этап: {Stage} | Время: {Ms} мс",
|
|
context.JobGroupName, jobGroupId, stage.StageName, stageSw.ElapsedMilliseconds);
|
|
}
|
|
|
|
totalSw.Stop();
|
|
_logger.LogInformation("[Perf] JobGroup '{JobGroupName}' ({JobGroupId}) | ИТОГО: {TotalMs} мс",
|
|
context.JobGroupName, jobGroupId, totalSw.ElapsedMilliseconds);
|
|
|
|
await SetStatusAsync(jobGroupId, "Синхронизация завершена успешно");
|
|
await _matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|
_logger.LogInformation("Синхронизация шаблонов завершена для JobGroup '{JobGroupName}' ({JobGroupId})",
|
|
context.JobGroupName, jobGroupId);
|
|
}
|
|
catch (GroupedSyncEarlyExitException ex)
|
|
{
|
|
totalSw.Stop();
|
|
_logger.LogInformation("JobGroup {JobGroupId}: {Reason} ({ElapsedMs} мс)",
|
|
jobGroupId, ex.Reason, totalSw.ElapsedMilliseconds);
|
|
await SetStatusAsync(jobGroupId, ex.Reason);
|
|
await _matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
totalSw.Stop();
|
|
_logger.LogError(ex, "Ошибка при синхронизации JobGroup {JobGroupId} через {ElapsedMs} мс",
|
|
jobGroupId, totalSw.ElapsedMilliseconds);
|
|
await SetStatusAsync(jobGroupId, $"Ошибка: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
|
{
|
|
_logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
|
{
|
|
_logger.LogWarning("GroupedTemplateSynchronizer: UpdateTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task SetStatusAsync(Guid jobGroupId, string comment)
|
|
{
|
|
var status = new MatchingStatusItemDto
|
|
{
|
|
DateStart = DateTimeOffset.UtcNow,
|
|
Action = TemplateMatcherActionEnum.Sync,
|
|
Comment = comment
|
|
};
|
|
await _matchingStatusService.SetMatchingStatusAsync(
|
|
jobGroupId, SyncTaskEntityTypeEnum.JobGroup,
|
|
new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) },
|
|
TimeSpan.FromMinutes(30));
|
|
}
|
|
} |