#650 Актуализировать работу с технологическими сообщениями под Event Service #10

Closed
rs.efremov wants to merge 3 commits from TechMessageRework into master
13 changed files with 343 additions and 294 deletions

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Persistence.Models; using Persistence.Models;
using Persistence.Models.Requests; using Persistence.Models.Requests;
@ -44,17 +44,17 @@ public class TechMessagesController : ControllerBase
return Ok(result); return Ok(result);
} }
/// <summary> /// <summary>
/// Получить статистику по системам /// Получить статистику по системам
/// </summary> /// </summary>
/// <param name="autoDrillingSystem"></param> /// <param name="autoDrillingSystem"></param>
/// <param name="categoryIds"></param> /// <param name="categoryIds"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("statistics")] [HttpGet("statistics")]
public async Task<ActionResult<IEnumerable<MessagesStatisticDto>>> GetStatistics([FromQuery] IEnumerable<string> autoDrillingSystem, [FromQuery] IEnumerable<int> categoryIds, CancellationToken token) public async Task<ActionResult<IEnumerable<MessagesStatisticDto>>> GetStatistics([FromQuery] IEnumerable<Guid> autoDrillingSystem, [FromQuery] IEnumerable<int> categoryIds, CancellationToken token)
{ {
var result = await techMessagesRepository.GetStatistics(autoDrillingSystem, categoryIds, token); var result = await techMessagesRepository.GetStatistics(autoDrillingSystem, categoryIds, token);
return Ok(result); return Ok(result);
} }
@ -100,19 +100,20 @@ public class TechMessagesController : ControllerBase
return Ok(result); return Ok(result);
} }
/// <summary> /// <summary>
/// Добавить новые технологические сообщения /// Добавить новые технологические сообщения
/// </summary> /// </summary>
/// <param name="dtos"></param> /// <param name="systemId"></param>
/// <param name="token"></param> /// <param name="dtos"></param>
/// <returns></returns> /// <param name="token"></param>
[HttpPost] /// <returns></returns>
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] [HttpPost("{systemId}")]
public async Task<IActionResult> AddRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token) [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
{ public async Task<IActionResult> AddRange([FromRoute] Guid systemId, [FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
var userId = User.GetUserId<Guid>(); {
var userId = User.GetUserId<Guid>();
var result = await techMessagesRepository.AddRange(dtos, userId, token); var result = await techMessagesRepository.AddRange(systemId, dtos, userId, token);
return CreatedAtAction(nameof(AddRange), result); return CreatedAtAction(nameof(AddRange), result);
} }

View File

@ -14,7 +14,7 @@ public interface ITechMessagesClient : IDisposable
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> AddRange(IEnumerable<TechMessageDto> dtos, CancellationToken token); Task<int> AddRange(Guid systemId, IEnumerable<TechMessageDto> dtos, CancellationToken token);
/// <summary> /// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории /// Получить диапазон дат, для которых есть данные в репозитории
@ -43,16 +43,16 @@ public interface ITechMessagesClient : IDisposable
/// <summary> /// <summary>
/// Получить статистику по системам /// Получить статистику по системам
/// </summary> /// </summary>
/// <param name="autoDrillingSystem"></param> /// <param name="systemIds"></param>
/// <param name="categoryId"></param> /// <param name="categoryId"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<MessagesStatisticDto>> GetStatistics(string autoDrillingSystem, int categoryId, CancellationToken token); Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<Guid> systemIds, IEnumerable<int> categoryIds, CancellationToken token);
/// <summary> /// <summary>
/// Получить список всех систем /// Получить список всех систем
/// </summary> /// </summary>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<string>> GetSystems(CancellationToken token); Task<IEnumerable<DrillingSystemDto>> GetSystems(CancellationToken token);
} }

View File

@ -1,4 +1,5 @@
using Persistence.Models; using Microsoft.AspNetCore.Mvc;
using Persistence.Models;
using Persistence.Models.Requests; using Persistence.Models.Requests;
using Refit; using Refit;
@ -11,11 +12,11 @@ namespace Persistence.Client.Clients.Interfaces.Refit
[Get($"{BaseRoute}")] [Get($"{BaseRoute}")]
Task<IApiResponse<PaginationContainer<TechMessageDto>>> GetPage([Query] PaginationRequest request, CancellationToken token); Task<IApiResponse<PaginationContainer<TechMessageDto>>> GetPage([Query] PaginationRequest request, CancellationToken token);
[Post($"{BaseRoute}")] [Post($"{BaseRoute}/{{systemId}}")]
Task<IApiResponse<int>> AddRange([Body] IEnumerable<TechMessageDto> dtos, CancellationToken token); Task<IApiResponse<int>> AddRange(Guid systemId, [Body] IEnumerable<TechMessageDto> dtos, CancellationToken token);
[Get($"{BaseRoute}/systems")] [Get($"{BaseRoute}/systems")]
Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token); Task<IApiResponse<IEnumerable<DrillingSystemDto>>> GetSystems(CancellationToken token);
[Get($"{BaseRoute}/range")] [Get($"{BaseRoute}/range")]
Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token); Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token);
@ -24,6 +25,6 @@ namespace Persistence.Client.Clients.Interfaces.Refit
Task<IApiResponse<IEnumerable<TechMessageDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token); Task<IApiResponse<IEnumerable<TechMessageDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
[Get($"{BaseRoute}/statistics")] [Get($"{BaseRoute}/statistics")]
Task<IApiResponse<IEnumerable<MessagesStatisticDto>>> GetStatistics([Query] string autoDrillingSystem, [Query] int categoryId, CancellationToken token); Task<IApiResponse<IEnumerable<MessagesStatisticDto>>> GetStatistics([Query] IEnumerable<Guid> systemIds, [Query] IEnumerable<int> categoryIds, CancellationToken token);
} }
} }

