feat(templateMatcher): В синхронизацию неиспользуемых шаблонов добавлена метрика времени выполнения

This commit is contained in:
Mikhail Kuznetsov
2026-07-03 14:01:56 +10:00
parent 9c27f81fe8
commit fb9578fec0
3 changed files with 122 additions and 138 deletions

View File

@@ -3,11 +3,12 @@ using PARR.Core.Services.MatchingStatusService;
using PARR.Domain.Cache.Models; using PARR.Domain.Cache.Models;
using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums; using PARR.Domain.Enums;
using PARR.TemplateMatcher.Services.GroupedSync;
using PARR.TemplateMatcher.Services.Implementations.GroupedSync; using PARR.TemplateMatcher.Services.Implementations.GroupedSync;
using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Services.Interfaces;
using System.Diagnostics; using System.Diagnostics;
namespace PARR.TemplateMatcher.Services.GroupedSync; namespace PARR.TemplateMatcher.Services.Implementations;
internal class GroupedTemplateSynchronizer : ITemplateSynchronizer internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{ {
@@ -20,7 +21,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
IEnumerable<IGroupedSyncStage> readStages, IEnumerable<IGroupedSyncStage> readStages,
IEnumerable<IGroupedSyncWriteStage> writeStages, IEnumerable<IGroupedSyncWriteStage> writeStages,
IMatchingStatusService matchingStatusService, IMatchingStatusService matchingStatusService,
ILogger<GroupedTemplateSynchronizer> logger) ILogger<GroupedTemplateSynchronizer> logger
)
{ {
_readStages = readStages; _readStages = readStages;
_writeStages = writeStages; _writeStages = writeStages;
@@ -33,7 +35,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
_logger.LogInformation("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId); _logger.LogInformation("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
var existingStatus = await _matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); var existingStatus = await _matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
if (existingStatus.DetailsJobGroups?.Any() == true) if (existingStatus.DetailsJobGroups?.Count > 0)
{ {
_logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId); _logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId);
return; return;

View File

@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using PARR.Core.Common.Interfaces.RabbitServices;
using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.JobRepositories; using PARR.Core.Repositories.Interfaces.JobRepositories;
using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Repositories.Interfaces.Unit;
@@ -26,41 +25,32 @@ namespace PARR.TemplateMatcher.Services.Implementations;
internal class SimpleTemplateSynchronizer : ITemplateSynchronizer internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{ {
#if DEBUG #if DEBUG
private readonly Guid targetUnitId = Guid.Parse("358437ac-1eeb-4c00-840c-998326f657ac"); private readonly Guid _targetUnitId = Guid.Parse("358437ac-1eeb-4c00-840c-998326f657ac");
#endif #endif
private const bool DefaultUsedTemplateState = false; private readonly IEnumerable<ISimpleSyncStage> _readStages;
private const bool DefaultUsedScheduleState = false; private readonly IEnumerable<ISimpleSyncWriteStage> _writeStages;
private readonly ILogger<SimpleTemplateSynchronizer> _logger;
private readonly IEnumerable<ISimpleSyncStage> readStages; private readonly IUnitFilterService _unitFilterService;
private readonly IEnumerable<ISimpleSyncWriteStage> writeStages; private readonly ITemplateRepository _templateService;
private readonly ILogger<SimpleTemplateSynchronizer> logger; private readonly IJobRepository _jobService;
private readonly IUnitFilterService unitFilterService; private readonly ITemplateNameNormalizer _templateNameNormalizer;
private readonly MqSettings mqSettings; private readonly ITemplateMqPublisher _templateMqPublisher;
private readonly IRabbitService mqService; private readonly IMatchingStatusService _matchingStatusService;
private readonly ITemplateRepository templateService; private readonly SettingsFromDb _settingsFromDb;
private readonly IJobRepository jobService; private readonly IOptions<TemplateSettings> _templateSettings;
private readonly ITemplateNameNormalizer templateNameNormalizer; private readonly IUnitFieldRepository _unitFieldService;
private readonly ITemplateAllocationService templateAllocationService; private readonly IUnitInValueRepository _unitInValueService;
private readonly ITemplateMqPublisher templateMqPublisher; private readonly IUnitRepository _unitRepository;
private readonly IMatchingStatusService matchingStatusService;
private readonly SettingsFromDb settingsFromDb;
private readonly IOptions<TemplateSettings> templateSettings;
private readonly IUnitFieldRepository unitFieldService;
private readonly IUnitInValueRepository unitInValueService;
private readonly IUnitRepository unitRepository;
public SimpleTemplateSynchronizer( public SimpleTemplateSynchronizer(
IEnumerable<ISimpleSyncStage> readStages, IEnumerable<ISimpleSyncStage> readStages,
IEnumerable<ISimpleSyncWriteStage> writeStages, IEnumerable<ISimpleSyncWriteStage> writeStages,
ILogger<SimpleTemplateSynchronizer> logger, ILogger<SimpleTemplateSynchronizer> logger,
IUnitFilterService unitFilterService, IUnitFilterService unitFilterService,
MqSettings mqSettings,
IRabbitService mqService,
ITemplateRepository templateService, ITemplateRepository templateService,
IJobRepository jobService, IJobRepository jobService,
ITemplateNameNormalizer templateNameNormalizer, ITemplateNameNormalizer templateNameNormalizer,
ITemplateAllocationService templateAllocationService,
ITemplateMqPublisher templateMqPublisher, ITemplateMqPublisher templateMqPublisher,
IMatchingStatusService matchingStatusService, IMatchingStatusService matchingStatusService,
SettingsFromDb settingsFromDb, SettingsFromDb settingsFromDb,
@@ -70,40 +60,37 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
IUnitRepository unitRepository IUnitRepository unitRepository
) )
{ {
this.readStages = readStages; this._readStages = readStages;
this.writeStages = writeStages; this._writeStages = writeStages;
this.logger = logger; this._logger = logger;
this.unitFilterService = unitFilterService; this._unitFilterService = unitFilterService;
this.mqSettings = mqSettings; this._templateService = templateService;
this.mqService = mqService; this._jobService = jobService;
this.templateService = templateService; this._templateNameNormalizer = templateNameNormalizer;
this.jobService = jobService; this._templateMqPublisher = templateMqPublisher;
this.templateNameNormalizer = templateNameNormalizer; this._matchingStatusService = matchingStatusService;
this.templateAllocationService = templateAllocationService; this._settingsFromDb = settingsFromDb;
this.templateMqPublisher = templateMqPublisher; this._templateSettings = templateSettings;
this.matchingStatusService = matchingStatusService; this._unitFieldService = unitFieldService;
this.settingsFromDb = settingsFromDb; this._unitInValueService = unitInValueService;
this.templateSettings = templateSettings; this._unitRepository = unitRepository;
this.unitFieldService = unitFieldService;
this.unitInValueService = unitInValueService;
this.unitRepository = unitRepository;
} }
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{ {
if (jobId == settingsFromDb.JobIdForUnusedTemplates) if (jobId == _settingsFromDb.JobIdForUnusedTemplates)
{ {
logger.LogInformation("Обработка синхронизации для Job неиспользуемых шаблонов '{JobId}'", jobId); _logger.LogInformation("Обработка синхронизации для Job неиспользуемых шаблонов '{JobId}'", jobId);
await SyncUnusedTemplatesAsync(jobId, initiator); await SyncUnusedTemplatesAsync(jobId, initiator);
return; return;
} }
logger.LogInformation("Начало синхронизации шаблонов для Job {JobId}", jobId); _logger.LogInformation("Начало синхронизации шаблонов для Job {JobId}", jobId);
var existingStatus = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); var existingStatus = await _matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
if (existingStatus.DetailsJobs?.Any() == true) if (existingStatus.DetailsJobs?.Count > 0)
{ {
logger.LogWarning("Синхронизация для Job {JobId} уже запущена. Пропускаем.", jobId); _logger.LogWarning("Синхронизация для Job {JobId} уже запущена. Пропускаем.", jobId);
return; return;
} }
@@ -113,49 +100,48 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
Action = TemplateMatcherActionEnum.Sync, Action = TemplateMatcherActionEnum.Sync,
Comment = "Начало синхронизации" Comment = "Начало синхронизации"
}; };
await matchingStatusService.SetMatchingStatusAsync( await _matchingStatusService.SetMatchingStatusAsync(
jobId, SyncTaskEntityTypeEnum.Job, jobId, SyncTaskEntityTypeEnum.Job,
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) }, new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },
TimeSpan.FromMinutes(35)); TimeSpan.FromMinutes(35));
// Таймер запускается ПОСЛЕ инфраструктурных операций (статус, проверка блокировки) // Таймер запускается ПОСЛЕ инфраструктурных операций (статус, проверка блокировки)
var totalSw = Stopwatch.StartNew(); var totalSw = Stopwatch.StartNew();
var context = new SimpleSyncContext { JobId = jobId, Initiator = initiator };
try try
{ {
var context = new SimpleSyncContext { JobId = jobId, Initiator = initiator }; foreach (var stage in _readStages)
foreach (var stage in readStages)
{ {
var stageSw = Stopwatch.StartNew(); var stageSw = Stopwatch.StartNew();
await stage.ExecuteAsync(context); await stage.ExecuteAsync(context);
stageSw.Stop(); stageSw.Stop();
logger.LogDebug("[Perf] Job '{JobName}' ({JobId}) | Этап: {Stage} | Время: {Ms} мс", _logger.LogDebug("[Perf] Job '{JobName}' ({JobId}) | Этап: {Stage} | Время: {Ms} мс",
context.JobName, jobId, stage.StageName, stageSw.ElapsedMilliseconds); context.JobName, jobId, stage.StageName, stageSw.ElapsedMilliseconds);
} }
foreach (var stage in writeStages) foreach (var stage in _writeStages)
{ {
var stageSw = Stopwatch.StartNew(); var stageSw = Stopwatch.StartNew();
await stage.ExecuteAsync(context); await stage.ExecuteAsync(context);
stageSw.Stop(); stageSw.Stop();
logger.LogDebug("[Perf] Job '{JobName}' ({JobId}) | Этап: {Stage} | Время: {Ms} мс", _logger.LogDebug("[Perf] Job '{JobName}' ({JobId}) | Этап: {Stage} | Время: {Ms} мс",
context.JobName, jobId, stage.StageName, stageSw.ElapsedMilliseconds); context.JobName, jobId, stage.StageName, stageSw.ElapsedMilliseconds);
} }
totalSw.Stop(); totalSw.Stop();
logger.LogInformation("[Perf] Job '{JobName}' ({JobId}) | ИТОГО: {TotalMs} мс", _logger.LogInformation("[Perf] Job '{JobName}' ({JobId}) | ИТОГО: {TotalMs} мс",
context.JobName, jobId, totalSw.ElapsedMilliseconds); context.JobName, jobId, totalSw.ElapsedMilliseconds);
await UpdateMatchingStatusAsync(jobId, "Синхронизация завершена успешно"); await UpdateMatchingStatusAsync(jobId, "Синхронизация завершена успешно");
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); await _matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
logger.LogInformation("Синхронизация шаблонов завершена для Job '{JobName}' ({JobId})", _logger.LogInformation("Синхронизация шаблонов завершена для Job '{JobName}' ({JobId})",
context.JobName, jobId); context.JobName, jobId);
} }
catch (Exception ex) catch (Exception ex)
{ {
totalSw.Stop(); totalSw.Stop();
logger.LogError(ex, "Ошибка при синхронизации Job '{JobName}' ({JobId}) через {ElapsedMs} мс", _logger.LogError(ex, "Ошибка при синхронизации Job '{JobName}' ({JobId}) через {ElapsedMs} мс",
string.Empty, jobId, totalSw.ElapsedMilliseconds); string.Empty, jobId, totalSw.ElapsedMilliseconds);
await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}"); await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}");
throw; throw;
@@ -165,19 +151,19 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator) public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
{ {
logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция.", jobGroupId); _logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция.", jobGroupId);
} }
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{ {
logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId); _logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId);
// === Проверка: уже запущена? === // === Проверка: уже запущена? ===
var existingStatus = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); var existingStatus = await _matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
if (existingStatus.DetailsJobs?.Any() == true) if (existingStatus.DetailsJobs?.Count > 0)
{ {
logger.LogWarning("Обновление для Job {JobId} уже запущено. Пропускаем.", jobId); _logger.LogWarning("Обновление для Job {JobId} уже запущено. Пропускаем.", jobId);
return; return;
} }
@@ -187,7 +173,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
Action = TemplateMatcherActionEnum.Update, Action = TemplateMatcherActionEnum.Update,
Comment = "Начало обновления имён" Comment = "Начало обновления имён"
}; };
await matchingStatusService.SetMatchingStatusAsync( await _matchingStatusService.SetMatchingStatusAsync(
jobId, jobId,
SyncTaskEntityTypeEnum.Job, SyncTaskEntityTypeEnum.Job,
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) }, new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },
@@ -196,7 +182,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
try try
{ {
var job = await jobService.Get() var job = await _jobService.Get()
.AsNoTracking() .AsNoTracking()
.Include(j => j.AutoControl) .Include(j => j.AutoControl)
.Include(j => j.Tnk) .Include(j => j.Tnk)
@@ -208,18 +194,18 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
if (job == null) if (job == null)
{ {
logger.LogWarning("Job {JobId} не найден.", jobId); _logger.LogWarning("Job {JobId} не найден.", jobId);
await UpdateMatchingStatusAsync(jobId, "Job не найден"); await UpdateMatchingStatusAsync(jobId, "Job не найден");
return; return;
} }
// === Получение отфильтрованных юнитов с полной информацией === // === Получение отфильтрованных юнитов с полной информацией ===
var filteredUnits = await unitFilterService.GetUnitsByJobFilterAsync(jobId); var filteredUnits = await _unitFilterService.GetUnitsByJobFilterAsync(jobId);
if (filteredUnits == null || !filteredUnits.Any()) if (filteredUnits == null || !filteredUnits.Any())
{ {
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId); _logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
await UpdateMatchingStatusAsync(jobId, "Нет Unit'ов — обновление не требуется"); await UpdateMatchingStatusAsync(jobId, "Нет Unit'ов — обновление не требуется");
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); await _matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
return; return;
} }
@@ -228,17 +214,17 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
#if DEBUG #if DEBUG
// Отладка: проверить, есть ли юнит в unitIds // Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId)) if (unitIds.Contains(_targetUnitId))
{ {
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId); _logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", _targetUnitId);
} }
else else
{ {
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", targetUnitId); _logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", _targetUnitId);
} }
#endif #endif
var existingTemplates = await templateService.Get() var existingTemplates = await _templateService.Get()
.AsNoTracking() .AsNoTracking()
.Include(t => t.Unit) .Include(t => t.Unit)
.Include(t => t.UnitsInTemplate) .Include(t => t.UnitsInTemplate)
@@ -254,10 +240,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{ {
if (unitIds.Contains(template.UnitId)) if (unitIds.Contains(template.UnitId))
{ {
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template); var expectedName = await _templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{ {
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName); _logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
var updateRequest = new TemplateUpdaterMessage var updateRequest = new TemplateUpdaterMessage
{ {
@@ -274,18 +260,18 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
UnitsInTemplate = new List<UnitInTemplateMessage>() // для простого шаблона UnitsInTemplate = new List<UnitInTemplateMessage>() // для простого шаблона
}; };
await templateMqPublisher.PublishUpdateAsync(updateRequest); await _templateMqPublisher.PublishUpdateAsync(updateRequest);
} }
} }
} }
await UpdateMatchingStatusAsync(jobId, "Обновление завершено"); await UpdateMatchingStatusAsync(jobId, "Обновление завершено");
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); await _matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId); _logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId);
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Ошибка при обновлении Job {JobId}", jobId); _logger.LogError(ex, "Ошибка при обновлении Job {JobId}", jobId);
await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}"); await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}");
throw; throw;
} }
@@ -297,10 +283,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
// Проверка отмены в самом начале // Проверка отмены в самом начале
ct.ThrowIfCancellationRequested(); ct.ThrowIfCancellationRequested();
var existingStatus = await matchingStatusService.GetStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job); var existingStatus = await _matchingStatusService.GetStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job);
if (existingStatus.DetailsJobs?.Any() == true) if (existingStatus.DetailsJobs?.Count > 0)
{ {
logger.LogWarning("Синхронизация для Job неиспользуемых шаблонов {JobId} уже запущена. Пропускаем.", unusedJobId); _logger.LogWarning("Синхронизация для Job неиспользуемых шаблонов {JobId} уже запущена. Пропускаем.", unusedJobId);
return; return;
} }
@@ -310,22 +296,24 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
Action = TemplateMatcherActionEnum.Sync, Action = TemplateMatcherActionEnum.Sync,
Comment = "Синхронизация неиспользуемых шаблонов" Comment = "Синхронизация неиспользуемых шаблонов"
}; };
await matchingStatusService.SetMatchingStatusAsync( await _matchingStatusService.SetMatchingStatusAsync(
unusedJobId, unusedJobId,
SyncTaskEntityTypeEnum.Job, SyncTaskEntityTypeEnum.Job,
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) }, new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },
TimeSpan.FromMinutes(30) TimeSpan.FromMinutes(30)
); );
var totalSw = Stopwatch.StartNew();
try try
{ {
// 1. Находим ID нужных полей // 1. Находим ID нужных полей
var responsableAreaField = await unitFieldService.GetByAihitNameAsync(UnusedTemplateConstants.ResponsibilityAreaFieldName); var responsableAreaField = await _unitFieldService.GetByAihitNameAsync(UnusedTemplateConstants.ResponsibilityAreaFieldName, ct);
var tagField = await unitFieldService.GetByAihitNameAsync(UnusedTemplateConstants.ParrTagFieldName); var tagField = await _unitFieldService.GetByAihitNameAsync(UnusedTemplateConstants.ParrTagFieldName, ct);
if (responsableAreaField == null || tagField == null) if (responsableAreaField == null || tagField == null)
{ {
logger.LogError("Не найдены поля '{Field1}' или '{Field2}'. Синхронизация прервана.", UnusedTemplateConstants.ResponsibilityAreaFieldName, UnusedTemplateConstants.NotUsedTagValue); _logger.LogError("Не найдены поля '{Field1}' или '{Field2}'. Синхронизация прервана.", UnusedTemplateConstants.ResponsibilityAreaFieldName, UnusedTemplateConstants.NotUsedTagValue);
await UpdateMatchingStatusAsync(unusedJobId, "Ошибка конфигурации полей"); await UpdateMatchingStatusAsync(unusedJobId, "Ошибка конфигурации полей");
return; return;
} }
@@ -334,7 +322,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
var tagFieldId = tagField.Id; var tagFieldId = tagField.Id;
// 2. Находим ValueId для тега "ПАРР-НЕИСП" // 2. Находим ValueId для тега "ПАРР-НЕИСП"
var targetTagValueId = await unitInValueService.Get() var targetTagValueId = await _unitInValueService.Get()
.AsNoTracking() .AsNoTracking()
.Where(uiv => uiv.FieldId == tagFieldId && uiv.Value != null && uiv.Value.Value == UnusedTemplateConstants.NotUsedTagValue) .Where(uiv => uiv.FieldId == tagFieldId && uiv.Value != null && uiv.Value.Value == UnusedTemplateConstants.NotUsedTagValue)
.Select(uiv => uiv.ValueId) .Select(uiv => uiv.ValueId)
@@ -342,31 +330,31 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
if (targetTagValueId == Guid.Empty) if (targetTagValueId == Guid.Empty)
{ {
logger.LogWarning("Значение '{TagValue}' для поля '{FieldName}' не найдено в справочнике UnitFieldValue.", UnusedTemplateConstants.NotUsedTagValue, UnusedTemplateConstants.ParrTagFieldName); _logger.LogWarning("Значение '{TagValue}' для поля '{FieldName}' не найдено в справочнике UnitFieldValue.", UnusedTemplateConstants.NotUsedTagValue, UnusedTemplateConstants.ParrTagFieldName);
} }
var unusedJob = await jobService.Get().AsNoTracking() var unusedJob = await _jobService.Get().AsNoTracking()
.Include(t => t!.Group).ThenInclude(t => t!.GroupType) .Include(t => t!.Group).ThenInclude(t => t!.GroupType)
.Include(t => t!.Tnk) .Include(t => t!.Tnk)
.FirstOrDefaultAsync(j => j.Id == unusedJobId, ct); .FirstOrDefaultAsync(j => j.Id == unusedJobId, ct);
if (unusedJob == null) if (unusedJob == null)
{ {
logger.LogError("Job неиспользуемых шаблонов {JobId} не найден.", unusedJobId); _logger.LogError("Job неиспользуемых шаблонов {JobId} не найден.", unusedJobId);
await UpdateMatchingStatusAsync(unusedJobId, "Job не найден"); await UpdateMatchingStatusAsync(unusedJobId, "Job не найден");
return; return;
} }
var unusedTemplates = await templateService.Get() var unusedTemplates = await _templateService.Get()
.Include(t => t.Unit) .Include(t => t.Unit)
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused) .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused)
.ToListAsync(ct); .ToListAsync(ct);
if (!unusedTemplates.Any()) if (!unusedTemplates.Any())
{ {
logger.LogInformation("Не найдено шаблонов со статусом Unused."); _logger.LogInformation("Не найдено шаблонов со статусом Unused.");
await UpdateMatchingStatusAsync(unusedJobId, "Нет шаблонов для обработки"); await UpdateMatchingStatusAsync(unusedJobId, "Нет шаблонов для обработки");
await matchingStatusService.DeleteMatchingStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job); await _matchingStatusService.DeleteMatchingStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job);
return; return;
} }
@@ -376,7 +364,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
var allTemplateUnitIds = unusedTemplates.Select(t => t.UnitId).Distinct().ToList(); var allTemplateUnitIds = unusedTemplates.Select(t => t.UnitId).Distinct().ToList();
// Получаем значения ЗОНА_ОТВЕТСТВЕННОСТИ для всех юнитов шаблонов // Получаем значения ЗОНА_ОТВЕТСТВЕННОСТИ для всех юнитов шаблонов
var unitResponsableAreaValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allTemplateUnitIds, new List<Guid> { responsableAreaFieldId }); var unitResponsableAreaValues = await _unitInValueService.GetByUnitIdsAndFieldIdsAsync(allTemplateUnitIds, new List<Guid> { responsableAreaFieldId }, ct);
var unitToResponsableAreaValueMap = unitResponsableAreaValues var unitToResponsableAreaValueMap = unitResponsableAreaValues
.Where(uiv => uiv.ValueId != Guid.Empty) .Where(uiv => uiv.ValueId != Guid.Empty)
.ToDictionary(uiv => uiv.UnitId, uiv => uiv.ValueId); .ToDictionary(uiv => uiv.UnitId, uiv => uiv.ValueId);
@@ -385,15 +373,16 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
var responsableAreaToTargetUnitMap = new Dictionary<Guid, Guid>(); var responsableAreaToTargetUnitMap = new Dictionary<Guid, Guid>();
var distinctResponsableAreaValues = unitToResponsableAreaValueMap.Values.Distinct().ToList(); var distinctResponsableAreaValues = unitToResponsableAreaValueMap.Values.Distinct().ToList();
if (targetTagValueId != Guid.Empty && distinctResponsableAreaValues.Any()) if (targetTagValueId != Guid.Empty &&
distinctResponsableAreaValues.Any())
{ {
logger.LogDebug("Пакетный поиск целевых юнитов с тегом 'ПАРР-НЕИСП' для {Count} уникальных значений ЗОНА_ОТВЕТСТВЕННОСТИ.", distinctResponsableAreaValues.Count); _logger.LogDebug("Пакетный поиск целевых юнитов с тегом 'ПАРР-НЕИСП' для {Count} уникальных значений ЗОНА_ОТВЕТСТВЕННОСТИ.", distinctResponsableAreaValues.Count);
// Передаем ct в ToListAsync // Передаем ct в ToListAsync
var matches = await unitInValueService.Get().AsNoTracking() var matches = await _unitInValueService.Get().AsNoTracking()
.Where(uiv => uiv.FieldId == responsableAreaFieldId && distinctResponsableAreaValues.Contains(uiv.ValueId)) .Where(uiv => uiv.FieldId == responsableAreaFieldId && distinctResponsableAreaValues.Contains(uiv.ValueId))
.Join( .Join(
unitInValueService.Get().AsNoTracking().Where(t => t.FieldId == tagFieldId && t.ValueId == targetTagValueId), _unitInValueService.Get().AsNoTracking().Where(t => t.FieldId == tagFieldId && t.ValueId == targetTagValueId),
responsableArea => responsableArea.UnitId, responsableArea => responsableArea.UnitId,
tag => tag.UnitId, tag => tag.UnitId,
(responsableArea, tag) => new { responsableArea.ValueId, responsableArea.UnitId } (responsableArea, tag) => new { responsableArea.ValueId, responsableArea.UnitId }
@@ -404,7 +393,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
.GroupBy(x => x.ValueId) .GroupBy(x => x.ValueId)
.ToDictionary(g => g.Key, g => g.First().UnitId); .ToDictionary(g => g.Key, g => g.First().UnitId);
logger.LogDebug("Сформирован кэш соответствий: найдено {Count} целевых юнитов.", responsableAreaToTargetUnitMap.Count); _logger.LogDebug("Сформирован кэш соответствий: найдено {Count} целевых юнитов.", responsableAreaToTargetUnitMap.Count);
} }
foreach (var template in unusedTemplates) foreach (var template in unusedTemplates)
@@ -416,7 +405,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
if (template.Unit == null) if (template.Unit == null)
{ {
logger.LogWarning("У шаблона {TemplateId} отсутствует Unit. Пропускаем.", template.Id); _logger.LogWarning("У шаблона {TemplateId} отсутствует Unit. Пропускаем.", template.Id);
processed++; processed++;
continue; continue;
} }
@@ -441,11 +430,11 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
if (targetUnitId.HasValue && targetUnitId.Value != template.UnitId) if (targetUnitId.HasValue && targetUnitId.Value != template.UnitId)
{ {
logger.LogInformation("Для шаблона {TemplateId} найден новый UnitId {NewUnitId} (был {OldUnitId}).", _logger.LogInformation("Для шаблона {TemplateId} найден новый UnitId {NewUnitId} (был {OldUnitId}).",
template.Id, targetUnitId.Value, template.UnitId); template.Id, targetUnitId.Value, template.UnitId);
// Передаем ct в запрос // Передаем ct в запрос
var newUnit = await unitRepository.Get().AsNoTracking() var newUnit = await _unitRepository.Get().AsNoTracking()
.FirstOrDefaultAsync(u => u.Id == targetUnitId.Value, ct); .FirstOrDefaultAsync(u => u.Id == targetUnitId.Value, ct);
if (newUnit != null) if (newUnit != null)
@@ -454,13 +443,13 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
} }
else else
{ {
logger.LogWarning("Не удалось загрузить новый юнит {UnitId}. Используем старый.", targetUnitId.Value); _logger.LogWarning("Не удалось загрузить новый юнит {UnitId}. Используем старый.", targetUnitId.Value);
finalUnitId = template.UnitId; finalUnitId = template.UnitId;
} }
} }
else if (!hasResponsableArea) else if (!hasResponsableArea)
{ {
logger.LogDebug("У юнита {UnitId} шаблона {TemplateId} нет значения поля ЗОНА_ОТВЕТСТВЕННОСТИ. Оставляем текущий UnitId.", template.UnitId, template.Id); _logger.LogDebug("У юнита {UnitId} шаблона {TemplateId} нет значения поля ЗОНА_ОТВЕТСТВЕННОСТИ. Оставляем текущий UnitId.", template.UnitId, template.Id);
} }
// 4. Генерация целевого имени // 4. Генерация целевого имени
@@ -473,30 +462,43 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
if (!unitChanged && !jobChanged && !nameChanged) if (!unitChanged && !jobChanged && !nameChanged)
{ {
logger.LogDebug("Шаблон {TemplateId} уже актуален. Пропуск отправки в MQ.", template.Id); _logger.LogDebug("Шаблон {TemplateId} уже актуален. Пропуск отправки в MQ.", template.Id);
processed++; processed++;
continue; continue;
} }
await SendUpdateRequest(template, unusedJobId, expectedName, initiator, finalUnitId); await SendUpdateRequest(template, unusedJobId, expectedName, initiator, finalUnitId);
logger.LogDebug("Отправлен запрос на обновление шаблона {TemplateId}. Изменения: Unit={U}, Job={J}, Name={N}", _logger.LogDebug("Отправлен запрос на обновление шаблона {TemplateId}. Изменения: Unit={U}, Job={J}, Name={N}",
template.Id, unitChanged, jobChanged, nameChanged); template.Id, unitChanged, jobChanged, nameChanged);
processed++; processed++;
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Ошибка при обработке шаблона {TemplateId}", template.Id); _logger.LogError(ex, "Ошибка при обработке шаблона {TemplateId}", template.Id);
} }
} }
totalSw.Stop();
totalSw.Stop();
_logger.LogInformation("[Perf] Job '{JobName}' ({JobId}) | ИТОГО: {TotalMs} мс",
unusedJob.Name, unusedJob.Id, totalSw.ElapsedMilliseconds);
await UpdateMatchingStatusAsync(unusedJobId, "Синхронизация неиспользуемых шаблонов завершена"); await UpdateMatchingStatusAsync(unusedJobId, "Синхронизация неиспользуемых шаблонов завершена");
await matchingStatusService.DeleteMatchingStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job); await _matchingStatusService.DeleteMatchingStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job);
logger.LogInformation("Синхронизация неиспользуемых шаблонов завершена. Обработано {Count} шаблонов.", unusedTemplates.Count); totalSw.Stop();
_logger.LogInformation(
"Синхронизация неиспользуемых шаблонов завершена. Обработано {Count} шаблонов",
unusedTemplates.Count);
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Ошибка при синхронизации неиспользуемых шаблонов для Job {JobId}", unusedJobId); totalSw.Stop();
_logger.LogError(ex,
"Ошибка при синхронизации неиспользуемых шаблонов для Job {JobId} через {ElapsedMs} мс",
unusedJobId, totalSw.ElapsedMilliseconds);
await UpdateMatchingStatusAsync(unusedJobId, $"Ошибка: {ex.Message}"); await UpdateMatchingStatusAsync(unusedJobId, $"Ошибка: {ex.Message}");
throw; throw;
} }
@@ -520,7 +522,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
UnitsInTemplate = new List<UnitInTemplateMessage>() UnitsInTemplate = new List<UnitInTemplateMessage>()
}; };
await templateMqPublisher.PublishUpdateAsync(updateRequest); await _templateMqPublisher.PublishUpdateAsync(updateRequest);
} }
@@ -534,7 +536,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
MinValueRelationships = unusedJob.MinValueRelationships, MinValueRelationships = unusedJob.MinValueRelationships,
MaxValueRelationships = unusedJob.MaxValueRelationships, MaxValueRelationships = unusedJob.MaxValueRelationships,
IsParentRelationships = unusedJob.IsParentRelationships, IsParentRelationships = unusedJob.IsParentRelationships,
TemplateNameMask = templateSettings.Value.UnusedTemplateNameMask, TemplateNameMask = _templateSettings.Value.UnusedTemplateNameMask,
WorkGroupMask = unusedJob.WorkGroupMask, WorkGroupMask = unusedJob.WorkGroupMask,
ResponseAreaMask = unusedJob.ResponseAreaMask, ResponseAreaMask = unusedJob.ResponseAreaMask,
TnkId = unusedJob.TnkId, TnkId = unusedJob.TnkId,
@@ -558,26 +560,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
UnitsInTemplate = new List<UnitsInTemplate>() UnitsInTemplate = new List<UnitsInTemplate>()
}; };
return await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); return await _templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
}
private async Task CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator)
{
logger.LogInformation("Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
var mqRequest = new TemplateGeneratorMessage
{
JobId = jobId,
UnitId = unitId,
UnitsInTemplate = new List<UnitInTemplateMessage>(), // для простого шаблона
HistoryInitiator = initiator
};
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { mqRequest });
if (!result.IsSuccess)
logger.LogError("Ошибка отправки команды создания простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
} }
@@ -589,7 +572,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
Action = TemplateMatcherActionEnum.Sync, Action = TemplateMatcherActionEnum.Sync,
Comment = comment Comment = comment
}; };
await matchingStatusService.SetMatchingStatusAsync( await _matchingStatusService.SetMatchingStatusAsync(
jobId, jobId,
SyncTaskEntityTypeEnum.Job, SyncTaskEntityTypeEnum.Job,
new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) }, new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },

View File

@@ -5,7 +5,6 @@ using PARR.Core.Repositories.Interfaces.JobRepositories;
using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.Base.History;
using PARR.Domain.Entities.JobEntities; using PARR.Domain.Entities.JobEntities;
using PARR.Domain.Enums; using PARR.Domain.Enums;
using PARR.TemplateMatcher.Services.GroupedSync;
using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Services.Interfaces;
namespace PARR.TemplateMatcher.Services.Implementations namespace PARR.TemplateMatcher.Services.Implementations