From 2a6fd7177d83890d34f3e271fff83dcb6b08bc45 Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Wed, 15 Apr 2026 16:48:23 +1000 Subject: [PATCH] =?UTF-8?q?feat(core):=20TaskManagementService=20-=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=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 --- .../V1/Statistics/StatWorkloadController.cs | 48 ++++++++++++++- PARR.Core/DependencyInjection.cs | 3 + PARR.Core/PARR.Core.csproj | 1 - .../Implementations}/TaskManagementService.cs | 60 +++++++++++-------- .../Interfaces}/ITaskManagementService.cs | 2 +- PARR.DAL/ParrDalInstaller.cs | 3 - PARR.DAL/Repositories/Base/BaseRepository.cs | 3 + .../Common/Rabbit/Messages/TaskMessage.cs | 16 +++++ 8 files changed, 103 insertions(+), 33 deletions(-) rename {PARR.DAL/TaskServices => PARR.Core/Services/Task/Implementations}/TaskManagementService.cs (57%) rename {PARR.DAL/TaskServices => PARR.Core/Services/Task/Interfaces}/ITaskManagementService.cs (95%) create mode 100644 PARR.Domain/Common/Rabbit/Messages/TaskMessage.cs diff --git a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs index c688fd7f..f4c2a764 100644 --- a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs +++ b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs @@ -1,8 +1,14 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PARR.API.Contracts.V1; +using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; +using PARR.API.Services.Interfaces; +using PARR.API.Settings; +using PARR.Core.Services.Task.Interfaces; using PARR.Domain.Common.Roles; +using PARR.Domain.Entities.Base.History; +using PARR.Domain.Enums; namespace PARR.API.Controllers.V1.Statistics { @@ -12,9 +18,22 @@ namespace PARR.API.Controllers.V1.Statistics [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)] public class StatWorkloadController : BaseApiController { - public StatWorkloadController() - { + private readonly ITaskManagementService taskManagementService; + private readonly IClientService clientService; + private readonly MqSettings mqSettings; + private readonly ILogger logger; + public StatWorkloadController( + ITaskManagementService taskManagementService, + IClientService clientService, + MqSettings mqSettings, + ILogger logger + ) + { + this.taskManagementService = taskManagementService; + this.clientService = clientService; + this.mqSettings = mqSettings; + this.logger = logger; } @@ -25,8 +44,31 @@ namespace PARR.API.Controllers.V1.Statistics [HttpPost(ApiRoutes.Workload.Build)] public async Task Build() { + var initiator = new HistoryInitiator + { + InitiatorIp = clientService.GetClientIp()?.ToString(), + InitiatorParrComponentId = ParrComponentsEnum.Api, + InitiatorComment = "Отправлен запрос из API на формирование отчета о загруженности" + }; - return Ok(); + try + { + mqSettings.Tasks.TryGetValue(TaskTypeEnum.Workload, out var queueSettings); + + if (queueSettings == null) + { + logger.LogError("Не удалось получить настройки очереди для TaskType: {TaskType}", TaskTypeEnum.Workload); + throw new InvalidOperationException($"Не удалось получить настройки очереди для TaskTypeEnum.Workload"); + } + + var taskId = await taskManagementService.CreateTaskAsync(TaskTypeEnum.Workload, default, initiator, queueSettings); + + return Ok(new Response(null, true, null!, $"Создана задача {taskId} на формирование отчета")); + } + catch (Exception ex) + { + return BadRequest(new Response(false, new List { new ErrorModel { Message = ex.Message } })); + } } diff --git a/PARR.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs index c003bf91..be87155e 100644 --- a/PARR.Core/DependencyInjection.cs +++ b/PARR.Core/DependencyInjection.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using PARR.Core.Common.Implementations; using PARR.Core.Common.Interfaces; +using PARR.Core.Services.Task.Implementations; +using PARR.Core.Services.Task.Interfaces; using PARR.Domain.Settings; namespace PARR.Core @@ -37,6 +39,7 @@ namespace PARR.Core #region Services + services.AddScoped(); //services.AddScoped(); #endregion diff --git a/PARR.Core/PARR.Core.csproj b/PARR.Core/PARR.Core.csproj index aef64738..a7524906 100644 --- a/PARR.Core/PARR.Core.csproj +++ b/PARR.Core/PARR.Core.csproj @@ -8,7 +8,6 @@ - diff --git a/PARR.DAL/TaskServices/TaskManagementService.cs b/PARR.Core/Services/Task/Implementations/TaskManagementService.cs similarity index 57% rename from PARR.DAL/TaskServices/TaskManagementService.cs rename to PARR.Core/Services/Task/Implementations/TaskManagementService.cs index dca781c5..636f2d52 100644 --- a/PARR.DAL/TaskServices/TaskManagementService.cs +++ b/PARR.Core/Services/Task/Implementations/TaskManagementService.cs @@ -1,6 +1,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using PARR.Core.Common.Interfaces.RabbitServices; using PARR.Core.Repositories.Interfaces.TaskRepositories; +using PARR.Core.Services.Task.Interfaces; +using PARR.Domain.Common.Rabbit.Messages; using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.TaskEntities; using PARR.Domain.Enums; @@ -8,29 +11,32 @@ using PARR.Domain.Settings; using System.Text.Encodings.Web; using System.Text.Json; -namespace PARR.DAL.TaskServices +namespace PARR.Core.Services.Task.Implementations { internal class TaskManagementService : ITaskManagementService { private readonly ILogger logger; - private readonly ITaskTypeRepository taskTypeService; - private readonly ITaskRepository taskService; + private readonly ITaskTypeRepository taskTypeRepository; + private readonly ITaskRepository taskRepository; + private readonly IRabbitService rabbitService; public TaskManagementService( ILogger logger, - ITaskTypeRepository taskTypeService, - ITaskRepository taskService + ITaskTypeRepository taskTypeRepository, + ITaskRepository taskRepository, + IRabbitService rabbitService ) { this.logger = logger; - this.taskTypeService = taskTypeService; - this.taskService = taskService; + this.taskTypeRepository = taskTypeRepository; + this.taskRepository = taskRepository; + this.rabbitService = rabbitService; } public async Task CreateTaskAsync(TaskTypeEnum typeCode, T payload, IHistoryInitiator initiator, IMqSettings mqSettings) { - var taskType = await taskTypeService.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Code == typeCode); + var taskType = await taskTypeRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Code == typeCode); if (taskType == null) { @@ -45,10 +51,7 @@ namespace PARR.DAL.TaskServices if (existingTask != null) { - logger.LogInformation( - "Задача типа {TypeCode} уже активна (id: {ExistingId}). Возвращаем существующую.", - typeCode, existingTask.Id); - + logger.LogInformation("Задача типа {TypeCode} уже активна (id: {ExistingId}). Возвращаем существующую.", typeCode, existingTask.Id); return existingTask.Id; } } @@ -63,26 +66,30 @@ namespace PARR.DAL.TaskServices StatusCode = TaskItemStatusEnum.Pending, Payload = payloadStr, RetryCount = 0, - ProcessedAt = null, - InitiatorIp = initiator.InitiatorIp, - InitiatorParrComponentId = initiator.InitiatorParrComponentId, - InitiatorComment = initiator.InitiatorComment + ProcessedAt = null }; - if (!await taskService.CreateAsync(task) || !await taskService.CommitAsync()) + if (!await taskRepository.CreateAsync(task) || !await taskRepository.CommitAsync(initiator)) { logger.LogError("Ошибка при сохранении задачи в БД"); - return default; + throw new DbUpdateException("Ошибка при сохранении задачи в БД"); } logger.LogInformation("Задача {TaskId} типа {TypeCode} сохранена в БД со статусом Pending", task.Id, typeCode); - // Публикуме задачу в очередь. Если вдруг даже не получится, - // задача останется в БД со статусом pending, и Reconciliation Task позже ее возмет в работу + var message = new TaskMessage { TaskId = task.Id, TypeCode = task.TypeCode }; - // todo: опубликовать в очередь - // вопросы, зачем scoped? может можно AddTransient? - // нужно придумать шаблонную модель для очереди + var sendResult = await rabbitService.SendAsync(mqSettings, new List { message }); + if (sendResult.IsSuccess) + { + logger.LogDebug("Задача {TaskId} типа {TypeCode} отправлена в очередь {QueueName}", task.Id, typeCode, mqSettings.QueueName); + } + else + { + // Не пробрасываем исключение дальше — задача создана, просто не в очереди. + // задача останется в БД со статусом pending, и Reconciliation Task позже ее возмет в работу + logger.LogError("Не удалось опубликовать задачу {TaskId} в очередь. Задача осталась в БД со статусом Pending.", task.Id); + } return task.Id; } @@ -95,7 +102,7 @@ namespace PARR.DAL.TaskServices /// private async Task GetActiveSingletonTaskAsync(TaskTypeEnum typeCode) { - return await taskService.Get().AsNoTracking() + return await taskRepository.Get().AsNoTracking() .FirstOrDefaultAsync(t => t.TypeCode == typeCode && ( t.StatusCode == TaskItemStatusEnum.Pending || t.StatusCode == TaskItemStatusEnum.Processing @@ -109,13 +116,16 @@ namespace PARR.DAL.TaskServices /// /// /// - private string PayloadToString(T payload) + private string? PayloadToString(T payload) { var jsonOptions = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; + if (payload == null) + return null; + return JsonSerializer.Serialize(payload, jsonOptions); } diff --git a/PARR.DAL/TaskServices/ITaskManagementService.cs b/PARR.Core/Services/Task/Interfaces/ITaskManagementService.cs similarity index 95% rename from PARR.DAL/TaskServices/ITaskManagementService.cs rename to PARR.Core/Services/Task/Interfaces/ITaskManagementService.cs index 161c264e..6f8a06a5 100644 --- a/PARR.DAL/TaskServices/ITaskManagementService.cs +++ b/PARR.Core/Services/Task/Interfaces/ITaskManagementService.cs @@ -2,7 +2,7 @@ using PARR.Domain.Enums; using PARR.Domain.Settings; -namespace PARR.DAL.TaskServices +namespace PARR.Core.Services.Task.Interfaces { /// /// Управления задачами (очередями) diff --git a/PARR.DAL/ParrDalInstaller.cs b/PARR.DAL/ParrDalInstaller.cs index 938002f7..c582903c 100644 --- a/PARR.DAL/ParrDalInstaller.cs +++ b/PARR.DAL/ParrDalInstaller.cs @@ -22,7 +22,6 @@ using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Schedule; using PARR.DAL.Services.Interfaces.Unit; -using PARR.DAL.TaskServices; using PARR.Domain.Settings; namespace PARR.DAL @@ -154,8 +153,6 @@ namespace PARR.DAL services.AddScoped(); services.AddScoped(); - services.AddTransient(); - #endregion //services.AddTransient(); diff --git a/PARR.DAL/Repositories/Base/BaseRepository.cs b/PARR.DAL/Repositories/Base/BaseRepository.cs index 32e213a8..c5f41def 100644 --- a/PARR.DAL/Repositories/Base/BaseRepository.cs +++ b/PARR.DAL/Repositories/Base/BaseRepository.cs @@ -99,6 +99,9 @@ namespace PARR.DAL.Repositories.Base /// private void SetInitiator(IHistoryInitiator? initiator) { + if (initiator == null) + return; + logger.LogDebug("Устанавливаю инициатора для изменений"); // Задаем инициатора только для новых и измененных записей diff --git a/PARR.Domain/Common/Rabbit/Messages/TaskMessage.cs b/PARR.Domain/Common/Rabbit/Messages/TaskMessage.cs new file mode 100644 index 00000000..1a96585c --- /dev/null +++ b/PARR.Domain/Common/Rabbit/Messages/TaskMessage.cs @@ -0,0 +1,16 @@ +using PARR.Domain.Enums; +using System.Text.Json.Serialization; + +namespace PARR.Domain.Common.Rabbit.Messages +{ + /// + /// Модель для отправки сообщений через ITaskManagement + /// + public record TaskMessage + { + public Guid TaskId { get; init; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public TaskTypeEnum TypeCode { get; init; } + } +}