View File

@ -24,17 +24,17 @@ public class TechMessagesClient : BaseClient, ITechMessagesClient
return result; return result;
} }
public async Task<int> AddRange(IEnumerable<TechMessageDto> dtos, CancellationToken token) public async Task<int> AddRange(Guid systemId, IEnumerable<TechMessageDto> dtos, CancellationToken token)
{ {
var result = await ExecutePostResponse( var result = await ExecutePostResponse(
async () => await refitTechMessagesClient.AddRange(dtos, token), token); async () => await refitTechMessagesClient.AddRange(systemId, dtos, token), token);
return result; return result;
} }
public async Task<IEnumerable<string>> GetSystems(CancellationToken token) public async Task<IEnumerable<DrillingSystemDto>> GetSystems(CancellationToken token)
{ {
var result = await ExecuteGetResponse<IEnumerable<string>>( var result = await ExecuteGetResponse<IEnumerable<DrillingSystemDto>>(
async () => await refitTechMessagesClient.GetSystems(token), token); async () => await refitTechMessagesClient.GetSystems(token), token);
return result; return result;
@ -56,10 +56,10 @@ public class TechMessagesClient : BaseClient, ITechMessagesClient
return result; return result;
} }
public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(string autoDrillingSystem, int categoryId, CancellationToken token) public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<Guid> systemIds, IEnumerable<int> categoryIds, CancellationToken token)
{ {
var result = await ExecuteGetResponse<IEnumerable<MessagesStatisticDto>>( var result = await ExecuteGetResponse<IEnumerable<MessagesStatisticDto>>(
async () => await refitTechMessagesClient.GetStatistics(autoDrillingSystem, categoryId, token), token); async () => await refitTechMessagesClient.GetStatistics(systemIds, categoryIds, token), token);
return result; return result;
} }

View File

