feat(api, core, dal, domain): В таблицу Tasks добавлено поле процент выполнения, так же добавлено это поле в response - статус формирования отчета. Создан интерфейс для отслеживания прогресса IProgressAsync. Из сервиса WorkloadService исключен ITaskManagementService.
This commit is contained in:
@@ -10,5 +10,7 @@ namespace PARR.API.Contracts.V1.Responses.Statistics
|
|||||||
public WorkloadCacheInfo.StatusEnum Status { get; set; }
|
public WorkloadCacheInfo.StatusEnum Status { get; set; }
|
||||||
|
|
||||||
public DateTimeOffset? Date { get; init; }
|
public DateTimeOffset? Date { get; init; }
|
||||||
|
|
||||||
|
public int? ProgressPercent { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
PARR.Core/Common/Interfaces/IProgressAsync.cs
Normal file
40
PARR.Core/Common/Interfaces/IProgressAsync.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
namespace PARR.Core.Common.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Отслеживание програсса (% от задачи)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public interface IProgressAsync<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Установить значение програсса (%)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task ReportAsync(T value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отслеживание програсса (% от задачи)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class ProgressAsync<T> : IProgressAsync<T>
|
||||||
|
{
|
||||||
|
private readonly Func<T, Task> _handler;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отслеживание програсса (% от задачи)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler"></param>
|
||||||
|
public ProgressAsync(Func<T, Task> handler)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ReportAsync(T value)
|
||||||
|
{
|
||||||
|
return _handler(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,5 +11,14 @@ namespace PARR.Core.Repositories.Interfaces.TaskRepositories
|
|||||||
/// <param name="taskId"></param>
|
/// <param name="taskId"></param>
|
||||||
/// <returns>Возвращает кол-во обработанных строк</returns>
|
/// <returns>Возвращает кол-во обработанных строк</returns>
|
||||||
Task<int> TaskCaptureAsync(Guid taskId);
|
Task<int> TaskCaptureAsync(Guid taskId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обновляет процент выполнения задачи.
|
||||||
|
/// Возвращает количество затронутых строк (0 или 1).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId">Id задачи</param>
|
||||||
|
/// <param name="percent">Процент (0-100)</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<int> UpdateProgressAsync(Guid taskId, int percent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
task.StatusCode = TaskItemStatusEnum.Processing;
|
task.StatusCode = TaskItemStatusEnum.Processing;
|
||||||
task.DateModified = DateTimeOffset.UtcNow;
|
task.DateModified = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
// Ставим процент 0
|
||||||
|
task.ProgressPercent = 0;
|
||||||
|
await taskRepository.UpdateProgressAsync(task.Id, 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +133,7 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
/// <param name="task"></param>
|
/// <param name="task"></param>
|
||||||
/// <param name="result"></param>
|
/// <param name="result"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async System.Threading.Tasks.Task HandleSuccessAsync(TaskItem task, HandlerResult result)
|
private async Task HandleSuccessAsync(TaskItem task, HandlerResult result)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Задача {TaskId} успешно выполнена", task.Id);
|
logger.LogInformation("Задача {TaskId} успешно выполнена", task.Id);
|
||||||
|
|
||||||
@@ -137,6 +141,9 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
task.ProcessedAt = DateTimeOffset.UtcNow;
|
task.ProcessedAt = DateTimeOffset.UtcNow;
|
||||||
task.DateModified = DateTimeOffset.UtcNow;
|
task.DateModified = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
// Ставим 100%
|
||||||
|
task.ProgressPercent = 100;
|
||||||
|
|
||||||
//todo: если есть результат, его можно сохранить, если надо
|
//todo: если есть результат, его можно сохранить, если надо
|
||||||
if (!string.IsNullOrEmpty(result.ResultData))
|
if (!string.IsNullOrEmpty(result.ResultData))
|
||||||
{
|
{
|
||||||
@@ -158,8 +165,19 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
/// <param name="task"></param>
|
/// <param name="task"></param>
|
||||||
/// <param name="ex"></param>
|
/// <param name="ex"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async System.Threading.Tasks.Task HandleErrorAsync(TaskItem task, Exception? ex)
|
private async Task HandleErrorAsync(TaskItem task, Exception? ex)
|
||||||
{
|
{
|
||||||
|
// задача в БД, у которой можно получить актуальный процент прогресса
|
||||||
|
var taskInDb = await taskRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(t=>t.Id==task.Id);
|
||||||
|
|
||||||
|
if (taskInDb != null)
|
||||||
|
{
|
||||||
|
//так как в БД мы обновлям атомарно прогресс, то пришедший нам task не знает фактическое значение прогресса
|
||||||
|
task.ProgressPercent = taskInDb.ProgressPercent;
|
||||||
|
}
|
||||||
|
|
||||||
var attemptNumber = task.RetryCount + 1;
|
var attemptNumber = task.RetryCount + 1;
|
||||||
var errorMessage = ex?.Message ?? "Неизвестная ошибка";
|
var errorMessage = ex?.Message ?? "Неизвестная ошибка";
|
||||||
var stackTrace = ex?.StackTrace;
|
var stackTrace = ex?.StackTrace;
|
||||||
@@ -219,7 +237,7 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="task"></param>
|
/// <param name="task"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async System.Threading.Tasks.Task ScheduleRetryAsync(TaskItem task)
|
protected virtual async Task ScheduleRetryAsync(TaskItem task)
|
||||||
{
|
{
|
||||||
// Здесь нужна интеграция с ITaskQueueService
|
// Здесь нужна интеграция с ITaskQueueService
|
||||||
// Для этого можно использовать событие или callback
|
// Для этого можно использовать событие или callback
|
||||||
@@ -254,7 +272,7 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
/// <param name="task"></param>
|
/// <param name="task"></param>
|
||||||
/// <param name="result"></param>
|
/// <param name="result"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual System.Threading.Tasks.Task OnSuccessAsync(TaskItem task, HandlerResult result) => System.Threading.Tasks.Task.CompletedTask;
|
protected virtual Task OnSuccessAsync(TaskItem task, HandlerResult result) => System.Threading.Tasks.Task.CompletedTask;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -264,7 +282,43 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
/// <param name="ex"></param>
|
/// <param name="ex"></param>
|
||||||
/// <param name="attemptNumber"></param>
|
/// <param name="attemptNumber"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual System.Threading.Tasks.Task OnErrorAsync(TaskItem task, Exception? ex, int attemptNumber) => System.Threading.Tasks.Task.CompletedTask;
|
protected virtual Task OnErrorAsync(TaskItem task, Exception? ex, int attemptNumber) => System.Threading.Tasks.Task.CompletedTask;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обновляет процент выполнения задачи.
|
||||||
|
/// Обновлять процент не чаще 1 раза в 5 сек.
|
||||||
|
/// Вызывается из конкретного хендлера при желании показать прогресс.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId">Id задачи</param>
|
||||||
|
/// <param name="percent">Процент (0-100). Значения >100 будут ограничены до 100.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected async Task UpdateProgressAsync(Guid taskId, int percent)
|
||||||
|
{
|
||||||
|
if (percent < 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Попытка установить отрицательный прогресс {Percent} для задачи {TaskId}", percent, taskId);
|
||||||
|
percent = 0;
|
||||||
|
}
|
||||||
|
else if (percent > 100)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Прогресс {Percent} для задачи {TaskId} превысил 100%. Записываем 100.", percent, taskId);
|
||||||
|
percent = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем в БД
|
||||||
|
var affectedRows = await taskRepository.UpdateProgressAsync(taskId, percent);
|
||||||
|
|
||||||
|
if (affectedRows == 0)
|
||||||
|
{
|
||||||
|
// Задача не найдена или уже завершена — не считаем ошибкой
|
||||||
|
logger.LogDebug("Не удалось обновить прогресс задачи {TaskId} (возможно, уже завершена)", taskId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogDebug("Прогресс задачи {TaskId} обновлен: {Percent}%", taskId, percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.Core.Common.Interfaces;
|
||||||
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
||||||
using PARR.Core.Services.Workload.Implementations;
|
using PARR.Core.Services.Workload.Implementations;
|
||||||
using PARR.Domain.Entities.TaskEntities;
|
using PARR.Domain.Entities.TaskEntities;
|
||||||
@@ -37,9 +38,18 @@ namespace PARR.Core.Services.TaskServices.Handlers
|
|||||||
|
|
||||||
// тут логика построения отчета
|
// тут логика построения отчета
|
||||||
|
|
||||||
|
|
||||||
|
// Создаём прогресс-репортер
|
||||||
|
var progress = new ProgressAsync<int>(async percent =>
|
||||||
|
{
|
||||||
|
await UpdateProgressAsync(task.Id, percent);
|
||||||
|
logger.LogDebug("Процент выполнения: {Percent}%", percent);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
await workloadCacheService.DeleteWorkloadCacheAsync();
|
await workloadCacheService.DeleteWorkloadCacheAsync();
|
||||||
await workloadCacheService.DeleteTemplateCacheAsync();
|
await workloadCacheService.DeleteTemplateCacheAsync();
|
||||||
await workloadCacheService.CreateTemplateCacheAsync();
|
await workloadCacheService.CreateTemplateCacheAsync(progress);
|
||||||
|
|
||||||
//await System.Threading.Tasks.Task.CompletedTask;
|
//await System.Threading.Tasks.Task.CompletedTask;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
||||||
|
using PARR.Domain.DTOs.TaskDTO;
|
||||||
|
using PARR.Domain.Enums;
|
||||||
|
|
||||||
|
namespace PARR.Core.Services.TaskServices.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Хелпеы для TaskManagement
|
||||||
|
/// </summary>
|
||||||
|
internal static class TaskManagementHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получить список активных задач
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskRepository"></param>
|
||||||
|
/// <param name="mapper"></param>
|
||||||
|
/// <param name="typeCode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<List<ActiveTask>> GetActiveTasksAsync(ITaskRepository taskRepository, IMapper mapper, TaskTypeEnum? typeCode = null)
|
||||||
|
{
|
||||||
|
var query = taskRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(t => t.TaskStatus)
|
||||||
|
.Include(t => t.TaskType)
|
||||||
|
.Where(t => t.StatusCode == TaskItemStatusEnum.Pending || t.StatusCode == TaskItemStatusEnum.Processing);
|
||||||
|
|
||||||
|
if (typeCode.HasValue)
|
||||||
|
query = query.Where(t => t.TypeCode == typeCode.Value);
|
||||||
|
|
||||||
|
var tasks = await query.OrderByDescending(t => t.DateCreated).ToListAsync();
|
||||||
|
|
||||||
|
return mapper.Map<List<ActiveTask>>(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||||
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
||||||
|
using PARR.Core.Services.TaskServices.Helpers;
|
||||||
using PARR.Core.Services.TaskServices.Interfaces;
|
using PARR.Core.Services.TaskServices.Interfaces;
|
||||||
using PARR.Domain.Common.Rabbit.Messages;
|
using PARR.Domain.Common.Rabbit.Messages;
|
||||||
using PARR.Domain.DTOs.TaskDTO;
|
using PARR.Domain.DTOs.TaskDTO;
|
||||||
@@ -112,18 +113,20 @@ namespace PARR.Core.Services.TaskServices.Implementations
|
|||||||
|
|
||||||
public async Task<List<ActiveTask>> GetActiveTasksAsync(TaskTypeEnum? typeCode = null)
|
public async Task<List<ActiveTask>> GetActiveTasksAsync(TaskTypeEnum? typeCode = null)
|
||||||
{
|
{
|
||||||
var query = taskRepository.Get()
|
//var query = taskRepository.Get()
|
||||||
.AsNoTracking()
|
// .AsNoTracking()
|
||||||
.Include(t => t.TaskStatus)
|
// .Include(t => t.TaskStatus)
|
||||||
.Include(t => t.TaskType)
|
// .Include(t => t.TaskType)
|
||||||
.Where(t => t.StatusCode == TaskItemStatusEnum.Pending || t.StatusCode == TaskItemStatusEnum.Processing);
|
// .Where(t => t.StatusCode == TaskItemStatusEnum.Pending || t.StatusCode == TaskItemStatusEnum.Processing);
|
||||||
|
|
||||||
if (typeCode.HasValue)
|
//if (typeCode.HasValue)
|
||||||
query = query.Where(t => t.TypeCode == typeCode.Value);
|
// query = query.Where(t => t.TypeCode == typeCode.Value);
|
||||||
|
|
||||||
var tasks = await query.OrderByDescending(t => t.DateCreated).ToListAsync();
|
//var tasks = await query.OrderByDescending(t => t.DateCreated).ToListAsync();
|
||||||
|
|
||||||
return mapper.Map<List<ActiveTask>>(tasks);
|
//return mapper.Map<List<ActiveTask>>(tasks);
|
||||||
|
|
||||||
|
return await TaskManagementHelpers.GetActiveTasksAsync(taskRepository, mapper, typeCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ namespace PARR.Core.Services.TaskServices.Implementations
|
|||||||
|
|
||||||
task.RetryCount++;
|
task.RetryCount++;
|
||||||
task.DateModified = DateTimeOffset.UtcNow;
|
task.DateModified = DateTimeOffset.UtcNow;
|
||||||
|
task.ProgressPercent = 0;
|
||||||
|
|
||||||
await taskRepository.CommitAsync();
|
await taskRepository.CommitAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
/// КЭШ рабочих групп и зон ответственности
|
/// КЭШ рабочих групп и зон ответственности
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CreateTemplateCacheAsync()
|
public async Task CreateTemplateCacheAsync(IProgressAsync<int> progress)
|
||||||
{
|
{
|
||||||
var dtStart = DateTimeOffset.UtcNow;
|
var dtStart = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
@@ -97,10 +97,25 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
|
|
||||||
logger.LogInformation("Получил из БД шаблонов {TemplateCount} шт.", templates.Count);
|
logger.LogInformation("Получил из БД шаблонов {TemplateCount} шт.", templates.Count);
|
||||||
|
|
||||||
|
//Ставим сразу 5% - получили из БД
|
||||||
|
await progress.ReportAsync(5);
|
||||||
|
|
||||||
|
|
||||||
|
if (templates.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Список шаблонов пуст, кэш не формируется");
|
||||||
|
await progress.ReportAsync(100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//2. Получить по каждому шаблону ЗО и РГ
|
//2. Получить по каждому шаблону ЗО и РГ
|
||||||
var cacheData = new List<TemplateCache>();
|
var cacheData = new List<TemplateCache>();
|
||||||
|
|
||||||
int templateCount = 0;
|
int templateCount = 0;
|
||||||
|
int totalTemplates = templates.Count;
|
||||||
|
// последний отправленный процент
|
||||||
|
int lastReportedPercent = 5;
|
||||||
|
|
||||||
foreach (var template in templates)
|
foreach (var template in templates)
|
||||||
{
|
{
|
||||||
var (workGroup, responseArea) = await ApplyShortcodesAsync(template);
|
var (workGroup, responseArea) = await ApplyShortcodesAsync(template);
|
||||||
@@ -116,7 +131,25 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
cacheData.Add(cacheItem);
|
cacheData.Add(cacheItem);
|
||||||
|
|
||||||
templateCount++;
|
templateCount++;
|
||||||
|
|
||||||
|
#region Расчет и отрпавка прогресса
|
||||||
|
// Считаем текущий процент(0 - 100). Этап идёт от 5% до 95%
|
||||||
|
var currentPercent = 5 + (int)((templateCount * 90.0) / totalTemplates);
|
||||||
|
|
||||||
|
// Округляем вниз до кратного 5 (33→30, 37→35, 99→95)
|
||||||
|
var roundedPercent = (currentPercent / 5) * 5;
|
||||||
|
|
||||||
|
// Отправляем, только если прогресс реально изменился (защита от дублей)
|
||||||
|
if (roundedPercent > lastReportedPercent && roundedPercent <= 95)
|
||||||
|
{
|
||||||
|
await progress.ReportAsync(roundedPercent);
|
||||||
|
lastReportedPercent = roundedPercent;
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
//95%
|
||||||
|
await progress.ReportAsync(95);
|
||||||
|
|
||||||
//3. Сохранить в Redis
|
//3. Сохранить в Redis
|
||||||
logger.LogDebug("Для сохранения в Redis подготовлен массив из {Count} строк", cacheData.Count);
|
logger.LogDebug("Для сохранения в Redis подготовлен массив из {Count} строк", cacheData.Count);
|
||||||
@@ -127,6 +160,8 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
await redisCacheService.SetHashFieldAsync(hashKey, item.Data.TemplateId.ToString(), item, cacheTemplateTtl);
|
await redisCacheService.SetHashFieldAsync(hashKey, item.Data.TemplateId.ToString(), item, cacheTemplateTtl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await progress.ReportAsync(100);
|
||||||
|
|
||||||
var duration = DateTimeOffset.UtcNow - dtStart;
|
var duration = DateTimeOffset.UtcNow - dtStart;
|
||||||
logger.LogInformation("Завершено формирование кэша. Key: {HashKey}, ttl: {Ttl}, кол-во записей: {CacheCount}. Продолжительность формирования КЭШ {Duration}", hashKey, cacheTemplateTtl, cacheData.Count, duration);
|
logger.LogInformation("Завершено формирование кэша. Key: {HashKey}, ttl: {Ttl}, кол-во записей: {CacheCount}. Продолжительность формирования КЭШ {Duration}", hashKey, cacheTemplateTtl, cacheData.Count, duration);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using AutoMapper;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
||||||
using PARR.Core.Services.NextRunServices;
|
using PARR.Core.Services.NextRunServices;
|
||||||
using PARR.Core.Services.TaskServices.Interfaces;
|
using PARR.Core.Services.TaskServices.Helpers;
|
||||||
using PARR.Core.Services.Workload.Interfaces;
|
using PARR.Core.Services.Workload.Interfaces;
|
||||||
using PARR.Core.Services.Workload.Models;
|
using PARR.Core.Services.Workload.Models;
|
||||||
using PARR.Domain.DTOs.TaskDTO;
|
|
||||||
using PARR.Domain.DTOs.Workload;
|
using PARR.Domain.DTOs.Workload;
|
||||||
using PARR.Domain.Enums;
|
using PARR.Domain.Enums;
|
||||||
|
|
||||||
@@ -14,19 +15,26 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
private readonly ILogger<WorkloadService> logger;
|
private readonly ILogger<WorkloadService> logger;
|
||||||
private readonly WorkloadCacheService workloadCacheService;
|
private readonly WorkloadCacheService workloadCacheService;
|
||||||
private readonly INextRunService nextRunService;
|
private readonly INextRunService nextRunService;
|
||||||
private readonly ITaskManagementService taskManagementService;
|
private readonly ITaskRepository taskRepository;
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
|
||||||
|
//private readonly ITaskManagementService taskManagementService;
|
||||||
|
|
||||||
public WorkloadService(
|
public WorkloadService(
|
||||||
ILogger<WorkloadService> logger,
|
ILogger<WorkloadService> logger,
|
||||||
WorkloadCacheService workloadCacheService,
|
WorkloadCacheService workloadCacheService,
|
||||||
INextRunService nextRunService,
|
INextRunService nextRunService,
|
||||||
ITaskManagementService taskManagementService
|
ITaskRepository taskRepository,
|
||||||
|
IMapper mapper
|
||||||
|
//ITaskManagementService taskManagementService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.workloadCacheService = workloadCacheService;
|
this.workloadCacheService = workloadCacheService;
|
||||||
this.nextRunService = nextRunService;
|
this.nextRunService = nextRunService;
|
||||||
this.taskManagementService = taskManagementService;
|
this.taskRepository = taskRepository;
|
||||||
|
this.mapper = mapper;
|
||||||
|
//this.taskManagementService = taskManagementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -122,14 +130,15 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
|
|
||||||
|
|
||||||
// 1. проверить что выполнились все задания на обновление кэш
|
// 1. проверить что выполнились все задания на обновление кэш
|
||||||
var activeTasks = await taskManagementService.GetActiveTasksAsync(TaskTypeEnum.Workload);
|
//var activeTasks = await taskManagementService.GetActiveTasksAsync(TaskTypeEnum.Workload);
|
||||||
|
var activeTasks = await TaskManagementHelpers.GetActiveTasksAsync(taskRepository, mapper, TaskTypeEnum.Workload);
|
||||||
if (activeTasks.Count > 0)
|
if (activeTasks.Count > 0)
|
||||||
{
|
{
|
||||||
// идет формирование отчета
|
// идет формирование отчета
|
||||||
// задача может быт только одна, так как у нее такой тип
|
// задача может быт только одна, так как у нее такой тип
|
||||||
var task = activeTasks.First();
|
var task = activeTasks.First();
|
||||||
|
|
||||||
return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.Processing, Date = task.DateCreated };
|
return new WorkloadCacheInfo { Status = WorkloadCacheInfo.StatusEnum.Processing, Date = task.DateCreated, ProgressPercent = task.ProgressPercent };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. получить дату кэш
|
// 2. получить дату кэш
|
||||||
|
|||||||
3818
PARR.DAL/Migrations/20260519233448_tblTaskItemAddProgressPercent.Designer.cs
generated
Normal file
3818
PARR.DAL/Migrations/20260519233448_tblTaskItemAddProgressPercent.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PARR.DAL.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class tblTaskItemAddProgressPercent : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ProgressPercent",
|
||||||
|
schema: "task",
|
||||||
|
table: "Tasks",
|
||||||
|
type: "integer",
|
||||||
|
nullable: true,
|
||||||
|
comment: "Процент выполнения задачи");
|
||||||
|
|
||||||
|
migrationBuilder.InsertData(
|
||||||
|
table: "ParrComponents",
|
||||||
|
columns: new[] { "Id", "Description", "Name" },
|
||||||
|
values: new object[] { 14, "Генератор кэша отчетов workload", "WorkloadBuilder" });
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
schema: "task",
|
||||||
|
table: "Types",
|
||||||
|
keyColumn: "Code",
|
||||||
|
keyValue: 0,
|
||||||
|
column: "MaxExecutionTimeMinutes",
|
||||||
|
value: 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "ParrComponents",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 14);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProgressPercent",
|
||||||
|
schema: "task",
|
||||||
|
table: "Tasks");
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
schema: "task",
|
||||||
|
table: "Types",
|
||||||
|
keyColumn: "Code",
|
||||||
|
keyValue: 0,
|
||||||
|
column: "MaxExecutionTimeMinutes",
|
||||||
|
value: 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ namespace PARR.DAL.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "7.0.20")
|
.HasAnnotation("ProductVersion", "9.0.16")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -754,6 +754,12 @@ namespace PARR.DAL.Migrations
|
|||||||
Id = 13,
|
Id = 13,
|
||||||
Description = "Генератор шаблонов",
|
Description = "Генератор шаблонов",
|
||||||
Name = "TemplateTaskGenerator"
|
Name = "TemplateTaskGenerator"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 14,
|
||||||
|
Description = "Генератор кэша отчетов workload",
|
||||||
|
Name = "WorkloadBuilder"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2406,6 +2412,10 @@ namespace PARR.DAL.Migrations
|
|||||||
b.Property<DateTimeOffset?>("ProcessedAt")
|
b.Property<DateTimeOffset?>("ProcessedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int?>("ProgressPercent")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("Процент выполнения задачи");
|
||||||
|
|
||||||
b.Property<int>("RetryCount")
|
b.Property<int>("RetryCount")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
@@ -2512,7 +2522,7 @@ namespace PARR.DAL.Migrations
|
|||||||
Code = 0,
|
Code = 0,
|
||||||
Description = "Формирование данных для отчетности - Загруженность",
|
Description = "Формирование данных для отчетности - Загруженность",
|
||||||
IsSingleton = true,
|
IsSingleton = true,
|
||||||
MaxExecutionTimeMinutes = 60,
|
MaxExecutionTimeMinutes = 90,
|
||||||
MaxRetries = 2,
|
MaxRetries = 2,
|
||||||
Name = "Workload",
|
Name = "Workload",
|
||||||
RetentionDays = 90
|
RetentionDays = 90
|
||||||
|
|||||||
@@ -15,24 +15,45 @@ namespace PARR.DAL.Repositories.TaskRepositories
|
|||||||
|
|
||||||
public async Task<int> TaskCaptureAsync(Guid taskId)
|
public async Task<int> TaskCaptureAsync(Guid taskId)
|
||||||
{
|
{
|
||||||
//var affectedRows = await EntityContext.Database.ExecuteSqlInterpolatedAsync($@"
|
//var sql = $@"
|
||||||
// UPDATE ""{DatabaseSchemas.Task}"".""Tasks""
|
// UPDATE ""{DatabaseSchemas.Task}"".""Tasks""
|
||||||
// SET ""{nameof(TaskItem.StatusCode)}""={(int)TaskItemStatusEnum.Processing},
|
// SET ""{nameof(TaskItem.StatusCode)}"" = {(int)TaskItemStatusEnum.Processing},
|
||||||
// ""{nameof(TaskItem.DateModified)}""={DateTimeOffset.UtcNow}
|
// ""{nameof(TaskItem.DateModified)}"" = {{0}}
|
||||||
// WHERE ""{nameof(TaskItem.Id)}""={taskId}
|
// WHERE ""{nameof(TaskItem.Id)}"" = {{1}}
|
||||||
// AND ""{nameof(TaskItem.StatusCode)}""={(int)TaskItemStatusEnum.Pending}
|
// AND ""{nameof(TaskItem.StatusCode)}"" = {(int)TaskItemStatusEnum.Pending}";
|
||||||
//");
|
|
||||||
|
|
||||||
var sql = $@"
|
//var affectedRows = await EntityContext.Database.ExecuteSqlRawAsync(sql, DateTimeOffset.UtcNow, taskId);
|
||||||
UPDATE ""{DatabaseSchemas.Task}"".""Tasks""
|
|
||||||
SET ""{nameof(TaskItem.StatusCode)}"" = {(int)TaskItemStatusEnum.Processing},
|
|
||||||
""{nameof(TaskItem.DateModified)}"" = {{0}}
|
|
||||||
WHERE ""{nameof(TaskItem.Id)}"" = {{1}}
|
|
||||||
AND ""{nameof(TaskItem.StatusCode)}"" = {(int)TaskItemStatusEnum.Pending}";
|
|
||||||
|
|
||||||
var affectedRows = await EntityContext.Database.ExecuteSqlRawAsync(sql, DateTimeOffset.UtcNow, taskId);
|
//return affectedRows;
|
||||||
|
|
||||||
return affectedRows;
|
return await EntityContext.Tasks.Where(t => t.Id == taskId && t.StatusCode == TaskItemStatusEnum.Pending)
|
||||||
|
.ExecuteUpdateAsync(t=>t
|
||||||
|
.SetProperty(p=>p.StatusCode, TaskItemStatusEnum.Processing)
|
||||||
|
.SetProperty(p => p.DateModified, DateTimeOffset.UtcNow)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<int> UpdateProgressAsync(Guid taskId, int percent)
|
||||||
|
{
|
||||||
|
// Валидация на уровне БД
|
||||||
|
if (percent < 0)
|
||||||
|
percent = 0;
|
||||||
|
|
||||||
|
if (percent > 100)
|
||||||
|
percent = 100;
|
||||||
|
|
||||||
|
//return await EntityContext.Database.ExecuteSqlInterpolatedAsync($@"
|
||||||
|
// UPDATE ""{DatabaseSchemas.Task}"".""Tasks""
|
||||||
|
// SET ""{nameof(TaskItem.ProgressPercent)}"" = {percent},
|
||||||
|
// ""{nameof(TaskItem.DateModified)}"" = {DateTimeOffset.UtcNow}
|
||||||
|
// WHERE ""{nameof(TaskItem.Id)}"" = {taskId}");
|
||||||
|
|
||||||
|
// Атомарное обновление
|
||||||
|
return await EntityContext.Tasks.Where(t => t.Id == taskId).ExecuteUpdateAsync(t => t
|
||||||
|
.SetProperty(x => x.ProgressPercent, percent)
|
||||||
|
//.SetProperty(x => x.DateModified, DateTimeOffset.UtcNow)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
public int RetryCount { get; init; }
|
public int RetryCount { get; init; }
|
||||||
|
|
||||||
public DateTimeOffset? ProcessedAt { get; init; }
|
public DateTimeOffset? ProcessedAt { get; init; }
|
||||||
|
|
||||||
|
public int? ProgressPercent { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -32,5 +32,7 @@ namespace PARR.Domain.DTOs.Workload
|
|||||||
public StatusEnum Status { get; init; }
|
public StatusEnum Status { get; init; }
|
||||||
|
|
||||||
public DateTimeOffset? Date { get; init; }
|
public DateTimeOffset? Date { get; init; }
|
||||||
|
|
||||||
|
public int? ProgressPercent { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ namespace PARR.Domain.Entities.TaskEntities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTimeOffset? ProcessedAt { get; set; }
|
public DateTimeOffset? ProcessedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Процент выполнения задачи (0-100).
|
||||||
|
/// null = ожидает начала, 100 = успешно завершена.
|
||||||
|
/// При ошибке сохраняется последнее значение.
|
||||||
|
/// </summary>
|
||||||
|
[Comment("Процент выполнения задачи")]
|
||||||
|
public int? ProgressPercent { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public string? InitiatorIp { get; set; }
|
public string? InitiatorIp { get; set; }
|
||||||
public ParrComponentsEnum? InitiatorParrComponentId { get; set; }
|
public ParrComponentsEnum? InitiatorParrComponentId { get; set; }
|
||||||
|
|||||||
Reference in New Issue
Block a user