feat(api,core,dal): RobotTasks - вынесение логики в Core, оптимизация взятия задания роботом
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
namespace PARR.API.Contracts.V1.Requests.Queries
|
namespace PARR.API.Contracts.V1.Requests.Queries
|
||||||
{
|
{
|
||||||
public class RobotTaskQuery
|
public record RobotTaskQuery
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Установить статус задания - InProgress (Робот взял в работу)
|
/// Установить статус задания - InProgress (Робот взял в работу)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? SetInProgressStatus { get; set; }
|
public bool? SetInProgressStatus { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,336 +1,86 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using PARR.API.Contracts.V1;
|
using PARR.API.Contracts.V1;
|
||||||
using PARR.API.Contracts.V1.Requests.Queries;
|
using PARR.API.Contracts.V1.Requests.Queries;
|
||||||
using PARR.API.Contracts.V1.Responses;
|
|
||||||
using PARR.API.Contracts.V1.Responses.Base;
|
|
||||||
using PARR.API.Controllers.V1.Base;
|
using PARR.API.Controllers.V1.Base;
|
||||||
using PARR.API.Services.Interfaces;
|
using PARR.API.Services.Interfaces;
|
||||||
using PARR.BLL.Helpers;
|
|
||||||
using PARR.Core.Repositories.Interfaces;
|
using PARR.Core.Repositories.Interfaces;
|
||||||
using PARR.Core.Services.NextRunServices;
|
using PARR.Core.Services.NextRunServices;
|
||||||
|
using PARR.Core.Services.RobotTask.Interfaces;
|
||||||
using PARR.Core.Services.Shortcodes;
|
using PARR.Core.Services.Shortcodes;
|
||||||
using PARR.Domain.Common.Roles;
|
using PARR.Domain.Common.Roles;
|
||||||
using PARR.Domain.Entities;
|
|
||||||
using PARR.Domain.Entities.Base.History;
|
|
||||||
using PARR.Domain.Enums;
|
using PARR.Domain.Enums;
|
||||||
using PARR.Domain.Settings;
|
using PARR.Domain.Settings;
|
||||||
|
|
||||||
namespace PARR.API.Controllers.V1
|
namespace PARR.API.Controllers.V1
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Формирование заданий роботам
|
||||||
|
/// </summary>
|
||||||
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||||||
public class RobotTaskController : BaseApiController
|
public class RobotTaskController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IMapper mapper;
|
private readonly IMapper mapper;
|
||||||
private readonly SettingsFromDb settingsFromDb;
|
private readonly SettingsFromDb settingsFromDb;
|
||||||
private readonly IRobotConfigurationRepository robotConfigurationService;
|
private readonly IRobotConfigurationRepository robotConfigurationService;
|
||||||
//private readonly IEsppScheduleTransformService esppScheduleTransformService;
|
private readonly ILogger<RobotTaskOldController> logger;
|
||||||
private readonly ILogger<RobotTaskController> logger;
|
|
||||||
private readonly IClientService clientService;
|
private readonly IClientService clientService;
|
||||||
private readonly IRobotHistoryRepository robotHistoryService;
|
private readonly IRobotHistoryRepository robotHistoryService;
|
||||||
private readonly IShortcodesService shortcodesService;
|
private readonly IShortcodesService shortcodesService;
|
||||||
private readonly INextRunService nextRunService;
|
private readonly INextRunService nextRunService;
|
||||||
//private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
|
private readonly IRobotTaskService robotTaskService;
|
||||||
|
|
||||||
public RobotTaskController(
|
public RobotTaskController(
|
||||||
IMapper mapper,
|
IMapper mapper,
|
||||||
SettingsFromDb settingsFromDb,
|
SettingsFromDb settingsFromDb,
|
||||||
IRobotConfigurationRepository robotConfigurationService,
|
IRobotConfigurationRepository robotConfigurationService,
|
||||||
//IEsppScheduleTransformService esppScheduleTransformService,
|
ILogger<RobotTaskOldController> logger,
|
||||||
ILogger<RobotTaskController> logger,
|
|
||||||
IClientService clientService,
|
IClientService clientService,
|
||||||
IRobotHistoryRepository robotHistoryService,
|
IRobotHistoryRepository robotHistoryService,
|
||||||
IShortcodesService shortcodesService,
|
IShortcodesService shortcodesService,
|
||||||
INextRunService nextRunService
|
INextRunService nextRunService,
|
||||||
//IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
|
|
||||||
|
|
||||||
|
IRobotTaskService robotTaskService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.settingsFromDb = settingsFromDb;
|
this.settingsFromDb = settingsFromDb;
|
||||||
this.robotConfigurationService = robotConfigurationService;
|
this.robotConfigurationService = robotConfigurationService;
|
||||||
//this.esppScheduleTransformService = esppScheduleTransformService;
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.clientService = clientService;
|
this.clientService = clientService;
|
||||||
this.robotHistoryService = robotHistoryService;
|
this.robotHistoryService = robotHistoryService;
|
||||||
this.shortcodesService = shortcodesService;
|
this.shortcodesService = shortcodesService;
|
||||||
this.nextRunService = nextRunService;
|
this.nextRunService = nextRunService;
|
||||||
//this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
|
this.robotTaskService = robotTaskService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить задание для робота по коду робота и по статусу задания
|
/// Получить задание для робота по коду робота и статусу задания
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="robotCode"></param>
|
/// <param name="robotCode">Код робота</param>
|
||||||
/// <param name="taskStatusCode"></param>
|
/// <param name="taskStatusCode">Статус задания</param>
|
||||||
|
/// <param name="requestQuery">Параметры</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
|
[HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
|
||||||
public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
|
public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
|
||||||
{
|
{
|
||||||
//Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
|
|
||||||
await robotConfigurationService.FindUnfulfilledTaskAndSetRobotErrorStatusAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
|
//robotTaskService
|
||||||
|
|
||||||
|
|
||||||
var query = robotConfigurationService.Get().AsSingleQuery()
|
|
||||||
.Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
|
|
||||||
|
|
||||||
switch (robotCode)
|
|
||||||
{
|
|
||||||
case RobotsEnum.TemplateOrder:
|
|
||||||
// шаблоны
|
|
||||||
query = query
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Unit)
|
|
||||||
.ThenInclude(t => t!.UnitValues)
|
|
||||||
.ThenInclude(t => t.Field)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Unit)
|
|
||||||
.ThenInclude(t => t!.UnitValues)
|
|
||||||
.ThenInclude(t => t.Value)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(a => a!.Job)
|
|
||||||
.ThenInclude(t => t!.Group)
|
|
||||||
.ThenInclude(g => g.GroupType)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(w => w!.Job)
|
|
||||||
.ThenInclude(t => t!.Tnk)
|
|
||||||
.ThenInclude(s => s!.Subprocess)
|
|
||||||
.ThenInclude(p => p!.Process);
|
|
||||||
|
|
||||||
query = query
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.UnitsInTemplate);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RobotsEnum.ScheduleOrder:
|
|
||||||
//расписание
|
|
||||||
query = query
|
|
||||||
.AsSingleQuery()
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Unit)
|
|
||||||
.ThenInclude(t => t!.UnitValues)
|
|
||||||
.ThenInclude(t => t.Field)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Unit)
|
|
||||||
.ThenInclude(t => t!.UnitValues)
|
|
||||||
.ThenInclude(t => t.Value)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(a => a!.Job)
|
|
||||||
.ThenInclude(t => t!.Group)
|
|
||||||
.ThenInclude(g => g.GroupType)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(a => a!.Job)
|
|
||||||
.ThenInclude(t => t!.Group)
|
|
||||||
.ThenInclude(t => t!.EsppSchValues)
|
|
||||||
.ThenInclude(t => t!.EsppSchTypeConfig)
|
|
||||||
.ThenInclude(t => t!.EsppSchTypeSchedule)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Job)
|
|
||||||
.ThenInclude(t => t!.Group)
|
|
||||||
.ThenInclude(t => t!.ScheduleExcludeType)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Job)
|
|
||||||
.ThenInclude(t => t!.Group)
|
|
||||||
.ThenInclude(t => t.ScheduleExcludeTypeCalendar)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.Job)
|
|
||||||
.ThenInclude(t => t!.Tnk)
|
|
||||||
.Include(t => t.Template)
|
|
||||||
.ThenInclude(t => t!.UnitsInTemplate);
|
|
||||||
|
|
||||||
//выбираем только записи с созданными шаблонами (у которых статус 20 или 30), а только потом у них ищем расписания
|
|
||||||
var createdTemplates = robotConfigurationService.Get()
|
|
||||||
.Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && (t.TaskStatusCode == (int)TaskStatusEnum.Ok))
|
|
||||||
.Select(t => t.TemplateId);
|
|
||||||
query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && createdTemplates.Contains(t.TemplateId));
|
|
||||||
// query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && t.TemplateId==Guid.Parse("7cb7c3be-506d-40e0-a63f-4554edb52459"));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
|
return Ok();
|
||||||
query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
|
|
||||||
|
|
||||||
RobotConfiguration? task = null;
|
|
||||||
|
|
||||||
//ищем задание в ожидании, если нашли, выбираем его
|
|
||||||
task = await query.FirstOrDefaultAsync(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait);
|
|
||||||
|
|
||||||
if (task == null)
|
|
||||||
{
|
|
||||||
//ищем задания в работе, которые можно перезапустить
|
|
||||||
//Поиск по `RobotStatusCode` = 22.
|
|
||||||
//Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
|
|
||||||
//и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
|
|
||||||
|
|
||||||
var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
|
|
||||||
task = await query
|
|
||||||
.FirstOrDefaultAsync(t =>
|
|
||||||
t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
|
||||||
&& t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
|
|
||||||
&& t.LastRobotStatusUpdated < endDate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task == null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
if (requestQuery?.SetInProgressStatus == true)
|
|
||||||
{
|
|
||||||
var resultSetStatus = await SetInProgressStatusAsync(task.Id);
|
|
||||||
if (resultSetStatus == false)
|
|
||||||
{
|
|
||||||
logger.LogError($"Ошибка при установке статуса {RobotStatusEnum.InProgress.ToString()} для задания RobotConfigutationId {task.Id} (при выдаче задания роботу)");
|
|
||||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (robotCode)
|
|
||||||
{
|
|
||||||
case RobotsEnum.TemplateOrder:
|
|
||||||
{ //RobotTaskTemplateResponse
|
|
||||||
var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task);
|
|
||||||
|
|
||||||
robotTaskTemplateResponse.FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!));
|
|
||||||
robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task.Template!);
|
|
||||||
robotTaskTemplateResponse.Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task.Template!));
|
|
||||||
robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task.Template!);
|
|
||||||
robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task.Template!);
|
|
||||||
robotTaskTemplateResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkGroup, task.Template!);
|
|
||||||
robotTaskTemplateResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ResponseArea, task.Template!);
|
|
||||||
|
|
||||||
return Ok(new Response<RobotTaskTemplateResponse>(robotTaskTemplateResponse, true));
|
|
||||||
}
|
|
||||||
case RobotsEnum.ScheduleOrder:
|
|
||||||
{ // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
|
|
||||||
var resultUpdateNextRun = await UpdateNextRunAsync(task);
|
|
||||||
if (!resultUpdateNextRun)
|
|
||||||
{
|
|
||||||
logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", task.TemplateId);
|
|
||||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
|
|
||||||
}
|
|
||||||
|
|
||||||
//RobotTaskScheduleResponse
|
|
||||||
var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);
|
|
||||||
|
|
||||||
robotTaskScheduleResponse.Timezone = settingsFromDb.EsppScheduleTimezone;
|
|
||||||
|
|
||||||
robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
|
|
||||||
robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
|
|
||||||
|
|
||||||
//var nextRunWithRobotTz = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
|
|
||||||
//nextRun в часовой зоне УЗ Робота ЕСПП
|
|
||||||
var nextRunWithRobotTz = task.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
|
|
||||||
|
|
||||||
//на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
|
|
||||||
if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
|
|
||||||
{
|
|
||||||
logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
|
|
||||||
task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
|
|
||||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
|
|
||||||
}
|
|
||||||
|
|
||||||
robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
|
|
||||||
robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
|
|
||||||
|
|
||||||
return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BadRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Обоновить NextRun если он устарел
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="task"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<bool> UpdateNextRunAsync(RobotConfiguration task)
|
|
||||||
{
|
|
||||||
var template = task.Template!;
|
|
||||||
|
|
||||||
//var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
|
|
||||||
var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
|
|
||||||
|
|
||||||
if (!nextRun.HasValue)
|
|
||||||
{
|
|
||||||
logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextRun.Value < DateTimeOffset.UtcNow)
|
|
||||||
{
|
|
||||||
logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextRun != template.NextRun)
|
|
||||||
{
|
|
||||||
logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
|
|
||||||
|
|
||||||
template.LastRun = template.NextRun;
|
|
||||||
template.NextRun = nextRun.Value;
|
|
||||||
|
|
||||||
await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="taskId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<bool> SetInProgressStatusAsync(Guid taskId)
|
|
||||||
{
|
|
||||||
var config = await robotConfigurationService.GetAsync(taskId);
|
|
||||||
|
|
||||||
//изменение статуса робота
|
|
||||||
robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);
|
|
||||||
|
|
||||||
if (!await robotConfigurationService.CommitAsync())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
//записываем в лог робота
|
|
||||||
var history = new RobotHistory
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
HistoryLevel = (int)RobotHistoryLevelEnum.Start,
|
|
||||||
TaskStatusCode = config.TaskStatusCode,
|
|
||||||
RobotConfigurationId = config.Id,
|
|
||||||
RobotIp = clientService.GetClientIp()?.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Приводит переносы строк в тексте к формату CRLF (\r\n)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">Исходный текст</param>
|
|
||||||
/// <returns>Текст с унифицированными переносами строк</returns>
|
|
||||||
private string NormalizeLineEndingsToCrlf(string? text)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(text))
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
// Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
|
|
||||||
return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
330
PARR.API/Controllers/V1/RobotTaskOldController.cs
Normal file
330
PARR.API/Controllers/V1/RobotTaskOldController.cs
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PARR.API.Contracts.V1;
|
||||||
|
using PARR.API.Contracts.V1.Requests.Queries;
|
||||||
|
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.BLL.Helpers;
|
||||||
|
using PARR.Core.Repositories.Interfaces;
|
||||||
|
using PARR.Core.Services.NextRunServices;
|
||||||
|
using PARR.Core.Services.Shortcodes;
|
||||||
|
using PARR.Domain.Common.Roles;
|
||||||
|
using PARR.Domain.Entities;
|
||||||
|
using PARR.Domain.Entities.Base.History;
|
||||||
|
using PARR.Domain.Enums;
|
||||||
|
using PARR.Domain.Settings;
|
||||||
|
|
||||||
|
namespace PARR.API.Controllers.V1
|
||||||
|
{
|
||||||
|
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||||||
|
public class RobotTaskOldController : BaseApiController
|
||||||
|
{
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
private readonly SettingsFromDb settingsFromDb;
|
||||||
|
private readonly IRobotConfigurationRepository robotConfigurationService;
|
||||||
|
private readonly ILogger<RobotTaskOldController> logger;
|
||||||
|
private readonly IClientService clientService;
|
||||||
|
private readonly IRobotHistoryRepository robotHistoryService;
|
||||||
|
private readonly IShortcodesService shortcodesService;
|
||||||
|
private readonly INextRunService nextRunService;
|
||||||
|
|
||||||
|
public RobotTaskOldController(
|
||||||
|
IMapper mapper,
|
||||||
|
SettingsFromDb settingsFromDb,
|
||||||
|
IRobotConfigurationRepository robotConfigurationService,
|
||||||
|
ILogger<RobotTaskOldController> logger,
|
||||||
|
IClientService clientService,
|
||||||
|
IRobotHistoryRepository robotHistoryService,
|
||||||
|
IShortcodesService shortcodesService,
|
||||||
|
INextRunService nextRunService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.settingsFromDb = settingsFromDb;
|
||||||
|
this.robotConfigurationService = robotConfigurationService;
|
||||||
|
this.logger = logger;
|
||||||
|
this.clientService = clientService;
|
||||||
|
this.robotHistoryService = robotHistoryService;
|
||||||
|
this.shortcodesService = shortcodesService;
|
||||||
|
this.nextRunService = nextRunService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить задание для робота по коду робота и по статусу задания
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="robotCode"></param>
|
||||||
|
/// <param name="taskStatusCode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
|
||||||
|
public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
|
||||||
|
{
|
||||||
|
//Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
|
||||||
|
await robotConfigurationService.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
|
||||||
|
|
||||||
|
|
||||||
|
var query = robotConfigurationService.Get()
|
||||||
|
.AsSingleQuery()
|
||||||
|
.Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
|
||||||
|
|
||||||
|
switch (robotCode)
|
||||||
|
{
|
||||||
|
case RobotsEnum.TemplateOrder:
|
||||||
|
// шаблоны
|
||||||
|
query = query
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Unit)
|
||||||
|
.ThenInclude(t => t!.UnitValues)
|
||||||
|
.ThenInclude(t => t.Field)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Unit)
|
||||||
|
.ThenInclude(t => t!.UnitValues)
|
||||||
|
.ThenInclude(t => t.Value)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(a => a!.Job)
|
||||||
|
.ThenInclude(t => t!.Group)
|
||||||
|
.ThenInclude(g => g.GroupType)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(w => w!.Job)
|
||||||
|
.ThenInclude(t => t!.Tnk)
|
||||||
|
.ThenInclude(s => s!.Subprocess)
|
||||||
|
.ThenInclude(p => p!.Process);
|
||||||
|
|
||||||
|
query = query
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.UnitsInTemplate);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RobotsEnum.ScheduleOrder:
|
||||||
|
//расписание
|
||||||
|
query = query
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Unit)
|
||||||
|
.ThenInclude(t => t!.UnitValues)
|
||||||
|
.ThenInclude(t => t.Field)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Unit)
|
||||||
|
.ThenInclude(t => t!.UnitValues)
|
||||||
|
.ThenInclude(t => t.Value)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(a => a!.Job)
|
||||||
|
.ThenInclude(t => t!.Group)
|
||||||
|
.ThenInclude(g => g.GroupType)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(a => a!.Job)
|
||||||
|
.ThenInclude(t => t!.Group)
|
||||||
|
.ThenInclude(t => t!.EsppSchValues)
|
||||||
|
.ThenInclude(t => t!.EsppSchTypeConfig)
|
||||||
|
.ThenInclude(t => t!.EsppSchTypeSchedule)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Job)
|
||||||
|
.ThenInclude(t => t!.Group)
|
||||||
|
.ThenInclude(t => t!.ScheduleExcludeType)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Job)
|
||||||
|
.ThenInclude(t => t!.Group)
|
||||||
|
.ThenInclude(t => t.ScheduleExcludeTypeCalendar)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.Job)
|
||||||
|
.ThenInclude(t => t!.Tnk)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ThenInclude(t => t!.UnitsInTemplate);
|
||||||
|
|
||||||
|
//выбираем только записи с созданными шаблонами (у которых статус 20 или 30), а только потом у них ищем расписания
|
||||||
|
var createdTemplates = robotConfigurationService.Get()
|
||||||
|
.Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && (t.TaskStatusCode == (int)TaskStatusEnum.Ok))
|
||||||
|
.Select(t => t.TemplateId);
|
||||||
|
query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && createdTemplates.Contains(t.TemplateId));
|
||||||
|
// query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && t.TemplateId==Guid.Parse("7cb7c3be-506d-40e0-a63f-4554edb52459"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
|
||||||
|
query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
|
||||||
|
|
||||||
|
RobotConfiguration? task = null;
|
||||||
|
|
||||||
|
//ищем задание в ожидании, если нашли, выбираем его
|
||||||
|
task = await query.FirstOrDefaultAsync(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait);
|
||||||
|
|
||||||
|
if (task == null)
|
||||||
|
{
|
||||||
|
//ищем задания в работе, которые можно перезапустить
|
||||||
|
//Поиск по `RobotStatusCode` = 22.
|
||||||
|
//Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
|
||||||
|
//и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
|
||||||
|
|
||||||
|
var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
|
||||||
|
task = await query
|
||||||
|
.FirstOrDefaultAsync(t =>
|
||||||
|
t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
||||||
|
&& t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
|
||||||
|
&& t.LastRobotStatusUpdated < endDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
if (requestQuery?.SetInProgressStatus == true)
|
||||||
|
{
|
||||||
|
var resultSetStatus = await SetInProgressStatusAsync(task.Id);
|
||||||
|
if (resultSetStatus == false)
|
||||||
|
{
|
||||||
|
logger.LogError($"Ошибка при установке статуса {RobotStatusEnum.InProgress.ToString()} для задания RobotConfigutationId {task.Id} (при выдаче задания роботу)");
|
||||||
|
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (robotCode)
|
||||||
|
{
|
||||||
|
case RobotsEnum.TemplateOrder:
|
||||||
|
{ //RobotTaskTemplateResponse
|
||||||
|
var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task);
|
||||||
|
|
||||||
|
robotTaskTemplateResponse.FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!));
|
||||||
|
robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task.Template!);
|
||||||
|
robotTaskTemplateResponse.Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task.Template!));
|
||||||
|
robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task.Template!);
|
||||||
|
robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task.Template!);
|
||||||
|
robotTaskTemplateResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkGroup, task.Template!);
|
||||||
|
robotTaskTemplateResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ResponseArea, task.Template!);
|
||||||
|
|
||||||
|
return Ok(new Response<RobotTaskTemplateResponse>(robotTaskTemplateResponse, true));
|
||||||
|
}
|
||||||
|
case RobotsEnum.ScheduleOrder:
|
||||||
|
{ // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
|
||||||
|
var resultUpdateNextRun = await UpdateNextRunAsync(task);
|
||||||
|
if (!resultUpdateNextRun)
|
||||||
|
{
|
||||||
|
logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", task.TemplateId);
|
||||||
|
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
//RobotTaskScheduleResponse
|
||||||
|
var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);
|
||||||
|
|
||||||
|
robotTaskScheduleResponse.Timezone = settingsFromDb.EsppScheduleTimezone;
|
||||||
|
|
||||||
|
robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
|
||||||
|
robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
|
||||||
|
|
||||||
|
//var nextRunWithRobotTz = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
|
||||||
|
//nextRun в часовой зоне УЗ Робота ЕСПП
|
||||||
|
var nextRunWithRobotTz = task.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
|
||||||
|
|
||||||
|
//на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
|
||||||
|
if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
|
||||||
|
task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
|
||||||
|
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
|
||||||
|
robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
|
||||||
|
|
||||||
|
return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обоновить NextRun если он устарел
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<bool> UpdateNextRunAsync(RobotConfiguration task)
|
||||||
|
{
|
||||||
|
var template = task.Template!;
|
||||||
|
|
||||||
|
//var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
|
||||||
|
var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
|
||||||
|
|
||||||
|
if (!nextRun.HasValue)
|
||||||
|
{
|
||||||
|
logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRun.Value < DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRun != template.NextRun)
|
||||||
|
{
|
||||||
|
logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
|
||||||
|
|
||||||
|
template.LastRun = template.NextRun;
|
||||||
|
template.NextRun = nextRun.Value;
|
||||||
|
|
||||||
|
await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<bool> SetInProgressStatusAsync(Guid taskId)
|
||||||
|
{
|
||||||
|
var config = await robotConfigurationService.GetAsync(taskId);
|
||||||
|
|
||||||
|
//изменение статуса робота
|
||||||
|
robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);
|
||||||
|
|
||||||
|
if (!await robotConfigurationService.CommitAsync())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//записываем в лог робота
|
||||||
|
var history = new RobotHistory
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
HistoryLevel = (int)RobotHistoryLevelEnum.Start,
|
||||||
|
TaskStatusCode = config.TaskStatusCode,
|
||||||
|
RobotConfigurationId = config.Id,
|
||||||
|
RobotIp = clientService.GetClientIp()?.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Приводит переносы строк в тексте к формату CRLF (\r\n)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Исходный текст</param>
|
||||||
|
/// <returns>Текст с унифицированными переносами строк</returns>
|
||||||
|
private string NormalizeLineEndingsToCrlf(string? text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
|
||||||
|
return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ using PARR.Core.Common.Interfaces;
|
|||||||
using PARR.Core.Services.MatchingStatusService;
|
using PARR.Core.Services.MatchingStatusService;
|
||||||
using PARR.Core.Services.NextRunServices;
|
using PARR.Core.Services.NextRunServices;
|
||||||
using PARR.Core.Services.NextRunServices.Subservices;
|
using PARR.Core.Services.NextRunServices.Subservices;
|
||||||
|
using PARR.Core.Services.RobotTask.Implementations;
|
||||||
|
using PARR.Core.Services.RobotTask.Interfaces;
|
||||||
using PARR.Core.Services.Shortcodes;
|
using PARR.Core.Services.Shortcodes;
|
||||||
using PARR.Core.Services.Shortcodes.Handlers;
|
using PARR.Core.Services.Shortcodes.Handlers;
|
||||||
using PARR.Core.Services.TaskServices.Handlers;
|
using PARR.Core.Services.TaskServices.Handlers;
|
||||||
@@ -78,8 +80,6 @@ namespace PARR.Core
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Services
|
|
||||||
|
|
||||||
#region Shortсodes
|
#region Shortсodes
|
||||||
|
|
||||||
services.AddScoped<IShortcodeHandler, ConstantsShortcodeHandler>();
|
services.AddScoped<IShortcodeHandler, ConstantsShortcodeHandler>();
|
||||||
@@ -95,10 +95,13 @@ namespace PARR.Core
|
|||||||
services.AddScoped<IShortcodesService, ShortcodesService>();
|
services.AddScoped<IShortcodesService, ShortcodesService>();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Services
|
||||||
|
|
||||||
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
|
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
|
||||||
|
|
||||||
//services.AddScoped<IUserService, UserService>();
|
services.AddScoped<IRobotTaskService, RobotTaskService>();
|
||||||
|
|
||||||
|
//services.AddScoped<IUserService, UserService>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace PARR.Core.Repositories.Interfaces
|
|||||||
/// <param name="robotAttemptsNumber"></param>
|
/// <param name="robotAttemptsNumber"></param>
|
||||||
/// <param name="robotWaitTime"></param>
|
/// <param name="robotWaitTime"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task FindUnfulfilledTaskAndSetRobotErrorStatusAsync(int robotAttemptsNumber, TimeSpan robotWaitTime);
|
Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить конфигурацию из шаблона по RobotCode. У шаблона обязательно должен быть Include таблицы RobotConfiguration
|
/// Получить конфигурацию из шаблона по RobotCode. У шаблона обязательно должен быть Include таблицы RobotConfiguration
|
||||||
@@ -42,5 +42,12 @@ namespace PARR.Core.Repositories.Interfaces
|
|||||||
/// <param name="configuration"></param>
|
/// <param name="configuration"></param>
|
||||||
/// <returns>true - если изменил статус, false - нельзя изменить статус</returns>
|
/// <returns>true - если изменил статус, false - нельзя изменить статус</returns>
|
||||||
bool SetUpdateTaskStatusIfAllow(RobotConfiguration configuration);
|
bool SetUpdateTaskStatusIfAllow(RobotConfiguration configuration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Атомарно установить статус задачи "В работе" если текущий статус "В ожидании".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> SetInProgressStatusAsync(Guid id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
204
PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
Normal file
204
PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
using InfluxDB.Client.Api.Domain;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.Core.Repositories.Interfaces;
|
||||||
|
using PARR.Core.Services.RobotTask.Interfaces;
|
||||||
|
using PARR.Domain.Entities;
|
||||||
|
using PARR.Domain.Enums;
|
||||||
|
using PARR.Domain.Exceptions;
|
||||||
|
using PARR.Domain.Settings;
|
||||||
|
|
||||||
|
namespace PARR.Core.Services.RobotTask.Implementations
|
||||||
|
{
|
||||||
|
internal class RobotTaskService : IRobotTaskService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Количество заданий которые рассматриваем для взятия в работу.
|
||||||
|
/// </summary>
|
||||||
|
private readonly int TakeTasks = 10;
|
||||||
|
|
||||||
|
private readonly ILogger<RobotTaskService> logger;
|
||||||
|
private readonly IRobotConfigurationRepository robotConfigurationRepository;
|
||||||
|
private readonly SettingsFromDb settingsFromDb;
|
||||||
|
private readonly IRobotHistoryRepository robotHistoryRepository;
|
||||||
|
|
||||||
|
public RobotTaskService(
|
||||||
|
ILogger<RobotTaskService> logger,
|
||||||
|
IRobotConfigurationRepository robotConfigurationRepository,
|
||||||
|
SettingsFromDb settingsFromDb,
|
||||||
|
IRobotHistoryRepository robotHistoryRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.robotConfigurationRepository = robotConfigurationRepository;
|
||||||
|
this.settingsFromDb = settingsFromDb;
|
||||||
|
this.robotHistoryRepository = robotHistoryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp)
|
||||||
|
{
|
||||||
|
// 1. Ищем все задания с превышенным кол-вом попыток и просроченным временем, ставим им статус ошибки
|
||||||
|
await robotConfigurationRepository.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
|
||||||
|
|
||||||
|
|
||||||
|
// 2. Ищем доступные задания
|
||||||
|
var availableTasks = await GetAvailableTasksAsync(robotCode, taskStatusCode);
|
||||||
|
|
||||||
|
if (availableTasks.Count == 0)
|
||||||
|
throw new NotFoundException("Нет доступных заданий для робота");
|
||||||
|
|
||||||
|
Guid? acquiredTaskId = null;
|
||||||
|
|
||||||
|
if (acquireTask)
|
||||||
|
{
|
||||||
|
// Берем задание в работу, устанавливаем ей статус "В работе"
|
||||||
|
acquiredTaskId = await AcquireTaskAsync(availableTasks, robotIp);
|
||||||
|
|
||||||
|
if (acquiredTaskId == null)
|
||||||
|
throw new NotFoundException($"Не удалось взять ни одну из доступных задач ({availableTasks.Count}) в работу");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Берем первую задачу из списка доступных
|
||||||
|
acquiredTaskId = availableTasks.First();
|
||||||
|
logger.LogDebug("Задача не требует захвата, взята первая из доступных: {TaskId}", acquiredTaskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//3. Готовим модель ответа
|
||||||
|
var task = await GetTaskWithAllDataAsync(acquiredTaskId.Value, robotCode);
|
||||||
|
|
||||||
|
//todo:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить список возможных заданий для взятия в работу.
|
||||||
|
/// Кол-во заданй ограничено переменной TakeTasks
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="robotCode"></param>
|
||||||
|
/// <param name="taskStatusCode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<List<Guid>> GetAvailableTasksAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode)
|
||||||
|
{
|
||||||
|
var query = robotConfigurationRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
|
||||||
|
|
||||||
|
// Если это задание для робота расписаний
|
||||||
|
if (robotCode == RobotsEnum.ScheduleOrder)
|
||||||
|
{
|
||||||
|
// Выбираем только записи с созданными шаблонами (у которых статус 30), а только потом ищем у них расписания
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сортируем по nextRun, чтобы те, у кого nextRun ближе к текущей, выполнились скорее
|
||||||
|
query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template!.IsActiveTemplate);
|
||||||
|
|
||||||
|
// Кандидаты заданий
|
||||||
|
var tasks = new List<Guid>();
|
||||||
|
|
||||||
|
// Ещем первые 10 заданий в статусе ОЖИДАНИЕ
|
||||||
|
tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait).Take(TakeTasks).Select(t => t.Id).ToListAsync();
|
||||||
|
|
||||||
|
logger.LogDebug("Найдено заданий в статусе 'Ожидание' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString());
|
||||||
|
|
||||||
|
if (tasks.Count == 0)
|
||||||
|
{
|
||||||
|
// Ищем задания в статусе В РАБОТЕ, которые можно перезапустить
|
||||||
|
// Поиск по `RobotStatusCode` = 22.
|
||||||
|
// Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
|
||||||
|
// и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
|
||||||
|
|
||||||
|
var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
|
||||||
|
|
||||||
|
tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
||||||
|
&& t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
|
||||||
|
&& t.LastRobotStatusUpdated < endDate)
|
||||||
|
.Take(TakeTasks)
|
||||||
|
.Select(t => t.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
logger.LogDebug("Найдено заданий в статусе 'В работе' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Взять задачу в работу
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<Guid?> AcquireTaskAsync(List<Guid> tasks, string? robotIp)
|
||||||
|
{
|
||||||
|
foreach (var taskId in tasks)
|
||||||
|
{
|
||||||
|
var isChangedStatus = await robotConfigurationRepository.SetInProgressStatusAsync(taskId);
|
||||||
|
if (isChangedStatus)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Захвачена задача {TaskId}", taskId);
|
||||||
|
|
||||||
|
var task = await robotConfigurationRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstAsync(t => t.Id == taskId);
|
||||||
|
|
||||||
|
// пишем в историю робота
|
||||||
|
var history = new RobotHistory
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
HistoryLevel = (int)RobotHistoryLevelEnum.Start,
|
||||||
|
TaskStatusCode = task.TaskStatusCode,
|
||||||
|
RobotConfigurationId = taskId,
|
||||||
|
RobotIp = robotIp
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!await robotHistoryRepository.CreateAsync(history) || !await robotHistoryRepository.CommitAsync())
|
||||||
|
throw new DbErrorException("Ошибка при добавлении истории робота, при взятии задания в работу.");
|
||||||
|
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogDebug("Не удалось захватить задачу {TaskId}", taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogDebug("Не удалось захватить ни одну из доступных задач для робота");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить задачу со всем необходимыми полями
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId"></param>
|
||||||
|
/// <param name="robotCode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<RobotConfiguration> GetTaskWithAllDataAsync(Guid taskId, RobotsEnum robotCode)
|
||||||
|
{
|
||||||
|
var query = robotConfigurationRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.AsSingleQuery()
|
||||||
|
//todo: тут общие инклуды
|
||||||
|
;
|
||||||
|
|
||||||
|
if(robotCode== RobotsEnum.ScheduleOrder)
|
||||||
|
{
|
||||||
|
// тут инклуды только для расписаний
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.FirstAsync(t => t.Id == taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace PARR.Core.Services.RobotTask.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Управление заданиями роботам
|
||||||
|
/// </summary>
|
||||||
|
public interface IRobotTaskService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,6 +102,22 @@ namespace PARR.DAL.Repositories
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<bool> SetInProgressStatusAsync(Guid id)
|
||||||
|
{
|
||||||
|
// Если задание все еще в статусе Wait (RobotStatusEnum.Wait), установить ему статус "InProgress"
|
||||||
|
|
||||||
|
// Выполняем атомарный UPDATE напрямую в базе данных
|
||||||
|
var affectedRows = await EntitySet.Where(t => t.Id == id && t.RobotStatusCode == (int)RobotStatusEnum.Wait)
|
||||||
|
.ExecuteUpdateAsync(s => s
|
||||||
|
.SetProperty(t => t.RobotStatusCode, (int)RobotStatusEnum.InProgress)
|
||||||
|
.SetProperty(t => t.LastRobotStatusUpdated, DateTimeOffset.UtcNow)
|
||||||
|
);
|
||||||
|
|
||||||
|
return affectedRows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public RobotConfiguration GetFromTemplateByRobotCode(RobotsEnum robotsEnum, Template template)
|
public RobotConfiguration GetFromTemplateByRobotCode(RobotsEnum robotsEnum, Template template)
|
||||||
{
|
{
|
||||||
var config = template.RobotConfigurations.FirstOrDefault(t => t.RobotCode == (int)robotsEnum);
|
var config = template.RobotConfigurations.FirstOrDefault(t => t.RobotCode == (int)robotsEnum);
|
||||||
@@ -117,7 +133,7 @@ namespace PARR.DAL.Repositories
|
|||||||
|
|
||||||
|
|
||||||
// Поиск невыполненных заданий и установка им статуса ошибки робота
|
// Поиск невыполненных заданий и установка им статуса ошибки робота
|
||||||
public async Task FindUnfulfilledTaskAndSetRobotErrorStatusAsync(int robotAttemptsNumber, TimeSpan robotWaitTime)
|
public async Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime)
|
||||||
{
|
{
|
||||||
//Ищем `RobotStatusCode` = 22 и `LastStatusUpdated` истекло и `AttemptsNumber` >= допустимого значения из настроек,
|
//Ищем `RobotStatusCode` = 22 и `LastStatusUpdated` истекло и `AttemptsNumber` >= допустимого значения из настроек,
|
||||||
//ставим всем этим записям `RobotStatusCode`= 33
|
//ставим всем этим записям `RobotStatusCode`= 33
|
||||||
|
|||||||
Reference in New Issue
Block a user