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; namespace PARR.DAL.Repositories { internal class RobotConfigurationRepository : BaseRepository, IRobotConfigurationRepository { private readonly SettingsFromDb settingsFromDb; public RobotConfigurationRepository(DataContext dataContext, ILogger logger, SettingsFromDb settingsFromDb) : base(logger, dataContext) { this.settingsFromDb = settingsFromDb; } public void ChangeTaskStatus(TaskStatusEnum taskStatus, RobotConfiguration configuration) { configuration.TaskStatusCode = (int)taskStatus; switch (taskStatus) { case TaskStatusEnum.Creating: ChangeRobotStatus(RobotStatusEnum.Wait, configuration); break; case TaskStatusEnum.Updating: ChangeRobotStatus(RobotStatusEnum.Wait, configuration); break; case TaskStatusEnum.Ok: ChangeRobotStatus(RobotStatusEnum.Complete, configuration); break; default: break; } } public bool SetUpdateTaskStatusIfAllow(RobotConfiguration configuration) { var updatingStatus = TaskStatusEnum.Updating; // ставить статус Updating можно только если текущий статус == Ok if (configuration.TaskStatusCode != (int)TaskStatusEnum.Ok) { int taskStatusValue = configuration.TaskStatusCode; string taskStatusName = Enum.IsDefined(typeof(TaskStatusEnum), taskStatusValue) ? ((TaskStatusEnum)taskStatusValue).ToString() : $"Unknown ({taskStatusValue})"; _logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}", updatingStatus, configuration.Id, configuration.TemplateId, taskStatusName); return false; } // Если хотим поставить Updating у шаблона, можно ставить только в том случае, если у шаблона уже есть ScheduleEsppId if (configuration.RobotCode == (int)RobotsEnum.TemplateOrder) { // есть ли связь у config с templetes, может инклуда нет, мало ли if (configuration.Template == null) { _logger.LogWarning("При изменении статуса задания на обновление шаблона, не смог проверить наличае ScheduleEsppId, так как нет Include с Templates. Пропустил эту проверку. configurationId: {configurationId}", configuration.Id); } else { if (configuration.Template.ScheduleEsppId == null) { _logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как у шаблона отсутсвтует ScheduleEsppId=null", updatingStatus, configuration.Id, configuration.TemplateId); return false; } } } // Статус ОК, можно ставить Updating ChangeTaskStatus(updatingStatus, configuration); _logger.LogInformation("Установлен статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}", updatingStatus, configuration.Id, configuration.TemplateId); return true; } public void ChangeRobotStatus(RobotStatusEnum robotStatus, RobotConfiguration configuration) { configuration.RobotStatusCode = (int)robotStatus; switch (robotStatus) { case RobotStatusEnum.InProgress: configuration.AttemptsNumber++; configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow; break; case RobotStatusEnum.Error: break; case RobotStatusEnum.Complete: configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow; break; case RobotStatusEnum.Wait: configuration.LastRobotStatusUpdated = null; configuration.AttemptsNumber = 0; break; default: break; } } public void SetErrorRobotStatusAndMaxAttempts(RobotConfiguration configuration) { ChangeRobotStatus(RobotStatusEnum.Error, configuration); configuration.AttemptsNumber = settingsFromDb.RobotAttemptsNumber; configuration.LastRobotStatusUpdated = DateTimeOffset.UtcNow; } public async Task SetInProgressStatusAsync(Guid id) { // Если задание все еще в статусе Wait (RobotStatusEnum.Wait), установить ему статус "InProgress" var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime); // Выполняем атомарный UPDATE напрямую в базе данных var affectedRows = await EntitySet.Where(t => t.Id == id && ( // Задания в статусе ожидание t.RobotStatusCode == (int)RobotStatusEnum.Wait || ( // Задание в статусе в работе, но они зависли t.RobotStatusCode == (int)RobotStatusEnum.InProgress && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber && t.LastRobotStatusUpdated < endDate ) ) ) .ExecuteUpdateAsync(s => s .SetProperty(t => t.RobotStatusCode, (int)RobotStatusEnum.InProgress) .SetProperty(t => t.LastRobotStatusUpdated, DateTimeOffset.UtcNow) .SetProperty(t => t.AttemptsNumber, t => t.AttemptsNumber + 1) ); return affectedRows > 0; } public RobotConfiguration GetFromTemplateByRobotCode(RobotsEnum robotsEnum, Template template) { var config = template.RobotConfigurations.FirstOrDefault(t => t.RobotCode == (int)robotsEnum); if (config == null) { _logger.LogError($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); throw new Exception($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); } return config; } // Поиск невыполненных заданий и установка им статуса ошибки робота public async Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime) { //Ищем `RobotStatusCode` = 22 и `LastStatusUpdated` истекло и `AttemptsNumber` >= допустимого значения из настроек, //ставим всем этим записям `RobotStatusCode`= 33 var endDate = DateTimeOffset.UtcNow.Add(-robotWaitTime); var expiredConfigs = await EntitySet.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress && t.AttemptsNumber >= robotAttemptsNumber && t.LastRobotStatusUpdated <= endDate ).ToListAsync(); if (!expiredConfigs.Any()) return; foreach (var item in expiredConfigs) { ChangeRobotStatus(RobotStatusEnum.Error, item); _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 для просроченных заданий. Откат транзакции."); //else // logger.LogInformation("Успешно обработано и переведено в статус Ошибки просроченных заданий: {Count} шт.", configObjs.Count + linksCount); } } }