feat(api,core,domain): Получение снапшотов работы роботов

This commit is contained in:
Mikhail Trubnikov
2026-06-11 11:40:12 +10:00
parent d736208a46
commit 7d26f1cf55
13 changed files with 187 additions and 25 deletions

View File

@@ -258,6 +258,7 @@
public static class StatRobotSnapshot public static class StatRobotSnapshot
{ {
public const string GetAll = BaseStat + "/robot-snapshots/";
public const string Create = BaseStat + "/robot-snapshots/"; public const string Create = BaseStat + "/robot-snapshots/";
} }

View File

@@ -0,0 +1,15 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public record StatRobotSnapshotQuery
{
/// <summary>
/// IP робота
/// </summary>
public string? Ip { get; init; }
/// <summary>
/// Кол-во минут от текущего времени, по умолчнию 12 часов.
/// </summary>
public int Minutes { get; init; } = 12 * 60;
}
}

View File

@@ -1,6 +1,13 @@
namespace PARR.API.Contracts.V1.Responses.Statistics namespace PARR.API.Contracts.V1.Responses.Statistics
{ {
public record StatRobotSnapshotItemResponse public record StatRobotSnapshotItemResponse
{
public required UserBaseResponse Robot { get; init; }
public List<StatRobotSnapshotResponse> Snapshots { get; init; } = new();
}
public record StatRobotSnapshotResponse
{ {
public Guid Id { get; init; } public Guid Id { get; init; }
@@ -11,7 +18,5 @@
public int ScheduleRobotsCount { get; init; } public int ScheduleRobotsCount { get; init; }
public int MaxRobots { get; init; } public int MaxRobots { get; init; }
public required string Ip { get; init; }
} }
} }

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1; using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Requests;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Contracts.V1.Responses.Statistics; using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.Controllers.V1.Base; using PARR.API.Controllers.V1.Base;
@@ -13,7 +14,7 @@ using PARR.Domain.DTOs.RobotSnapshotDTO;
namespace PARR.API.Controllers.V1.Statistics namespace PARR.API.Controllers.V1.Statistics
{ {
[Authorize(Roles = ParrRoles.Administrator.Role)] [Authorize]
public class StatRobotSnapshotController : BaseApiController public class StatRobotSnapshotController : BaseApiController
{ {
private readonly IRobotSnapshotService robotSnapshotService; private readonly IRobotSnapshotService robotSnapshotService;
@@ -31,6 +32,28 @@ namespace PARR.API.Controllers.V1.Statistics
this.clientService = clientService; this.clientService = clientService;
} }
/// <summary>
/// Получить снапшоты роботов.
/// </summary>
/// <returns></returns>
[Authorize(Roles = ParrRoles.Administrator.Role)]
[HttpGet(ApiRoutes.StatRobotSnapshot.GetAll)]
public async Task<IActionResult> GetAll([FromQuery] StatRobotSnapshotQuery query)
{
var queryDto = mapper.Map<RobotSnapshotQuery>(query);
var snapshots = await robotSnapshotService.GetAsync(queryDto);
if (snapshots.Count == 0)
return NoContent();
var response = mapper.Map<List<StatRobotSnapshotItemResponse>>(snapshots);
return Ok(new Response<List<StatRobotSnapshotItemResponse>>(response, true));
}
/// <summary> /// <summary>
/// Отправить снапшот роботов. /// Отправить снапшот роботов.
/// Доступ только с ролью "Робот ЕСПП" /// Доступ только с ролью "Робот ЕСПП"

View File

@@ -9,6 +9,7 @@ using PARR.Domain.DTOs.RobotSnapshotDTO;
using PARR.Domain.DTOs.RobotTask; using PARR.Domain.DTOs.RobotTask;
using PARR.Domain.DTOs.Shortcode; using PARR.Domain.DTOs.Shortcode;
using PARR.Domain.DTOs.TaskDTO; using PARR.Domain.DTOs.TaskDTO;
using PARR.Domain.DTOs.User;
using PARR.Domain.DTOs.Workload; using PARR.Domain.DTOs.Workload;
using PARR.Domain.Entities; using PARR.Domain.Entities;
using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.Base.History;
@@ -312,6 +313,8 @@ namespace PARR.API.MappingProfiles
CreateMap<UserCache, UserProfileResponse>(); CreateMap<UserCache, UserProfileResponse>();
CreateMap<UserBaseDto, UserBaseResponse>();
#endregion #endregion
#region Order #region Order
@@ -486,8 +489,13 @@ namespace PARR.API.MappingProfiles
#endregion #endregion
#region RobotSnapshot
CreateMap<RobotSnapshotItem, StatRobotSnapshotItemResponse>(); CreateMap<RobotSnapshotDto, StatRobotSnapshotResponse>();
CreateMap<RobotSnapshotItemDto, StatRobotSnapshotItemResponse>();
#endregion
} }
} }

