Merge branch 'dev' into feature/telemetry

# Conflicts:
#	AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
This commit is contained in:
Степанов Дмитрий 2023-10-24 11:29:33 +05:00
commit ea7e8cbd4b
19 changed files with 447 additions and 201 deletions

View File

@ -10,7 +10,7 @@ public class WellboreDto
/// <summary> /// <summary>
/// Скважина /// Скважина
/// </summary> /// </summary>
public WellWithTimezoneDto Well { get; set; } = null!; public WellDto Well { get; set; } = null!;
/// <summary> /// <summary>
/// Идентификатор /// Идентификатор

View File

@ -0,0 +1,62 @@
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using System;
using System.Collections.Generic;
namespace AsbCloudApp.Repositories
{
/// <summary>
/// Хранилище кеша
/// </summary>
/// <typeparam name="TDto"></typeparam>
public interface ITelemetryDataCache<TDto> where TDto : ITelemetryData
{
/// <summary>
/// добавить в кеш чанк записей по телеметрии
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="range"></param>
void AddRange(int idTelemetry, IEnumerable<TDto> range);
/// <summary>
/// вернуть последнюю записть
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
TDto? GetLastOrDefault(int idTelemetry);
/// <summary>
/// Получить кешированые записи
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="dateBegin"></param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount">приблизительное кол-во возвращаемых записей после их прореживания</param>
/// <returns></returns>
IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600, int approxPointsCount = 1024);
/// <summary>
/// Получить кешированые записи
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="request"></param>
/// <returns></returns>
IEnumerable<TDto>? GetOrDefault(int idTelemetry, TelemetryDataRequest request);
/// <summary>
/// Получить диапазон дат телеметрии.
/// Дата первой записи телеметрии храниться отдельно и запоняется при инициализации
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
DatesRangeDto? GetOrDefaultDataDateRange(int idTelemetry);
/// <summary>
/// Получение первой и последней записи телеметрии.
/// Первая запись телеметрии храниться отдельно и запоняется при инициализации
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
(TDto First, TDto Last)? GetOrDefaultFirstLast(int idTelemetry);
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса для ствола скважины
/// </summary>
public class WellboreRequest : RequestBase
{
/// <summary>
/// Пары идентификаторов скважины и секции
/// </summary>
public IEnumerable<(int idWell, int? idSection)> Ids { get; set; } = null!;
}

View File

@ -42,7 +42,6 @@ namespace AsbCloudApp.Services
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <param name="start"></param> /// <param name="start"></param>
/// <param name="end"></param> /// <param name="end"></param>
/// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
DatesRangeDto? GetRange(int idWell, DateTimeOffset start, DateTimeOffset end); DatesRangeDto? GetRange(int idWell, DateTimeOffset start, DateTimeOffset end);

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Requests;
namespace AsbCloudApp.Services; namespace AsbCloudApp.Services;
@ -11,20 +10,11 @@ namespace AsbCloudApp.Services;
/// </summary> /// </summary>
public interface IWellboreService public interface IWellboreService
{ {
/// <summary>
/// Получение ствола скважины
/// </summary>
/// <param name="idWell"></param>
/// <param name="idSection"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<WellboreDto?> GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Получение стволов скважин /// Получение стволов скважин
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="idsWells"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<WellboreDto>> GetWellboresAsync(WellboreRequest request, CancellationToken cancellationToken); Task<IEnumerable<WellboreDto>> GetWellboresAsync(IEnumerable<int> idsWells, CancellationToken cancellationToken);
} }

View File

@ -162,8 +162,8 @@ namespace AsbCloudInfrastructure
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>()); services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>());
services.AddSingleton(new WitsInfoService()); services.AddSingleton(new WitsInfoService());
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider)); services.AddSingleton<ITelemetryDataCache<TelemetryDataSaubDto>>(provider => TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider));
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider)); services.AddSingleton<ITelemetryDataCache<TelemetryDataSpinDto>>(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>(); services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
services.AddSingleton<BackgroundWorker>(); services.AddSingleton<BackgroundWorker>();
services.AddSingleton<NotificationBackgroundWorker>(); services.AddSingleton<NotificationBackgroundWorker>();

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
@ -18,12 +19,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
{ {
protected readonly IAsbCloudDbContext db; protected readonly IAsbCloudDbContext db;
protected readonly ITelemetryService telemetryService; protected readonly ITelemetryService telemetryService;
protected readonly TelemetryDataCache<TDto> telemetryDataCache; protected readonly ITelemetryDataCache<TDto> telemetryDataCache;
public TelemetryDataBaseService( public TelemetryDataBaseService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
TelemetryDataCache<TDto> telemetryDataCache) ITelemetryDataCache<TDto> telemetryDataCache)
{ {
this.db = db; this.db = db;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;

View File

@ -11,15 +11,15 @@ using AsbCloudInfrastructure.Background;
using System.Threading; using System.Threading;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure.Services.SAUB namespace AsbCloudInfrastructure.Services.SAUB
{ {
public class TelemetryDataCache<TDto> public class TelemetryDataCache<TDto> : ITelemetryDataCache<TDto> where TDto : AsbCloudApp.Data.ITelemetryData
where TDto : AsbCloudApp.Data.ITelemetryData
{ {
class TelemetryDataCacheItem class TelemetryDataCacheItem
{ {
public TDto? FirstByDate { get; init; } public TDto FirstByDate { get; init; } = default!;
public CyclycArray<TDto> LastData { get; init; } = null!; public CyclycArray<TDto> LastData { get; init; } = null!;
public double TimezoneHours { get; init; } = 5; public double TimezoneHours { get; init; } = 5;
} }
@ -48,7 +48,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
instance = new TelemetryDataCache<TDto>(); instance = new TelemetryDataCache<TDto>();
var worker = provider.GetRequiredService<BackgroundWorker>(); var worker = provider.GetRequiredService<BackgroundWorker>();
var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}"; var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}";
var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) => { var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) =>
{
var db = provider.GetRequiredService<IAsbCloudDbContext>(); var db = provider.GetRequiredService<IAsbCloudDbContext>();
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token); await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
}); });
@ -125,7 +126,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
return items; return items;
} }
public TDto? GetLastOrDefault(int idTelemetry) public virtual TDto? GetLastOrDefault(int idTelemetry)
{ {
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem)) if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
return default; return default;
@ -148,6 +149,19 @@ namespace AsbCloudInfrastructure.Services.SAUB
return new DatesRangeDto { From = from.Value, To = to }; return new DatesRangeDto { From = from.Value, To = to };
} }
public (TDto First, TDto Last)? GetOrDefaultFirstLast(int idTelemetry)
{
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
return null;
if (!cacheItem.LastData.Any())
return null;
var last = cacheItem.LastData[^1];
var first = cacheItem.FirstByDate;
return (first, last);
}
private async Task InitializeCacheFromDBAsync<TEntity>(IAsbCloudDbContext db, Action<string, double?> onProgress, CancellationToken token) private async Task InitializeCacheFromDBAsync<TEntity>(IAsbCloudDbContext db, Action<string, double?> onProgress, CancellationToken token)
where TEntity : class, AsbCloudDb.Model.ITelemetryData where TEntity : class, AsbCloudDb.Model.ITelemetryData
{ {
@ -215,7 +229,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
var dtos = entities var dtos = entities
.AsEnumerable() .AsEnumerable()
.Reverse() .Reverse()
.Select(entity => { .Select(entity =>
{
var dto = entity.Adapt<TDto>(); var dto = entity.Adapt<TDto>();
dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset); dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset);
return dto; return dto;

View File

@ -1,6 +1,7 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Exceptions; using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Mapster; using Mapster;
@ -26,7 +27,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
ITelemetryUserService telemetryUserService, ITelemetryUserService telemetryUserService,
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache) ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache) : base(db, telemetryService, telemetryDataCache)
{ {
this.telemetryUserService = telemetryUserService; this.telemetryUserService = telemetryUserService;

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Mapster; using Mapster;
@ -11,7 +12,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryDataSpinService( public TelemetryDataSpinService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache) ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache) : base(db, telemetryService, telemetryDataCache)
{ } { }

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
@ -18,7 +19,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly TelemetryDataCache<TelemetryDataSaubDto> dataSaubCache; //TODO: методы использующие ITelemetryDataCache, скорее всего, тут не нужны
private readonly ITelemetryDataCache<TelemetryDataSaubDto> dataSaubCache;
private readonly ITimezoneService timezoneService; private readonly ITimezoneService timezoneService;
public ITimezoneService TimeZoneService => timezoneService; public ITimezoneService TimeZoneService => timezoneService;
@ -26,7 +28,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryService( public TelemetryService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
IMemoryCache memoryCache, IMemoryCache memoryCache,
TelemetryDataCache<TelemetryDataSaubDto> dataSaubCache, ITelemetryDataCache<TelemetryDataSaubDto> dataSaubCache,
ITimezoneService timezoneService) ITimezoneService timezoneService)
{ {
this.db = db; this.db = db;

View File

@ -36,7 +36,7 @@ public class WellInfoService
var operationsStatService = services.GetRequiredService<IOperationsStatService>(); var operationsStatService = services.GetRequiredService<IOperationsStatService>();
var processMapPlanWellDrillingRepository = services.GetRequiredService<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>>(); var processMapPlanWellDrillingRepository = services.GetRequiredService<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>>();
var subsystemOperationTimeService = services.GetRequiredService<ISubsystemOperationTimeService>(); var subsystemOperationTimeService = services.GetRequiredService<ISubsystemOperationTimeService>();
var telemetryDataSaubCache = services.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>(); var telemetryDataSaubCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
var messageHub = services.GetRequiredService<IIntegrationEventHandler<UpdateWellInfoEvent>>(); var messageHub = services.GetRequiredService<IIntegrationEventHandler<UpdateWellInfoEvent>>();
var wells = await wellService.GetAllAsync(token); var wells = await wellService.GetAllAsync(token);
@ -180,16 +180,16 @@ public class WellInfoService
public IEnumerable<int> IdsCompanies { get; set; } = null!; public IEnumerable<int> IdsCompanies { get; set; } = null!;
} }
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache; private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
private readonly TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache; private readonly ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
private readonly IWitsRecordRepository<Record7Dto> witsRecord7Repository; private readonly IWitsRecordRepository<Record7Dto> witsRecord7Repository;
private readonly IWitsRecordRepository<Record1Dto> witsRecord1Repository; private readonly IWitsRecordRepository<Record1Dto> witsRecord1Repository;
private readonly IGtrRepository gtrRepository; private readonly IGtrRepository gtrRepository;
private static IEnumerable<WellMapInfoWithComanies> WellMapInfo = Enumerable.Empty<WellMapInfoWithComanies>(); private static IEnumerable<WellMapInfoWithComanies> WellMapInfo = Enumerable.Empty<WellMapInfoWithComanies>();
public WellInfoService( public WellInfoService(
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache, ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache, ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache,
IWitsRecordRepository<Record7Dto> witsRecord7Repository, IWitsRecordRepository<Record7Dto> witsRecord7Repository,
IWitsRecordRepository<Record1Dto> witsRecord1Repository, IWitsRecordRepository<Record1Dto> witsRecord1Repository,
IGtrRepository gtrRepository) IGtrRepository gtrRepository)

View File

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure.Services.WellOperationService; namespace AsbCloudInfrastructure.Services.WellOperationService;
@ -19,10 +20,10 @@ public class OperationsStatService : IOperationsStatService
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache; private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService,
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache) ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
{ {
this.db = db; this.db = db;
this.memoryCache = memoryCache; this.memoryCache = memoryCache;

View File

@ -1,91 +1,134 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.SAUB;
using Mapster;
namespace AsbCloudInfrastructure.Services; namespace AsbCloudInfrastructure.Services;
public class WellboreService : IWellboreService public class WellboreService : IWellboreService
{ {
const string WellboreNameFormat = "Ñòâîë {0}";
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IWellOperationRepository wellOperationRepository; private readonly IWellOperationRepository wellOperationRepository;
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
public WellboreService(IWellService wellService, IWellOperationRepository wellOperationRepository) public WellboreService(
IWellService wellService,
IWellOperationRepository wellOperationRepository,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
{ {
this.wellService = wellService; this.wellService = wellService;
this.wellOperationRepository = wellOperationRepository; this.wellOperationRepository = wellOperationRepository;
this.telemetryDataCache = telemetryDataCache;
} }
public async Task<WellboreDto?> GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken) public async Task<IEnumerable<WellboreDto>> GetWellboresAsync(IEnumerable<int> idsWells,
{
var request = new WellboreRequest
{
Ids = new (int, int?)[] { (idWell, idSection) },
Take = 1,
};
var data = await GetWellboresAsync(request, cancellationToken);
return data.FirstOrDefault();
}
public async Task<IEnumerable<WellboreDto>> GetWellboresAsync(WellboreRequest request,
CancellationToken token) CancellationToken token)
{ {
var wellbores = new List<WellboreDto>(request.Ids.Count()); var wellRequest = new WellRequest { Ids = idsWells };
var skip = request.Skip ?? 0; var wells = await wellService.GetAsync(wellRequest, token);
var take = request.Take ?? 10;
var sections = wellOperationRepository.GetSectionTypes() var rowSections = await wellOperationRepository.GetSectionsAsync(idsWells, token);
.ToDictionary(w => w.Id, w => w); var groupedSections = rowSections
.Where(section => section.IdType == 1)
.GroupBy(s => s.IdWell);
var ids = request.Ids.GroupBy(i => i.idWell, i => i.idSection); var wellbores = wells
.SelectMany(well => {
var wellSections = groupedSections.FirstOrDefault(group => group.Key == well.Id);
if (wellSections is not null)
return MakeWellboreBySections(wellSections, well);
else
return MakeWellboreDefault(well);
})
.OrderBy(w => w.Well.Id)
.ThenBy(w => w.Id);
var idsWells = request.Ids.Select(i => i.idWell); return wellbores;
}
var allSections = await wellOperationRepository.GetSectionsAsync(idsWells, token); private IEnumerable<WellboreDto> MakeWellboreDefault(WellDto well)
foreach (var id in ids)
{ {
var well = await wellService.GetOrDefaultAsync(id.Key, token); var wellbore = new WellboreDto {
Id = 1,
Name = string.Format(WellboreNameFormat, 1),
Well = well,
};
//if(well.)
if (well is null) if(well.IdTelemetry is not null)
continue; {
var dataCache = telemetryDataCache.GetOrDefaultFirstLast(well.IdTelemetry.Value);
if (dataCache is not null)
{
wellbore.DateStart = dataCache.Value.First.DateTime;
wellbore.DepthStart = dataCache.Value.First.WellDepth!.Value;
var wellTimezoneOffset = TimeSpan.FromHours(well.Timezone.Hours); wellbore.DateEnd = dataCache.Value.Last.DateTime;
wellbore.DepthEnd = dataCache.Value.Last.WellDepth!.Value;
}
}
var wellFactSections = allSections return new[] { wellbore };
.Where(section => section.IdWell == id.Key) }
.Where(section => section.IdType == WellOperation.IdOperationTypeFact);
var idsSections = id private IEnumerable<WellboreDto> MakeWellboreBySections(IEnumerable<SectionByOperationsDto> sections, WellDto well)
.Where(i => i.HasValue) {
.Select(i => i!.Value); var orderedSections = sections.OrderBy(s => s.DateStart);
var wellbores = new List<WellboreDto>();
int wellboreId = 1;
if (idsSections.Any()) SectionByOperationsDto? preSection = null;
wellFactSections = wellFactSections WellboreDto? wellbore = null;
.Where(section => idsSections.Contains(section.IdWellSectionType));
var wellWellbores = wellFactSections.Select(section => new WellboreDto { foreach (var section in orderedSections)
Id = section.IdWellSectionType, {
Name = sections[section.IdWellSectionType].Caption, if (wellbore is null || wellbore.DepthEnd > section.DepthStart)
Well = well.Adapt<WellWithTimezoneDto>(), {
DateStart = section.DateStart.ToOffset(wellTimezoneOffset), wellbore = new WellboreDto
DateEnd = section.DateEnd.ToOffset(wellTimezoneOffset), {
Name = string.Format(WellboreNameFormat, wellboreId),
Id = wellboreId,
Well = well,
DateStart = section.DateStart,
DateEnd = section.DateEnd,
DepthStart = section.DepthStart, DepthStart = section.DepthStart,
DepthEnd = section.DepthEnd, DepthEnd = section.DepthEnd,
}); };
wellbores.AddRange(wellWellbores); wellbores.Add(wellbore);
wellboreId++;
}
else
{
wellbore.DepthEnd = section.DepthEnd;
wellbore.DateEnd = section.DateEnd;
} }
return wellbores preSection = section;
.OrderBy(w => w.Well.Id).ThenBy(w => w.Id) }
.Skip(skip).Take(take);
if (wellbore is not null)
{
if (well.IdTelemetry is not null)
{
var dataCache = telemetryDataCache.GetOrDefaultFirstLast(well.IdTelemetry.Value);
if (dataCache is not null)
{
wellbore.DateEnd = dataCache.Value.Last.DateTime;
wellbore.DepthEnd = dataCache.Value.Last.WellDepth!.Value;
}
}
}
return wellbores;
} }
} }

View File

@ -15,6 +15,7 @@ using AsbCloudInfrastructure.Services.Subsystems;
using System.Linq; using System.Linq;
using DocumentFormat.OpenXml.InkML; using DocumentFormat.OpenXml.InkML;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure
{ {
@ -29,8 +30,8 @@ namespace AsbCloudInfrastructure
context.Database.EnshureCreatedAndMigrated(); context.Database.EnshureCreatedAndMigrated();
// TODO: Сделать инициализацию кеша телеметрии более явной. // TODO: Сделать инициализацию кеша телеметрии более явной.
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>(); _ = provider.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSpinDto>>(); _ = provider.GetRequiredService<ITelemetryDataCache<TelemetryDataSpinDto>>();
var backgroundWorker = provider.GetRequiredService<BackgroundWorker>(); var backgroundWorker = provider.GetRequiredService<BackgroundWorker>();
backgroundWorker.WorkStore.AddPeriodic<WellInfoService.WorkWellInfoUpdate>(TimeSpan.FromMinutes(30)); backgroundWorker.WorkStore.AddPeriodic<WellInfoService.WorkWellInfoUpdate>(TimeSpan.FromMinutes(30));

View File

@ -3,14 +3,11 @@ using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.SAUB;
using NSubstitute; using NSubstitute;
using Org.BouncyCastle.Asn1.Ocsp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
@ -19,25 +16,207 @@ namespace AsbCloudWebApi.Tests.ServicesTests
{ {
public class WellboreServiceTest public class WellboreServiceTest
{ {
private IWellService wellService;
private IWellOperationRepository wellOperationRepository;
private ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
private WellboreService wellboreService;
private WellDto well1 = new WellDto
{
Id = 1,
IdState = 1,
IdTelemetry = 1,
LastTelemetryDate = DateTime.Now,
Caption = "well 1"
};
private WellDto well2 = new WellDto
{
Id = 2,
IdState = 1,
IdTelemetry = 100,
LastTelemetryDate = DateTime.Now,
Caption = "well 2"
};
public WellboreServiceTest() public WellboreServiceTest()
{ {
var wellService = Substitute.For<IWellService>(); wellService = Substitute.For<IWellService>();
wellService.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
.Returns(Enumerable.Empty<WellDto>());
var wellOperationRepository = Substitute.For<IWellOperationRepository>(); wellOperationRepository = Substitute.For<IWellOperationRepository>();
wellOperationRepository.GetSectionsAsync(Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
.Returns(Enumerable.Empty<SectionByOperationsDto>());
var telemetryDataCache = Substitute.For<TelemetryDataCache<TelemetryDataSaubDto>>(); telemetryDataCache = Substitute.For<ITelemetryDataCache<TelemetryDataSaubDto>>();
telemetryDataCache.GetOrDefaultFirstLast(Arg.Any<int>());
wellboreService = new WellboreService(wellService, wellOperationRepository, telemetryDataCache);
} }
[Fact] [Fact]
public async Task GetWellboresAsync() public async Task GetWellboresAsync_returns_empty_collection()
{ {
var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);
Assert.NotNull(result);
Assert.False(result.Any());
}
[Fact]
public async Task GetWellboresAsync_returns_one_bore_by_well_only()
{
wellService.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
.Returns(new WellDto[] { well1 });
var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);
Assert.Single(result);
var wellbore0 = result.ElementAt(0);
Assert.Equal(well1.Caption, wellbore0.Well.Caption);
Assert.Equal(well1.Id, wellbore0.Well.Id);
Assert.Equal("Ствол 1", wellbore0.Name);
Assert.Equal(1, wellbore0.Id);
Assert.Equal(default, wellbore0.DateStart);
Assert.Equal(default, wellbore0.DateEnd);
Assert.Equal(default, wellbore0.DepthStart);
Assert.Equal(default, wellbore0.DepthEnd);
}
[Fact]
public async Task GetWellboresAsync_returns_two_bore_by_two_wells_only()
{
wellService.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
.Returns(new WellDto[] { well1, well2 });
var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);
Assert.Equal(2, result.Count());
}
[Fact]
public async Task GetWellboresAsync_returns_two_bore_by_well_with_sections()
{
wellService.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
.Returns(new WellDto[] { well1 });
var section0 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 01), DateEnd = new DateTime(2023, 01, 02), DepthStart = 000, DepthEnd = 100 };
var section1 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 02), DateEnd = new DateTime(2023, 01, 03), DepthStart = 100, DepthEnd = 300 };
var section2 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 03), DateEnd = new DateTime(2023, 01, 04), DepthStart = 200, DepthEnd = 210 };
var section3 = new SectionByOperationsDto()
{ IdWell = int.MaxValue, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 03), DateEnd = new DateTime(2023, 01, 04), DepthStart = 200, DepthEnd = 220 };
var section4 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 0, DateStart = new DateTime(2023, 01, 05), DateEnd = new DateTime(2023, 01, 06), DepthStart = 150, DepthEnd = 220 };
wellOperationRepository.GetSectionsAsync(Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
.Returns(new SectionByOperationsDto[]{section0, section1, section2, section3, section4, });
var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);
Assert.Equal(2, result.Count());
var wellbore0 = result.ElementAt(0);
Assert.Equal(well1.Caption, wellbore0.Well.Caption);
Assert.Equal(well1.Id, wellbore0.Well.Id);
Assert.Equal("Ствол 1", wellbore0.Name);
Assert.Equal(1, wellbore0.Id);
Assert.Equal(section0.DateStart, wellbore0.DateStart);
Assert.Equal(section0.DepthStart, wellbore0.DepthStart);
Assert.Equal(section1.DateEnd, wellbore0.DateEnd);
Assert.Equal(section1.DepthEnd, wellbore0.DepthEnd);
var wellbore1 = result.ElementAt(1);
Assert.Equal(well1.Caption, wellbore1.Well.Caption);
Assert.Equal(well1.Id, wellbore1.Well.Id);
Assert.Equal("Ствол 2", wellbore1.Name);
Assert.Equal(2, wellbore1.Id);
Assert.Equal(section2.DateStart, wellbore1.DateStart);
Assert.Equal(section2.DepthStart, wellbore1.DepthStart);
Assert.Equal(section2.DateEnd, wellbore1.DateEnd);
Assert.Equal(section2.DepthEnd, wellbore1.DepthEnd);
}
[Fact]
public async Task GetWellboresAsync_returns_one_bore_by_well_with_telemetry()
{
wellService.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
.Returns(new WellDto[] { well1 });
var firstCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2000, 01, 01), WellDepth = 0, };
var lastCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2023, 01, 05), WellDepth = 321, };
telemetryDataCache.GetOrDefaultFirstLast(Arg.Any<int>())
.Returns((firstCacheItem, lastCacheItem));
var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);
Assert.Single(result);
var wellbore0 = result.ElementAt(0);
Assert.Equal(well1.Caption, wellbore0.Well.Caption);
Assert.Equal(well1.Id, wellbore0.Well.Id);
Assert.Equal("Ствол 1", wellbore0.Name);
Assert.Equal(1, wellbore0.Id);
Assert.Equal(firstCacheItem.DateTime, wellbore0.DateStart);
Assert.Equal(firstCacheItem.WellDepth!.Value, wellbore0.DepthStart);
Assert.Equal(lastCacheItem.DateTime, wellbore0.DateEnd);
Assert.Equal(lastCacheItem.WellDepth!.Value, wellbore0.DepthEnd);
}
[Fact]
public async Task GetWellboresAsync_returns_two_bore_by_well_with_all()
{
wellService.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
.Returns(new WellDto[] { well1 });
var section0 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 01), DateEnd = new DateTime(2023, 01, 02), DepthStart = 000, DepthEnd = 100 };
var section1 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 02), DateEnd = new DateTime(2023, 01, 03), DepthStart = 100, DepthEnd = 300 };
var section2 = new SectionByOperationsDto()
{ IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 03), DateEnd = new DateTime(2023, 01, 04), DepthStart = 200, DepthEnd = 210 };
wellOperationRepository.GetSectionsAsync(Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
.Returns(new SectionByOperationsDto[] { section0, section1, section2});
var firstCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2000, 01, 01), WellDepth = 0, };
var lastCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2023, 01, 05), WellDepth = 321, };
telemetryDataCache.GetOrDefaultFirstLast(Arg.Any<int>())
.Returns((firstCacheItem, lastCacheItem));
var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);
Assert.Equal(2, result.Count());
var wellbore0 = result.ElementAt(0);
Assert.Equal(well1.Caption, wellbore0.Well.Caption);
Assert.Equal(well1.Id, wellbore0.Well.Id);
Assert.Equal("Ствол 1", wellbore0.Name);
Assert.Equal(1, wellbore0.Id);
Assert.Equal(section0.DateStart, wellbore0.DateStart);
Assert.Equal(section0.DepthStart, wellbore0.DepthStart);
Assert.Equal(section1.DateEnd, wellbore0.DateEnd);
Assert.Equal(section1.DepthEnd, wellbore0.DepthEnd);
var wellbore1 = result.ElementAt(1);
Assert.Equal(well1.Caption, wellbore1.Well.Caption);
Assert.Equal(well1.Id, wellbore1.Well.Id);
Assert.Equal("Ствол 2", wellbore1.Name);
Assert.Equal(2, wellbore1.Id);
Assert.Equal(section2.DateStart, wellbore1.DateStart);
Assert.Equal(section2.DepthStart, wellbore1.DepthStart);
Assert.Equal(lastCacheItem.DateTime, wellbore1.DateEnd);
Assert.Equal(lastCacheItem.WellDepth!.Value, wellbore1.DepthEnd);
} }
} }
} }