@ -9,10 +9,10 @@ using Persistence.Database.Model;
#nullable disable #nullable disable
namespace Persistence.Database.Postgres.Migrations namespace Persistence.Database.Postgres.Migrations.PersistencePostgres
{ {
[DbContext(typeof(PersistenceDbContext))] [DbContext(typeof(PersistencePostgresContext))]
[Migration("20241202072250_TechMessageMigration")] [Migration("20241212041758_TechMessageMigration")]
partial class TechMessageMigration partial class TechMessageMigration
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -48,6 +48,30 @@ namespace Persistence.Database.Postgres.Migrations
b.ToTable("DrillingSystem"); b.ToTable("DrillingSystem");
}); });
modelBuilder.Entity("Persistence.Database.Entity.ParameterData", b =>
{
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор системы");
b.Property<int>("ParameterId")
.HasColumnType("integer")
.HasComment("Id параметра");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone")
.HasComment("Временная отметка");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("varchar(256)")
.HasComment("Значение параметра в виде строки");
b.HasKey("DiscriminatorId", "ParameterId", "Timestamp");
b.ToTable("ParameterData");
});
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{ {
b.Property<Guid>("EventId") b.Property<Guid>("EventId")
@ -59,27 +83,23 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasComment("Id Категории важности"); .HasComment("Id Категории важности");
b.Property<double?>("Depth") b.Property<int>("EventState")
.HasColumnType("double precision") .HasColumnType("integer")
.HasComment("Глубина забоя"); .HasComment("Статус события");
b.Property<string>("MessageText") b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы, к которой относится сообщение");
b.Property<string>("Text")
.IsRequired() .IsRequired()
.HasColumnType("varchar(512)") .HasColumnType("varchar(512)")
.HasComment("Текст сообщения"); .HasComment("Текст сообщения");
b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы автобурения, к которой относится сообщение");
b.Property<DateTimeOffset>("Timestamp") b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата возникновения"); .HasComment("Дата возникновения");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasComment("Id пользователя за пультом бурильщика");
b.HasKey("EventId"); b.HasKey("EventId");
b.HasIndex("SystemId"); b.HasIndex("SystemId");
@ -110,6 +130,59 @@ namespace Persistence.Database.Postgres.Migrations
}); });
}); });
modelBuilder.Entity("Persistence.Database.Model.ChangeLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Ключ записи");
b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи");
b.Property<double>("DepthEnd")
.HasColumnType("double precision")
.HasComment("Глубина забоя на дату окончания интервала");
b.Property<double>("DepthStart")
.HasColumnType("double precision")
.HasComment("Глубина забоя на дату начала интервала");
b.Property<Guid>("IdAuthor")
.HasColumnType("uuid")
.HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor")
.HasColumnType("uuid")
.HasComment("Редактор");
b.Property<Guid?>("IdNext")
.HasColumnType("uuid")
.HasComment("Id заменяющей записи");
b.Property<Guid>("IdSection")
.HasColumnType("uuid")
.HasComment("Ключ секции");
b.Property<DateTimeOffset?>("Obsolete")
.HasColumnType("timestamp with time zone")
.HasComment("Дата устаревания (например при удалении)");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Значение");
b.HasKey("Id");
b.ToTable("ChangeLog");
});
modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
{ {
b.Property<DateTimeOffset>("Date") b.Property<DateTimeOffset>("Date")

View File

@ -2,7 +2,7 @@
#nullable disable #nullable disable
namespace Persistence.Database.Postgres.Migrations namespace Persistence.Database.Postgres.Migrations.PersistencePostgres
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class TechMessageMigration : Migration public partial class TechMessageMigration : Migration
@ -30,10 +30,9 @@ namespace Persistence.Database.Postgres.Migrations
EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"), EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
CategoryId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"), CategoryId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"), Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"), Text = table.Column<string>(type: "varchar(512)", nullable: false, comment: "Текст сообщения"),
MessageText = table.Column<string>(type: "varchar(512)", nullable: false, comment: "Текст сообщения"), SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы, к которой относится сообщение"),
SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения, к которой относится сообщение"), EventState = table.Column<int>(type: "integer", nullable: false, comment: "Статус события")
UserId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика")
}, },
constraints: table => constraints: table =>
{ {

View File

@ -1,6 +1,5 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@ -48,8 +47,8 @@ namespace Persistence.Database.Postgres.Migrations
modelBuilder.Entity("Persistence.Database.Entity.ParameterData", b => modelBuilder.Entity("Persistence.Database.Entity.ParameterData", b =>
{ {
b.Property<int>("DiscriminatorId") b.Property<Guid>("DiscriminatorId")
.HasColumnType("integer") .HasColumnType("uuid")
.HasComment("Дискриминатор системы"); .HasComment("Дискриминатор системы");
b.Property<int>("ParameterId") b.Property<int>("ParameterId")
@ -81,27 +80,23 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasComment("Id Категории важности"); .HasComment("Id Категории важности");
b.Property<double?>("Depth") b.Property<int>("EventState")
.HasColumnType("double precision") .HasColumnType("integer")
.HasComment("Глубина забоя"); .HasComment("Статус события");
b.Property<string>("MessageText") b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы, к которой относится сообщение");
b.Property<string>("Text")
.IsRequired() .IsRequired()
.HasColumnType("varchar(512)") .HasColumnType("varchar(512)")
.HasComment("Текст сообщения"); .HasComment("Текст сообщения");
b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы автобурения, к которой относится сообщение");
b.Property<DateTimeOffset>("Timestamp") b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата возникновения"); .HasComment("Дата возникновения");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasComment("Id пользователя за пультом бурильщика");
b.HasKey("EventId"); b.HasKey("EventId");
b.HasIndex("SystemId"); b.HasIndex("SystemId");
@ -137,48 +132,48 @@ namespace Persistence.Database.Postgres.Migrations
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("Id"); .HasComment("Ключ записи");
b.Property<DateTimeOffset>("Creation") b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("Creation"); .HasComment("Дата создания записи");
b.Property<double>("DepthEnd") b.Property<double>("DepthEnd")
.HasColumnType("double precision") .HasColumnType("double precision")
.HasColumnName("DepthEnd"); .HasComment("Глубина забоя на дату окончания интервала");
b.Property<double>("DepthStart") b.Property<double>("DepthStart")
.HasColumnType("double precision") .HasColumnType("double precision")
.HasColumnName("DepthStart"); .HasComment("Глубина забоя на дату начала интервала");
b.Property<Guid>("IdAuthor") b.Property<Guid>("IdAuthor")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("IdAuthor"); .HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator") b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("IdDiscriminator"); .HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor") b.Property<Guid?>("IdEditor")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("IdEditor"); .HasComment("Редактор");
b.Property<Guid?>("IdNext") b.Property<Guid?>("IdNext")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("IdNext"); .HasComment("Id заменяющей записи");
b.Property<Guid>("IdSection") b.Property<Guid>("IdSection")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("IdSection"); .HasComment("Ключ секции");
b.Property<DateTimeOffset?>("Obsolete") b.Property<DateTimeOffset?>("Obsolete")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("Obsolete"); .HasComment("Дата устаревания (например при удалении)");
b.Property<IDictionary<string, object>>("Value") b.Property<string>("Value")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasColumnName("Value"); .HasComment("Значение");
b.HasKey("Id"); b.HasKey("Id");

View File

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -15,19 +15,16 @@ namespace Persistence.Database.Entity
[Comment("Дата возникновения")] [Comment("Дата возникновения")]
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
[Comment("Глубина забоя")] [Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
public double? Depth { get; set; } public required string Text { get; set; }
[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")] [Required, Comment("Id системы, к которой относится сообщение")]
public required string MessageText { get; set; } public required Guid SystemId { get; set; }
[Required, Comment("Id системы автобурения, к которой относится сообщение")]
public required Guid SystemId { get; set; }
[Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")] [Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")]
public virtual required DrillingSystem System { get; set; } public virtual required DrillingSystem System { get; set; }
[Comment("Id пользователя за пультом бурильщика")] [Comment("Статус события")]
public Guid UserId { get; set; } public int EventState { get; set; }
} }
} }

View File

@ -4,6 +4,7 @@ using Persistence.Client;
using Persistence.Client.Clients.Interfaces; using Persistence.Client.Clients.Interfaces;
using Persistence.Database.Entity; using Persistence.Database.Entity;
using Persistence.Models; using Persistence.Models;
using Persistence.Models.Enumerations;
using Persistence.Models.Requests; using Persistence.Models.Requests;
using System.Net; using System.Net;
using Xunit; using Xunit;
@ -50,18 +51,18 @@ namespace Persistence.IntegrationTests.Controllers
Assert.Equal(requestDto.Take, response.Take); Assert.Equal(requestDto.Take, response.Take);
} }
[Fact] [Fact]
public async Task GetPage_AfterSave_returns_success() public async Task GetPage_AfterSave_returns_success()
{ {
//arrange //arrange
var dtos = await InsertRange(); var dtos = await InsertRange(Guid.NewGuid());
var dtosCount = dtos.Count(); var dtosCount = dtos.Count();
var requestDto = new PaginationRequest() var requestDto = new PaginationRequest()
{ {
Skip = 0, Skip = 0,
Take = 2, Take = 2,
SortSettings = nameof(TechMessage.CategoryId) SortSettings = nameof(TechMessage.CategoryId)
}; };
//act //act
var response = await techMessagesClient.GetPage(requestDto, CancellationToken.None); var response = await techMessagesClient.GetPage(requestDto, CancellationToken.None);
@ -71,17 +72,18 @@ namespace Persistence.IntegrationTests.Controllers
Assert.Equal(dtosCount, response.Count); Assert.Equal(dtosCount, response.Count);
} }
[Fact] [Fact]
public async Task InsertRange_returns_success() public async Task InsertRange_returns_success()
{ {
await InsertRange(); await InsertRange(Guid.NewGuid());
} }
[Fact] [Fact]
public async Task InsertRange_returns_BadRequest() public async Task InsertRange_returns_BadRequest()
{ {
//arrange //arrange
const string exceptionMessage = "Ошибка валидации, формата или маршрутизации запроса"; const string exceptionMessage = "Ошибка валидации, формата или маршрутизации запроса";
var systemId = Guid.NewGuid();
var dtos = new List<TechMessageDto>() var dtos = new List<TechMessageDto>()
{ {
new TechMessageDto() new TechMessageDto()
@ -89,17 +91,15 @@ namespace Persistence.IntegrationTests.Controllers
EventId = Guid.NewGuid(), EventId = Guid.NewGuid(),
CategoryId = -1, // < 0 CategoryId = -1, // < 0
Timestamp = DateTimeOffset.UtcNow, Timestamp = DateTimeOffset.UtcNow,
Depth = -1, // < 0 Text = string.Empty, // length < 0
MessageText = string.Empty, // length < 0 EventState = EventState.Triggered
System = string.Concat(Enumerable.Repeat(nameof(TechMessageDto.System), 100)), // length > 256 }
UserId = Guid.NewGuid() };
}
};
try try
{ {
//act //act
var response = await techMessagesClient.AddRange(dtos, new CancellationToken()); var response = await techMessagesClient.AddRange(systemId, dtos, new CancellationToken());
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -124,23 +124,19 @@ namespace Persistence.IntegrationTests.Controllers
Assert.Empty(response); Assert.Empty(response);
} }
[Fact] [Fact]
public async Task GetSystems_AfterSave_returns_success() public async Task GetSystems_AfterSave_returns_success()
{ {
//arrange //arrange
var dtos = await InsertRange(); var dtos = await InsertRange(Guid.NewGuid());
var systems = dtos
.Select(e => e.System)
.Distinct()
.ToArray();
//act //act
var response = await techMessagesClient.GetSystems(CancellationToken.None); var response = await techMessagesClient.GetSystems(CancellationToken.None);
//assert //assert
Assert.NotNull(response); Assert.NotNull(response);
string?[]? content = response?.ToArray(); var expectedSystemCount = 1;
Assert.Equal(systems, content); Assert.Equal(expectedSystemCount, response!.Count());
} }
[Fact] [Fact]
@ -151,34 +147,35 @@ namespace Persistence.IntegrationTests.Controllers
dbContext.CleanupDbSet<TechMessage>(); dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>(); dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
var importantId = 1; var imortantIds = new [] { 1 };
var autoDrillingSystem = nameof(TechMessageDto.System); var systemIds = new [] { Guid.NewGuid() };
//act //act
var response = await techMessagesClient.GetStatistics(autoDrillingSystem, importantId, CancellationToken.None); var response = await techMessagesClient.GetStatistics(systemIds, imortantIds, CancellationToken.None);
//assert //assert
Assert.NotNull(response); Assert.NotNull(response);
Assert.Empty(response); Assert.Empty(response);
} }
[Fact] [Fact]
public async Task GetStatistics_AfterSave_returns_success() public async Task GetStatistics_AfterSave_returns_success()
{ {
//arrange //arrange
var importantId = 0; var categoryIds = new[] { 1 };
var autoDrillingSystem = nameof(TechMessageDto.System); var systemId = Guid.NewGuid();
var dtos = await InsertRange(); var dtos = await InsertRange(systemId);
var filteredDtos = dtos.Where(e => e.CategoryId == importantId && e.System == autoDrillingSystem); var filteredDtos = dtos.Where(e => categoryIds.Contains(e.CategoryId));
//act //act
var response = await techMessagesClient.GetStatistics(autoDrillingSystem, importantId, CancellationToken.None); var response = await techMessagesClient.GetStatistics([systemId], categoryIds, CancellationToken.None);
//assert //assert
Assert.NotNull(response); Assert.NotNull(response);
Assert.NotEmpty(response);
var categories = response var categories = response
.FirstOrDefault()?.Categories .FirstOrDefault()!.Categories
.FirstOrDefault(e => e.Key == 0).Value; .Count();
Assert.Equal(filteredDtos.Count(), categories); Assert.Equal(filteredDtos.Count(), categories);
} }
@ -199,11 +196,11 @@ namespace Persistence.IntegrationTests.Controllers
Assert.Equal(DateTimeOffset.MaxValue, response?.To); Assert.Equal(DateTimeOffset.MaxValue, response?.To);
} }
[Fact] [Fact]
public async Task GetDatesRange_AfterSave_returns_success() public async Task GetDatesRange_AfterSave_returns_success()
{ {
//arrange //arrange
await InsertRange(); await InsertRange(Guid.NewGuid());
//act //act
var response = await techMessagesClient.GetDatesRangeAsync(CancellationToken.None); var response = await techMessagesClient.GetDatesRangeAsync(CancellationToken.None);
@ -229,13 +226,13 @@ namespace Persistence.IntegrationTests.Controllers
Assert.Empty(response); Assert.Empty(response);
} }
[Fact] [Fact]
public async Task GetPart_AfterSave_returns_success() public async Task GetPart_AfterSave_returns_success()
{ {
//arrange //arrange
var dateBegin = DateTimeOffset.UtcNow; var dateBegin = DateTimeOffset.UtcNow;
var take = 1; var take = 1;
await InsertRange(); await InsertRange(Guid.NewGuid());
//act //act
var response = await techMessagesClient.GetPart(dateBegin, take, CancellationToken.None); var response = await techMessagesClient.GetPart(dateBegin, take, CancellationToken.None);
@ -245,40 +242,36 @@ namespace Persistence.IntegrationTests.Controllers
Assert.NotEmpty(response); Assert.NotEmpty(response);
} }
private async Task<IEnumerable<TechMessageDto>> InsertRange() private async Task<IEnumerable<TechMessageDto>> InsertRange(Guid systemId)
{ {
//arrange //arrange
memoryCache.Remove(SystemCacheKey); memoryCache.Remove(SystemCacheKey);
dbContext.CleanupDbSet<TechMessage>(); dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<DrillingSystem>(); dbContext.CleanupDbSet<DrillingSystem>();
var dtos = new List<TechMessageDto>() var dtos = new List<TechMessageDto>()
{ {
new() new TechMessageDto()
{ {
EventId = Guid.NewGuid(), EventId = Guid.NewGuid(),
CategoryId = 1, CategoryId = 1,
Timestamp = DateTimeOffset.UtcNow, Timestamp = DateTimeOffset.UtcNow,
Depth = 1.11, Text = nameof(TechMessageDto.Text),
MessageText = nameof(TechMessageDto.MessageText), EventState = Models.Enumerations.EventState.Triggered
System = nameof(TechMessageDto.System).ToLower(), },
UserId = Guid.NewGuid() new TechMessageDto()
}, {
new() EventId = Guid.NewGuid(),
{ CategoryId = 2,
EventId = Guid.NewGuid(), Timestamp = DateTimeOffset.UtcNow,
CategoryId = 2, Text = nameof(TechMessageDto.Text),
Timestamp = DateTimeOffset.UtcNow, EventState = Models.Enumerations.EventState.Triggered
Depth = 2.22, }
MessageText = nameof(TechMessageDto.MessageText), };
System = nameof(TechMessageDto.System).ToLower(),
UserId = Guid.NewGuid()
}
};
//act //act
var response = await techMessagesClient.AddRange(dtos, CancellationToken.None); var response = await techMessagesClient.AddRange(systemId, dtos, CancellationToken.None);
//assert //assert
Assert.Equal(dtos.Count, response); Assert.Equal(dtos.Count, response);

View File

@ -1,6 +1,7 @@
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json.Linq;
using Persistence.Database.Entity; using Persistence.Database.Entity;
using Persistence.Models; using Persistence.Models;
using Persistence.Models.Requests; using Persistence.Models.Requests;
@ -50,19 +51,18 @@ namespace Persistence.Repository.Repositories
return dto; return dto;
} }
public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<string> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token) public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<Guid> systems, IEnumerable<int> categoryIds, CancellationToken token)
{ {
var query = GetQueryReadOnly(); var query = GetQueryReadOnly();
var systems = autoDrillingSystem.Select(s => s.ToLower().Trim()); var result = await query
var result = await query .Where(e => systems.Count() == 0 || systems.Contains(e.System.SystemId))
Review

Для IEnumerable проверка Any должна работать быстрее, чем Count, - потому что Any проверяет, есть ли хотя бы один элемент в последовательности, а Count считает всю последовательность

Для IEnumerable проверка Any должна работать быстрее, чем Count, - потому что Any проверяет, есть ли хотя бы один элемент в последовательности, а Count считает всю последовательность
.Where(e => !systems.Any() || systems.Contains(e.System.Name.ToLower().Trim())) .GroupBy(e => e.System.Name, (key, group) => new
.GroupBy(e => e.System.Name, (key, group) => new {
{ System = key,
System = key, Categories = group
Categories = group .Where(g => categoryIds.Count() == 0 || categoryIds.Contains(g.CategoryId))
.Where(g => !categoryIds.Any() || categoryIds.Contains(g.CategoryId)) })
}) .ToArrayAsync(token);
.ToArrayAsync(token);
var entities = new List<MessagesStatisticDto>(); var entities = new List<MessagesStatisticDto>();
foreach (var e in result) foreach (var e in result)
@ -81,27 +81,16 @@ namespace Persistence.Repository.Repositories
return entities; return entities;
} }
public async Task<IEnumerable<string>> GetSystems(CancellationToken token) public async Task<int> AddRange(Guid systemId, IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token)
{ {
var entities = await GetDrillingSystems(token);
var result = entities.Select(e => e.Name);
return result; var entities = new List<TechMessage>();
} foreach (var dto in dtos)
{
var entity = dto.Adapt<TechMessage>();
public async Task<int> AddRange(IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token) await CreateSystemIfNotExist(systemId, token);
{ entity.SystemId = systemId;
var entities = new List<TechMessage>();
foreach (var dto in dtos)
{
var entity = dto.Adapt<TechMessage>();
var systems = await GetDrillingSystems(token);
var systemId = systems.FirstOrDefault(e => e.Name.ToLower().Trim() == dto.System.ToLower().Trim())?.SystemId
?? await CreateDrillingSystem(dto.System, token);
entity.SystemId = systemId;
entity.UserId = userId;
entities.Add(entity); entities.Add(entity);
} }
@ -125,54 +114,61 @@ namespace Persistence.Repository.Repositories
return dtos; return dtos;
} }
public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token) public async Task<IEnumerable<DrillingSystemDto>> GetSystems(CancellationToken token)
{ {
var query = GetQueryReadOnly() var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f =>
.GroupBy(e => 1) {
.Select(group => new f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationInMinutes);
{
Min = group.Min(e => e.Timestamp), var query = db.Set<Database.Entity.DrillingSystem>();
Max = group.Max(e => e.Timestamp), var entities = await query.ToListAsync(token);
}); var dtos = entities.Select(e => e.Adapt<Models.DrillingSystemDto>());
var values = await query.FirstOrDefaultAsync(token);
var result = new DatesRangeDto() return dtos;
{ });
From = values?.Min ?? DateTimeOffset.MinValue,
To = values?.Max ?? DateTimeOffset.MaxValue return systems ?? [];
}; }
public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token)
{
var query = GetQueryReadOnly()
.GroupBy(e => 1)
.Select(group => new
{
Min = group.Min(e => e.Timestamp),
Max = group.Max(e => e.Timestamp),
});
var values = await query.FirstOrDefaultAsync(token);
var result = new DatesRangeDto()
{
From = values?.Min ?? DateTimeOffset.MinValue,
To = values?.Max ?? DateTimeOffset.MaxValue
};
return result; return result;
} }
private async Task<IEnumerable<Models.DrillingSystemDto>> GetDrillingSystems(CancellationToken token) private async Task CreateSystemIfNotExist(Guid systemId, CancellationToken token)
{ {
var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f => var systems = await GetSystems(token);
{ var system = systems?.FirstOrDefault(e => e.SystemId == systemId);
f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationInMinutes);
var query = db.Set<Database.Entity.DrillingSystem>(); if (system == null)
var entities = await query.ToListAsync(token); {
var dtos = entities.Select(e => e.Adapt<Models.DrillingSystemDto>()); system = new DrillingSystemDto()
{
SystemId = systemId,
Name = string.Empty
};
return dtos; var entity = system.Adapt<DrillingSystem>();
});
return systems!; await db.Set<Database.Entity.DrillingSystem>().AddAsync(entity);
} await db.SaveChangesAsync(token);
private async Task<Guid> CreateDrillingSystem(string name, CancellationToken token)
{
memoryCache.Remove(SystemCacheKey);
var entity = new DrillingSystem() memoryCache.Remove(SystemCacheKey);
{ }
SystemId = Uuid7.Guid(), }
Name = name.ToLower().Trim() }
};
await db.Set<DrillingSystem>().AddAsync(entity, token);
await db.SaveChangesAsync(token);
return entity.SystemId;
}
}
} }

