From 0477637abfe9218aa2cb69a02e3ad7158d75a0fe Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Mon, 15 Dec 2025 14:04:48 +1000 Subject: [PATCH] =?UTF-8?q?feat(templateMatcher):=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D1=81=D0=B8=D1=82=D1=83=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BA=D0=BE=D0=B3=D0=B4=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B6=D0=B8=D0=B4=D0=B0=D0=B5=D0=BC=D1=8B=D1=85=20unit=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=88=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D0=BE=D0=B2=200?= =?UTF-8?q?=20=D0=B0=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B8=D1=85>0.=20?= =?UTF-8?q?=D0=A0=D0=B0=D0=BD=D0=B5=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B8=D0=BB=D0=B0=20=D0=B8=D0=B7=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=D0=B0=20=D1=81=D0=B8=D0=BD=D1=85=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.TemplateMatcher/TemplateMatcher.cs | 317 +++++++++++++----------- 1 file changed, 179 insertions(+), 138 deletions(-) diff --git a/PARR.TemplateMatcher/TemplateMatcher.cs b/PARR.TemplateMatcher/TemplateMatcher.cs index 6f12e687..74ba13fb 100644 --- a/PARR.TemplateMatcher/TemplateMatcher.cs +++ b/PARR.TemplateMatcher/TemplateMatcher.cs @@ -22,6 +22,7 @@ namespace PARR.TemplateMatcher private const bool DefaultUnusedScheduleState = false; private const bool DefaultUsedTemplateState = false; private const bool DefaultUsedScheduleState = false; + private readonly ILogger logger; private readonly IUnitFilterService unitFilterService; private readonly MqSettings mqSettings; @@ -39,8 +40,7 @@ namespace PARR.TemplateMatcher ITemplateService templateService, IJobService jobService, IShortcodesService shortcodesService, - IEsppScheduleTransformService esppScheduleTransformService - ) + IEsppScheduleTransformService esppScheduleTransformService) { this.logger = logger; this.unitFilterService = unitFilterService; @@ -52,17 +52,12 @@ namespace PARR.TemplateMatcher this.esppScheduleTransformService = esppScheduleTransformService; } - public async Task SyncTemplatesForJob(Guid jobId, HistoryInitiator initiator) { logger.LogDebug("Начало синхронизации шаблонов для JobId {JobId}", jobId); - var expectedUnitIds = await GetExpectedUnitIdsAsync(jobId); - if (!expectedUnitIds.Any()) - { - logger.LogInformation("Для JobId {JobId} не найдено Unit'ов по фильтрам.", jobId); - return; - } + var expectedUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet(); + logger.LogDebug("JobId {JobId}: найдено {Count} UnitId по фильтрам.", jobId, expectedUnitIds.Count); var job = await GetJobWithGroupAndAutoControlAsync(jobId); if (job == null) @@ -78,54 +73,46 @@ namespace PARR.TemplateMatcher logger.LogDebug("JobId {JobId}: {Expected} ожидаемых UnitId, {Existing} существующих шаблонов.", jobId, expectedUnitIds.Count, existingTemplates.Count); - // 4. Шаблоны, НЕ в фильтре → отправляем на деактивацию через worker + // Обработка случая: фильтр вернул 0 UnitId → деактивировать ВСЕ шаблоны + if (!expectedUnitIds.Any()) + { + if (existingTemplates.Any()) + { + logger.LogInformation("Для JobId {JobId} фильтры не дали Unit'ов — будет деактивировано {Count} шаблонов.", + jobId, existingTemplates.Count); + + foreach (var template in existingTemplates) + { + await DeactivateTemplateAsync(template, jobId, initiator); + } + } + else + { + logger.LogInformation("Для JobId {JobId} нет Unit'ов по фильтрам и нет существующих шаблонов — синхронизация завершена.", jobId); + } + + logger.LogInformation("Синхронизация завершена для JobId {JobId} (фильтр пуст).", jobId); + return; + } + + // Деактивация шаблонов, которые вышли из фильтра var templatesToDeactivate = existingTemplates .Where(t => !expectedUnitIds.Contains(t.UnitId)) .ToList(); foreach (var template in templatesToDeactivate) { - if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) continue; - - logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация через worker.", - template.Id, template.UnitId); - - // Только переводим в Updating — остальное пусть делает worker - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating для деактивации.", template.Id); - continue; - } - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = jobId, - UnitId = template.UnitId, - Name = GetTemplateNameForUnsed(template.Name), - IsActiveTemplate = DefaultUnusedTemplateState, - IsActiveSchedule = DefaultUnusedScheduleState, - LastRun = template.LastRun, - NextRun = template.NextRun, - Index = template.Index, - StatusTypeId = TemplateStatusTypeEnum.Unused, - Initiator = initiator - }; - - await SendTemplateUpdateMessage(updateRequest); + await DeactivateTemplateAsync(template, jobId, initiator); } - // 5. Перечитываем шаблоны + // Перечитываем шаблоны после деактивации existingTemplates = await templateService.Get() .Where(t => t.JobId == jobId) .ToListAsync(); var unitToTemplate = existingTemplates.ToDictionary(t => t.UnitId, t => t); - // 6. UnitId без шаблона + // UnitId без шаблона → попытка переиспользования или создание var unitIdsMissingTemplates = expectedUnitIds .Where(unitId => !unitToTemplate.ContainsKey(unitId)) .ToList(); @@ -140,9 +127,7 @@ namespace PARR.TemplateMatcher logger.LogInformation("Переиспользован шаблон {TemplateId} для UnitId {UnitId}.", reused.Id, unitId); var expectedName = await GetNormalizedTemplateNameAsync(job, unitId); - var nextRun = await GetNextRunAsync(job); // новый шаблон → всегда пересчитываем - - + var nextRun = await GetNextRunAsync(job); // всегда пересчитываем для нового назначения var updateRequest = new TemplateUpdaterMq { @@ -166,7 +151,7 @@ namespace PARR.TemplateMatcher } } - // 9. Шаблоны В ФИЛЬТРЕ → обновление имени / реактивация + // Обновление/реактивация шаблонов, оставшихся в фильтре var templatesInFilter = existingTemplates .Where(t => expectedUnitIds.Contains(t.UnitId)) .ToList(); @@ -174,65 +159,18 @@ namespace PARR.TemplateMatcher foreach (var template in templatesInFilter) { var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId); - bool needsUpdate = template.Name != expectedName - || template.StatusTypeId != TemplateStatusTypeEnum.Used; - - if (needsUpdate && template.StatusTypeId != TemplateStatusTypeEnum.Updating) - { - logger.LogInformation("Шаблон {TemplateId}: требуется обновление имени или реактивация.", template.Id); - - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); - continue; - } - - var nextRun = await GetNextRunAsync(job, template.NextRun); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = jobId, - UnitId = template.UnitId, - Name = expectedName, - IsActiveTemplate = template.IsActiveTemplate, - IsActiveSchedule = template.IsActiveSchedule, - LastRun = template.LastRun, - NextRun = nextRun, - Index = template.Index, - StatusTypeId = TemplateStatusTypeEnum.Used, - Initiator = initiator - }; - - await SendTemplateUpdateMessage(updateRequest); - } + await ReactivateOrRenameTemplateAsync(template, job, expectedName, initiator); } - // 10. Создание новых шаблонов + // Создание новых шаблонов foreach (var unitId in unitIdsToCreateFresh) { - logger.LogInformation("Создание нового шаблона для UnitId {UnitId}.", unitId); - var mqRequest = new TemplateGeneratorWorkerMq - { - JobId = jobId, - UnitId = unitId, - HistoryInitiator = initiator - }; - - var msg = JsonSerializer.Serialize(mqRequest); - var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); - - if (!result.IsSuccess) - logger.LogError("Ошибка отправки команды создания шаблона для UnitId {UnitId}.", unitId); + await SendTemplateGeneratorMessageAsync(jobId, unitId, initiator); } logger.LogInformation("Синхронизация завершена для JobId {JobId}.", jobId); } - public async Task UpdateTemplatesForJob(Guid jobId, HistoryInitiator initiator) { logger.LogDebug("Начало обновления шаблонов для JobId {JobId}", jobId); @@ -246,8 +184,7 @@ namespace PARR.TemplateMatcher var job = await GetJobWithGroupAndAutoControlAsync(jobId); if (job == null) return; - var currentUnitIds = await GetExpectedUnitIdsAsync(jobId); - + var currentUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet(); logger.LogDebug("JobId {JobId}: {Count} UnitId по текущему фильтру.", jobId, currentUnitIds.Count); foreach (var template in existingTemplates) @@ -272,7 +209,7 @@ namespace PARR.TemplateMatcher targetStatus = TemplateStatusTypeEnum.Unused; targetIsActiveTemplate = DefaultUnusedTemplateState; targetIsActiveSchedule = DefaultUnusedScheduleState; - expectedName = GetTemplateNameForUnsed(expectedName); + expectedName = GetTemplateNameForUnused(expectedName); logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", template.Id, template.UnitId); } @@ -285,49 +222,154 @@ namespace PARR.TemplateMatcher template.IsActiveSchedule != targetIsActiveSchedule ); - if (needsUpdate) + if (!needsUpdate) continue; + + template.StatusTypeId = TemplateStatusTypeEnum.Updating; + template.DateModified = DateTimeOffset.UtcNow; + + if (!await templateService.CommitAsync(initiator)) { - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); - continue; - } - - var nextRun = await GetNextRunAsync(job, template.NextRun); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = jobId, - UnitId = template.UnitId, - Name = expectedName, - IsActiveTemplate = targetIsActiveTemplate, - IsActiveSchedule = targetIsActiveSchedule, - LastRun = template.LastRun, - NextRun = nextRun, - Index = template.Index, - StatusTypeId = targetStatus, - Initiator = initiator - }; - - await SendTemplateUpdateMessage(updateRequest); + logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); + continue; } + + var nextRun = await GetNextRunAsync(job, template.NextRun); + + var updateRequest = new TemplateUpdaterMq + { + TemplateId = template.Id, + JobId = jobId, + UnitId = template.UnitId, + Name = expectedName, + IsActiveTemplate = targetIsActiveTemplate, + IsActiveSchedule = targetIsActiveSchedule, + LastRun = template.LastRun, + NextRun = nextRun, + Index = template.Index, + StatusTypeId = targetStatus, + Initiator = initiator + }; + + await SendTemplateUpdateMessage(updateRequest); } logger.LogInformation("Обновление шаблонов завершено для JobId {JobId}.", jobId); } + private async Task DeactivateTemplateAsync( + Template template, + Guid jobId, + HistoryInitiator initiator) + { + if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) + return true; // уже в обработке + + logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", + template.Id, template.UnitId); + + template.StatusTypeId = TemplateStatusTypeEnum.Updating; + template.DateModified = DateTimeOffset.UtcNow; + + if (!await templateService.CommitAsync(initiator)) + { + logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); + return false; + } + + var updateRequest = new TemplateUpdaterMq + { + TemplateId = template.Id, + JobId = jobId, + UnitId = template.UnitId, + Name = GetTemplateNameForUnused(template.Name), + IsActiveTemplate = DefaultUnusedTemplateState, + IsActiveSchedule = DefaultUnusedScheduleState, + LastRun = template.LastRun, + NextRun = template.NextRun, + Index = template.Index, + StatusTypeId = TemplateStatusTypeEnum.Unused, + Initiator = initiator + }; + + await SendTemplateUpdateMessage(updateRequest); + return true; + } + + private async Task ReactivateOrRenameTemplateAsync( + Template template, + Job job, + string expectedName, + HistoryInitiator initiator) + { + if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) + return true; + + bool needsUpdate = template.Name != expectedName + || template.StatusTypeId != TemplateStatusTypeEnum.Used; + + if (!needsUpdate) return true; + + logger.LogInformation("Шаблон {TemplateId}: требуется обновление имени или реактивация.", template.Id); + + template.StatusTypeId = TemplateStatusTypeEnum.Updating; + template.DateModified = DateTimeOffset.UtcNow; + + if (!await templateService.CommitAsync(initiator)) + { + logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); + return false; + } + + var nextRun = await GetNextRunAsync(job, template.NextRun); + + var updateRequest = new TemplateUpdaterMq + { + TemplateId = template.Id, + JobId = job.Id, + UnitId = template.UnitId, + Name = expectedName, + IsActiveTemplate = template.IsActiveTemplate, + IsActiveSchedule = template.IsActiveSchedule, + LastRun = template.LastRun, + NextRun = nextRun, + Index = template.Index, + StatusTypeId = TemplateStatusTypeEnum.Used, + Initiator = initiator + }; + + await SendTemplateUpdateMessage(updateRequest); + return true; + } + + private async Task SendTemplateGeneratorMessageAsync( + Guid jobId, + Guid unitId, + HistoryInitiator initiator) + { + logger.LogInformation("Создание нового шаблона для UnitId {UnitId}.", unitId); + var mqRequest = new TemplateGeneratorWorkerMq + { + JobId = jobId, + UnitId = unitId, + HistoryInitiator = initiator + }; + + var msg = JsonSerializer.Serialize(mqRequest); + var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); + + if (!result.IsSuccess) + logger.LogError("Ошибка отправки команды создания шаблона для UnitId {UnitId}.", unitId); + + return result.IsSuccess; + } + private async Task SendTemplateUpdateMessage(TemplateUpdaterMq updateRequest) { logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}", mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId); var msg = JsonSerializer.Serialize(updateRequest); - var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg }); if (result.IsSuccess) @@ -336,13 +378,16 @@ namespace PARR.TemplateMatcher } else { - logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId}: {Message}", - updateRequest.TemplateId, msg); + logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.", + updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName); } } - - private async Task TryReuseOneUnusedTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator, int maxAttempts = 3) + private async Task TryReuseOneUnusedTemplateAsync( + Guid jobId, + Guid unitId, + HistoryInitiator initiator, + int maxAttempts = 3) { for (int attempt = 1; attempt <= maxAttempts; attempt++) { @@ -405,12 +450,11 @@ namespace PARR.TemplateMatcher return null; } - private string GetTemplateNameForUnsed(string templateName) + private string GetTemplateNameForUnused(string templateName) { return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } - private async Task GetJobWithGroupAndAutoControlAsync(Guid jobId) { return await jobService.Get() @@ -419,21 +463,18 @@ namespace PARR.TemplateMatcher .FirstOrDefaultAsync(j => j.Id == jobId); } - private async Task> GetExpectedUnitIdsAsync(Guid jobId) { var units = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); return units?.ToHashSet() ?? new HashSet(); } - private async Task GetNormalizedTemplateNameAsync(Job job, Guid unitId) { var rawName = await shortcodesService.ApplyShortcodesAsync(job.TemplateNameMask, unitId, job.Id); return rawName.ToUpper(); } - private async Task GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null) { var now = DateTimeOffset.UtcNow;