diff --git a/PARR.API/Controllers/V1/RobotTaskRobotStatusController.cs b/PARR.API/Controllers/V1/RobotTaskRobotStatusController.cs index 91918a33..d2c2aa58 100644 --- a/PARR.API/Controllers/V1/RobotTaskRobotStatusController.cs +++ b/PARR.API/Controllers/V1/RobotTaskRobotStatusController.cs @@ -1,40 +1,34 @@ using AutoMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Responses; using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; using PARR.API.Services.Interfaces; -using PARR.Core.Repositories.Interfaces; +using PARR.Core.Services.RobotTaskRobotStatus.Interfaces; using PARR.Domain.Common.Roles; -using PARR.Domain.Entities; -using PARR.Domain.Entities.RobotEntities; -using PARR.Domain.Enums; +using PARR.Domain.DTOs.RobotTaskRobotStatus; namespace PARR.API.Controllers.V1 { [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)] public class RobotTaskRobotStatusController : BaseApiController { - private readonly IRobotConfigurationRepository robotConfigurationService; - private readonly IRobotHistoryRepository robotHistoryService; - private readonly IMapper mapper; - private readonly IClientService clientService; + private readonly IMapper _mapper; + private readonly IClientService _clientService; + private readonly IRobotTaskRobotStatusService _robotTaskRobotStatusService; public RobotTaskRobotStatusController( - IRobotConfigurationRepository robotConfigurationService, - IRobotHistoryRepository robotHistoryService, IMapper mapper, - IClientService clientService + IClientService clientService, + IRobotTaskRobotStatusService robotTaskRobotStatusService ) { - this.robotConfigurationService = robotConfigurationService; - this.robotHistoryService = robotHistoryService; - this.mapper = mapper; - this.clientService = clientService; + _mapper = mapper; + _clientService = clientService; + _robotTaskRobotStatusService = robotTaskRobotStatusService; } @@ -46,47 +40,58 @@ namespace PARR.API.Controllers.V1 [HttpPut(ApiRoutes.RobotTaskRobotStatus.ChangeRobotStatus)] public async Task ChangeStatus([FromRoute] Guid taskId, [FromBody] RobotTaskChangeRobotStatusRequest request) { - var config = await robotConfigurationService.Get() - .FirstOrDefaultAsync(t => t.Id == taskId); + #region Old - if (config == null) - return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найдено задание с id: {taskId}" } })); + //var config = await _robotConfigurationRepository.Get() + // .FirstOrDefaultAsync(t => t.Id == taskId); - //изменение статуса робота - robotConfigurationService.ChangeRobotStatus(request.RobotStatusCode, config); + //if (config == null) + // return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найдено задание с id: {taskId}" } })); - //если успех, изменяем статус задания на успех - if (request.RobotStatusCode == RobotStatusEnum.Complete) - robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Ok, config); + ////изменение статуса робота + //_robotConfigurationRepository.ChangeRobotStatus(request.RobotStatusCode, config); - if (!await robotConfigurationService.CommitAsync()) - return BadRequest("Ошибка при изменении статуса работы робота."); + ////если успех, изменяем статус задания на успех + //if (request.RobotStatusCode == RobotStatusEnum.Complete) + // _robotConfigurationRepository.ChangeTaskStatus(TaskStatusEnum.Ok, config); - //записываем в лог робота - if (request.RobotStatusCode == RobotStatusEnum.InProgress || request.RobotStatusCode == RobotStatusEnum.Complete) - { - var historyLevel = request.RobotStatusCode == RobotStatusEnum.InProgress ? RobotHistoryLevelEnum.Start : RobotHistoryLevelEnum.Complete; + //if (!await _robotConfigurationRepository.CommitAsync()) + // return BadRequest("Ошибка при изменении статуса работы робота."); - var history = new RobotHistory - { - Id = Guid.NewGuid(), - HistoryLevel = (int)historyLevel, - TaskStatusCode = config.TaskStatusCode, - RobotConfigurationId = config.Id, - RobotIp = clientService.GetClientIp()?.ToString(), - RobotId = request.RobotId - }; - await robotHistoryService.CreateAsync(history); - await robotHistoryService.CommitAsync(); - } + ////записываем в лог робота + //if (request.RobotStatusCode == RobotStatusEnum.InProgress || request.RobotStatusCode == RobotStatusEnum.Complete) + //{ + // var historyLevel = request.RobotStatusCode == RobotStatusEnum.InProgress ? RobotHistoryLevelEnum.Start : RobotHistoryLevelEnum.Complete; - var configToResponse = await robotConfigurationService.Get() - .Include(t => t.Robot) - .Include(t => t.TaskStatus) - .Include(t => t.RobotStatus) - .FirstOrDefaultAsync(t => t.Id == taskId); + // var history = new RobotHistory + // { + // Id = Guid.NewGuid(), + // HistoryLevel = (int)historyLevel, + // TaskStatusCode = config.TaskStatusCode, + // RobotConfigurationId = config.Id, + // RobotIp = _clientService.GetClientIp()?.ToString(), + // RobotId = request.RobotId + // }; + // await _robotHistoryRepository.CreateAsync(history); + // await _robotHistoryRepository.CommitAsync(); + //} - var response = mapper.Map(configToResponse); + //var configToResponse = await _robotConfigurationRepository.Get() + // .Include(t => t.Robot) + // .Include(t => t.TaskStatus) + // .Include(t => t.RobotStatus) + // .FirstOrDefaultAsync(t => t.Id == taskId); + + //var response = _mapper.Map(configToResponse); + + //return Ok(new Response(response, true)); + + #endregion + + var changeRequest = new ChangeRobotStatus(taskId, request.RobotStatusCode, request.RobotId, _clientService.GetClientIp()?.ToString()); + var result = await _robotTaskRobotStatusService.ChangeStatusAsync(changeRequest); + + var response = _mapper.Map(result); return Ok(new Response(response, true)); } diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 82b1d151..b10ab1fb 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -8,6 +8,8 @@ using PARR.Domain.DTOs.Matching; using PARR.Domain.DTOs.RobotMetrics; using PARR.Domain.DTOs.RobotSnapshotDTO; using PARR.Domain.DTOs.RobotTask; +using PARR.Domain.DTOs.RobotTaskRobotStatus; +using PARR.Domain.DTOs.Shared; using PARR.Domain.DTOs.Shortcode; using PARR.Domain.DTOs.TaskDTO; using PARR.Domain.DTOs.User; @@ -258,10 +260,17 @@ namespace PARR.API.MappingProfiles CreateMap(); - CreateMap() - .ForMember(d => d.Robot, o => o.MapFrom(s => s.Robot)) - .ForMember(d => d.TaskStatus, o => o.MapFrom(s => s.TaskStatus)) - .ForMember(d => d.RobotStatus, o => o.MapFrom(s => s.RobotStatus)); + //CreateMap() + // .ForMember(d => d.Robot, o => o.MapFrom(s => s.Robot)) + // .ForMember(d => d.TaskStatus, o => o.MapFrom(s => s.TaskStatus)) + // .ForMember(d => d.RobotStatus, o => o.MapFrom(s => s.RobotStatus)); + + CreateMap(); + CreateMap(); + CreateMap(); + + CreateMap(); + // === RobotConfiguration === #endregion diff --git a/PARR.API/appsettings.json b/PARR.API/appsettings.json index 1d9e827e..63a9ebff 100644 --- a/PARR.API/appsettings.json +++ b/PARR.API/appsettings.json @@ -6,7 +6,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None" } }, "Serilog": { @@ -14,7 +15,8 @@ "Default": "Information", "Override": { "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "Fatal" } } }, @@ -111,7 +113,7 @@ } ], "RabbitMq": { - "ThresholdConnections": 33 + "ThresholdConnections": 32 } }, "CommonSettings": { diff --git a/PARR.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs index c1d29715..e611a188 100644 --- a/PARR.Core/DependencyInjection.cs +++ b/PARR.Core/DependencyInjection.cs @@ -12,6 +12,8 @@ using PARR.Core.Services.RobotMetrics; using PARR.Core.Services.RobotSnapshotServices; using PARR.Core.Services.RobotTask.Implementations; using PARR.Core.Services.RobotTask.Interfaces; +using PARR.Core.Services.RobotTaskRobotStatus.Implemetations; +using PARR.Core.Services.RobotTaskRobotStatus.Interfaces; using PARR.Core.Services.Shortcodes; using PARR.Core.Services.Shortcodes.Handlers; using PARR.Core.Services.Snapshots.Implementations; @@ -111,6 +113,7 @@ namespace PARR.Core services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/PARR.Core/Infrastructure/Mapping/RobotTaskRobotStatus/RobotConfigurationResultMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/RobotTaskRobotStatus/RobotConfigurationResultMappingProfile.cs new file mode 100644 index 00000000..6b7bae01 --- /dev/null +++ b/PARR.Core/Infrastructure/Mapping/RobotTaskRobotStatus/RobotConfigurationResultMappingProfile.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using PARR.Domain.DTOs.RobotTaskRobotStatus; +using PARR.Domain.Entities; + +namespace PARR.Core.Infrastructure.Mapping.RobotTaskRobotStatus +{ + internal class RobotConfigurationResultMappingProfile : Profile + { + public RobotConfigurationResultMappingProfile() + { + CreateMap() + .ForMember(d => d.Robot, o => o.MapFrom(s => s.Robot)) + .ForMember(d => d.TaskStatus, o => o.MapFrom(s => s.TaskStatus)) + .ForMember(d => d.RobotStatus, o => o.MapFrom(s => s.RobotStatus)); + } + } +} diff --git a/PARR.Core/Infrastructure/Mapping/Shared/RobotResultMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/Shared/RobotResultMappingProfile.cs new file mode 100644 index 00000000..1cfc6b51 --- /dev/null +++ b/PARR.Core/Infrastructure/Mapping/Shared/RobotResultMappingProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using PARR.Domain.DTOs.Shared; +using PARR.Domain.Entities.RobotEntities; + +namespace PARR.Core.Infrastructure.Mapping.Shared +{ + public class RobotResultMappingProfile : Profile + { + public RobotResultMappingProfile() + { + CreateMap(); + } + } +} diff --git a/PARR.Core/Infrastructure/Mapping/Shared/RobotStatusResultMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/Shared/RobotStatusResultMappingProfile.cs new file mode 100644 index 00000000..89fb4d26 --- /dev/null +++ b/PARR.Core/Infrastructure/Mapping/Shared/RobotStatusResultMappingProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using PARR.Domain.DTOs.Shared; +using PARR.Domain.Entities.RobotEntities; + +namespace PARR.Core.Infrastructure.Mapping.Shared +{ + public class RobotStatusResultMappingProfile : Profile + { + public RobotStatusResultMappingProfile() + { + CreateMap(); + } + } +} diff --git a/PARR.Core/Infrastructure/Mapping/Shared/RobotTaskStatusResultMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/Shared/RobotTaskStatusResultMappingProfile.cs new file mode 100644 index 00000000..404a7f1b --- /dev/null +++ b/PARR.Core/Infrastructure/Mapping/Shared/RobotTaskStatusResultMappingProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using PARR.Domain.DTOs.Shared; + +namespace PARR.Core.Infrastructure.Mapping.Shared +{ + public class RobotTaskStatusResultMappingProfile : Profile + { + public RobotTaskStatusResultMappingProfile() + { + CreateMap(); + } + } +} diff --git a/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs b/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs index 5bc2d65a..0160ee45 100644 --- a/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs +++ b/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs @@ -49,5 +49,11 @@ namespace PARR.Core.Repositories.Interfaces /// /// Task SetInProgressStatusAsync(Guid id); + + /// + /// Установить статус робота - Ошибка, и поставить максимальное значение попыток + /// + /// + void SetErrorRobotStatusAndMaxAttempts(RobotConfiguration configuration); } } diff --git a/PARR.Core/Repositories/Interfaces/TemplateRepositories/ITemplateRenamePendingRepository.cs b/PARR.Core/Repositories/Interfaces/TemplateRepositories/ITemplateRenamePendingRepository.cs new file mode 100644 index 00000000..9df16628 --- /dev/null +++ b/PARR.Core/Repositories/Interfaces/TemplateRepositories/ITemplateRenamePendingRepository.cs @@ -0,0 +1,11 @@ +using PARR.Domain.Entities.TemplateEntities; + +namespace PARR.Core.Repositories.Interfaces.TemplateRepositories +{ + public interface ITemplateRenamePendingRepository + { + Task CreateAsync(TemplateRenamePending obj); + IQueryable Get(); + void Remove(TemplateRenamePending obj); + } +} diff --git a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs index a10536b5..0ff18d5b 100644 --- a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs +++ b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs @@ -3,8 +3,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.BLL.Helpers; using PARR.Core.Repositories.Interfaces; +using PARR.Core.Repositories.Interfaces.TemplateRepositories; using PARR.Core.Services.NextRunServices; using PARR.Core.Services.RobotTask.Interfaces; +using PARR.Core.Services.RobotTask.Models; using PARR.Core.Services.Shortcodes; using PARR.Domain.DTOs.RobotTask; using PARR.Domain.Entities; @@ -20,16 +22,18 @@ namespace PARR.Core.Services.RobotTask.Implementations { /// /// Количество заданий которые рассматриваем для взятия в работу. + /// Рекомендованное значение, кол-во роботов * 3 /// - private readonly int TakeTasks = 10; + private readonly int TakeTasks = 15 * 3; - private readonly ILogger logger; - private readonly IRobotConfigurationRepository robotConfigurationRepository; - private readonly SettingsFromDb settingsFromDb; - private readonly IRobotHistoryRepository robotHistoryRepository; - private readonly IMapper mapper; - private readonly IShortcodesService shortcodesService; - private readonly INextRunService nextRunService; + private readonly ILogger _logger; + private readonly IRobotConfigurationRepository _robotConfigurationRepository; + private readonly SettingsFromDb _settingsFromDb; + private readonly IRobotHistoryRepository _robotHistoryRepository; + private readonly IMapper _mapper; + private readonly IShortcodesService _shortcodesService; + private readonly INextRunService _nextRunService; + private readonly ITemplateRenamePendingRepository _templateRenamePendingRepository; public RobotTaskService( ILogger logger, @@ -38,16 +42,18 @@ namespace PARR.Core.Services.RobotTask.Implementations IRobotHistoryRepository robotHistoryRepository, IMapper mapper, IShortcodesService shortcodesService, - INextRunService nextRunService + INextRunService nextRunService, + ITemplateRenamePendingRepository templateRenamePendingRepository ) { - this.logger = logger; - this.robotConfigurationRepository = robotConfigurationRepository; - this.settingsFromDb = settingsFromDb; - this.robotHistoryRepository = robotHistoryRepository; - this.mapper = mapper; - this.shortcodesService = shortcodesService; - this.nextRunService = nextRunService; + _logger = logger; + _robotConfigurationRepository = robotConfigurationRepository; + _settingsFromDb = settingsFromDb; + _robotHistoryRepository = robotHistoryRepository; + _mapper = mapper; + _shortcodesService = shortcodesService; + _nextRunService = nextRunService; + _templateRenamePendingRepository = templateRenamePendingRepository; } @@ -55,19 +61,19 @@ namespace PARR.Core.Services.RobotTask.Implementations { var templateTask = await GetTaskAsync(RobotsEnum.TemplateOrder, taskStatusCode, acquireTask, robotIp, robotId, TimeSpan.Zero); - var task = mapper.Map(templateTask); + var task = _mapper.Map(templateTask); - task = task with { FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(task.FullDescription, templateTask.Template!)) }; - task = task with { ShortDescription = await shortcodesService.ApplyShortcodesAsync(task.ShortDescription, templateTask.Template!) }; - task = task with { Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(task.Solution, templateTask.Template!)) }; - task = task with { TnkName = await shortcodesService.ApplyShortcodesAsync(task.TnkName, templateTask.Template!) }; - task = task with { WorkName = await shortcodesService.ApplyShortcodesAsync(task.WorkName, templateTask.Template!) }; - task = task with { WorkGroup = await shortcodesService.ApplyShortcodesAsync(task.WorkGroup, templateTask.Template!) }; - task = task with { ResponseArea = await shortcodesService.ApplyShortcodesAsync(task.ResponseArea, templateTask.Template!) }; + task = task with { FullDescription = NormalizeLineEndingsToCrlf(await _shortcodesService.ApplyShortcodesAsync(task.FullDescription, templateTask.Template!)) }; + task = task with { ShortDescription = await _shortcodesService.ApplyShortcodesAsync(task.ShortDescription, templateTask.Template!) }; + task = task with { Solution = NormalizeLineEndingsToCrlf(await _shortcodesService.ApplyShortcodesAsync(task.Solution, templateTask.Template!)) }; + task = task with { TnkName = await _shortcodesService.ApplyShortcodesAsync(task.TnkName, templateTask.Template!) }; + task = task with { WorkName = await _shortcodesService.ApplyShortcodesAsync(task.WorkName, templateTask.Template!) }; + task = task with { WorkGroup = await _shortcodesService.ApplyShortcodesAsync(task.WorkGroup, templateTask.Template!) }; + task = task with { ResponseArea = await _shortcodesService.ApplyShortcodesAsync(task.ResponseArea, templateTask.Template!) }; - task = task with { ClosingCode = settingsFromDb.ClosingCode }; - task = task with { Initiator = settingsFromDb.Initiator }; - task = task with { Category = settingsFromDb.Category }; + task = task with { ClosingCode = _settingsFromDb.ClosingCode }; + task = task with { Initiator = _settingsFromDb.Initiator }; + task = task with { Category = _settingsFromDb.Category }; return task; } @@ -82,22 +88,22 @@ namespace PARR.Core.Services.RobotTask.Implementations var resultUpdateNextRun = await UpdateNextRunAsync(scheduleTask, historyInitiator); if (!resultUpdateNextRun) { - logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", scheduleTask.TemplateId); + _logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", scheduleTask.TemplateId); throw new NextRunException($"Ошибка при расчете NextRun для templateId: {scheduleTask.TemplateId}"); } - var task = mapper.Map(scheduleTask); + var task = _mapper.Map(scheduleTask); - task = task with { Timezone = settingsFromDb.EsppScheduleTimezone }; - task = task with { WorkGroup = await shortcodesService.ApplyShortcodesAsync(task.WorkGroup, scheduleTask.Template!) }; - task = task with { ResponseArea = await shortcodesService.ApplyShortcodesAsync(task.ResponseArea, scheduleTask.Template!) }; + task = task with { Timezone = _settingsFromDb.EsppScheduleTimezone }; + task = task with { WorkGroup = await _shortcodesService.ApplyShortcodesAsync(task.WorkGroup, scheduleTask.Template!) }; + task = task with { ResponseArea = await _shortcodesService.ApplyShortcodesAsync(task.ResponseArea, scheduleTask.Template!) }; //nextRun в часовой зоне УЗ Робота ЕСПП - var nextRunWithRobotTz = scheduleTask.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset()); + var nextRunWithRobotTz = scheduleTask.Template!.NextRun.Add(_nextRunService.GetEsppAccountOffset()); //на всякий случай еще раз проверяем, что дата не устарела и отправляем задание if (nextRunWithRobotTz < DateTimeOffset.UtcNow) { - logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}", + _logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}", task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow); throw new NextRunException($"Ошибка при расчете NextRun для templateId: {scheduleTask.TemplateId}"); } @@ -105,7 +111,7 @@ namespace PARR.Core.Services.RobotTask.Implementations task = task with { NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz) }; task = task with { GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz) }; - task = task with { RepeatRange = settingsFromDb.ScheduleRepeatRange }; + task = task with { RepeatRange = _settingsFromDb.ScheduleRepeatRange }; task = task with { }; return task; @@ -125,7 +131,7 @@ namespace PARR.Core.Services.RobotTask.Implementations private async Task GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, TimeSpan scheduleCooldownDuration) { // 1. Ищем все задания с превышенным кол-вом попыток и просроченным временем, ставим им статус ошибки - await robotConfigurationRepository.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime); + await _robotConfigurationRepository.MarkExpiredTasksAsFailedAsync(_settingsFromDb.RobotAttemptsNumber, _settingsFromDb.RobotWaitTime); // 2. Ищем доступные задания @@ -148,7 +154,7 @@ namespace PARR.Core.Services.RobotTask.Implementations { // Берем первую задачу из списка доступных acquiredTaskId = availableTasks.First(); - logger.LogDebug("Задача не требует захвата, взята первая из доступных: {TaskId}", acquiredTaskId); + _logger.LogDebug("Задача не требует захвата, взята первая из доступных: {TaskId}", acquiredTaskId); } @@ -168,20 +174,14 @@ namespace PARR.Core.Services.RobotTask.Implementations /// private async Task> GetAvailableTasksAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, TimeSpan scheduleCooldownDuration) { - var query = robotConfigurationRepository.Get() + var query = _robotConfigurationRepository.Get() .AsNoTracking() - .Where(t => t.RobotCode == (int)robotCode/* && t.TaskStatusCode == (int)taskStatusCode*/); + .Where(t => t.RobotCode == (int)robotCode); // Если это задание для робота расписаний if (robotCode == RobotsEnum.ScheduleOrder) { // Выбираем только записи с созданными шаблонами (у которых статус 30), а только потом ищем у них расписания - #region Старый не оптимизированный запрос - //var createdTemplates = robotConfigurationRepository.Get() - // .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && t.TaskStatusCode == (int)TaskStatusEnum.Ok) - // .Select(t => t.TemplateId); - //query = query.Where(t => createdTemplates.Contains(t.TemplateId)); - #endregion query = query.Where(t => t.Template!.RobotConfigurations.Any(x => x.RobotCode == (int)RobotsEnum.TemplateOrder && x.TaskStatusCode == (int)TaskStatusEnum.Ok)); @@ -200,20 +200,22 @@ namespace PARR.Core.Services.RobotTask.Implementations // Сортируем по nextRun, чтобы те, у кого nextRun ближе к текущей, выполнились скорее query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template!.IsActiveTemplate); - // Кандидаты заданий - var tasks = new List(); + // Кандидаты заданий, Id задания и имя шаблона + //var tasks = new List(); + var tasks = new List(); - // Ещем первые 10 заданий в статусе ОЖИДАНИЕ + // Ищем первые TakeTasks заданий в статусе ОЖИДАНИЕ tasks = await query .Where(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait && t.TaskStatusCode == (int)taskStatusCode ).Take(TakeTasks) - .Select(t => t.Id) + //.Select(t => t.Id) + .Select(t => new RobotTaskDetails(t.Id, t.Template!.Name, t.Template.NextRun)) .ToListAsync(); - logger.LogDebug("Найдено заданий в статусе 'Ожидание' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString()); + _logger.LogDebug("Найдено заданий в статусе 'Ожидание' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString()); if (tasks.Count == 0) { @@ -222,20 +224,209 @@ namespace PARR.Core.Services.RobotTask.Implementations // Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`) // и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись. - var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime); + var endDate = DateTimeOffset.UtcNow.Add(-_settingsFromDb.RobotWaitTime); tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress - && t.TaskStatusCode==(int)taskStatusCode - && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber + && t.TaskStatusCode == (int)taskStatusCode + && t.AttemptsNumber < _settingsFromDb.RobotAttemptsNumber && t.LastRobotStatusUpdated < endDate) .Take(TakeTasks) - .Select(t => t.Id) + //.Select(t => t.Id) + .Select(t => new RobotTaskDetails(t.Id, t.Template!.Name, t.Template.NextRun)) .ToListAsync(); - logger.LogDebug("Найдено заданий в статусе 'В работе' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString()); + _logger.LogDebug("Найдено заданий в статусе 'В работе' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString()); } - return tasks; + if (robotCode == RobotsEnum.TemplateOrder) + { + // Если запрашиваем шаблоны, смотрим корректируем список заданий в зависимости от статуса переименования. + // Это не относится к расписаниям, потому что у переименованных расписаний статус Updating, а оно не возьмется в работу, пока не обновится шаблон + tasks = await ReplaceTemplateTasksForRenameAsync(tasks, robotCode); + } + + return tasks.Select(t => t.TaskId).ToList(); + } + + + /// + /// Проверяет наличие шаблонов в процессе переименования и заменяет обычные задания на задания по переименованию. + /// Если связанный шаблон не переименован, и у него статус ошибки, целевому шаблону устанавливается статус ошибки. + /// + /// + /// + private async Task> ReplaceTemplateTasksForRenameAsync(List tasks, RobotsEnum robotCode) + { + if (tasks.Count == 0 || robotCode != RobotsEnum.TemplateOrder) + return tasks; + + + // Ищем есть ли связанные шаблоны с таким имененм на переименование + var taskTemplateNames = tasks.Select(t => t.TemplateName).Distinct().ToList(); + // Ищем записи в таблице переименований, где OldName совпадает с именами наших новых задач + var templatesToRename = await _templateRenamePendingRepository.Get().AsNoTracking() + .Where(t => taskTemplateNames.Contains(t.OldName)) + .ToListAsync(); + + _logger.LogDebug("Найдено шаблонов в процессе переименования для текущих задач: {Count} шт.", templatesToRename.Count); + + if (templatesToRename.Count == 0) + return tasks; + + // Ищем конфигурации роботов для СТАРЫХ шаблонов (которые переименовываются) по ИД, смотрим, можем ли взять их в работу + var renameTemplateIds = templatesToRename.Select(t => t.TemplateId).ToList(); + var renameTasks = await _robotConfigurationRepository.Get() + .AsNoTracking() + .Include(t => t.Template) + .Where(t => + t.RobotCode == (int)robotCode + && renameTemplateIds.Contains(t.TemplateId) + // Это может быть только обновление. Так как переименования для создаваемого шаблона быть не может + && t.TaskStatusCode == (int)TaskStatusEnum.Updating + ).ToListAsync(); + + // --- Блок обработки ошибок --- + + // Если старый шаблон в ошибке и лимит попыток исчерпан, ставим ошибку и новому шаблону + var errorTasks = renameTasks + .Where(t => + t.RobotStatusCode == (int)RobotStatusEnum.Error + && t.AttemptsNumber >= _settingsFromDb.RobotAttemptsNumber + ).ToList(); + + var tasksToSetErrorStatus = new List(); + if (errorTasks.Count > 0) + { + _logger.LogDebug("Найдено старых заданий на переименование с ошибками: {ErrorCount}. Ставим ошибку целевым (новым) заданиям.", errorTasks.Count); + + var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet(); + // Берем целевые таски, находим в них задания которым надо поставить ошибку + tasksToSetErrorStatus = tasks + .Where(t => errorTemplateNames.Contains(t.TemplateName)) + .Select(t => t.TaskId) + .ToList(); + + if (tasksToSetErrorStatus.Count > 0) + { + // Устанавливаем ошибку целевым + пишем комментарий от робота + нажимаем комит + var logMessage = "[RobotTaskService] Установлен статус ошибки, так как не переименован связанный шаблон"; + await SetErrorStatusAsync(tasksToSetErrorStatus, logMessage); + } + } + + // --- Блок подмены задач --- + + var endDate = DateTimeOffset.UtcNow.Add(-_settingsFromDb.RobotWaitTime); + + // Фильтруем старые задачи, которые МОЖНО взять в работу. Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены) + var allowedRenameTasks = renameTasks.Where(t => + t.RobotStatusCode == (int)RobotStatusEnum.Wait + || (t.RobotStatusCode == (int)RobotStatusEnum.InProgress + && t.AttemptsNumber < _settingsFromDb.RobotAttemptsNumber + && t.LastRobotStatusUpdated < endDate) + ).ToList(); + + // Словарь для поиска подменной задачи по имени шаблона. + // GroupBy + First на случай, если в бд есть дубликаты, но такого быть не может + var renameTasksToDictionary = allowedRenameTasks + .GroupBy(t => t.Template!.Name) + .ToDictionary( + t => t.Key, + t => new RobotTaskDetails(t.First().Id, t.First().Template!.Name, t.First().Template!.NextRun) + ); + + var errorTaskIdsSet = tasksToSetErrorStatus.ToHashSet(); + + // Создаем итоговый список + var finalTasks = new List(tasks.Count); + int replacedCount = 0; + int errorCount = errorTaskIdsSet.Count; + + // Проходим по ИСХОДНОМУ списку, чтобы сохранить его порядок сортировки + foreach (var task in tasks) + { + // Если задаче нужно поставить ошибку, просто пропускаем ее (она не попадет в итоговый список) + if (errorTaskIdsSet.Contains(task.TaskId)) + { + continue; + } + + // Если для этого имени шаблона есть разрешенная задача на переименование - вставляем ее на место текущей + if (renameTasksToDictionary.TryGetValue(task.TemplateName, out var renameTask)) + { + finalTasks.Add(renameTask); + replacedCount++; + } + else + { + // Иначе оставляем исходную задачу на месте + finalTasks.Add(task); + } + } + + _logger.LogInformation( + "Трансформация пула задач (Rename). Исходных: {OriginalCount}. Отклонено (Error): {ErrorCount}. " + + "Заменено на старые: {ReplacedCount}. Итого к выдаче: {FinalCount}", + tasks.Count, errorCount, replacedCount, finalTasks.Count + ); + + // Возвращаем без дополнительной сортировки по NextRun. Порядок сохранен начального списка + return finalTasks; + } + + + /// + /// Установить статус задания - ошибка + /// + /// + /// + private async Task SetErrorStatusAsync(List taskIds, string logMessage) + { + if (taskIds == null || taskIds.Count == 0) + return; + + var tasks = await _robotConfigurationRepository.Get() + .Include(t => t.Template) + .Where(t => taskIds.Contains(t.Id)) + .ToListAsync(); + + if (tasks.Count == 0) + return; + + foreach (var task in tasks) + { + // Так как это целевой шаблон, то ставим ему сразу максимальное кол-во попыток и ошибку, чтоб больше он не выдавался в заданиях, пока не исправим связанный + // Устанавливаем статус ошибки + _robotConfigurationRepository.SetErrorRobotStatusAndMaxAttempts(task); + + // Пишем в лог роботу + var history = new RobotHistory + { + Id = Guid.NewGuid(), + HistoryLevel = (int)RobotStatusEnum.Error, + TaskStatusCode = task.TaskStatusCode, + RobotConfigurationId = task.Id, + RobotIp = null, + RobotId = ParrComponentsEnum.Api.ToString(), + RobotMessage = logMessage + }; + + await _robotHistoryRepository.CreateAsync(history); + + _logger.LogInformation("Для целевого задания {TaskId} (шаблон '{TemplateName}') установлен статус ошибки, " + + "так как связанное задание со старым шаблоном не было успешно выполнено.", + task.Id, task.Template!.Name); + } + + if (await _robotHistoryRepository.CommitAsync()) + { + _logger.LogDebug("Установлен статус 'Ошибка', для заданий {TaskCount} шт.", tasks.Count); + } + else + { + _logger.LogError("Ошибка при установке статуса задания 'Ошибка', для заданий {TaskCount} шт. Транзакция отменена", tasks.Count); + throw new DbErrorException("Не удалось сохранить изменения статусов заданий при обработке переименования шаблона."); + } } @@ -248,12 +439,12 @@ namespace PARR.Core.Services.RobotTask.Implementations { foreach (var taskId in tasks) { - var isChangedStatus = await robotConfigurationRepository.SetInProgressStatusAsync(taskId); + var isChangedStatus = await _robotConfigurationRepository.SetInProgressStatusAsync(taskId); if (isChangedStatus) { - logger.LogDebug("Захвачена задача {TaskId}", taskId); + _logger.LogDebug("Захвачена задача {TaskId}", taskId); - var task = await robotConfigurationRepository.Get() + var task = await _robotConfigurationRepository.Get() .AsNoTracking() .FirstAsync(t => t.Id == taskId); @@ -268,18 +459,18 @@ namespace PARR.Core.Services.RobotTask.Implementations RobotId = robotId }; - if (!await robotHistoryRepository.CreateAsync(history) || !await robotHistoryRepository.CommitAsync()) + if (!await _robotHistoryRepository.CreateAsync(history) || !await _robotHistoryRepository.CommitAsync()) throw new DbErrorException("Ошибка при добавлении истории робота, при взятии задания в работу."); return taskId; } else { - logger.LogDebug("Не удалось захватить задачу {TaskId}", taskId); + _logger.LogDebug("Не удалось захватить задачу {TaskId}", taskId); } } - logger.LogDebug("Не удалось захватить ни одну из доступных задач для робота"); + _logger.LogDebug("Не удалось захватить ни одну из доступных задач для робота"); return null; } @@ -293,7 +484,7 @@ namespace PARR.Core.Services.RobotTask.Implementations /// private async Task GetTaskWithAllDataAsync(Guid taskId, RobotsEnum robotCode) { - IQueryable query = robotConfigurationRepository.Get() + IQueryable query = _robotConfigurationRepository.Get() //.AsNoTracking() // нужно обязательно трекать, так как может измениться nextRun и его нужно будет сохранить .AsSingleQuery() // Общие инклуды для шаблонов и расписаний @@ -372,23 +563,23 @@ namespace PARR.Core.Services.RobotTask.Implementations var template = task.Template!; //var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate); - var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false); + var nextRun = await _nextRunService.GetNextRunForTemplateAsync(template.Id, false); if (!nextRun.HasValue) { - logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id); + _logger.LogError("При обновлении nextRun для шаблона {TemplateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id); return false; } if (nextRun.Value < DateTimeOffset.UtcNow) { - logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun + /// Модель задания для робота + /// + /// + /// + internal record RobotTaskDetails(Guid TaskId, string TemplateName, DateTimeOffset NextRun); +} diff --git a/PARR.Core/Services/RobotTaskRobotStatus/Implemetations/RobotTaskRobotStatusService.cs b/PARR.Core/Services/RobotTaskRobotStatus/Implemetations/RobotTaskRobotStatusService.cs new file mode 100644 index 00000000..2e7ac095 --- /dev/null +++ b/PARR.Core/Services/RobotTaskRobotStatus/Implemetations/RobotTaskRobotStatusService.cs @@ -0,0 +1,98 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Repositories.Interfaces; +using PARR.Core.Repositories.Interfaces.TemplateRepositories; +using PARR.Core.Services.RobotTaskRobotStatus.Interfaces; +using PARR.Domain.DTOs.RobotTaskRobotStatus; +using PARR.Domain.Entities.RobotEntities; +using PARR.Domain.Enums; +using PARR.Domain.Exceptions; + +namespace PARR.Core.Services.RobotTaskRobotStatus.Implemetations +{ + internal class RobotTaskRobotStatusService : IRobotTaskRobotStatusService + { + private readonly ILogger _logger; + private readonly IRobotConfigurationRepository _robotConfigurationRepository; + private readonly IRobotHistoryRepository _robotHistoryRepository; + private readonly ITemplateRenamePendingRepository _templateRenamePendingRepository; + private readonly IMapper _mapper; + + public RobotTaskRobotStatusService( + ILogger logger, + IRobotConfigurationRepository robotConfigurationRepository, + IRobotHistoryRepository robotHistoryRepository, + ITemplateRenamePendingRepository templateRenamePendingRepository, + IMapper mapper + ) + { + _logger = logger; + _robotConfigurationRepository = robotConfigurationRepository; + _robotHistoryRepository = robotHistoryRepository; + _templateRenamePendingRepository = templateRenamePendingRepository; + _mapper = mapper; + } + + + public async Task ChangeStatusAsync(ChangeRobotStatus request) + { + var config = await _robotConfigurationRepository.Get() + .FirstOrDefaultAsync(t => t.Id == request.TaskId); + + if (config == null) + throw new NotFoundException($"Не найдено задание с id: {request.TaskId}"); + + // изменение статуса робота + _robotConfigurationRepository.ChangeRobotStatus(request.RobotStatusCode, config); + + // если успех, изменяем статус задания на успех + if (request.RobotStatusCode == RobotStatusEnum.Complete) + { + _robotConfigurationRepository.ChangeTaskStatus(TaskStatusEnum.Ok, config); + // Тут нужно посмотреть, если этот шаблон был на переименование, удалить у него старое название, так как он успешно переименовался + if (config.RobotCode == (int)RobotsEnum.TemplateOrder) + { + var renaming = await _templateRenamePendingRepository.Get().FirstOrDefaultAsync(t => t.TemplateId == config.TemplateId); + if (renaming != null) + { + // Удаляем + _templateRenamePendingRepository.Remove(renaming); + _logger.LogInformation("Шаблон {TemplateId} успешно переименован. Запись TemplateRenamePending удалена.", config.TemplateId); + } + } + } + + if (!await _robotConfigurationRepository.CommitAsync()) + throw new DbErrorException("Ошибка при сохранении в БД"); + + + //записываем в лог робота + if (request.RobotStatusCode == RobotStatusEnum.InProgress || request.RobotStatusCode == RobotStatusEnum.Complete) + { + var historyLevel = request.RobotStatusCode == RobotStatusEnum.InProgress ? RobotHistoryLevelEnum.Start : RobotHistoryLevelEnum.Complete; + + var history = new RobotHistory + { + Id = Guid.NewGuid(), + HistoryLevel = (int)historyLevel, + TaskStatusCode = config.TaskStatusCode, + RobotConfigurationId = config.Id, + RobotIp = request.RobotIp, + RobotId = request.RobotId + }; + await _robotHistoryRepository.CreateAsync(history); + await _robotHistoryRepository.CommitAsync(); + } + + var configToResponse = await _robotConfigurationRepository.Get() + .AsNoTracking() + .Include(t => t.Robot) + .Include(t => t.TaskStatus) + .Include(t => t.RobotStatus) + .FirstOrDefaultAsync(t => t.Id == request.TaskId); + + return _mapper.Map(configToResponse); + } + } +} diff --git a/PARR.Core/Services/RobotTaskRobotStatus/Interfaces/IRobotTaskRobotStatusService.cs b/PARR.Core/Services/RobotTaskRobotStatus/Interfaces/IRobotTaskRobotStatusService.cs new file mode 100644 index 00000000..d6a77067 --- /dev/null +++ b/PARR.Core/Services/RobotTaskRobotStatus/Interfaces/IRobotTaskRobotStatusService.cs @@ -0,0 +1,17 @@ +using PARR.Domain.DTOs.RobotTaskRobotStatus; + +namespace PARR.Core.Services.RobotTaskRobotStatus.Interfaces +{ + /// + /// Сервис по изменению статуса выполнения задания роботами + /// + public interface IRobotTaskRobotStatusService + { + /// + /// Изменить статус выполнения задания роботом по ИД задания + /// + /// + /// + Task ChangeStatusAsync(ChangeRobotStatus request); + } +} diff --git a/PARR.DAL/DependencyInjection.cs b/PARR.DAL/DependencyInjection.cs index 8a41b0f7..c2d342b9 100644 --- a/PARR.DAL/DependencyInjection.cs +++ b/PARR.DAL/DependencyInjection.cs @@ -8,6 +8,7 @@ using PARR.Core.Repositories.Interfaces.JobRepositories; using PARR.Core.Repositories.Interfaces.RobotRepositories; using PARR.Core.Repositories.Interfaces.Schedule; using PARR.Core.Repositories.Interfaces.TaskRepositories; +using PARR.Core.Repositories.Interfaces.TemplateRepositories; using PARR.Core.Repositories.Interfaces.Unit; using PARR.DAL.Configurations.DbSettings; using PARR.DAL.Context; @@ -18,6 +19,7 @@ using PARR.DAL.Repositories.JobRepositories; using PARR.DAL.Repositories.RobotRepositories; using PARR.DAL.Repositories.Schedule; using PARR.DAL.Repositories.TaskRepositories; +using PARR.DAL.Repositories.TemplateRepositories; using PARR.DAL.Repositories.Unit; using PARR.Domain.Settings; @@ -142,6 +144,12 @@ namespace PARR.DAL #endregion + #region Templates + + services.AddScoped(); + + #endregion + //services.AddTransient(); #region NextRun Services diff --git a/PARR.DAL/Repositories/Base/BaseRepository.cs b/PARR.DAL/Repositories/Base/BaseRepository.cs index 7eedce14..229b1c23 100644 --- a/PARR.DAL/Repositories/Base/BaseRepository.cs +++ b/PARR.DAL/Repositories/Base/BaseRepository.cs @@ -15,23 +15,13 @@ namespace PARR.DAL.Repositories.Base { internal abstract class BaseRepository : IBaseRepository where T : class, IBaseEntity { - //private readonly ILogger> logger; - - //protected abstract DbSet EntitySet { get; } - //protected abstract DataContext EntitiContext { get; } - - //public BaseRepository(ILogger> logger) - //{ - // this.logger = logger; - //} - - protected readonly ILogger logger; + protected readonly ILogger _logger; protected readonly DbSet EntitySet; protected readonly DataContext EntityContext; protected BaseRepository(ILogger logger, DataContext dataContext) { - this.logger = logger; + this._logger = logger; this.EntityContext = dataContext; this.EntitySet = dataContext.Set(); } @@ -39,7 +29,7 @@ namespace PARR.DAL.Repositories.Base public virtual async Task AddRangeAsync(List objs) { - logger.LogDebug("Начинаю добавление диапазона объектов типа {EntityType}, количество: {Count}", + _logger.LogDebug("Начинаю добавление диапазона объектов типа {EntityType}, количество: {Count}", typeof(T).Name, objs.Count); objs.ForEach(item => item.DateCreated = DateTimeOffset.UtcNow); @@ -47,26 +37,26 @@ namespace PARR.DAL.Repositories.Base try { await EntitySet.AddRangeAsync(objs); - logger.LogDebug("Успешно добавлено {Count} объектов типа {EntityType}", + _logger.LogDebug("Успешно добавлено {Count} объектов типа {EntityType}", objs.Count, typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении диапазона объектов типа {EntityType}", typeof(T).Name); + _logger.LogError(ex, "Ошибка при добавлении диапазона объектов типа {EntityType}", typeof(T).Name); return false; } } public async Task CommitAsync(IHistoryInitiator? initiator = null) { - logger.LogDebug("Начинаю сохранение изменений в БД для объектов типа {EntityType}", typeof(T).Name); + _logger.LogDebug("Начинаю сохранение изменений в БД для объектов типа {EntityType}", typeof(T).Name); #region Изменения var modifiedEntrities = EntityContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified/* || t.State == EntityState.Deleted*/); - logger.LogDebug("Найдено {Count} измененных сущностей для обработки истории", modifiedEntrities.Count()); + _logger.LogDebug("Найдено {Count} измененных сущностей для обработки истории", modifiedEntrities.Count()); foreach (var obj in modifiedEntrities) { @@ -83,13 +73,13 @@ namespace PARR.DAL.Repositories.Base try { var changedCount = await EntityContext.SaveChangesAsync(); - logger.LogDebug("Успешно сохранено {ChangedCount} изменений в БД для объектов типа {EntityType}", + _logger.LogDebug("Успешно сохранено {ChangedCount} изменений в БД для объектов типа {EntityType}", changedCount, typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при сохранении изменений в БД для объектов типа {EntityType}", typeof(T).Name); + _logger.LogError(ex, "Ошибка при сохранении изменений в БД для объектов типа {EntityType}", typeof(T).Name); return false; } } @@ -104,14 +94,14 @@ namespace PARR.DAL.Repositories.Base if (initiator == null) return; - logger.LogDebug("Устанавливаю инициатора для изменений"); + _logger.LogDebug("Устанавливаю инициатора для изменений"); // Задаем инициатора только для новых и измененных записей var entrities = EntityContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified || t.State == EntityState.Added); var entityCount = entrities.Count(); - logger.LogDebug("Найдено {Count} сущностей для установки инициатора", entityCount); + _logger.LogDebug("Найдено {Count} сущностей для установки инициатора", entityCount); // смотрим есть ли у объекта интерфейс IHistoryInitiator, если есть, задаём значения foreach (var obj in entrities) @@ -123,7 +113,7 @@ namespace PARR.DAL.Repositories.Base (obj.Entity as IHistoryInitiator)!.InitiatorParrComponentId = initiator?.InitiatorParrComponentId ?? null; (obj.Entity as IHistoryInitiator)!.InitiatorComment = initiator?.InitiatorComment ?? null; - logger.LogDebug("Установлен инициатор для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("Установлен инициатор для сущности типа {EntityType}", obj.Entity.GetType().Name); } } } @@ -150,12 +140,12 @@ namespace PARR.DAL.Repositories.Base if (!isManual) { - logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name); entity.DateModified = DateTimeOffset.UtcNow; } else { - logger.LogDebug("Пропуск обновления DateModified (ManualControl) для {EntityType}", entityType.Name); + _logger.LogDebug("Пропуск обновления DateModified (ManualControl) для {EntityType}", entityType.Name); } } } @@ -166,7 +156,7 @@ namespace PARR.DAL.Repositories.Base /// private void TableHistoryResolver(EntityEntry obj) { - logger.LogDebug("Проверяю необходимость создания истории для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("Проверяю необходимость создания истории для сущности типа {EntityType}", obj.Entity.GetType().Name); var myHistoryInterface = obj.Entity.GetType().GetInterfaces() .Where(t => t.IsGenericType) @@ -176,7 +166,7 @@ namespace PARR.DAL.Repositories.Base // у этого объекта нет интерфейса IMyHistory<>. Не ведем историю if (myHistoryInterface == null) { - logger.LogDebug("Сущность типа {EntityType} не требует ведения истории", obj.Entity.GetType().Name); + _logger.LogDebug("Сущность типа {EntityType} не требует ведения истории", obj.Entity.GetType().Name); return; } @@ -186,13 +176,13 @@ namespace PARR.DAL.Repositories.Base var historyType = myHistoryInterface.GetGenericArguments().First(); var historyProps = historyType.GetProperties(/*BindingFlags.DeclaredOnly | */ /*BindingFlags.Public*/).ToList(); - logger.LogDebug("Создаю историю для сущности типа {EntityType}, тип истории: {HistoryType}", + _logger.LogDebug("Создаю историю для сущности типа {EntityType}, тип истории: {HistoryType}", obj.Entity.GetType().Name, historyType.Name); var historyInstance = Activator.CreateInstance(historyType); if (historyInstance == null) { - logger.LogError("Не смог создать инстанс для ведения истории {HistoryType}", historyType.Name); + _logger.LogError("Не смог создать инстанс для ведения истории {HistoryType}", historyType.Name); return; } @@ -208,11 +198,11 @@ namespace PARR.DAL.Repositories.Base try { EntityContext.Add(historyInstance); - logger.LogDebug("История добавлена для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("История добавлена для сущности типа {EntityType}", obj.Entity.GetType().Name); } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении объекта в историю {HistoryType}", historyType.Name); + _logger.LogError(ex, "Ошибка при добавлении объекта в историю {HistoryType}", historyType.Name); } } @@ -225,7 +215,7 @@ namespace PARR.DAL.Repositories.Base /// private void FillHistoryProps(EntityEntry originalObj, ref object historyInstance, List propsList) { - logger.LogDebug("Заполняю историю для сущности типа {EntityType}", originalObj.Entity.GetType().Name); + _logger.LogDebug("Заполняю историю для сущности типа {EntityType}", originalObj.Entity.GetType().Name); foreach (var prop in propsList) { @@ -250,7 +240,7 @@ namespace PARR.DAL.Repositories.Base histProp.SetValue(historyInstance, origValues); } - logger.LogDebug("Завершено заполнение истории для сущности типа {EntityType}", originalObj.Entity.GetType().Name); + _logger.LogDebug("Завершено заполнение истории для сущности типа {EntityType}", originalObj.Entity.GetType().Name); } @@ -266,14 +256,14 @@ namespace PARR.DAL.Repositories.Base var histProp = instanceObj.GetType().GetProperty(propName); if (histProp == null) { - logger.LogError("При изменении объекта для БД, не найдено свойство {PropertyName}", propName); + _logger.LogError("При изменении объекта для БД, не найдено свойство {PropertyName}", propName); return; } // сравним типы if (histProp.PropertyType != typeof(TValue)) { - logger.LogError("При изменении объекта для БД, не совпадают типы у свойства {PropertyName}, {PropertyType}!={ValueType}", + _logger.LogError("При изменении объекта для БД, не совпадают типы у свойства {PropertyName}, {PropertyType}!={ValueType}", propName, histProp.PropertyType.Name, typeof(TValue).Name); return; } @@ -340,7 +330,7 @@ namespace PARR.DAL.Repositories.Base public virtual async Task CreateAsync(T obj) { - logger.LogDebug("Начинаю создание объекта типа {EntityType}", typeof(T).Name); + _logger.LogDebug("Начинаю создание объекта типа {EntityType}", typeof(T).Name); if (obj.DateCreated == DateTimeOffset.MinValue) obj.DateCreated = DateTimeOffset.UtcNow; @@ -348,73 +338,73 @@ namespace PARR.DAL.Repositories.Base try { await EntitySet.AddAsync(obj); - logger.LogDebug("Объект типа {EntityType} добавлен в контекст", typeof(T).Name); + _logger.LogDebug("Объект типа {EntityType} добавлен в контекст", typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении объекта типа {EntityType} в БД", typeof(T).Name); + _logger.LogError(ex, "Ошибка при добавлении объекта типа {EntityType} в БД", typeof(T).Name); return false; } } public virtual bool Delete(T obj) { - logger.LogDebug("Начинаю удаление объекта типа {EntityType}", obj.GetType().Name); + _logger.LogDebug("Начинаю удаление объекта типа {EntityType}", obj.GetType().Name); try { EntitySet.Remove(obj); - logger.LogDebug("Объект типа {EntityType} удален из контекста", obj.GetType().Name); + _logger.LogDebug("Объект типа {EntityType} удален из контекста", obj.GetType().Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД", obj.GetType().Name); + _logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД", obj.GetType().Name); return false; } } public virtual async Task DeleteAsync(Guid id) { - logger.LogDebug("Начинаю удаление объекта типа {EntityType} по ID: {Id}", typeof(T).Name, id); + _logger.LogDebug("Начинаю удаление объекта типа {EntityType} по ID: {Id}", typeof(T).Name, id); try { var exist = await GetAsync(id); if (exist == null) { - logger.LogError("Ошибка при удалении из БД. Не найдена запись в БД типа {EntityType} с id: {Id}", + _logger.LogError("Ошибка при удалении из БД. Не найдена запись в БД типа {EntityType} с id: {Id}", typeof(T).Name, id); return false; } EntitySet.Remove(exist); - logger.LogDebug("Объект типа {EntityType} с ID {Id} удален из контекста", typeof(T).Name, id); + _logger.LogDebug("Объект типа {EntityType} с ID {Id} удален из контекста", typeof(T).Name, id); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД по ID: {Id}", typeof(T).Name, id); + _logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД по ID: {Id}", typeof(T).Name, id); return false; } } public virtual IQueryable Get() { - logger.LogDebug("Получаю набор объектов типа {EntityType}", typeof(T).Name); + _logger.LogDebug("Получаю набор объектов типа {EntityType}", typeof(T).Name); return EntitySet; } public virtual async Task GetAsync(Guid id) { - logger.LogDebug("Получаю объект типа {EntityType} по ID: {Id}", typeof(T).Name, id); + _logger.LogDebug("Получаю объект типа {EntityType} по ID: {Id}", typeof(T).Name, id); return await EntitySet.FirstOrDefaultAsync(t => t.Id == id); } public virtual IQueryable GetPage(IQueryable query, PaginationFilter paginationFilter) { - logger.LogDebug("Получаю страницу объектов типа {EntityType}, страница: {PageNumber}, размер: {PageSize}", + _logger.LogDebug("Получаю страницу объектов типа {EntityType}, страница: {PageNumber}, размер: {PageSize}", typeof(T).Name, paginationFilter.PageNumber, paginationFilter.PageSize); int skip = (paginationFilter.PageNumber - 1) * paginationFilter.PageSize; diff --git a/PARR.DAL/Repositories/RobotConfigurationRepository.cs b/PARR.DAL/Repositories/RobotConfigurationRepository.cs index efbb22be..b8db45b1 100644 --- a/PARR.DAL/Repositories/RobotConfigurationRepository.cs +++ b/PARR.DAL/Repositories/RobotConfigurationRepository.cs @@ -1,9 +1,11 @@ -using Microsoft.EntityFrameworkCore; +using InfluxDB.Client.Api.Domain; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Core.Repositories.Interfaces; using PARR.DAL.Context; using PARR.DAL.Repositories.Base; using PARR.Domain.Entities; +using PARR.Domain.Entities.RobotEntities; using PARR.Domain.Enums; using PARR.Domain.Settings; @@ -52,7 +54,7 @@ namespace PARR.DAL.Repositories ? ((TaskStatusEnum)taskStatusValue).ToString() : $"Unknown ({taskStatusValue})"; - logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}", + _logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}", updatingStatus, configuration.Id, configuration.TemplateId, taskStatusName); return false; } @@ -63,13 +65,13 @@ namespace PARR.DAL.Repositories // есть ли связь у config с templetes, может инклуда нет, мало ли if (configuration.Template == null) { - logger.LogWarning("При изменении статуса задания на обновление шаблона, не смог проверить наличае ScheduleEsppId, так как нет Include с Templates. Пропустил эту проверку. configurationId: {configurationId}", configuration.Id); + _logger.LogWarning("При изменении статуса задания на обновление шаблона, не смог проверить наличае ScheduleEsppId, так как нет Include с Templates. Пропустил эту проверку. configurationId: {configurationId}", configuration.Id); } else { if (configuration.Template.ScheduleEsppId == null) { - logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как у шаблона отсутсвтует ScheduleEsppId=null", + _logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как у шаблона отсутсвтует ScheduleEsppId=null", updatingStatus, configuration.Id, configuration.TemplateId); return false; } @@ -78,7 +80,7 @@ namespace PARR.DAL.Repositories // Статус ОК, можно ставить Updating ChangeTaskStatus(updatingStatus, configuration); - logger.LogInformation("Установлен статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}", updatingStatus, configuration.Id, configuration.TemplateId); + _logger.LogInformation("Установлен статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}", updatingStatus, configuration.Id, configuration.TemplateId); return true; } @@ -94,8 +96,8 @@ namespace PARR.DAL.Repositories configuration.AttemptsNumber++; configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow; break; - //case RobotStatusEnum.Error: - // break; + case RobotStatusEnum.Error: + break; case RobotStatusEnum.Complete: configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow; break; @@ -108,6 +110,14 @@ namespace PARR.DAL.Repositories } } + public void SetErrorRobotStatusAndMaxAttempts(RobotConfiguration configuration) + { + ChangeRobotStatus(RobotStatusEnum.Error, configuration); + + configuration.AttemptsNumber = settingsFromDb.RobotAttemptsNumber; + configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow; + } + public async Task SetInProgressStatusAsync(Guid id) { @@ -144,7 +154,7 @@ namespace PARR.DAL.Repositories if (config == null) { - logger.LogError($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); + _logger.LogError($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); throw new Exception($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); } @@ -160,25 +170,82 @@ namespace PARR.DAL.Repositories var endDate = DateTimeOffset.UtcNow.Add(-robotWaitTime); - var configObjs = await EntitySet.Where(t => + var expiredConfigs = await EntitySet.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress && t.AttemptsNumber >= robotAttemptsNumber && t.LastRobotStatusUpdated <= endDate ).ToListAsync(); - if (!configObjs.Any()) + if (!expiredConfigs.Any()) return; - configObjs.ForEach(item => + foreach (var item in expiredConfigs) { ChangeRobotStatus(RobotStatusEnum.Error, item); - logger.LogInformation($"Устанавливаю RobotStatus: {RobotStatusEnum.Error} для RobotConfigurationId {item.Id}"); - }); + _logger.LogInformation("Устанавливаю статус RobotStatus: {RobotStatus} для RobotConfigurationId: {RobotConfigurationId}", RobotStatusEnum.Error, item.Id); + } + + #region Ищем, есть ли связанные шаблоны, которые должны переименоваться, им тоже нужно установить статус ошибки, но только для Шаблонов + + // Проактивная обработка связанных шаблонов переименования Old->New + // Если старый шаблон умен, мы должны сразу убить (!!!замочить!!!) и новый (целевой), чтобы он не висел вечно в ожидании. + + var expiredTemplateIds = expiredConfigs + .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder) + .Select(t => t.TemplateId) + .Distinct() + .ToList(); + + if (expiredTemplateIds.Any()) + { + // Находим OldName для этих шаблонов из таблицы переименований. + var oldNamesToFail = await EntityContext.Templates + .Where(t => expiredTemplateIds.Contains(t.Id) && t.TemplateRenamePending != null) + .Select(t => t.TemplateRenamePending!.OldName) + .Distinct() + .ToListAsync(); + + // Находим целевые (новые задачи), имена которых совпадают с найденными OldName + if (oldNamesToFail.Any()) + { + var targetConfigs = await EntitySet + .Where(t => + t.RobotCode == (int)RobotsEnum.TemplateOrder + && t.RobotStatusCode != (int)RobotStatusEnum.Error // Не трогаем те, что уже в ошибке + && oldNamesToFail.Contains(t.Template!.Name) + ).ToListAsync(); + + foreach (var item in targetConfigs) + { + SetErrorRobotStatusAndMaxAttempts(item); + + // Пишем в лог роботу + var history = new RobotHistory + { + Id = Guid.NewGuid(), + HistoryLevel = (int)RobotStatusEnum.Error, + TaskStatusCode = item.TaskStatusCode, + RobotConfigurationId = item.Id, + RobotIp = null, + RobotId = ParrComponentsEnum.Api.ToString(), + RobotMessage = "[RobotConfigurationRepository] Установлен статус ошибки, так как не переименован связанный шаблон" + }; + + // Синхронный Add работает быстрее и безопаснее внутри цикла + EntityContext.RobotHistories.Add(history); + + _logger.LogInformation("Проактивно установлен статус {Status} для целевого задания RobotConfigurationID: {Id} из-за ошибки старого шаблона.", RobotStatusEnum.Error, item.Id); + } + } + } + #endregion var result = await CommitAsync(); if (!result) - logger.LogError($"Ошибка при сохранении изменений RobotStatus для RobotConfigurationId: item.Id, RobotStatus: {RobotStatusEnum.Error}"); + _logger.LogError("Ошибка при сохранении изменений RobotStatus для просроченных заданий. Откат транзакции."); + //else + // logger.LogInformation("Успешно обработано и переведено в статус Ошибки просроченных заданий: {Count} шт.", configObjs.Count + linksCount); } diff --git a/PARR.DAL/Repositories/TemplateRepositories/TemplateRenamePendingRepository.cs b/PARR.DAL/Repositories/TemplateRepositories/TemplateRenamePendingRepository.cs new file mode 100644 index 00000000..cf931a3d --- /dev/null +++ b/PARR.DAL/Repositories/TemplateRepositories/TemplateRenamePendingRepository.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Logging; +using PARR.Core.Repositories.Interfaces.TemplateRepositories; +using PARR.DAL.Context; +using PARR.Domain.Entities.TemplateEntities; + +namespace PARR.DAL.Repositories.TemplateRepositories +{ + internal class TemplateRenamePendingRepository : ITemplateRenamePendingRepository + { + private readonly DataContext _dataContext; + private readonly ILogger _logger; + + public TemplateRenamePendingRepository( + DataContext dataContext, + ILogger logger + ) + { + _dataContext = dataContext; + _logger = logger; + } + + + public IQueryable Get() + { + return _dataContext.TemplateRenamePendings; + } + + public void Remove(TemplateRenamePending obj) + { + _dataContext.TemplateRenamePendings.Remove(obj); + } + + + public async Task CreateAsync(TemplateRenamePending obj) + { + try + { + await _dataContext.TemplateRenamePendings.AddAsync(obj); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка при добавлении объекта типа TemplateRenamePending в БД"); + return false; + } + } + } +} diff --git a/PARR.DAL/Repositories/TemplateRepository.cs b/PARR.DAL/Repositories/TemplateRepository.cs index ab8e6530..ee87dbd7 100644 --- a/PARR.DAL/Repositories/TemplateRepository.cs +++ b/PARR.DAL/Repositories/TemplateRepository.cs @@ -16,7 +16,7 @@ namespace PARR.DAL.Repositories public async Task GetTemplateByNameAsync(string name) { - logger.LogDebug("Поиск шаблона по имени: {TemplateName}", name); + _logger.LogDebug("Поиск шаблона по имени: {TemplateName}", name); var template = await GetWithIncludes() .Include(t => t.RobotConfigurations) @@ -24,11 +24,11 @@ namespace PARR.DAL.Repositories if (template != null) { - logger.LogDebug("Шаблон найден: {TemplateId}, имя: {TemplateName}", template.Id, template.Name); + _logger.LogDebug("Шаблон найден: {TemplateId}, имя: {TemplateName}", template.Id, template.Name); } else { - logger.LogDebug("Шаблон с именем {TemplateName} не найден", name); + _logger.LogDebug("Шаблон с именем {TemplateName} не найден", name); } return template; @@ -36,7 +36,7 @@ namespace PARR.DAL.Repositories public IQueryable