From b7fca08d28eb53b91f9be2fde19c62ee0b05a111 Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Thu, 22 Jan 2026 10:35:27 +1000 Subject: [PATCH] =?UTF-8?q?feat(templateDistributor,=20BLL):=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=80=D0=B0=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=88?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D0=BE=D0=B2.=20=D0=A1=D0=BE?= =?UTF-8?q?=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D0=B5=D1=82=20=D0=B2=20=D0=91?= =?UTF-8?q?=D0=94=20+=20=D1=81=D1=82=D0=B0=D0=B2=D0=B8=D1=82=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=80=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=20=D0=BD=D0=B0=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5.=20MqService=20-=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B6=D0=B5=D1=82=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D1=81=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8,=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/V1/DistributorController.cs | 25 ++++++- PARR.BLL/Domain/Mq/TemplateDistributorMq.cs | 6 +- .../Services/Implementations/MqServiceV2.cs | 16 +++- PARR.BLL/Services/Interfaces/IMqService.cs | 16 ++++ .../ITemplateDistributor.cs | 6 +- .../MqTemplateDistributor.cs | 3 +- .../TemplateDistributor.cs | 73 +++++++++++++++++-- .../appsettings.Development.json | 6 +- 8 files changed, 132 insertions(+), 19 deletions(-) diff --git a/PARR.API/Controllers/V1/DistributorController.cs b/PARR.API/Controllers/V1/DistributorController.cs index 31975fca..10dce4f5 100644 --- a/PARR.API/Controllers/V1/DistributorController.cs +++ b/PARR.API/Controllers/V1/DistributorController.cs @@ -5,10 +5,13 @@ using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; +using PARR.API.Services.Interfaces; using PARR.API.Settings; using PARR.BLL.Domain.Mq; using PARR.BLL.Services.Interfaces; +using PARR.Common.Domain; using PARR.Constants; +using System.Text.Encodings.Web; using System.Text.Json; namespace PARR.API.Controllers.V1 @@ -22,16 +25,19 @@ namespace PARR.API.Controllers.V1 private readonly IMqService mqService; private readonly MqSettings mqSettings; private readonly IValidator validator; + private readonly IClientService clientService; public DistributorController( IMqService mqService, MqSettings mqSettings, - IValidator validator + IValidator validator, + IClientService clientService ) { this.mqService = mqService; this.mqSettings = mqSettings; this.validator = validator; + this.clientService = clientService; } @@ -48,12 +54,23 @@ namespace PARR.API.Controllers.V1 var requestToMq = new TemplateDistributorMq { - JobGroupId = request.JobGroupId + JobGroupId = request.JobGroupId, + Initiator = new HistoryInitiator + { + InitiatorComment = "Через API отправлен запрос на распределение шаблонов", + InitiatorIp = clientService.GetClientIp()?.ToString(), + InitiatorParrComponentId = ParrComponentsEnum.Api + } }; - var msg = JsonSerializer.Serialize(requestToMq); + //var jsonOptions = new JsonSerializerOptions + //{ + // Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + //}; + //var msg = JsonSerializer.Serialize(requestToMq, jsonOptions); - var sendResult = await mqService.SendAsync(mqSettings.TemplateDistributor, new[] { msg }); + //var sendResult = await mqService.SendAsync(mqSettings.TemplateDistributor, new[] { msg }); + var sendResult = await mqService.SendAsync(mqSettings.TemplateDistributor, new List { requestToMq }); if (sendResult.IsSuccess) return Created("", new Response(null, true, new List(), "Отправлен запрос на перераспределение регламентных работ.")); diff --git a/PARR.BLL/Domain/Mq/TemplateDistributorMq.cs b/PARR.BLL/Domain/Mq/TemplateDistributorMq.cs index 82bb7d4a..63b88e5f 100644 --- a/PARR.BLL/Domain/Mq/TemplateDistributorMq.cs +++ b/PARR.BLL/Domain/Mq/TemplateDistributorMq.cs @@ -1,4 +1,6 @@ -namespace PARR.BLL.Domain.Mq +using PARR.Common.Domain; + +namespace PARR.BLL.Domain.Mq { /// /// Модель в MQ, распределить шаблоны для JobGroupId (для TemplateDistributor) @@ -6,5 +8,7 @@ public class TemplateDistributorMq { public Guid JobGroupId { get; set; } + + public required HistoryInitiator Initiator { get; set; } } } diff --git a/PARR.BLL/Services/Implementations/MqServiceV2.cs b/PARR.BLL/Services/Implementations/MqServiceV2.cs index 11282afd..c56cd347 100644 --- a/PARR.BLL/Services/Implementations/MqServiceV2.cs +++ b/PARR.BLL/Services/Implementations/MqServiceV2.cs @@ -5,6 +5,8 @@ using PARR.BLL.Services.Interfaces; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; namespace PARR.BLL.Services.Implementations { @@ -17,6 +19,11 @@ namespace PARR.BLL.Services.Implementations // реализация для RabbitMQ.Client 7.1.2 + private static readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + public MqServiceV2(ILogger logger) { this.logger = logger; @@ -108,6 +115,12 @@ namespace PARR.BLL.Services.Implementations } } + public async Task SendAsync(IMqSettings mqSettings, List msgObjectList) + { + var msgStringList = msgObjectList.Select(t => JsonSerializer.Serialize(t, jsonOptions)).ToArray(); + + return await SendAsync(mqSettings, msgStringList); + } public async Task SendAsync(IMqSettings mqSettings, string[] msgList) { @@ -136,7 +149,8 @@ namespace PARR.BLL.Services.Implementations props.DeliveryMode = DeliveryModes.Persistent; //время жизни, мс //props.Expiration = "60000"; - props.ContentType = "text/plain";//"application/json"; + //props.ContentType = "text/plain";//"application/json"; + props.ContentType = "text/plain; charset=utf-8";//"application/json"; channel.BasicReturnAsync += async (sender, ea) => { diff --git a/PARR.BLL/Services/Interfaces/IMqService.cs b/PARR.BLL/Services/Interfaces/IMqService.cs index 52344182..65af0579 100644 --- a/PARR.BLL/Services/Interfaces/IMqService.cs +++ b/PARR.BLL/Services/Interfaces/IMqService.cs @@ -8,6 +8,22 @@ namespace PARR.BLL.Services.Interfaces public interface IMqService : IAsyncDisposable // IDisposable { Task InitConsumerAsync(IMqSettings mqSettings, MqMessageHandlerDelegate messageHandler); + + /// + /// Отправить сообщение в очередь используя список строк + /// !!! Избавиться от этого метода, вместо него использовать со списком объектов !!! + /// + /// + /// + /// Task SendAsync(IMqSettings mqSettings, string[] msgList); + + /// + /// Отправить сообщение в очередь используя список объектов + /// + /// + /// + /// + Task SendAsync(IMqSettings mqSettings, List msgObjectList); } } diff --git a/PARR.TemplateDistributor/ITemplateDistributor.cs b/PARR.TemplateDistributor/ITemplateDistributor.cs index a819104d..e4842544 100644 --- a/PARR.TemplateDistributor/ITemplateDistributor.cs +++ b/PARR.TemplateDistributor/ITemplateDistributor.cs @@ -1,4 +1,6 @@ -namespace PARR.TemplateDistributor +using PARR.BLL.Domain.Mq; + +namespace PARR.TemplateDistributor { public interface ITemplateDistributor { @@ -7,6 +9,6 @@ /// /// /// - Task DistributeAsync(Guid jobGroupId); + Task DistributeAsync(TemplateDistributorMq mqResponse); } } diff --git a/PARR.TemplateDistributor/MqTemplateDistributor.cs b/PARR.TemplateDistributor/MqTemplateDistributor.cs index 1d3d9abd..bf0c9211 100644 --- a/PARR.TemplateDistributor/MqTemplateDistributor.cs +++ b/PARR.TemplateDistributor/MqTemplateDistributor.cs @@ -65,8 +65,7 @@ namespace PARR.TemplateDistributor if (service == null) throw new Exception($"Не найден сервис: {nameof(ITemplateDistributor)}"); - await service.DistributeAsync(query.JobGroupId); - + await service.DistributeAsync(query); } } diff --git a/PARR.TemplateDistributor/TemplateDistributor.cs b/PARR.TemplateDistributor/TemplateDistributor.cs index 02b6d675..1c64ba45 100644 --- a/PARR.TemplateDistributor/TemplateDistributor.cs +++ b/PARR.TemplateDistributor/TemplateDistributor.cs @@ -1,12 +1,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using PARR.BLL.Services.Interfaces; +using PARR.BLL.Domain.Mq; using PARR.Constants; -using PARR.DAL.Models; +using PARR.DAL.Contracts; using PARR.DAL.NextRunServices; using PARR.DAL.Services.Interfaces; -using PARR.DAL.TransformServices; -using System.Reflection.Metadata.Ecma335; namespace PARR.TemplateDistributor { @@ -14,19 +12,27 @@ namespace PARR.TemplateDistributor { private readonly ILogger logger; private readonly INextRunService nextRunService; + private readonly ITemplateService templateService; + private readonly IRobotConfigurationService robotConfigurationService; public TemplateDistributor( ILogger logger, - INextRunService nextRunService + INextRunService nextRunService, + ITemplateService templateService, + IRobotConfigurationService robotConfigurationService ) { this.logger = logger; this.nextRunService = nextRunService; + this.templateService = templateService; + this.robotConfigurationService = robotConfigurationService; } - public async Task DistributeAsync(Guid jobGroupId) + public async Task DistributeAsync(TemplateDistributorMq mqResponse) { + var jobGroupId = mqResponse.JobGroupId; + // вызвать метод распределения, и получить новые даты var distributedTemplates = await nextRunService.GetNextRunForJobGroupWithAutoDistributionAsync(jobGroupId); @@ -37,6 +43,61 @@ namespace PARR.TemplateDistributor } // получить список шаблонов, сравнить их с распределенными, обновить даты, сохранить + var groupTemplates = await templateService.Get() + .Include(t => t.RobotConfigurations) + .Where(t => t.Job.GroupId == jobGroupId) + .ToListAsync(); + + if (!groupTemplates.Any()) + { + logger.LogWarning("Для jobGroupId {jobGroupId} не найдено ни одного шаблона в БД. Прекращаю обновление.", jobGroupId); + return; + } + + // создать словарь для быстрого поиска + var distributedDict = distributedTemplates.ToDictionary(t => t.Id); + + int updatedCount = 0; + + foreach (var templateDb in groupTemplates) + { + if (distributedDict.TryGetValue(templateDb.Id, out var distributedTemplate)) + { + if (templateDb.NextRun != distributedTemplate.NextRun) + { + templateDb.NextRun = distributedTemplate.NextRun; + templateDb.LastRun = distributedTemplate.NextRunOld; + templateDb.DateModified = DateTimeOffset.UtcNow; + + // ставим задание роботу - обновить расписания + var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, templateDb); + robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config); + + updatedCount++; + } + } + else + { + logger.LogWarning("Шаблон с Id {templateId} не найден в распределённых данных.", templateDb.Id); + } + } + + if (updatedCount > 0) + { + // сохранить изменения + if (await templateService.CommitAsync(mqResponse.Initiator)) + { + logger.LogInformation("Обновлено {updatedCount} шаблонов в БД для jobGroupId {jobGroupId}. Для расписаний установлен статус: {taskStatus}", updatedCount, jobGroupId, TaskStatusEnum.Updating.ToString()); + } + else + { + logger.LogError("Ошибка при обновлении записей в БД. jobGroupId {jobGroupId}, требовалось обновить шаблонов: {updatedCount}", jobGroupId, updatedCount); + } + } + else + { + logger.LogInformation("Для jobGroupId {jobGroupId} не найдено изменений. Ничего не обновлено.", jobGroupId); + } } } } diff --git a/PARR.TemplateDistributorWorker/appsettings.Development.json b/PARR.TemplateDistributorWorker/appsettings.Development.json index 89fc256d..8f88b46e 100644 --- a/PARR.TemplateDistributorWorker/appsettings.Development.json +++ b/PARR.TemplateDistributorWorker/appsettings.Development.json @@ -10,10 +10,10 @@ }, "Serilog": { "MinimumLevel": { - "Default": "Information", + "Default": "Debug", "Override": { - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft": "Debug", + "Microsoft.Hosting.Lifetime": "Debug" } }, "WriteTo": [