View File

@@ -2,6 +2,7 @@
using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Requests;
using PARR.API.Contracts.V1.Requests.Queries; using PARR.API.Contracts.V1.Requests.Queries;
using PARR.Domain.Common.Pagination; using PARR.Domain.Common.Pagination;
using PARR.Domain.DTOs.RobotSnapshotDTO;
using PARR.Domain.Entities.Job; using PARR.Domain.Entities.Job;
namespace PARR.API.MappingProfiles namespace PARR.API.MappingProfiles
@@ -45,6 +46,8 @@ namespace PARR.API.MappingProfiles
CreateMap<RelationshipFilterRequest, JobRelationshipFilter>(); CreateMap<RelationshipFilterRequest, JobRelationshipFilter>();
#endregion #endregion
CreateMap<StatRobotSnapshotQuery, RobotSnapshotQuery>();
} }
} }
} }

View File

@@ -7,7 +7,7 @@ namespace PARR.Core.Infrastructure.Mapping.RobotSnapshot
{ {
public RobotSnapshotMappingProfile() public RobotSnapshotMappingProfile()
{ {
CreateMap<PARR.Domain.Entities.RobotEntities.RobotSnapshot, RobotSnapshotItem>(); CreateMap<PARR.Domain.Entities.RobotEntities.RobotSnapshot, RobotSnapshotDto>();
} }
} }
} }

View File

@@ -4,6 +4,8 @@ namespace PARR.Core.Services.RobotSnapshotServices
{ {
public interface IRobotSnapshotService public interface IRobotSnapshotService
{ {
Task<RobotSnapshotItem> CreateAsync(CreateRobotSnapshot robotSnapshot); Task<List<RobotSnapshotItemDto>> GetAsync(RobotSnapshotQuery queryDto);
Task<RobotSnapshotItemDto> CreateAsync(CreateRobotSnapshot robotSnapshot);
} }
} }

View File

