fix(templateMatcher): Исправление ошибок в именах шаблонов и рефакторинг синхронизации
- Устранена ошибка, когда при переиспользовании шаблона для шорткодов передавался ЭК "КОМПЛЕКСЫ-[ЗО]" вместо целевого ЭК шаблона. - Проведен небольшой рефакторинг общих процессов синхронизации различных типов групп работ.
This commit is contained in:
17
PARR.TemplateMatcher/Models/TemplateAllocationRequest.cs
Normal file
17
PARR.TemplateMatcher/Models/TemplateAllocationRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
|
||||
namespace PARR.TemplateMatcher.Models
|
||||
{
|
||||
public record TemplateAllocationRequest(
|
||||
Job TargetJob,
|
||||
Guid TargetUnitId,
|
||||
Unit? TargetUnit,
|
||||
int? Index,
|
||||
List<UnitInTemplateMessage> UnitsInTemplate,
|
||||
bool IsActiveTemplate,
|
||||
bool IsActiveSchedule,
|
||||
HistoryInitiator Initiator);
|
||||
}
|
||||
@@ -19,30 +19,26 @@ internal class GroupedTemplateProcessor : IGroupedTemplateProcessor
|
||||
private readonly ILogger<GroupedTemplateProcessor> logger;
|
||||
private readonly ITemplateRepository templateRepository;
|
||||
private readonly IUnitRepository unitRepository;
|
||||
private readonly ITemplateReuser templateReuser;
|
||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||
private readonly MqSettings mqSettings;
|
||||
private readonly IRabbitService mqService;
|
||||
private readonly ITemplateAllocationService templateAllocationService;
|
||||
private readonly ITemplateMqPublisher templateMqPublisher;
|
||||
|
||||
public GroupedTemplateProcessor(
|
||||
ILogger<GroupedTemplateProcessor> logger,
|
||||
ITemplateRepository templateRepository,
|
||||
IUnitRepository unitRepository,
|
||||
ITemplateReuser templateReuser,
|
||||
ITemplateNameNormalizer templateNameNormalizer,
|
||||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||
ITemplateAllocationService templateAllocationService,
|
||||
ITemplateMqPublisher templateMqPublisher,
|
||||
MqSettings mqSettings,
|
||||
IRabbitService mqService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.templateRepository = templateRepository;
|
||||
this.unitRepository = unitRepository;
|
||||
this.templateReuser = templateReuser;
|
||||
this.templateNameNormalizer = templateNameNormalizer;
|
||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||
this.mqSettings = mqSettings;
|
||||
this.mqService = mqService;
|
||||
this.templateAllocationService = templateAllocationService;
|
||||
this.templateMqPublisher = templateMqPublisher;
|
||||
}
|
||||
|
||||
public async Task<HashSet<(Guid JobId, Guid UnitId, int Index)>> ProcessAsync(
|
||||
@@ -89,7 +85,7 @@ internal class GroupedTemplateProcessor : IGroupedTemplateProcessor
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleNewOrReusableTemplateAsync(potentialUnitId, unitsInTemplateSubGroup, targetJob, globalIndex, initiator);
|
||||
await HandleNewOrReusableTemplateAsync(potentialUnitId, unitsInTemplateSubGroup, targetJob, globalIndex, initiator, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,7 +155,7 @@ internal class GroupedTemplateProcessor : IGroupedTemplateProcessor
|
||||
UnitFieldValueId = t.UnitFieldValueId
|
||||
}).ToList()
|
||||
};
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
await templateMqPublisher.PublishUpdateAsync(updateRequest);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -174,50 +170,33 @@ internal class GroupedTemplateProcessor : IGroupedTemplateProcessor
|
||||
List<(Guid UnitId, Guid UnitFieldValueId)> unitsInTemplateSubGroup,
|
||||
Job targetJob,
|
||||
int globalIndex,
|
||||
HistoryInitiator initiator)
|
||||
HistoryInitiator initiator,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, potentialUnitId, initiator);
|
||||
// Вычисляем флаги активности
|
||||
var isActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? false;
|
||||
var isActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? false;
|
||||
|
||||
if (reusableTemplate != null)
|
||||
{
|
||||
logger.LogInformation("Переиспользован шаблон {TemplateId}.", reusableTemplate.Id);
|
||||
|
||||
var tempTemplateForName = new Template
|
||||
// Маппим кортежи в сообщения
|
||||
var unitsInTemplateMsg = unitsInTemplateSubGroup
|
||||
.Select(e => new UnitInTemplateMessage
|
||||
{
|
||||
Id = reusableTemplate.Id,
|
||||
Name = reusableTemplate.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = potentialUnitId,
|
||||
Index = globalIndex,
|
||||
Job = targetJob,
|
||||
Unit = reusableTemplate.Unit,
|
||||
UnitsInTemplate = unitsInTemplateSubGroup.Select(e => new UnitsInTemplate { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
UnitId = e.UnitId,
|
||||
UnitFieldValueId = e.UnitFieldValueId
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||
var request = new TemplateAllocationRequest(
|
||||
TargetJob: targetJob,
|
||||
TargetUnitId: potentialUnitId,
|
||||
TargetUnit: null,
|
||||
Index: globalIndex,
|
||||
UnitsInTemplate: unitsInTemplateMsg,
|
||||
IsActiveTemplate: isActiveTemplate,
|
||||
IsActiveSchedule: isActiveSchedule,
|
||||
Initiator: initiator);
|
||||
|
||||
var updateRequest = new TemplateUpdaterMessage
|
||||
{
|
||||
TemplateId = reusableTemplate.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = potentialUnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? false,
|
||||
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? false,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
IsNew = true,
|
||||
Index = globalIndex,
|
||||
UnitsInTemplate = unitsInTemplateSubGroup.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Создание нового шаблона.");
|
||||
await CreateGroupedTemplateAsync(targetJob.Id, potentialUnitId, unitsInTemplateSubGroup, globalIndex, initiator);
|
||||
}
|
||||
await templateAllocationService.AllocateAsync(request, ct);
|
||||
}
|
||||
|
||||
private async Task UpdateTemplateUnitsAsync(
|
||||
@@ -270,32 +249,9 @@ internal class GroupedTemplateProcessor : IGroupedTemplateProcessor
|
||||
UnitsInTemplate = newUnitEntries.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
await templateMqPublisher.PublishUpdateAsync(updateRequest);
|
||||
}
|
||||
|
||||
private async Task CreateGroupedTemplateAsync(
|
||||
Guid jobId,
|
||||
Guid relationshipUnitId,
|
||||
List<(Guid UnitId, Guid UnitFieldValueId)> unitsInTemplate,
|
||||
int index,
|
||||
HistoryInitiator initiator)
|
||||
{
|
||||
logger.LogInformation("Создание нового группового шаблона.");
|
||||
|
||||
var mqRequest = new TemplateGeneratorMessage
|
||||
{
|
||||
JobId = jobId,
|
||||
UnitId = relationshipUnitId,
|
||||
UnitsInTemplate = unitsInTemplate.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList(),
|
||||
Index = index,
|
||||
HistoryInitiator = initiator
|
||||
};
|
||||
|
||||
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { mqRequest });
|
||||
|
||||
if (!result.IsSuccess)
|
||||
logger.LogError("Ошибка отправки команды создания шаблона.");
|
||||
}
|
||||
|
||||
private static Job SelectTargetJob(List<Job> jobsInGroup, int subGroupSize, Job maxJob)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Settings;
|
||||
using PARR.TemplateMatcher.Models;
|
||||
using PARR.TemplateMatcher.Services.Interfaces;
|
||||
using PARR.TemplateMatcher.Settings;
|
||||
|
||||
@@ -40,10 +41,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
private readonly IRabbitService mqService;
|
||||
private readonly ITemplateRepository templateService;
|
||||
private readonly IJobRepository jobService;
|
||||
private readonly ITemplateReuser templateReuser;
|
||||
private readonly ITemplateDeactivator templateDeactivator;
|
||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||
private readonly ITemplateAllocationService templateAllocationService;
|
||||
private readonly ITemplateMqPublisher templateMqPublisher;
|
||||
private readonly IMatchingStatusService matchingStatusService;
|
||||
private readonly SettingsFromDb settingsFromDb;
|
||||
private readonly IOptions<TemplateSettings> templateSettings;
|
||||
@@ -58,10 +59,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
IRabbitService mqService,
|
||||
ITemplateRepository templateService,
|
||||
IJobRepository jobService,
|
||||
ITemplateReuser templateReuser,
|
||||
ITemplateDeactivator templateDeactivator,
|
||||
ITemplateNameNormalizer templateNameNormalizer,
|
||||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||
ITemplateAllocationService templateAllocationService,
|
||||
ITemplateMqPublisher templateMqPublisher,
|
||||
IMatchingStatusService matchingStatusService,
|
||||
SettingsFromDb settingsFromDb,
|
||||
IOptions<TemplateSettings> templateSettings,
|
||||
@@ -76,10 +77,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
this.mqService = mqService;
|
||||
this.templateService = templateService;
|
||||
this.jobService = jobService;
|
||||
this.templateReuser = templateReuser;
|
||||
this.templateDeactivator = templateDeactivator;
|
||||
this.templateNameNormalizer = templateNameNormalizer;
|
||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||
this.templateAllocationService = templateAllocationService;
|
||||
this.templateMqPublisher = templateMqPublisher;
|
||||
this.matchingStatusService = matchingStatusService;
|
||||
this.settingsFromDb = settingsFromDb;
|
||||
this.templateSettings = templateSettings;
|
||||
@@ -209,49 +210,21 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
|
||||
foreach (var unitId in newUnitIds)
|
||||
{
|
||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
|
||||
if (reusableTemplate != null)
|
||||
{
|
||||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId);
|
||||
// Вычисляем флаги активности из конфигурации автоконтрола
|
||||
var isActiveTemplate = job.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState;
|
||||
var isActiveSchedule = job.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState;
|
||||
|
||||
// === Создаём временный Template для нормализации имени ===
|
||||
var tempTemplateForName = new Template
|
||||
{
|
||||
Id = reusableTemplate.Id,
|
||||
Name = reusableTemplate.Name,
|
||||
JobId = jobId,
|
||||
UnitId = unitId,
|
||||
Index = reusableTemplate.Index,
|
||||
Job = job, // загруженный job
|
||||
Unit = reusableTemplate.Unit, // может быть null — нормально
|
||||
UnitsInTemplate = new List<UnitsInTemplate>() // для простого шаблона
|
||||
};
|
||||
var request = new TemplateAllocationRequest(
|
||||
TargetJob: job,
|
||||
TargetUnitId: unitId,
|
||||
TargetUnit: null, // Для простого шаблона не критично, нормализатор загрузит при необходимости
|
||||
Index: null,
|
||||
UnitsInTemplate: new List<UnitInTemplateMessage>(),
|
||||
IsActiveTemplate: isActiveTemplate,
|
||||
IsActiveSchedule: isActiveSchedule,
|
||||
Initiator: initiator);
|
||||
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||
//var nextRun = await nextRunService.GetNextRunForTemplateAsync(reusableTemplate.Id, true);
|
||||
|
||||
var updateRequest = new TemplateUpdaterMessage
|
||||
{
|
||||
TemplateId = reusableTemplate.Id,
|
||||
JobId = jobId,
|
||||
UnitId = unitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = job.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
|
||||
IsActiveSchedule = job.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
//NextRun = nextRun,
|
||||
IsNew = true,
|
||||
UnitsInTemplate = new List<UnitInTemplateMessage>() // для простого шаблона
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
|
||||
await CreateSimpleTemplateAsync(jobId, unitId, initiator);
|
||||
}
|
||||
await templateAllocationService.AllocateAsync(request);
|
||||
}
|
||||
|
||||
// === Обработка существующих шаблонов (проверка имени) ===
|
||||
@@ -280,7 +253,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
UnitsInTemplate = new List<UnitInTemplateMessage>() // для простого шаблона
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
await templateMqPublisher.PublishUpdateAsync(updateRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,7 +388,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
UnitsInTemplate = new List<UnitInTemplateMessage>() // для простого шаблона
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
await templateMqPublisher.PublishUpdateAsync(updateRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -662,7 +635,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
UnitsInTemplate = new List<UnitInTemplateMessage>()
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
await templateMqPublisher.PublishUpdateAsync(updateRequest);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
using PARR.TemplateMatcher.Models;
|
||||
using PARR.TemplateMatcher.Services.Interfaces;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Implementations
|
||||
{
|
||||
internal class TemplateAllocationService : ITemplateAllocationService
|
||||
{
|
||||
private readonly ILogger<TemplateAllocationService> logger;
|
||||
private readonly ITemplateReuser reuser;
|
||||
private readonly ITemplateMqPublisher publisher;
|
||||
|
||||
public TemplateAllocationService(
|
||||
ILogger<TemplateAllocationService> logger,
|
||||
ITemplateReuser reuser,
|
||||
ITemplateMqPublisher publisher)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.reuser = reuser;
|
||||
this.publisher = publisher;
|
||||
}
|
||||
|
||||
public async Task AllocateAsync(TemplateAllocationRequest request, CancellationToken ct = default)
|
||||
{
|
||||
// Пробуем переиспользовать
|
||||
bool reused = await reuser.TryReuseAsync(
|
||||
request.TargetJob,
|
||||
request.TargetUnitId,
|
||||
request.TargetUnit,
|
||||
request.Index,
|
||||
request.UnitsInTemplate,
|
||||
request.IsActiveTemplate,
|
||||
request.IsActiveSchedule,
|
||||
request.Initiator,
|
||||
ct);
|
||||
|
||||
if (!reused)
|
||||
{
|
||||
logger.LogDebug("Переиспользование не удалось. Запрос на создание шаблона для Job {JobId}, Unit {UnitId}, Index {Index}.",
|
||||
request.TargetJob.Id, request.TargetUnitId, request.Index);
|
||||
|
||||
var createMessage = new TemplateGeneratorMessage
|
||||
{
|
||||
JobId = request.TargetJob.Id,
|
||||
UnitId = request.TargetUnitId,
|
||||
Index = request.Index,
|
||||
UnitsInTemplate = request.UnitsInTemplate ?? new List<UnitInTemplateMessage>(),
|
||||
IsActiveTemplate = request.IsActiveTemplate,
|
||||
IsActiveSchedule = request.IsActiveSchedule,
|
||||
HistoryInitiator = request.Initiator
|
||||
};
|
||||
|
||||
await publisher.PublishCreateAsync(createMessage, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ internal class TemplateDeactivator : ITemplateDeactivator
|
||||
private readonly ITemplateRepository templateService;
|
||||
private readonly IJobRepository jobService;
|
||||
private readonly ITemplateNameNormalizer namenormalizer;
|
||||
private readonly ITemplateUpdaterMqSender sender;
|
||||
private readonly ITemplateMqPublisher templateMqPublisher;
|
||||
private readonly SettingsFromDb settingsFromDb;
|
||||
private readonly IOptions<TemplateSettings> templateSettings;
|
||||
|
||||
@@ -32,7 +32,7 @@ internal class TemplateDeactivator : ITemplateDeactivator
|
||||
ITemplateRepository templateService,
|
||||
IJobRepository jobService,
|
||||
ITemplateNameNormalizer namenormalizer,
|
||||
ITemplateUpdaterMqSender sender,
|
||||
ITemplateMqPublisher templateMqPublisher,
|
||||
SettingsFromDb settingsFromDb,
|
||||
IOptions<TemplateSettings> templateSettings
|
||||
)
|
||||
@@ -41,7 +41,7 @@ internal class TemplateDeactivator : ITemplateDeactivator
|
||||
this.templateService = templateService;
|
||||
this.jobService = jobService;
|
||||
this.namenormalizer = namenormalizer;
|
||||
this.sender = sender;
|
||||
this.templateMqPublisher = templateMqPublisher;
|
||||
this.settingsFromDb = settingsFromDb;
|
||||
this.templateSettings = templateSettings;
|
||||
}
|
||||
@@ -115,7 +115,7 @@ internal class TemplateDeactivator : ITemplateDeactivator
|
||||
UnitsInTemplate = new List<UnitInTemplateMessage>()
|
||||
};
|
||||
|
||||
await sender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
await templateMqPublisher.PublishUpdateAsync(updateRequest);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
using PARR.TemplateMatcher.Services.Interfaces;
|
||||
using PARR.TemplateMatcher.Settings;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Implementations
|
||||
{
|
||||
internal class TemplateMqPublisher : ITemplateMqPublisher
|
||||
{
|
||||
private readonly ILogger<TemplateMqPublisher> logger;
|
||||
private readonly IRabbitService mqService;
|
||||
private readonly MqSettings mqSettings;
|
||||
|
||||
public TemplateMqPublisher(ILogger<TemplateMqPublisher> logger, IRabbitService mqService, MqSettings mqSettings)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.mqService = mqService;
|
||||
this.mqSettings = mqSettings;
|
||||
}
|
||||
|
||||
public async Task PublishUpdateAsync(TemplateUpdaterMessage message, CancellationToken ct = default)
|
||||
{
|
||||
var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new List<object> { message });
|
||||
if (!result.IsSuccess)
|
||||
logger.LogError("Ошибка публикации команды обновления шаблона {TemplateId}.", message.TemplateId);
|
||||
}
|
||||
|
||||
public async Task PublishCreateAsync(TemplateGeneratorMessage message, CancellationToken ct = default)
|
||||
{
|
||||
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { message });
|
||||
if (!result.IsSuccess)
|
||||
logger.LogError("Ошибка публикации команды создания шаблона для Job {JobId}, Unit {UnitId}.", message.JobId, message.UnitId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.TemplateMatcher.Services.Interfaces;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Implementations;
|
||||
@@ -10,71 +14,105 @@ namespace PARR.TemplateMatcher.Services.Implementations;
|
||||
internal class TemplateReuser : ITemplateReuser
|
||||
{
|
||||
private readonly ILogger<TemplateReuser> logger;
|
||||
private readonly ITemplateRepository templateService;
|
||||
private readonly ITemplateRepository templateRepository;
|
||||
private readonly ITemplateNameNormalizer nameNormalizer;
|
||||
private readonly ITemplateMqPublisher mqPublisher;
|
||||
|
||||
public TemplateReuser(
|
||||
ILogger<TemplateReuser> logger,
|
||||
ITemplateRepository templateService)
|
||||
ITemplateRepository templateRepository,
|
||||
ITemplateNameNormalizer nameNormalizer,
|
||||
ITemplateMqPublisher mqPublisher)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.templateService = templateService;
|
||||
this.templateRepository = templateRepository;
|
||||
this.nameNormalizer = nameNormalizer;
|
||||
this.mqPublisher = mqPublisher;
|
||||
}
|
||||
|
||||
|
||||
public async Task<Template?> TryReuseOneUnusedTemplateAsync(
|
||||
Guid jobId,
|
||||
Guid unitId,
|
||||
public async Task<bool> TryReuseAsync(
|
||||
Job targetJob,
|
||||
Guid targetUnitId,
|
||||
Unit? targetUnit,
|
||||
int? index,
|
||||
List<UnitInTemplateMessage> unitsInTemplate,
|
||||
bool isActiveTemplate,
|
||||
bool isActiveSchedule,
|
||||
HistoryInitiator initiator,
|
||||
int maxAttempts = 3)
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
const int maxAttempts = 3;
|
||||
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
// Атомарно резервируем один шаблон через DAL
|
||||
var templateId = await templateService.ReserveUnusedTemplateAsync(unitId, initiator);
|
||||
|
||||
// 1. Атомарный захват шаблона
|
||||
var templateId = await templateRepository.ReserveUnusedTemplateAsync(targetUnitId, initiator);
|
||||
if (templateId == null)
|
||||
{
|
||||
logger.LogDebug("Нет доступных Unused-шаблонов для переиспользования (попытка {Attempt}).", attempt);
|
||||
return null;
|
||||
logger.LogDebug("Нет доступных Unused-шаблонов (попытка {Attempt}).", attempt);
|
||||
return false; // Шаблоны закончились
|
||||
}
|
||||
|
||||
// Загружаем зарезервированный шаблон
|
||||
var template = await templateService.Get()
|
||||
// Загружаем захваченный шаблон
|
||||
var template = await templateRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.Unit)
|
||||
.Include(t => t.Job)
|
||||
.ThenInclude(t => t!.Tnk)
|
||||
.Include(t => t.Job)
|
||||
.ThenInclude(t => t!.Group)
|
||||
.ThenInclude(t => t!.GroupType)
|
||||
.FirstOrDefaultAsync(t => t.Id == templateId);
|
||||
.FirstOrDefaultAsync(t => t.Id == templateId, ct);
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден при загрузке.", templateId);
|
||||
continue;
|
||||
logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден.", templateId);
|
||||
continue; // Попробовать еще раз
|
||||
}
|
||||
|
||||
logger.LogInformation(
|
||||
"Успешно захвачен шаблон {TemplateId} (старый Job {OldJobId}) для нового Job {NewJobId}, Unit {UnitId} (попытка {Attempt}).",
|
||||
template.Id, template.JobId, jobId, unitId, attempt);
|
||||
logger.LogInformation("Шаблон {TemplateId} захвачен для переиспользования.", templateId);
|
||||
|
||||
return template;
|
||||
// 2. Нормализация имени
|
||||
var templateForName = new Template
|
||||
{
|
||||
Id = template.Id,
|
||||
Name = template.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = targetUnitId,
|
||||
Index = index,
|
||||
Job = targetJob,
|
||||
Unit = targetUnit,
|
||||
UnitsInTemplate = unitsInTemplate.Select(m => new UnitsInTemplate { UnitId = m.UnitId, UnitFieldValueId = m.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
var expectedName = await nameNormalizer.GetNormalizedTemplateNameAsync(templateForName);
|
||||
|
||||
// 3. Отправка команды в MQ
|
||||
var message = new TemplateUpdaterMessage
|
||||
{
|
||||
TemplateId = template.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = targetUnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = isActiveTemplate,
|
||||
IsActiveSchedule = isActiveSchedule,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
IsNew = true,
|
||||
Index = index ?? template.Index,
|
||||
UnitsInTemplate = unitsInTemplate
|
||||
};
|
||||
|
||||
await mqPublisher.PublishUpdateAsync(message, ct);
|
||||
return true; // Успех
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Ошибка при попытке захвата шаблона (попытка {Attempt}).", attempt);
|
||||
|
||||
if (attempt == maxAttempts)
|
||||
throw;
|
||||
|
||||
// Небольшая задержка перед повтором
|
||||
await Task.Delay(Random.Shared.Next(10, 50));
|
||||
logger.LogError(ex, "Ошибка при переиспользовании (попытка {Attempt}).", attempt);
|
||||
if (attempt == maxAttempts) return false; // После 3 попыток сдаемся
|
||||
await Task.Delay(Random.Shared.Next(10, 50), ct);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
using PARR.TemplateMatcher.Services.Interfaces;
|
||||
using PARR.TemplateMatcher.Settings;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Implementations;
|
||||
|
||||
internal class TemplateUpdaterMqSender : ITemplateUpdaterMqSender
|
||||
{
|
||||
private readonly ILogger<TemplateUpdaterMqSender> logger;
|
||||
private readonly IRabbitService mqService;
|
||||
private readonly MqSettings mqSettings;
|
||||
|
||||
public TemplateUpdaterMqSender(
|
||||
ILogger<TemplateUpdaterMqSender> logger,
|
||||
IRabbitService mqService,
|
||||
MqSettings mqSettings
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.mqService = mqService;
|
||||
this.mqSettings = mqSettings;
|
||||
}
|
||||
|
||||
public async Task SendTemplateUpdateMessageAsync(TemplateUpdaterMessage updateRequest)
|
||||
{
|
||||
logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}",
|
||||
mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId);
|
||||
|
||||
var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new List<object> { updateRequest });
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.",
|
||||
updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using PARR.TemplateMatcher.Models;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Оркестрирует выделение шаблона: попытка переиспользования или создание нового.
|
||||
/// </summary>
|
||||
public interface ITemplateAllocationService
|
||||
{
|
||||
Task AllocateAsync(TemplateAllocationRequest request, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Публикует команды создания и обновления шаблонов в RabbitMQ.
|
||||
/// </summary>
|
||||
public interface ITemplateMqPublisher
|
||||
{
|
||||
Task PublishUpdateAsync(TemplateUpdaterMessage message, CancellationToken ct = default);
|
||||
Task PublishCreateAsync(TemplateGeneratorMessage message, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,31 @@
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Interfaces
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Отвечает за полный цикл переиспользования Unused-шаблона:
|
||||
/// 1. Атомарный захват в БД (с retry).
|
||||
/// 2. Нормализация имени.
|
||||
/// 3. Отправка команды на обновление (активацию) в MQ.
|
||||
/// </summary>
|
||||
public interface ITemplateReuser
|
||||
{
|
||||
/// <summary>
|
||||
/// Пытается переиспользовать неиспользуемый шаблон для указанной работы и ЭК.
|
||||
/// При резервировании шаблона для правильного расчета NextRun Id работы
|
||||
/// и ЭК устанавливается при захвате.
|
||||
/// Пытается переиспользовать шаблон. Возвращает true, если успешно захвачен и отправлена команда.
|
||||
/// </summary>
|
||||
/// <param name="jobId">Id работы.</param>
|
||||
/// <param name="unitId">Id ЭК.</param>
|
||||
/// <param name="initiator">Данные инициатора операции.</param>
|
||||
/// <param name="maxAttempts">Максимальное количество попыток (по умолчанию 3).</param>
|
||||
/// <returns>
|
||||
/// Зарезервированный шаблон или <see langword="null"/>,
|
||||
/// если не удалось получить шаблон после всех попыток.
|
||||
/// </returns>
|
||||
Task<Template?> TryReuseOneUnusedTemplateAsync(
|
||||
Guid jobId,
|
||||
Guid unitId,
|
||||
Task<bool> TryReuseAsync(
|
||||
Job targetJob,
|
||||
Guid targetUnitId,
|
||||
Unit? targetUnit,
|
||||
int? index,
|
||||
List<UnitInTemplateMessage> unitsInTemplate,
|
||||
bool isActiveTemplate,
|
||||
bool isActiveSchedule,
|
||||
HistoryInitiator initiator,
|
||||
int maxAttempts = 3);
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Interfaces
|
||||
{
|
||||
public interface ITemplateUpdaterMqSender
|
||||
{
|
||||
Task SendTemplateUpdateMessageAsync(TemplateUpdaterMessage updateRequest);
|
||||
}
|
||||
}
|
||||
@@ -26,20 +26,34 @@ namespace PARR.TemplateMatcher
|
||||
configuration.GetSection(nameof(TemplateSettings)).Bind(templateSettings);
|
||||
services.AddSingleton(templateSettings);
|
||||
|
||||
services.AddTransient<IMqTemplateMatcher, MqTemplateMatcher>();
|
||||
services.AddTransient<IJobValidatorService, JobValidatorService>();
|
||||
services.AddTransient<IJobGroupValidatorService, JobGroupValidatorService>();
|
||||
services.AddTransient<ITemplateMatcher, TemplateMatcher>();
|
||||
services.AddTransient<ITemplateReuser, TemplateReuser>();
|
||||
// === 1. Singleton: Долгоживущие сервисы и обработчики очередей ===
|
||||
services.AddSingleton<IMqTemplateMatcher, MqTemplateMatcher>();
|
||||
|
||||
// === 2. Scoped: Бизнес-логика и работа с БД (DbContext) ===
|
||||
// Создаются заново для каждого сообщения из очереди (внутри CreateAsyncScope)
|
||||
services.AddScoped<ITemplateMatcher, TemplateMatcher>();
|
||||
services.AddScoped<ITemplateSynchronizer, SimpleTemplateSynchronizer>();
|
||||
services.AddScoped<ITemplateSynchronizer, GroupedTemplateSynchronizer>();
|
||||
|
||||
// Пайплайн аллокации шаблонов
|
||||
services.AddScoped<ITemplateAllocationService, TemplateAllocationService>();
|
||||
services.AddScoped<ITemplateReuser, TemplateReuser>();
|
||||
services.AddScoped<ITemplateDeactivator, TemplateDeactivator>();
|
||||
|
||||
// Пайплайн групповых шаблонов
|
||||
services.AddScoped<IGroupedTemplateUnitFilter, GroupedTemplateUnitFilter>();
|
||||
services.AddScoped<IUnitInTemplateConflictMapper, UnitInTemplateConflictMapper>();
|
||||
services.AddScoped<IGroupedTemplateBuilder, GroupedTemplateBuilder>();
|
||||
services.AddScoped<IGroupedTemplateProcessor, GroupedTemplateProcessor>();
|
||||
|
||||
// Валидаторы
|
||||
services.AddScoped<IJobValidatorService, JobValidatorService>();
|
||||
services.AddScoped<IJobGroupValidatorService, JobGroupValidatorService>();
|
||||
|
||||
// === 3. Transient: Stateless утилиты и инфраструктура ===
|
||||
// Легковесные сервисы без состояния, создаются по требованию
|
||||
services.AddTransient<ITemplateMqPublisher, TemplateMqPublisher>();
|
||||
services.AddTransient<ITemplateNameNormalizer, TemplateNameNormalizer>();
|
||||
services.AddTransient<ITemplateUpdaterMqSender, TemplateUpdaterMqSender>();
|
||||
services.AddTransient<ITemplateDeactivator, TemplateDeactivator>();
|
||||
services.AddTransient<IUnitInTemplateConflictMapper, UnitInTemplateConflictMapper>();
|
||||
services.AddTransient<IGroupedTemplateUnitFilter, GroupedTemplateUnitFilter>();
|
||||
services.AddTransient<IGroupedTemplateBuilder, GroupedTemplateBuilder>();
|
||||
services.AddTransient<IGroupedTemplateProcessor, GroupedTemplateProcessor>();
|
||||
services.AddTransient<ITemplateSynchronizer, SimpleTemplateSynchronizer>();
|
||||
services.AddTransient<ITemplateSynchronizer, GroupedTemplateSynchronizer>();
|
||||
}
|
||||
|
||||
public static IConfigurationBuilder AddTemplateMatcherConfigurations(this IConfigurationBuilder builder, IServiceCollection services)
|
||||
|
||||
Reference in New Issue
Block a user