View File

@ -9,6 +9,7 @@ using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Org.BouncyCastle.Asn1.Ocsp;
namespace AsbCloudWebApi.Controllers; namespace AsbCloudWebApi.Controllers;
@ -27,65 +28,19 @@ public class WellboreController : ControllerBase
this.wellboreService = wellboreService; this.wellboreService = wellboreService;
} }
/// <summary>
/// Получение ствола скважины
/// </summary>
/// <param name="idWell">Id скважины</param>
/// <param name="idSection">Id типа секции скважины</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet("{idWell:int}/{idSection:int}")]
[ProducesResponseType(typeof(WellboreDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> GetAsync(int idWell, int idSection, CancellationToken cancellationToken)
{
var wellbore = await wellboreService.GetWellboreAsync(idWell, idSection, cancellationToken);
if (wellbore is null)
return NoContent();
return Ok(wellbore);
}
/// <summary> /// <summary>
/// Получение списка стволов скважин /// Получение списка стволов скважин
/// </summary> /// </summary>
/// <param name="ids">Пары идентификаторов скважины и секции</param> /// <param name="idsWells">Идентификаторы скважин</param>
/// <param name="skip">Опциональный параметр. Количество пропускаемых записей</param>
/// <param name="take">Опциональный параметр. Количество получаемых записей</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(IEnumerable<WellboreDto>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(IEnumerable<WellboreDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAllAsync([FromQuery] IEnumerable<string> ids, public async Task<IActionResult> GetAllAsync([FromQuery] IEnumerable<int> idsWells,
int? skip,
int? take,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var request = new WellboreRequest var result = await wellboreService.GetWellboresAsync(idsWells, cancellationToken);
{
Ids = ids.Select(id => ParseId(id)),
Skip = skip,
Take = take
};
return Ok(await wellboreService.GetWellboresAsync(request, cancellationToken)); return Ok(result);
}
private static (int, int?) ParseId(string id)
{
var idPair = id.Split(',');
if (!int.TryParse(idPair[0], out var idWell))
throw new ArgumentInvalidException(nameof(id), $"Не удалось получить Id скважины \"{idPair[0]}\"");
if (idPair.Length > 1)
{
if (int.TryParse(idPair[1], out int idWellSectionType))
return (idWell, idWellSectionType);
else
throw new ArgumentInvalidException(nameof(id), $"Не удалось получить Id ствола \"{idPair[1]}\"");
}
return (idWell, null);
} }
} }

