Реализовать хранение данных WITS0
This commit is contained in:
parent
efed33e648
commit
d67b6c61d8
81
Persistence.API/Controllers/WitsDataController.cs
Normal file
81
Persistence.API/Controllers/WitsDataController.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.Models;
|
||||
using Persistence.Services.Interfaces;
|
||||
|
||||
namespace Persistence.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
public class WitsDataController : ControllerBase, IWitsDataApi
|
||||
{
|
||||
private readonly IWitsDataService witsDataService;
|
||||
|
||||
public WitsDataController(IWitsDataService witsDataService)
|
||||
{
|
||||
this.witsDataService = witsDataService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="discriminatorId"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("datesRange")]
|
||||
public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync([FromQuery] int discriminatorId, CancellationToken token)
|
||||
{
|
||||
var result = await witsDataService.GetDatesRangeAsync(discriminatorId, token);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить порцию записей, начиная с заданной даты
|
||||
/// </summary>
|
||||
/// <param name="discriminatorId"></param>
|
||||
/// <param name="dateBegin"></param>
|
||||
/// <param name="take"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("part")]
|
||||
public async Task<ActionResult<IEnumerable<WitsDataDto>>> GetPart([FromQuery] int discriminatorId, [FromQuery] DateTimeOffset dateBegin, [FromQuery] int take, CancellationToken token)
|
||||
{
|
||||
var result = await witsDataService.GetPart(discriminatorId, dateBegin, take, token);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить набор параметров (Wits) для построения графика
|
||||
/// </summary>
|
||||
/// <param name="dateFrom"></param>
|
||||
/// <param name="dateTo"></param>
|
||||
/// <param name="limit"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("graph")]
|
||||
public async Task<ActionResult<IEnumerable<WitsDataDto>>> GetValuesForGraph([FromQuery] DateTimeOffset dateFrom, [FromQuery] DateTimeOffset dateTo, [FromQuery] int limit, CancellationToken token)
|
||||
{
|
||||
var result = await witsDataService.GetValuesForGraph(dateFrom, dateTo);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сохранить набор параметров (Wits)
|
||||
/// </summary>
|
||||
/// <param name="dtos"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
|
||||
public async Task<IActionResult> InsertRange([FromBody] IEnumerable<WitsDataDto> dtos, CancellationToken token)
|
||||
{
|
||||
var result = await witsDataService.InsertRange(dtos, token);
|
||||
|
||||
return CreatedAtAction(nameof(InsertRange), result);
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ using Microsoft.OpenApi.Models;
|
||||
using Persistence.Database.Entity;
|
||||
using Persistence.Models;
|
||||
using Persistence.Models.Configurations;
|
||||
using Persistence.Services;
|
||||
using Persistence.Services.Interfaces;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Persistence.API;
|
||||
@ -55,6 +57,11 @@ public static class DependencyInjection
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IWitsDataService, WitsDataService>();
|
||||
}
|
||||
|
||||
#region Authentication
|
||||
public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
|
@ -24,13 +24,13 @@ public class Startup
|
||||
services.AddPersistenceDbContext(Configuration);
|
||||
services.AddJWTAuthentication(Configuration);
|
||||
services.AddMemoryCache();
|
||||
services.AddServices();
|
||||
|
||||
DependencyInjection.MapsterSetup();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
|
20
Persistence.Client/Clients/IWitsDataClient.cs
Normal file
20
Persistence.Client/Clients/IWitsDataClient.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Persistence.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Persistence.Client.Clients;
|
||||
public interface IWitsDataClient
|
||||
{
|
||||
private const string BaseRoute = "/api/witsData";
|
||||
|
||||
[Get($"{BaseRoute}/graph")]
|
||||
Task<IApiResponse<IEnumerable<WitsDataDto>>> GetValuesForGraph([Query] DateTimeOffset dateFrom, [Query] DateTimeOffset dateTo, [Query] int limit, CancellationToken token);
|
||||
|
||||
[Post($"{BaseRoute}/")]
|
||||
Task<IApiResponse<int>> InsertRange([Body] IEnumerable<WitsDataDto> dtos, CancellationToken token);
|
||||
|
||||
[Get($"{BaseRoute}/part")]
|
||||
Task<IApiResponse<IEnumerable<WitsDataDto>>> GetPart([Query] int discriminatorId, [Query] DateTimeOffset dateBegin, [Query] int take = 24 * 60 * 60, CancellationToken token = default);
|
||||
|
||||
[Get($"{BaseRoute}/datesRange")]
|
||||
Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync([Query] int discriminatorId, CancellationToken token);
|
||||
}
|
257
Persistence.Database.Postgres/Migrations/20241203120141_ParameterDataMigration.Designer.cs
generated
Normal file
257
Persistence.Database.Postgres/Migrations/20241203120141_ParameterDataMigration.Designer.cs
generated
Normal file
@ -0,0 +1,257 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Persistence.Database.Model;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.Database.Postgres.Migrations
|
||||
{
|
||||
[DbContext(typeof(PersistenceDbContext))]
|
||||
[Migration("20241203120141_ParameterDataMigration")]
|
||||
partial class ParameterDataMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.UseCollation("Russian_Russia.1251")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b =>
|
||||
{
|
||||
b.Property<Guid>("SystemId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Id системы автобурения");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text")
|
||||
.HasComment("Описание системы автобурения");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(256)")
|
||||
.HasComment("Наименование системы автобурения");
|
||||
|
||||
b.HasKey("SystemId");
|
||||
|
||||
b.ToTable("DrillingSystem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Entity.ParameterData", b =>
|
||||
{
|
||||
b.Property<int>("DiscriminatorId")
|
||||
.HasColumnType("integer")
|
||||
.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 =>
|
||||
{
|
||||
b.Property<Guid>("EventId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Id события");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("Id Категории важности");
|
||||
|
||||
b.Property<double?>("Depth")
|
||||
.HasColumnType("double precision")
|
||||
.HasComment("Глубина забоя");
|
||||
|
||||
b.Property<string>("MessageText")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(512)")
|
||||
.HasComment("Текст сообщения");
|
||||
|
||||
b.Property<Guid>("SystemId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Id системы автобурения, к которой относится сообщение");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("Дата возникновения");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Id пользователя за пультом бурильщика");
|
||||
|
||||
b.HasKey("EventId");
|
||||
|
||||
b.HasIndex("SystemId");
|
||||
|
||||
b.ToTable("TechMessage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b =>
|
||||
{
|
||||
b.Property<Guid>("IdDiscriminator")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Дискриминатор ссылка на тип сохраняемых данных");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("Отметка времени, строго в UTC");
|
||||
|
||||
b.Property<string>("Set")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasComment("Набор сохраняемых данных");
|
||||
|
||||
b.HasKey("IdDiscriminator", "Timestamp");
|
||||
|
||||
b.ToTable("TimestampedSets", t =>
|
||||
{
|
||||
t.HasComment("Общая таблица данных временных рядов");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
|
||||
{
|
||||
b.Property<DateTimeOffset>("Date")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("date");
|
||||
|
||||
b.Property<double?>("AxialLoad")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("axialLoad");
|
||||
|
||||
b.Property<double?>("BitDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("bitDepth");
|
||||
|
||||
b.Property<double?>("BlockPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockPosition");
|
||||
|
||||
b.Property<double?>("BlockSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockSpeed");
|
||||
|
||||
b.Property<double?>("Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("flow");
|
||||
|
||||
b.Property<double?>("HookWeight")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("hookWeight");
|
||||
|
||||
b.Property<int>("IdFeedRegulator")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("idFeedRegulator");
|
||||
|
||||
b.Property<int?>("Mode")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("mode");
|
||||
|
||||
b.Property<double?>("Mse")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("mse");
|
||||
|
||||
b.Property<short>("MseState")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("mseState");
|
||||
|
||||
b.Property<double?>("Pressure")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pressure");
|
||||
|
||||
b.Property<double?>("Pump0Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump0Flow");
|
||||
|
||||
b.Property<double?>("Pump1Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump1Flow");
|
||||
|
||||
b.Property<double?>("Pump2Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump2Flow");
|
||||
|
||||
b.Property<double?>("RotorSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorSpeed");
|
||||
|
||||
b.Property<double?>("RotorTorque")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorTorque");
|
||||
|
||||
b.Property<string>("User")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("user");
|
||||
|
||||
b.Property<double?>("WellDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("wellDepth");
|
||||
|
||||
b.HasKey("Date");
|
||||
|
||||
b.ToTable("DataSaub");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Model.Setpoint", b =>
|
||||
{
|
||||
b.Property<Guid>("Key")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Ключ");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("Дата создания уставки");
|
||||
|
||||
b.Property<Guid>("IdUser")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Id автора последнего изменения");
|
||||
|
||||
b.Property<object>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasComment("Значение уставки");
|
||||
|
||||
b.HasKey("Key", "Created");
|
||||
|
||||
b.ToTable("Setpoint");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
|
||||
{
|
||||
b.HasOne("Persistence.Database.Entity.DrillingSystem", "System")
|
||||
.WithMany()
|
||||
.HasForeignKey("SystemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("System");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.Database.Postgres.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ParameterDataMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ParameterData",
|
||||
columns: table => new
|
||||
{
|
||||
DiscriminatorId = table.Column<int>(type: "integer", nullable: false, comment: "Дискриминатор системы"),
|
||||
ParameterId = table.Column<int>(type: "integer", nullable: false, comment: "Id параметра"),
|
||||
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Временная отметка"),
|
||||
Value = table.Column<string>(type: "varchar(256)", nullable: false, comment: "Значение параметра в виде строки")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ParameterData", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp });
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ParameterData");
|
||||
}
|
||||
}
|
||||
}
|
@ -45,6 +45,30 @@ namespace Persistence.Database.Postgres.Migrations
|
||||
b.ToTable("DrillingSystem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Entity.ParameterData", b =>
|
||||
{
|
||||
b.Property<int>("DiscriminatorId")
|
||||
.HasColumnType("integer")
|
||||
.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 =>
|
||||
{
|
||||
b.Property<Guid>("EventId")
|
||||
|
@ -1,7 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
using Persistence.Database.Entity;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Persistence.Database.Model;
|
||||
public partial class PersistenceDbContext : DbContext
|
||||
@ -14,6 +12,8 @@ public partial class PersistenceDbContext : DbContext
|
||||
|
||||
public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>();
|
||||
|
||||
public DbSet<ParameterData> ParameterData => Set<ParameterData>();
|
||||
|
||||
public PersistenceDbContext()
|
||||
: base()
|
||||
{
|
||||
|
21
Persistence.Database/Entity/ParameterData.cs
Normal file
21
Persistence.Database/Entity/ParameterData.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Persistence.Database.Entity;
|
||||
|
||||
[PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))]
|
||||
public class ParameterData
|
||||
{
|
||||
[Required, Comment("Дискриминатор системы")]
|
||||
public int DiscriminatorId { get; set; }
|
||||
|
||||
[Comment("Id параметра")]
|
||||
public int ParameterId { get; set; }
|
||||
|
||||
[Column(TypeName = "varchar(256)"), Comment("Значение параметра в виде строки")]
|
||||
public required string Value { get; set; }
|
||||
|
||||
[Comment("Временная отметка")]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Persistence.Client;
|
||||
using Persistence.Client.Clients;
|
||||
using Persistence.Database.Entity;
|
||||
using Persistence.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace Persistence.IntegrationTests.Controllers;
|
||||
public class WitsDataControllerTest : BaseIntegrationTest
|
||||
{
|
||||
private IWitsDataClient witsDataClient;
|
||||
|
||||
public WitsDataControllerTest(WebAppFactoryFixture factory) : base(factory)
|
||||
{
|
||||
var scope = factory.Services.CreateScope();
|
||||
var persistenceClientFactory = scope.ServiceProvider
|
||||
.GetRequiredService<PersistenceClientFactory>();
|
||||
|
||||
witsDataClient = persistenceClientFactory.GetClient<IWitsDataClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDatesRangeAsync_returns_success()
|
||||
{
|
||||
//arrange
|
||||
dbContext.CleanupDbSet<ParameterData>();
|
||||
|
||||
var discriminatorId = 1;
|
||||
|
||||
//act
|
||||
var response = await witsDataClient.GetDatesRangeAsync(discriminatorId, new CancellationToken());
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPart_returns_success()
|
||||
{
|
||||
//arrange
|
||||
dbContext.CleanupDbSet<ParameterData>();
|
||||
|
||||
var discriminatorId = 1;
|
||||
var dateBegin = DateTimeOffset.UtcNow;
|
||||
var take = 1;
|
||||
|
||||
//act
|
||||
var response = await witsDataClient.GetPart(discriminatorId, dateBegin, take, new CancellationToken());
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.Empty(response.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InsertRange_returns_success()
|
||||
{
|
||||
//arrange
|
||||
dbContext.CleanupDbSet<ParameterData>();
|
||||
|
||||
//act
|
||||
await InsertRange();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetValuesForGraph_returns_success()
|
||||
{
|
||||
//arrange
|
||||
dbContext.CleanupDbSet<ParameterData>();
|
||||
|
||||
//act
|
||||
|
||||
//assert
|
||||
}
|
||||
|
||||
#region AfterSave
|
||||
[Fact]
|
||||
public async Task GetDatesRangeAsync_AfterSave_returns_success()
|
||||
{
|
||||
//arrange
|
||||
dbContext.CleanupDbSet<ParameterData>();
|
||||
|
||||
var dtos = await InsertRange();
|
||||
var discriminatorId = dtos.FirstOrDefault()!.DiscriminatorId;
|
||||
|
||||
//act
|
||||
var response = await witsDataClient.GetDatesRangeAsync(discriminatorId, new CancellationToken());
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
|
||||
var expectedDateFrom = dtos
|
||||
.Select(e => e.Timestamped)
|
||||
.Min()
|
||||
.ToString("dd.MM.yyyy-HH:mm:ss");
|
||||
var actualDateFrom = response.Content.From.DateTime
|
||||
.ToString("dd.MM.yyyy-HH:mm:ss");
|
||||
Assert.Equal(expectedDateFrom, actualDateFrom);
|
||||
|
||||
var expectedDateTo = dtos
|
||||
.Select(e => e.Timestamped)
|
||||
.Min()
|
||||
.ToString("dd.MM.yyyy-HH:mm:ss");
|
||||
var actualDateTo = response.Content.From.DateTime
|
||||
.ToString("dd.MM.yyyy-HH:mm:ss");
|
||||
Assert.Equal(expectedDateTo, actualDateTo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPart_AfterSave_returns_success()
|
||||
{
|
||||
//arrange
|
||||
dbContext.CleanupDbSet<ParameterData>();
|
||||
|
||||
var dtos = await InsertRange();
|
||||
var discriminatorId = dtos.FirstOrDefault()!.DiscriminatorId;
|
||||
var dateBegin = dtos.FirstOrDefault()!.Timestamped;
|
||||
var take = 1;
|
||||
|
||||
//act
|
||||
var response = await witsDataClient.GetPart(discriminatorId, dateBegin, take, new CancellationToken());
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.NotEmpty(response.Content);
|
||||
Assert.Equal(take, response.Content.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetValuesForGraph_AfterSave_returns_success()
|
||||
{
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BadRequest
|
||||
[Fact]
|
||||
public async Task InsertRange_returns_BadRequest()
|
||||
{
|
||||
//arrange
|
||||
var dtos = new List<WitsDataDto>()
|
||||
{
|
||||
new WitsDataDto()
|
||||
{
|
||||
DiscriminatorId = -1, // < 0
|
||||
Timestamped = DateTimeOffset.UtcNow,
|
||||
Values = new List<WitsValueDto>()
|
||||
{
|
||||
new WitsValueDto()
|
||||
{
|
||||
RecordId = -1, // < 0
|
||||
ItemId = 101, // > 100
|
||||
Value = "string value"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//act
|
||||
var response = await witsDataClient.InsertRange(dtos, new CancellationToken());
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private async Task<IEnumerable<WitsDataDto>> InsertRange()
|
||||
{
|
||||
//arrange
|
||||
var dtos = new List<WitsDataDto>()
|
||||
{
|
||||
new WitsDataDto()
|
||||
{
|
||||
DiscriminatorId = 1,
|
||||
Timestamped = DateTimeOffset.UtcNow,
|
||||
Values = new List<WitsValueDto>()
|
||||
{
|
||||
new WitsValueDto()
|
||||
{
|
||||
RecordId = 11,
|
||||
ItemId = 22,
|
||||
Value = "string value"
|
||||
},
|
||||
new WitsValueDto()
|
||||
{
|
||||
RecordId = 11,
|
||||
ItemId = 27,
|
||||
Value = 2.22
|
||||
}
|
||||
}
|
||||
},
|
||||
new WitsDataDto()
|
||||
{
|
||||
DiscriminatorId = 2,
|
||||
Timestamped = DateTimeOffset.UtcNow,
|
||||
Values = new List<WitsValueDto>()
|
||||
{
|
||||
new WitsValueDto()
|
||||
{
|
||||
RecordId = 13,
|
||||
ItemId = 14,
|
||||
Value = "string value"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//act
|
||||
var response = await witsDataClient.InsertRange(dtos, new CancellationToken());
|
||||
|
||||
//assert
|
||||
var count = dtos.SelectMany(e => e.Values).Count();
|
||||
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
|
||||
Assert.Equal(count, response.Content);
|
||||
|
||||
return dtos;
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ public static class DependencyInjection
|
||||
services.AddTransient<ITimeSeriesDataRepository<DataSaubDto>, TimeSeriesDataCachedRepository<DataSaub, DataSaubDto>>();
|
||||
services.AddTransient<ITimestampedSetRepository, TimestampedSetRepository>();
|
||||
services.AddTransient<ITechMessagesRepository, TechMessagesRepository>();
|
||||
services.AddTransient<IParameterRepository, ParameterRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
66
Persistence.Repository/Repositories/ParameterRepository.cs
Normal file
66
Persistence.Repository/Repositories/ParameterRepository.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Persistence.Database.Entity;
|
||||
using Persistence.Models;
|
||||
using Persistence.Repositories;
|
||||
|
||||
namespace Persistence.Repository.Repositories;
|
||||
public class ParameterRepository : IParameterRepository
|
||||
{
|
||||
private DbContext db;
|
||||
|
||||
public ParameterRepository(DbContext db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
protected virtual IQueryable<ParameterData> GetQueryReadOnly() => db.Set<ParameterData>();
|
||||
|
||||
public async Task<DatesRangeDto> GetDatesRangeAsync(int idDiscriminator, CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly()
|
||||
.Where(e => e.DiscriminatorId == idDiscriminator)
|
||||
.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;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ParameterDto>> GetPart(int idDiscriminator, DateTimeOffset dateBegin, int take, CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly();
|
||||
var universalDate = dateBegin.ToUniversalTime();
|
||||
var entities = await query
|
||||
.Where(e => e.DiscriminatorId == idDiscriminator && e.Timestamp >= universalDate)
|
||||
.Take(take)
|
||||
.ToArrayAsync(token);
|
||||
var dtos = entities.Select(e => e.Adapt<ParameterDto>());
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public Task<ParameterDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<int> InsertRange(IEnumerable<ParameterDto> dtos, CancellationToken token)
|
||||
{
|
||||
var entities = dtos.Select(e => e.Adapt<ParameterData>());
|
||||
|
||||
await db.Set<ParameterData>().AddRangeAsync(entities, token);
|
||||
var result = await db.SaveChangesAsync(token);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
28
Persistence/API/ISyncWithDiscriminatorApi.cs
Normal file
28
Persistence/API/ISyncWithDiscriminatorApi.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.Models;
|
||||
|
||||
namespace Persistence.API;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для API, предназначенного для синхронизации данных, у которых есть дискриминатор
|
||||
/// </summary>
|
||||
public interface ISyncWithDiscriminatorApi<TDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить порцию записей, начиная с заданной даты
|
||||
/// </summary>
|
||||
/// <param name="idDiscriminator"></param>
|
||||
/// <param name="dateBegin"></param>
|
||||
/// <param name="take">количество записей</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<TDto>>> GetPart(int idDiscriminator, DateTimeOffset dateBegin, int take = 24 * 60 * 60, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="idDiscriminator"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync(int idDiscriminator, CancellationToken token);
|
||||
}
|
26
Persistence/API/IWitsDataApi.cs
Normal file
26
Persistence/API/IWitsDataApi.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.Models;
|
||||
|
||||
namespace Persistence.API;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для работы с параметрами Wits
|
||||
/// </summary>
|
||||
public interface IWitsDataApi : ISyncWithDiscriminatorApi<WitsDataDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить набор параметров (Wits) для построения графика
|
||||
/// </summary>
|
||||
/// <param name="dateFrom"></param>
|
||||
/// <param name="dateTo"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<WitsDataDto>>> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo, int limit, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Сохранить набор параметров (Wits)
|
||||
/// </summary>
|
||||
/// <param name="dtos"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IActionResult> InsertRange(IEnumerable<WitsDataDto> dtos, CancellationToken token);
|
||||
}
|
32
Persistence/Models/ParameterDto.cs
Normal file
32
Persistence/Models/ParameterDto.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Модель параметра
|
||||
/// </summary>
|
||||
public class ParameterDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Дискриминатор системы
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Дискриминатор системы не может быть меньше 0")]
|
||||
public int DiscriminatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id параметра
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Id параметра не может быть меньше 0")]
|
||||
public int ParameterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение параметра в виде строки
|
||||
/// </summary>
|
||||
[StringLength(256, MinimumLength = 1, ErrorMessage = "Допустимая длина значения параметра от 1 до 256 символов")]
|
||||
public required string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Временная отметка
|
||||
/// </summary>
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
}
|
25
Persistence/Models/WitsDataDto.cs
Normal file
25
Persistence/Models/WitsDataDto.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Группа параметров Wits
|
||||
/// </summary>
|
||||
public class WitsDataDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Временная отметка
|
||||
/// </summary>
|
||||
public required DateTimeOffset Timestamped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дискриминатор системы
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Дискриминатор системы не может быть меньше 0")]
|
||||
public required int DiscriminatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Параметры
|
||||
/// </summary>
|
||||
public IEnumerable<WitsValueDto> Values { get; set; } = [];
|
||||
}
|
26
Persistence/Models/WitsValueDto.cs
Normal file
26
Persistence/Models/WitsValueDto.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Параметр Wits
|
||||
/// </summary>
|
||||
public class WitsValueDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Wits - Record Number
|
||||
/// </summary>
|
||||
[Range(1, 100, ErrorMessage = "Значение \"Record Number\" обязано находиться в диапозоне от 1 до 100")]
|
||||
public int RecordId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Wits - Record Item
|
||||
/// </summary>
|
||||
[Range(1, 100, ErrorMessage = "Значение \"Wits Record Item\" обязано находиться в диапозоне от 1 до 100")]
|
||||
public int ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение параметра
|
||||
/// </summary>
|
||||
public required object Value { get; set; }
|
||||
}
|
38
Persistence/Repositories/IParameterRepository.cs
Normal file
38
Persistence/Repositories/IParameterRepository.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Persistence.Models;
|
||||
|
||||
namespace Persistence.Repositories;
|
||||
public interface IParameterRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить порцию записей, начиная с заданной даты
|
||||
/// </summary>
|
||||
/// <param name="dateBegin"></param>
|
||||
/// <param name="take"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<ParameterDto>> GetPart(int idDiscriminator, DateTimeOffset dateBegin, int take, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<DatesRangeDto> GetDatesRangeAsync(int idDiscriminator, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить набор параметров (Wits) для построения графика
|
||||
/// </summary>
|
||||
/// <param name="dateFrom"></param>
|
||||
/// <param name="dateTo"></param>
|
||||
/// <returns></returns>
|
||||
Task<ParameterDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo);
|
||||
|
||||
/// <summary>
|
||||
/// Сохранить набор параметров (Wits)
|
||||
/// </summary>
|
||||
/// <param name="dtos"></param>
|
||||
/// <param name="witsIds"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> InsertRange(IEnumerable<ParameterDto> dtos, CancellationToken token);
|
||||
}
|
10
Persistence/Services/Interfaces/IWitsDataService.cs
Normal file
10
Persistence/Services/Interfaces/IWitsDataService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Persistence.Models;
|
||||
|
||||
namespace Persistence.Services.Interfaces;
|
||||
public interface IWitsDataService
|
||||
{
|
||||
Task<DatesRangeDto> GetDatesRangeAsync(int idDiscriminator, CancellationToken token);
|
||||
Task<IEnumerable<WitsDataDto>> GetPart(int idDiscriminator, DateTimeOffset dateBegin, int take, CancellationToken token);
|
||||
Task<WitsDataDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo);
|
||||
Task<int> InsertRange(IEnumerable<WitsDataDto> dtos, CancellationToken token);
|
||||
}
|
89
Persistence/Services/WitsDataService.cs
Normal file
89
Persistence/Services/WitsDataService.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using Persistence.Models;
|
||||
using Persistence.Repositories;
|
||||
using Persistence.Services.Interfaces;
|
||||
|
||||
namespace Persistence.Services;
|
||||
public class WitsDataService : IWitsDataService
|
||||
{
|
||||
private readonly IParameterRepository witsDataRepository;
|
||||
private const int multiplier = 1000;
|
||||
public WitsDataService(IParameterRepository witsDataRepository)
|
||||
{
|
||||
this.witsDataRepository = witsDataRepository;
|
||||
}
|
||||
|
||||
public Task<DatesRangeDto> GetDatesRangeAsync(int idDiscriminator, CancellationToken token)
|
||||
{
|
||||
var result = witsDataRepository.GetDatesRangeAsync(idDiscriminator, token);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WitsDataDto>> GetPart(int idDiscriminator, DateTimeOffset dateBegin, int take, CancellationToken token)
|
||||
{
|
||||
var dtos = await witsDataRepository.GetPart(idDiscriminator, dateBegin, take, token);
|
||||
|
||||
var result = new List<WitsDataDto>();
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
var witsDataDto = result.FirstOrDefault(e => e.DiscriminatorId == dto.DiscriminatorId && e.Timestamped == dto.Timestamp);
|
||||
if (witsDataDto == null)
|
||||
{
|
||||
witsDataDto = new WitsDataDto()
|
||||
{
|
||||
DiscriminatorId = dto.DiscriminatorId,
|
||||
Timestamped = dto.Timestamp
|
||||
};
|
||||
result.Add(witsDataDto);
|
||||
}
|
||||
var witsValueDto = new WitsValueDto()
|
||||
{
|
||||
RecordId = DecodeRecordId(dto.ParameterId),
|
||||
ItemId = DecodeItemId(dto.ParameterId),
|
||||
Value = dto.Value
|
||||
};
|
||||
witsDataDto.Values.Append(witsValueDto);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<WitsDataDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<int> InsertRange(IEnumerable<WitsDataDto> dtos, CancellationToken token)
|
||||
{
|
||||
var parameterDtos = dtos.SelectMany(e => e.Values.Select(t => new ParameterDto()
|
||||
{
|
||||
DiscriminatorId = e.DiscriminatorId,
|
||||
ParameterId = EncodeId(t.RecordId, t.ItemId),
|
||||
Value = t.Value.ToString()!,
|
||||
Timestamp = e.Timestamped
|
||||
}));
|
||||
var result = await witsDataRepository.InsertRange(parameterDtos, token);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int EncodeId(int recordId, int itemId)
|
||||
{
|
||||
var resultId = multiplier * recordId + itemId;
|
||||
return resultId;
|
||||
}
|
||||
|
||||
private int DecodeRecordId(int id)
|
||||
{
|
||||
var resultId = id / multiplier;
|
||||
|
||||
return resultId;
|
||||
}
|
||||
|
||||
private int DecodeItemId(int id)
|
||||
{
|
||||
var resultId = id % multiplier;
|
||||
|
||||
return resultId;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user