@@ -1,6 +1,9 @@
using AutoMapper; using AutoMapper;
using Microsoft.EntityFrameworkCore;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.RobotRepositories; using PARR.Core.Repositories.Interfaces.RobotRepositories;
using PARR.Domain.DTOs.RobotSnapshotDTO; using PARR.Domain.DTOs.RobotSnapshotDTO;
using PARR.Domain.DTOs.User;
using PARR.Domain.Entities.RobotEntities; using PARR.Domain.Entities.RobotEntities;
using PARR.Domain.Exceptions; using PARR.Domain.Exceptions;
@@ -9,19 +12,63 @@ namespace PARR.Core.Services.RobotSnapshotServices
internal class RobotSnapshotService : IRobotSnapshotService internal class RobotSnapshotService : IRobotSnapshotService
{ {
private readonly IRobotSnapshotRepository robotSnapshotRepository; private readonly IRobotSnapshotRepository robotSnapshotRepository;
private readonly IUserRepository userRepository;
private readonly IMapper mapper; private readonly IMapper mapper;
public RobotSnapshotService( public RobotSnapshotService(
IRobotSnapshotRepository robotSnapshotRepository, IRobotSnapshotRepository robotSnapshotRepository,
IUserRepository userRepository,
IMapper mapper IMapper mapper
) )
{ {
this.robotSnapshotRepository = robotSnapshotRepository; this.robotSnapshotRepository = robotSnapshotRepository;
this.userRepository = userRepository;
this.mapper = mapper; this.mapper = mapper;
} }
public async Task<RobotSnapshotItem> CreateAsync(CreateRobotSnapshot robotSnapshot) public async Task<List<RobotSnapshotItemDto>> GetAsync(RobotSnapshotQuery queryDto)
{
// Просто возвращаем данные из БД, не проверяем что есть промежутки между которыми не было данных
// Т.е это просто чтоб посмотреть что есть в БД
var minutes = queryDto.Minutes > 0 ? queryDto.Minutes : 60 * 12;
var dateEnd = DateTimeOffset.UtcNow;
var dateStart = dateEnd.AddMinutes(-minutes);
var query = robotSnapshotRepository.Get().AsNoTracking()
.Where(t => dateStart <= t.DateCreated && t.DateCreated <= dateEnd);
if (!string.IsNullOrEmpty(queryDto.Ip))
query = query.Where(t => t.Ip == queryDto.Ip);
var groupingSnapshots = (await query.ToListAsync()).GroupBy(t => t.Ip);
var robotIps = groupingSnapshots.Select(t => t.Key).ToHashSet();
var robotList = await userRepository.Get()
.AsNoTracking()
.Where(t => robotIps.Contains(t.Ip))
.ToDictionaryAsync(t => t.Ip);
var result = new List<RobotSnapshotItemDto>();
foreach (var item in groupingSnapshots)
{
robotList.TryGetValue(item.Key, out var robot);
var userDto = new UserBaseDto { Ip = item.Key, Description = robot?.Description ?? string.Empty, Name = robot?.Name ?? string.Empty };
var snapshots = mapper.Map<List<RobotSnapshotDto>>(item).OrderBy(t => t.DateCreated).ToList();
result.Add(new RobotSnapshotItemDto { Robot = userDto, Snapshots = snapshots });
}
return result.OrderBy(t => t.Robot.Ip).ThenBy(t => t.Robot.Name).ToList();
}
public async Task<RobotSnapshotItemDto> CreateAsync(CreateRobotSnapshot robotSnapshot)
{ {
var snapshot = new RobotSnapshot var snapshot = new RobotSnapshot
{ {
@@ -38,7 +85,16 @@ namespace PARR.Core.Services.RobotSnapshotServices
if (!createdResult || !commitResult) if (!createdResult || !commitResult)
throw new DbErrorException("Ошибка сохранения в БД"); throw new DbErrorException("Ошибка сохранения в БД");
return mapper.Map<RobotSnapshotItem>(snapshot); var user = await userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
var userDto = new UserBaseDto { Ip = robotSnapshot.Ip, Description = user?.Description ?? string.Empty, Name = user?.Name ?? string.Empty };
var result = new RobotSnapshotItemDto
{
Robot = userDto,
Snapshots = mapper.Map<List<RobotSnapshotDto>>(new List<RobotSnapshot> { snapshot })
};
return result;
} }

View File

@@ -1,17 +0,0 @@
namespace PARR.Domain.DTOs.RobotSnapshotDTO
{
public record RobotSnapshotItem
{
public Guid Id { get; init; }
public DateTimeOffset DateCreated { get; init; }
public int TemplateRobotsCount { get; init; }
public int ScheduleRobotsCount { get; init; }
public int MaxRobots { get; init; }
public required string Ip { get; init; }
}
}

View File

@@ -0,0 +1,26 @@

using PARR.Domain.DTOs.User;
namespace PARR.Domain.DTOs.RobotSnapshotDTO
{
public record RobotSnapshotItemDto
{
public required UserBaseDto Robot { get; init; }
public List<RobotSnapshotDto> Snapshots { get; init; } = new();
}
public record RobotSnapshotDto
{
public Guid Id { get; init; }
public DateTimeOffset DateCreated { get; init; }
public int TemplateRobotsCount { get; init; }
public int ScheduleRobotsCount { get; init; }
public int MaxRobots { get; init; }
}
}

View File

@@ -0,0 +1,15 @@
namespace PARR.Domain.DTOs.RobotSnapshotDTO
{
public record RobotSnapshotQuery
{
/// <summary>
/// IP робота
/// </summary>
public string? Ip { get; init; }
/// <summary>
/// Кол-во минут от текущего времени
/// </summary>
public int Minutes { get; init; }
}
}

View File

@@ -0,0 +1,25 @@
namespace PARR.Domain.DTOs.User
{
//todo: Не делал маппингов
public record UserDto : UserBaseDto
{
public Guid Id { get; set; }
public DateTimeOffset DateCreated { get; set; }
public DateTimeOffset? LastLogon { get; set; }
//todo:
//public List<RoleResponse>? Roles { get; set; }
}
public record UserBaseDto
{
public required string Ip { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
}