View File

@ -0,0 +1,12 @@
@baseUrl = http://127.0.0.1:5000
@contentType = application/json
## Auth
POST {{baseUrl}}/auth/login
Content-Type: {{contentType}}
accept: */*
{
"login": "dev",
"password": "Rp7gsNMk"
}

View File

@ -1,15 +1,13 @@
@baseUrl = http://127.0.0.1:5000 @baseUrl = http://127.0.0.1:5000
@contentType = application/json @contentType = application/json
@auth = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2NjI1NDgxNjIsImV4cCI6MTY5NDEwNTc2MiwiaXNzIjoiYSIsImF1ZCI6ImEifQ.OEAlNzxi7Jat6pzDBTAjTbChskc-tdJthJexyWwwUKE @auth = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2OTgxMjY1MTUsImV4cCI6MTcyOTY4NDExNSwiaXNzIjoiYSIsImF1ZCI6ImEifQ.3Lq256cYtHnKlGWChPhZv2rUJPjbJEHU-18xdyJlYDE
@uid = 20210101_000000000 @idWell = 2
@idCluster = 1
@idWell = 1
# https://marketplace.visualstudio.com/items?itemName=humao.rest-client # https://marketplace.visualstudio.com/items?itemName=humao.rest-client
### ###
GET {{baseUrl}}/api/well/wellbore?ids=1,2 GET {{baseUrl}}/api/wellbore?idsWells=1
Content-Type: {{contentType}} Content-Type: {{contentType}}
accept: */* accept: */*
Authorization: {{auth}} Authorization: {{auth}}