View File

@ -0,0 +1,6 @@
namespace Persistence.Models.Enumerations;
public enum EventState
{
NotTriggered = 0,
Triggered = 1,
}

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Persistence.Models.Enumerations;
namespace Persistence.Models namespace Persistence.Models
{ {
@ -24,29 +25,16 @@ namespace Persistence.Models
/// </summary> /// </summary>
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
/// <summary> /// <summary>
/// Глубина забоя /// Текст сообщения
/// </summary> /// </summary>
[Range(0, double.MaxValue, ErrorMessage = "Глубина забоя не может быть меньше 0")] [Required]
public double? Depth { get; set; } [StringLength(512, MinimumLength = 1, ErrorMessage = "Допустимая длина текста сообщения от 1 до 512 символов")]
public required string Text { get; set; }
/// <summary> /// <summary>
/// Текст сообщения /// Статус события
/// </summary> /// </summary>
[Required] public EventState EventState { get; set; }
[StringLength(512, MinimumLength = 1, ErrorMessage = "Допустимая длина текста сообщения от 1 до 512 символов")] }
public required string MessageText { get; set; }
/// <summary>
/// Система автобурения, к которой относится сообщение
/// </summary>
[Required]
[StringLength(256, MinimumLength = 1, ErrorMessage = "Допустимая длина наименования системы АБ от 1 до 256 символов")]
public required string System { get; set; }
/// <summary>
/// Id пользователя за пультом бурильщика
/// </summary>
public Guid UserId { get; set; }
}
} }

View File

@ -22,14 +22,14 @@ namespace Persistence.Repositories
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> AddRange(IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token); Task<int> AddRange(Guid systemId, IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token);
/// <summary> /// <summary>
/// Получение списка уникальных названий систем АБ /// Получение списка систем
/// </summary> /// </summary>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<string>> GetSystems(CancellationToken token); Task<IEnumerable<DrillingSystemDto>> GetSystems(CancellationToken token);
/// <summary> /// <summary>
/// Получение количества сообщений по категориям и системам автобурения /// Получение количества сообщений по категориям и системам автобурения
@ -38,7 +38,7 @@ namespace Persistence.Repositories
/// <param name="autoDrillingSystem">Система автобурения</param> /// <param name="autoDrillingSystem">Система автобурения</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<string> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token); Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<Guid> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token);
/// <summary> /// <summary>
/// Получить порцию записей, начиная с заданной даты /// Получить порцию записей, начиная с заданной даты