forked from ddrilling/AsbCloudServer
Merge branch 'dev' into feature/detected_operations
# Conflicts: # AsbCloudInfrastructure/DependencyInjection.cs
This commit is contained in:
commit
23e8615e8a
@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudApp", "AsbCloudApp\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudInfrastructure", "AsbCloudInfrastructure\AsbCloudInfrastructure.csproj", "{67DBFC52-BAE4-4903-827A-AD0288C292B6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{D04A84E7-5F08-4042-8FB5-476EE49E9D22}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudDb", "AsbCloudDb\AsbCloudDb.csproj", "{40FBD29B-724B-4496-B5D9-1A5D14102456}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudWebApi.Tests", "AsbCloudWebApi.Tests\AsbCloudWebApi.Tests.csproj", "{9CF6FBB1-9AF5-45AB-A521-24F11A79B540}"
|
||||
@ -35,10 +33,6 @@ Global
|
||||
{67DBFC52-BAE4-4903-827A-AD0288C292B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{67DBFC52-BAE4-4903-827A-AD0288C292B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{67DBFC52-BAE4-4903-827A-AD0288C292B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{40FBD29B-724B-4496-B5D9-1A5D14102456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{40FBD29B-724B-4496-B5D9-1A5D14102456}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{40FBD29B-724B-4496-B5D9-1A5D14102456}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -5,21 +5,10 @@ namespace AsbCloudApp.Data.AutogeneratedDailyReport;
|
||||
/// <summary>
|
||||
/// Базовая информация о суточном отчёте
|
||||
/// </summary>
|
||||
public class AutoGeneratedDailyReportInfoDto
|
||||
public class AutoGeneratedDailyReportInfoDto : ReportInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Дата формирования отчёта
|
||||
/// </summary>
|
||||
public DateOnly ReportDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Название файла
|
||||
/// </summary>
|
||||
public string FileName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Размер файла
|
||||
/// </summary>
|
||||
public int FileSize { get; set; }
|
||||
|
||||
}
|
@ -172,7 +172,7 @@ namespace AsbCloudApp.Data
|
||||
if (progress.HasValue)
|
||||
CurrentState.Progress = progress.Value;
|
||||
|
||||
Trace.TraceInformation($"{WorkNameForTrace} state: {newState}");
|
||||
Trace.TraceInformation($"{WorkNameForTrace} state[{100*progress:#}%]: {newState}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
26
AsbCloudApp/Data/DrillTestReport/DrillTestReportDataDto.cs
Normal file
26
AsbCloudApp/Data/DrillTestReport/DrillTestReportDataDto.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using System;
|
||||
|
||||
namespace AsbCloudApp.Data.DrillTestReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Информация о drill test, выгружаемая в отчете
|
||||
/// </summary>
|
||||
public class DrillTestReportDataDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Данные для отчета
|
||||
/// </summary>
|
||||
public DrillTestDto Data { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Заголовок отчета
|
||||
/// </summary>
|
||||
public string Caption { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Дата отчета
|
||||
/// </summary>
|
||||
public DateTime Date { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
25
AsbCloudApp/Data/DrillTestReport/DrillTestReportInfoDto.cs
Normal file
25
AsbCloudApp/Data/DrillTestReport/DrillTestReportInfoDto.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace AsbCloudApp.Data.DrillTestReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Базовая информация о drill_test отчёте
|
||||
/// </summary>
|
||||
public class DrillTestReportInfoDto : ReportInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор отчета
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Проходка
|
||||
/// </summary>
|
||||
public float DrillDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата и время
|
||||
/// </summary>
|
||||
public DateTime DateTime { get; set; }
|
||||
}
|
||||
}
|
18
AsbCloudApp/Data/ReportInfoDto.cs
Normal file
18
AsbCloudApp/Data/ReportInfoDto.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace AsbCloudApp.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Справочная информация об отчете
|
||||
/// </summary>
|
||||
public class ReportInfoDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Название файла
|
||||
/// </summary>
|
||||
public string FileName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Размер файла
|
||||
/// </summary>
|
||||
public int FileSize { get; set; } = 0;
|
||||
}
|
||||
}
|
37
AsbCloudApp/Data/SAUB/DrillTestDto.cs
Normal file
37
AsbCloudApp/Data/SAUB/DrillTestDto.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AsbCloudApp.Data.SAUB
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO для описания записи drill_test
|
||||
/// </summary>
|
||||
public class DrillTestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор drill test
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Время начала drill test
|
||||
/// </summary>
|
||||
public DateTimeOffset TimeStampStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Глубина начала drill test
|
||||
/// </summary>
|
||||
public float DepthStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Связанная с drill_test телеметрия
|
||||
/// </summary>
|
||||
public TelemetryDto? Telemetry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Параметры теста
|
||||
/// </summary>
|
||||
public IEnumerable<DrillTestParamsDto> Params { get; set; } = Enumerable.Empty<DrillTestParamsDto>();
|
||||
}
|
||||
}
|
38
AsbCloudApp/Data/SAUB/DrillTestParamsDto.cs
Normal file
38
AsbCloudApp/Data/SAUB/DrillTestParamsDto.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace AsbCloudApp.Data.SAUB
|
||||
{
|
||||
/// <summary>
|
||||
/// Параметры Drill Test
|
||||
/// </summary>
|
||||
public class DrillTestParamsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Шаг
|
||||
/// </summary>
|
||||
public int Step { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Нагрузка
|
||||
/// </summary>
|
||||
public float? Workload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Заданная скорость
|
||||
/// </summary>
|
||||
public float? Speed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Скорость проходки
|
||||
/// </summary>
|
||||
public float? DepthSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Время бурения шага, сек
|
||||
/// </summary>
|
||||
public float? TimeDrillStep { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Глубина бурения шага
|
||||
/// </summary>
|
||||
public float? DepthDrillStep { get; set; }
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ namespace AsbCloudApp.Data.SAUB
|
||||
/// <summary>
|
||||
/// id категории события
|
||||
/// </summary>
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Id категории события не может быть отрицательным")]
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Id категории события не может быть отрицательным")]
|
||||
public int IdCategory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -32,7 +32,7 @@ namespace AsbCloudApp.Data.SAUB
|
||||
/// <summary>
|
||||
/// тип определения наступления события
|
||||
/// </summary>
|
||||
[Range(1, int.MaxValue, ErrorMessage = "Id типа события не может быть отрицательным")]
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Id типа события не может быть отрицательным")]
|
||||
public int EventType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -44,4 +44,5 @@ public class SectionByOperationsDto
|
||||
/// Дата после завершения последней операции операции в секции
|
||||
/// </summary>
|
||||
public DateTimeOffset DateEnd { get; set; }
|
||||
public string Caption { get; set; }
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ public class WellboreDto
|
||||
/// <summary>
|
||||
/// Скважина
|
||||
/// </summary>
|
||||
public WellWithTimezoneDto Well { get; set; } = null!;
|
||||
public WellDto Well { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AsbCloudApp.Exceptions
|
||||
{
|
||||
@ -8,9 +10,9 @@ namespace AsbCloudApp.Exceptions
|
||||
public class ArgumentInvalidException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// название аргумента
|
||||
/// словарь с ошибками, где ключ - имя аргумента, а значение - массив из одного сообщения
|
||||
/// </summary>
|
||||
public string ParamName { get; } = string.Empty;
|
||||
public IDictionary<string, string[]> ErrorState { get; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// конструктор
|
||||
@ -20,7 +22,20 @@ namespace AsbCloudApp.Exceptions
|
||||
public ArgumentInvalidException(string paramName, string message)
|
||||
: base(message)
|
||||
{
|
||||
ParamName = paramName;
|
||||
ErrorState = new Dictionary<string, string[]>() {
|
||||
{ paramName, new[]{ message } }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// конструктор
|
||||
/// </summary>
|
||||
/// <param name="paramsNames"></param>
|
||||
/// <param name="message"></param>
|
||||
public ArgumentInvalidException(string[] paramsNames, string message)
|
||||
: base(message)
|
||||
{
|
||||
ErrorState = paramsNames.ToDictionary(paramName => paramName, item => new[] { message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
41
AsbCloudApp/Repositories/IDrillTestRepository.cs
Normal file
41
AsbCloudApp/Repositories/IDrillTestRepository.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudApp.Requests;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudApp.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// репозиторий по работе с данными drill_test
|
||||
/// </summary>
|
||||
public interface IDrillTestRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить данные drill_test в соответствии с параметрами запроса
|
||||
/// </summary>
|
||||
/// <param name="idTelemetry">ключ телеметрии</param>
|
||||
/// <param name="request">запрос</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<DrillTestDto>> GetAllAsync(int idTelemetry, FileReportRequest request, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Получить запись drill_test
|
||||
/// </summary>
|
||||
/// <param name="idTelemetry">ключ телеметрии</param>
|
||||
/// <param name="id">ключ записи drill_test</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<DrillTestDto> GetAsync(int idTelemetry, int id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Сохранить данные drill_test
|
||||
/// </summary>
|
||||
/// <param name="idTelemetry">ключ телеметрии</param>
|
||||
/// <param name="dto">запись drill test</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> SaveDataAsync(int idTelemetry, DrillTestDto dto, CancellationToken token);
|
||||
}
|
||||
}
|
69
AsbCloudApp/Repositories/ITelemetryDataCache.cs
Normal file
69
AsbCloudApp/Repositories/ITelemetryDataCache.cs
Normal file
@ -0,0 +1,69 @@
|
||||
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? GetOrDefaultCachedaDateRange(int idTelemetry);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace AsbCloudApp.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Параметры запроса для получения авто-генерируемых суточных отчётов
|
||||
/// </summary>
|
||||
public class AutoGeneratedDailyReportRequest : RequestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Дата начала периода
|
||||
/// </summary>
|
||||
public DateOnly? StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата конца периода
|
||||
/// </summary>
|
||||
public DateOnly? FinishDate { get; set; }
|
||||
}
|
19
AsbCloudApp/Requests/FileReportRequest.cs
Normal file
19
AsbCloudApp/Requests/FileReportRequest.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace AsbCloudApp.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Параметры запроса для получения отчетов (файлов)
|
||||
/// </summary>
|
||||
public class FileReportRequest : RequestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Дата начала периода
|
||||
/// </summary>
|
||||
public DateOnly? GeDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата конца периода
|
||||
/// </summary>
|
||||
public DateOnly? LeDate { get; set; }
|
||||
}
|
@ -84,7 +84,7 @@ namespace AsbCloudApp.Requests
|
||||
{
|
||||
if (LtDepth < GtDepth)
|
||||
yield return new ValidationResult(
|
||||
$"{nameof(LtDepth)} должно быть больше {nameof(GtDepth)}. ({LtDepth:O} < {GtDepth:O})",
|
||||
$"{nameof(LtDepth)} должно быть больше {nameof(GtDepth)}. ({LtDepth} < {GtDepth})",
|
||||
new[] { nameof(LtDepth), nameof(GtDepth) });
|
||||
}
|
||||
|
||||
|
@ -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!;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsbCloudApp.Data.AutogeneratedDailyReport;
|
||||
|
||||
namespace AsbCloudApp.Services.AutoGeneratedDailyReports;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для генерации файлов авто-генерируемых суточный отчётов
|
||||
/// </summary>
|
||||
public interface IAutoGeneratedDailyReportMakerService
|
||||
{
|
||||
/// <summary>
|
||||
/// Генерация файла
|
||||
/// </summary>
|
||||
/// <param name="report"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<Stream> MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken);
|
||||
}
|
@ -21,7 +21,7 @@ public interface IAutoGeneratedDailyReportService
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<PaginationContainer<AutoGeneratedDailyReportInfoDto>> GetListAsync(int idWell,
|
||||
AutoGeneratedDailyReportRequest request,
|
||||
FileReportRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
35
AsbCloudApp/Services/IDrillTestReportService.cs
Normal file
35
AsbCloudApp/Services/IDrillTestReportService.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Data.DrillTestReport;
|
||||
using AsbCloudApp.Requests;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudApp.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// сервис по работе с отчетами drill test
|
||||
/// </summary>
|
||||
public interface IDrillTestReportService
|
||||
{
|
||||
/// <summary>
|
||||
/// Список файлов drill test
|
||||
/// </summary>
|
||||
/// <param name="idWell">ключ скважины</param>
|
||||
/// <param name="request">параметры запроса</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<PaginationContainer<DrillTestReportInfoDto>> GetListAsync(int idWell,
|
||||
FileReportRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Генерация файла с отчётом
|
||||
/// </summary>
|
||||
/// <param name="idWell">ключ скважины</param>
|
||||
/// <param name="id">ключ drill test записи</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<(string fileName, Stream stream)> GenerateAsync(int idWell, int id, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
19
AsbCloudApp/Services/IReportMakerService.cs
Normal file
19
AsbCloudApp/Services/IReportMakerService.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для генерации файлов отчётов
|
||||
/// </summary>
|
||||
public interface IReportMakerService<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Генерация файла
|
||||
/// </summary>
|
||||
/// <param name="report">модель с данными для построения отчета</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<Stream> MakeReportAsync(T report, CancellationToken cancellationToken);
|
||||
}
|
@ -57,5 +57,13 @@ namespace AsbCloudApp.Services
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<ReportPropertiesDto>> GetAllReportsByWellAsync(int idWell, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Удаление отчетов, если превышен их период хранения
|
||||
/// </summary>
|
||||
/// <param name="lifetime">период хранения отчетов</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> DeleteAllOldReportsAsync(TimeSpan lifetime, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
@ -26,17 +26,32 @@ namespace AsbCloudApp.Services
|
||||
Task<IEnumerable<TDto>> GetAsync(int idWell,
|
||||
DateTime dateBegin = default, double intervalSec = 600d,
|
||||
int approxPointsCount = 1024, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получить данные тех. процесса
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<TDto>> GetAsync(int idWell, TelemetryDataRequest request, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получение статистики за период
|
||||
/// Период за который есть данные по скважине в рамках временного интервала
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
/// <param name="geDate"></param>
|
||||
/// <param name="leDate"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token);
|
||||
Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Период за который есть данные по скважине
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <returns></returns>
|
||||
DatesRangeDto? GetRange(int idWell);
|
||||
|
||||
/// <summary>
|
||||
/// добавить/изменить данные тех. процесса (используется панелью)
|
||||
|
@ -2,7 +2,6 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Requests;
|
||||
|
||||
namespace AsbCloudApp.Services;
|
||||
|
||||
@ -11,20 +10,11 @@ namespace AsbCloudApp.Services;
|
||||
/// </summary>
|
||||
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>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="idsWells"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<WellboreDto>> GetWellboresAsync(WellboreRequest request, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<WellboreDto>> GetWellboresAsync(IEnumerable<int> idsWells, CancellationToken cancellationToken);
|
||||
}
|
50
AsbCloudDb/EFExtentionsInnitialization.cs
Normal file
50
AsbCloudDb/EFExtentionsInnitialization.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace AsbCloudDb
|
||||
{
|
||||
public static class EFExtentionsInnitialization
|
||||
{
|
||||
public static void EnshureCreatedAndMigrated(this DatabaseFacade db)
|
||||
{
|
||||
db.SetCommandTimeout(TimeSpan.FromMinutes(5));
|
||||
if (db.EnsureCreated())
|
||||
{
|
||||
db.CreateMigrationTable();
|
||||
db.WriteMigrationsInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
db.SetCommandTimeout(TimeSpan.FromMinutes(20));
|
||||
db.Migrate();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateMigrationTable(this DatabaseFacade db)
|
||||
{
|
||||
var sqlCreateMigrationTable =
|
||||
$"CREATE TABLE public.\"__EFMigrationsHistory\" " +
|
||||
$"(\"MigrationId\" varchar(150) NOT NULL, " +
|
||||
$" \"ProductVersion\" varchar(32) NOT NULL, " +
|
||||
$" CONSTRAINT \"PK___EFMigrationsHistory\" PRIMARY KEY (\"MigrationId\"));";
|
||||
db.ExecuteSqlRaw(sqlCreateMigrationTable);
|
||||
}
|
||||
|
||||
private static void WriteMigrationsInfo(this DatabaseFacade db)
|
||||
{
|
||||
var efVersion = db.GetType().Assembly.GetName().Version!;
|
||||
var efVersionString = $"{efVersion.Major}.{efVersion.Minor}.{efVersion.Build}";
|
||||
|
||||
var migrations = db.GetPendingMigrations()
|
||||
.Select(migration => $" ('{migration}', '{efVersionString}')");
|
||||
|
||||
var sqlAddLastMigration =
|
||||
$"INSERT INTO public.\"__EFMigrationsHistory\" " +
|
||||
$"(\"MigrationId\", \"ProductVersion\") " +
|
||||
$"VALUES {string.Join(',', migrations)};";
|
||||
db.ExecuteSqlRaw(sqlAddLastMigration);
|
||||
}
|
||||
}
|
||||
}
|
8803
AsbCloudDb/Migrations/20231017094813_Add_Drill_Test.Designer.cs
generated
Normal file
8803
AsbCloudDb/Migrations/20231017094813_Add_Drill_Test.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
AsbCloudDb/Migrations/20231017094813_Add_Drill_Test.cs
Normal file
46
AsbCloudDb/Migrations/20231017094813_Add_Drill_Test.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AsbCloudDb.Migrations
|
||||
{
|
||||
public partial class Add_Drill_Test : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "t_drill_test",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор"),
|
||||
id_telemetry = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор телеметрии"),
|
||||
timestamp_start = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Время начала"),
|
||||
depthStart = table.Column<float>(type: "real", nullable: false, comment: "Глубина начала"),
|
||||
t_drill_test_params = table.Column<string>(type: "jsonb", nullable: false, comment: "Параметры записи drill test")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_t_drill_test", x => new { x.id, x.id_telemetry });
|
||||
table.ForeignKey(
|
||||
name: "FK_t_drill_test_t_telemetry_id_telemetry",
|
||||
column: x => x.id_telemetry,
|
||||
principalTable: "t_telemetry",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "Drill_test");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_t_drill_test_id_telemetry",
|
||||
table: "t_drill_test",
|
||||
column: "id_telemetry");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "t_drill_test");
|
||||
}
|
||||
}
|
||||
}
|
8814
AsbCloudDb/Migrations/20231101110412_Update_EntityFillerSubsystem.Designer.cs
generated
Normal file
8814
AsbCloudDb/Migrations/20231101110412_Update_EntityFillerSubsystem.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AsbCloudDb.Migrations
|
||||
{
|
||||
public partial class Update_EntityFillerSubsystem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_subsystem",
|
||||
keyColumn: "id",
|
||||
keyValue: 65536,
|
||||
columns: new[] { "description", "name" },
|
||||
values: new object[] { "Осцилляция", "Осцилляция" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_subsystem",
|
||||
keyColumn: "id",
|
||||
keyValue: 65537,
|
||||
columns: new[] { "description", "name" },
|
||||
values: new object[] { "Демпфер", "Демпфер" });
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_subsystem",
|
||||
keyColumn: "id",
|
||||
keyValue: 65536,
|
||||
columns: new[] { "description", "name" },
|
||||
values: new object[] { "Spin master", "Spin master" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_subsystem",
|
||||
keyColumn: "id",
|
||||
keyValue: 65537,
|
||||
columns: new[] { "description", "name" },
|
||||
values: new object[] { "Torque master", "Torque master" });
|
||||
}
|
||||
}
|
||||
}
|
8815
AsbCloudDb/Migrations/20231102045101_Rename_Field_IsContact_In_CompanyType.Designer.cs
generated
Normal file
8815
AsbCloudDb/Migrations/20231102045101_Rename_Field_IsContact_In_CompanyType.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AsbCloudDb.Migrations
|
||||
{
|
||||
public partial class Rename_Field_IsContact_In_CompanyType : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "IsContact",
|
||||
table: "t_company_type",
|
||||
newName: "is_contact");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "is_contact",
|
||||
table: "t_company_type",
|
||||
newName: "IsContact");
|
||||
}
|
||||
}
|
||||
}
|
8857
AsbCloudDb/Migrations/20231102045600_Add_Or_Update_Data_In_CompanyType.Designer.cs
generated
Normal file
8857
AsbCloudDb/Migrations/20231102045600_Add_Or_Update_Data_In_CompanyType.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,101 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AsbCloudDb.Migrations
|
||||
{
|
||||
public partial class Add_Or_Update_Data_In_CompanyType : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 1,
|
||||
columns: new[] { "caption", "order" },
|
||||
values: new object[] { "Недропользователь", 3 });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 2,
|
||||
columns: new[] { "is_contact", "order" },
|
||||
values: new object[] { true, 2 });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 3,
|
||||
columns: new[] { "is_contact", "order" },
|
||||
values: new object[] { true, 0 });
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (4, 'Сервис по ГТИ', true, 6) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по ГТИ', is_contact=true, ""order""=6;");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (5, 'Растворный сервис', true, 4) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Растворный сервис', is_contact=true, ""order""=4;");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (6, 'Сервис по ННБ', true, 5) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по ННБ', is_contact=true, ""order""=5;");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (7, 'Служба супервайзинга', true, 1) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Служба супервайзинга', is_contact=true, ""order""=1;");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (9, 'Сервис по цементированию', true, 7) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по цементированию', is_contact=true, ""order""=7;");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (11, 'Дизельный сервис', false, 9) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Дизельный сервис', is_contact=false, ""order""=9;");
|
||||
|
||||
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
|
||||
@"VALUES (12, 'Сервис по обслуживанию верхних силовых приводов', true, 8) " +
|
||||
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по обслуживанию верхних силовых приводов', is_contact=false, ""order""=8;");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 4);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 5);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 6);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 7);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 9);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 12);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "t_company_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 1,
|
||||
columns: new[] { "caption", "order" },
|
||||
values: new object[] { "Недрапользователь", 1 });
|
||||
}
|
||||
}
|
||||
}
|
8894
AsbCloudDb/Migrations/20231107091439_Add_Data_To_WellSectionType.Designer.cs
generated
Normal file
8894
AsbCloudDb/Migrations/20231107091439_Add_Data_To_WellSectionType.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AsbCloudDb.Migrations
|
||||
{
|
||||
public partial class Add_Data_To_WellSectionType : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "t_well_section_type",
|
||||
columns: new[] { "id", "caption", "order" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ 34, "Хвостовик 6", 6.5f },
|
||||
{ 35, "Хвостовик 7", 6.6f },
|
||||
{ 36, "Хвостовик 8", 6.7f },
|
||||
{ 37, "Хвостовик 9", 6.8f },
|
||||
{ 38, "Хвостовик 10", 6.9f }
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_well_section_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 34);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_well_section_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 35);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_well_section_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 36);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_well_section_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 37);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "t_well_section_type",
|
||||
keyColumn: "id",
|
||||
keyValue: 38);
|
||||
}
|
||||
}
|
||||
}
|
8894
AsbCloudDb/Migrations/20231110080246_UpdateTable_t_telemetry_data_Set_ImportantColumns_NotNull.Designer.cs
generated
Normal file
8894
AsbCloudDb/Migrations/20231110080246_UpdateTable_t_telemetry_data_Set_ImportantColumns_NotNull.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,224 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AsbCloudDb.Migrations
|
||||
{
|
||||
public partial class UpdateTable_t_telemetry_data_Set_ImportantColumns_NotNull : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("DELETE FROM t_telemetry_data_saub WHERE " +
|
||||
"well_depth IS NULL OR " +
|
||||
"rotor_torque IS NULL OR " +
|
||||
"rotor_speed IS NULL OR " +
|
||||
"pressure IS NULL OR " +
|
||||
"mode IS NULL OR " +
|
||||
"hook_weight IS NULL OR " +
|
||||
"block_position IS NULL OR " +
|
||||
"bit_depth IS NULL OR " +
|
||||
"axial_load IS NULL;");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "well_depth",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Глубина забоя",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Глубина забоя");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "rotor_torque",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Момент на роторе",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Момент на роторе");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "rotor_speed",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Обороты ротора",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Обороты ротора");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "pressure",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Давление",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Давление");
|
||||
|
||||
migrationBuilder.AlterColumn<short>(
|
||||
name: "mode",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "smallint",
|
||||
nullable: false,
|
||||
defaultValue: (short)0,
|
||||
comment: "Режим САУБ",
|
||||
oldClrType: typeof(short),
|
||||
oldType: "smallint",
|
||||
oldNullable: true,
|
||||
oldComment: "Режим САУБ");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "hook_weight",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Вес на крюке",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Вес на крюке");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "block_position",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Высота талевого блока",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Высота талевого блока");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "bit_depth",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Положение инструмента",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Положение инструмента");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "axial_load",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: false,
|
||||
defaultValue: 0f,
|
||||
comment: "Осевая нагрузка",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldNullable: true,
|
||||
oldComment: "Осевая нагрузка");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "well_depth",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Глубина забоя",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Глубина забоя");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "rotor_torque",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Момент на роторе",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Момент на роторе");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "rotor_speed",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Обороты ротора",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Обороты ротора");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "pressure",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Давление",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Давление");
|
||||
|
||||
migrationBuilder.AlterColumn<short>(
|
||||
name: "mode",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "smallint",
|
||||
nullable: true,
|
||||
comment: "Режим САУБ",
|
||||
oldClrType: typeof(short),
|
||||
oldType: "smallint",
|
||||
oldComment: "Режим САУБ");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "hook_weight",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Вес на крюке",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Вес на крюке");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "block_position",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Высота талевого блока",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Высота талевого блока");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "bit_depth",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Положение инструмента",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Положение инструмента");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "axial_load",
|
||||
table: "t_telemetry_data_saub",
|
||||
type: "real",
|
||||
nullable: true,
|
||||
comment: "Осевая нагрузка",
|
||||
oldClrType: typeof(float),
|
||||
oldType: "real",
|
||||
oldComment: "Осевая нагрузка");
|
||||
}
|
||||
}
|
||||
}
|
@ -120,7 +120,8 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("caption");
|
||||
|
||||
b.Property<bool>("IsContact")
|
||||
.HasColumnType("boolean");
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_contact");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("integer")
|
||||
@ -134,9 +135,9 @@ namespace AsbCloudDb.Migrations
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
Caption = "Недрапользователь",
|
||||
Caption = "Недропользователь",
|
||||
IsContact = false,
|
||||
Order = 1
|
||||
Order = 3
|
||||
},
|
||||
new
|
||||
{
|
||||
@ -151,6 +152,48 @@ namespace AsbCloudDb.Migrations
|
||||
Caption = "Сервис автоматизации бурения",
|
||||
IsContact = false,
|
||||
Order = 0
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 4,
|
||||
Caption = "Сервис по ГТИ",
|
||||
IsContact = true,
|
||||
Order = 6
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 5,
|
||||
Caption = "Растворный сервис",
|
||||
IsContact = true,
|
||||
Order = 4
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 6,
|
||||
Caption = "Сервис по ННБ",
|
||||
IsContact = true,
|
||||
Order = 5
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 7,
|
||||
Caption = "Служба супервайзинга",
|
||||
IsContact = true,
|
||||
Order = 1
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 9,
|
||||
Caption = "Сервис по цементированию",
|
||||
IsContact = true,
|
||||
Order = 7
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 12,
|
||||
Caption = "Сервис по обслуживанию верхних силовых приводов",
|
||||
IsContact = true,
|
||||
Order = 7
|
||||
});
|
||||
});
|
||||
|
||||
@ -413,6 +456,43 @@ namespace AsbCloudDb.Migrations
|
||||
b.HasComment("части программ бурения");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AsbCloudDb.Model.DrillTest", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<int>("IdTelemetry")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id_telemetry")
|
||||
.HasComment("Идентификатор телеметрии");
|
||||
|
||||
b.Property<float>("DepthStart")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("depthStart")
|
||||
.HasComment("Глубина начала");
|
||||
|
||||
b.Property<string>("Params")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("t_drill_test_params")
|
||||
.HasComment("Параметры записи drill test");
|
||||
|
||||
b.Property<DateTimeOffset>("TimeStampStart")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("timestamp_start")
|
||||
.HasComment("Время начала");
|
||||
|
||||
b.HasKey("Id", "IdTelemetry");
|
||||
|
||||
b.HasIndex("IdTelemetry");
|
||||
|
||||
b.ToTable("t_drill_test");
|
||||
|
||||
b.HasComment("Drill_test");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AsbCloudDb.Model.Faq", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -4531,14 +4611,14 @@ namespace AsbCloudDb.Migrations
|
||||
new
|
||||
{
|
||||
Id = 65536,
|
||||
Description = "Spin master",
|
||||
Name = "Spin master"
|
||||
Description = "Осцилляция",
|
||||
Name = "Осцилляция"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 65537,
|
||||
Description = "Torque master",
|
||||
Name = "Torque master"
|
||||
Description = "Демпфер",
|
||||
Name = "Демпфер"
|
||||
});
|
||||
});
|
||||
|
||||
@ -4638,7 +4718,7 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("date")
|
||||
.HasComment("'2021-10-19 18:23:54+05'");
|
||||
|
||||
b.Property<float?>("AxialLoad")
|
||||
b.Property<float>("AxialLoad")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("axial_load")
|
||||
.HasComment("Осевая нагрузка");
|
||||
@ -4653,12 +4733,12 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("axial_load_sp")
|
||||
.HasComment("Осевая нагрузка. Задание");
|
||||
|
||||
b.Property<float?>("BitDepth")
|
||||
b.Property<float>("BitDepth")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("bit_depth")
|
||||
.HasComment("Положение инструмента");
|
||||
|
||||
b.Property<float?>("BlockPosition")
|
||||
b.Property<float>("BlockPosition")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("block_position")
|
||||
.HasComment("Высота талевого блока");
|
||||
@ -4713,7 +4793,7 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("flow_idle")
|
||||
.HasComment("Расход. Холостой ход");
|
||||
|
||||
b.Property<float?>("HookWeight")
|
||||
b.Property<float>("HookWeight")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("hook_weight")
|
||||
.HasComment("Вес на крюке");
|
||||
@ -4743,7 +4823,7 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("id_user")
|
||||
.HasComment("Пользователь САУБ");
|
||||
|
||||
b.Property<short?>("Mode")
|
||||
b.Property<short>("Mode")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("mode")
|
||||
.HasComment("Режим САУБ");
|
||||
@ -4758,7 +4838,7 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("mse_state")
|
||||
.HasComment("Текущее состояние работы MSE");
|
||||
|
||||
b.Property<float?>("Pressure")
|
||||
b.Property<float>("Pressure")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("pressure")
|
||||
.HasComment("Давление");
|
||||
@ -4808,12 +4888,12 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("pump2_flow")
|
||||
.HasComment("Расход. Буровой насос 3");
|
||||
|
||||
b.Property<float?>("RotorSpeed")
|
||||
b.Property<float>("RotorSpeed")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("rotor_speed")
|
||||
.HasComment("Обороты ротора");
|
||||
|
||||
b.Property<float?>("RotorTorque")
|
||||
b.Property<float>("RotorTorque")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("rotor_torque")
|
||||
.HasComment("Момент на роторе");
|
||||
@ -4833,7 +4913,7 @@ namespace AsbCloudDb.Migrations
|
||||
.HasColumnName("rotor_torque_sp")
|
||||
.HasComment("Момент на роторе. Задание");
|
||||
|
||||
b.Property<float?>("WellDepth")
|
||||
b.Property<float>("WellDepth")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("well_depth")
|
||||
.HasComment("Глубина забоя");
|
||||
@ -7022,6 +7102,36 @@ namespace AsbCloudDb.Migrations
|
||||
Id = 33,
|
||||
Caption = "Техническая колонна 3",
|
||||
Order = 2.2f
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 34,
|
||||
Caption = "Хвостовик 6",
|
||||
Order = 6.5f
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 35,
|
||||
Caption = "Хвостовик 7",
|
||||
Order = 6.6f
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 36,
|
||||
Caption = "Хвостовик 8",
|
||||
Order = 6.7f
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 37,
|
||||
Caption = "Хвостовик 9",
|
||||
Order = 6.8f
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = 38,
|
||||
Caption = "Хвостовик 10",
|
||||
Order = 6.9f
|
||||
});
|
||||
});
|
||||
|
||||
@ -7846,6 +7956,17 @@ namespace AsbCloudDb.Migrations
|
||||
b.Navigation("Well");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AsbCloudDb.Model.DrillTest", b =>
|
||||
{
|
||||
b.HasOne("AsbCloudDb.Model.Telemetry", "Telemetry")
|
||||
.WithMany()
|
||||
.HasForeignKey("IdTelemetry")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Telemetry");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AsbCloudDb.Model.Faq", b =>
|
||||
{
|
||||
b.HasOne("AsbCloudDb.Model.User", "AuthorAnswer")
|
||||
|
@ -84,6 +84,7 @@ namespace AsbCloudDb.Model
|
||||
public DbSet<ManualDirectory> ManualDirectories => Set<ManualDirectory>();
|
||||
public DbSet<Contact> Contacts => Set<Contact>();
|
||||
|
||||
public DbSet<DrillTest> DrillTests => Set<DrillTest>();
|
||||
public AsbCloudDbContext() : base()
|
||||
{
|
||||
Interlocked.Increment(ref referenceCount);
|
||||
@ -103,7 +104,7 @@ namespace AsbCloudDb.Model
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"
|
||||
optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True;Include Error Detail=True;"
|
||||
//, builder=>builder.EnableRetryOnFailure(2, System.TimeSpan.FromMinutes(1))
|
||||
);
|
||||
}
|
||||
@ -411,6 +412,15 @@ namespace AsbCloudDb.Model
|
||||
.HasForeignKey(m => m.IdDirectory)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DrillTest>()
|
||||
.HasKey(m => new { m.Id, m.IdTelemetry });
|
||||
|
||||
modelBuilder.Entity<DrillTest>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Params)
|
||||
.HasJsonConversion();
|
||||
});
|
||||
|
||||
DefaultData.DefaultContextData.Fill(modelBuilder);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ namespace AsbCloudDb.Model
|
||||
[Column("caption")]
|
||||
[StringLength(255)]
|
||||
public string Caption { get; set; } = null!;
|
||||
|
||||
[Column("is_contact")]
|
||||
public bool IsContact { get; set; }
|
||||
|
||||
[Column("order")]
|
||||
|
@ -3,9 +3,16 @@
|
||||
internal class EntityFillerCompanyType : EntityFiller<CompanyType>
|
||||
{
|
||||
public override CompanyType[] GetData() => new CompanyType[] {
|
||||
new (){ Id = 1, Caption = "Недрапользователь", Order = 1 },
|
||||
new (){ Id = 2, Caption = "Буровой подрядчик", Order = 2 },
|
||||
new (){ Id = 3, Caption = "Сервис автоматизации бурения", Order = 0 }
|
||||
new (){ Id = 1, Caption = "Недропользователь", IsContact = true, Order = 3 },
|
||||
new (){ Id = 2, Caption = "Буровой подрядчик", IsContact = true, Order = 2 },
|
||||
new (){ Id = 3, Caption = "Сервис автоматизации бурения", IsContact = true, Order = 0 },
|
||||
new (){ Id = 4, Caption = "Сервис по ГТИ", IsContact = true, Order = 6 },
|
||||
new (){ Id = 5, Caption = "Растворный сервис", IsContact = true, Order = 4 },
|
||||
new (){ Id = 6, Caption = "Сервис по ННБ", IsContact = true, Order = 5 },
|
||||
new (){ Id = 7, Caption = "Служба супервайзинга", Order = 1 },
|
||||
new (){ Id = 9, Caption = "Сервис по цементированию", IsContact = true, Order = 7 },
|
||||
new (){ Id = 11, Caption = "Дизельный сервис", IsContact = false, Order = 9 },
|
||||
new (){ Id = 12, Caption = "Сервис по обслуживанию верхних силовых приводов", IsContact = true, Order = 8 },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +161,8 @@
|
||||
|
||||
new() { Id = 527, Name = "Manual.delete", Description = "Разрешение на удаление инструкций"},
|
||||
new (){ Id = 528, Name="WellContact.delete", Description="Разрешение на удаление контакта"},
|
||||
|
||||
new (){ Id = 529, Name="DrillTestReport.get", Description="Разрешение на получение отчетов drill test"},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ namespace AsbCloudDb.Model.DefaultData
|
||||
new () {Id = 12, Name = "АПД слайд", Description = "Режим работы \"Бурение в слайде\""},
|
||||
new () {Id = 2, Name = "MSE", Description = "Алгоритм поиска оптимальных параметров бурения САУБ"},
|
||||
//Spin master - id подсистем с 65_536 до 131_071
|
||||
new () {Id = 65536, Name = "Spin master", Description = "Spin master"},
|
||||
new () {Id = 65537, Name = "Torque master", Description = "Torque master"}
|
||||
new () {Id = 65536, Name = "Осцилляция", Description = "Осцилляция"},
|
||||
new () {Id = 65537, Name = "Демпфер", Description = "Демпфер"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,12 @@
|
||||
new (){ Id = 31, Caption = "Техническая колонна", Order = 2},
|
||||
new (){ Id = 32, Caption = "Техническая колонна 2", Order = 2.1f},
|
||||
new (){ Id = 33, Caption = "Техническая колонна 3", Order = 2.2f},
|
||||
|
||||
new (){ Id = 34, Caption = "Хвостовик 6", Order = 6.5f},
|
||||
new (){ Id = 35, Caption = "Хвостовик 7", Order = 6.6f},
|
||||
new (){ Id = 36, Caption = "Хвостовик 8", Order = 6.7f},
|
||||
new (){ Id = 37, Caption = "Хвостовик 9", Order = 6.8f},
|
||||
new (){ Id = 38, Caption = "Хвостовик 10", Order = 6.9f},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
42
AsbCloudDb/Model/DrillTest.cs
Normal file
42
AsbCloudDb/Model/DrillTest.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AsbCloudDb.Model
|
||||
{
|
||||
[Table("t_drill_test"), Comment("Drill_test")]
|
||||
public class DrillTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор drill test
|
||||
/// </summary>
|
||||
[Key, Column("id"), Comment("Идентификатор")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор телеметрии drill test
|
||||
/// </summary>
|
||||
[Key, Column("id_telemetry"), Comment("Идентификатор телеметрии")]
|
||||
public int IdTelemetry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Время начала drill test
|
||||
/// </summary>
|
||||
[Column("timestamp_start", TypeName = "timestamp with time zone"), Comment("Время начала")]
|
||||
public DateTimeOffset TimeStampStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Глубина начала drill test
|
||||
/// </summary>
|
||||
[Column("depthStart"), Comment("Глубина начала")]
|
||||
public float DepthStart { get; set; }
|
||||
|
||||
[ForeignKey(nameof(IdTelemetry))]
|
||||
public virtual Telemetry Telemetry { get; set; } = null!;
|
||||
|
||||
[Column("t_drill_test_params", TypeName = "jsonb"), Comment("Параметры записи drill test")]
|
||||
public virtual ICollection<DrillTestParameter> Params { get; set; } = null!;
|
||||
}
|
||||
}
|
42
AsbCloudDb/Model/DrillTestParameter.cs
Normal file
42
AsbCloudDb/Model/DrillTestParameter.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AsbCloudDb.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// Параметры записи drill test
|
||||
/// </summary>
|
||||
public class DrillTestParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Шаг
|
||||
/// </summary>
|
||||
public int Step { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Нагрузка
|
||||
/// </summary>
|
||||
public float? Workload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Заданная скорость
|
||||
/// </summary>
|
||||
public float? Speed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Скорость проходки
|
||||
/// </summary>
|
||||
public float? DepthSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Время бурения шага
|
||||
/// </summary>
|
||||
public float? TimeDrillStep { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Глубина бурения шага
|
||||
/// </summary>
|
||||
public float? DepthDrillStep { get; set; }
|
||||
}
|
||||
}
|
@ -76,6 +76,7 @@ namespace AsbCloudDb.Model
|
||||
DbSet<Manual> Manuals { get; }
|
||||
DbSet<ManualDirectory> ManualDirectories { get; }
|
||||
DbSet<Contact> Contacts { get; }
|
||||
DbSet<DrillTest> DrillTests { get; }
|
||||
DatabaseFacade Database { get; }
|
||||
|
||||
Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token);
|
||||
|
@ -18,7 +18,7 @@ namespace AsbCloudDb.Model
|
||||
public DateTimeOffset DateTime { get; set; }
|
||||
|
||||
[Column("mode"), Comment("Режим САУБ")]
|
||||
public short? Mode { get; set; }
|
||||
public short Mode { get; set; }
|
||||
|
||||
[Column("id_feed_regulator"), Comment("Текущий критерий бурения")]
|
||||
public short? IdFeedRegulator { get; set; }
|
||||
@ -27,13 +27,13 @@ namespace AsbCloudDb.Model
|
||||
public short? MseState { get; set; }
|
||||
|
||||
[Column("well_depth"), Comment("Глубина забоя")]
|
||||
public float? WellDepth { get; set; }
|
||||
public float WellDepth { get; set; }
|
||||
|
||||
[Column("bit_depth"), Comment("Положение инструмента")]
|
||||
public float? BitDepth { get; set; }
|
||||
public float BitDepth { get; set; }
|
||||
|
||||
[Column("block_position"), Comment("Высота талевого блока")]
|
||||
public float? BlockPosition { get; set; }
|
||||
public float BlockPosition { get; set; }
|
||||
|
||||
[Column("block_position_min"), Comment("Талевый блок. Мин положение")]
|
||||
public float? BlockPositionMin { get; set; }
|
||||
@ -57,7 +57,7 @@ namespace AsbCloudDb.Model
|
||||
public float? BlockSpeedSpDevelop { get; set; }
|
||||
|
||||
[Column("pressure"), Comment("Давление")]
|
||||
public float? Pressure { get; set; }
|
||||
public float Pressure { get; set; }
|
||||
|
||||
[Column("pressure_idle"), Comment("Давление. Холостой ход")]
|
||||
public float? PressureIdle { get; set; }
|
||||
@ -78,7 +78,7 @@ namespace AsbCloudDb.Model
|
||||
public float? PressureDeltaLimitMax { get; set; }
|
||||
|
||||
[Column("axial_load"), Comment("Осевая нагрузка")]
|
||||
public float? AxialLoad { get; set; }
|
||||
public float AxialLoad { get; set; }
|
||||
|
||||
[Column("axial_load_sp"), Comment("Осевая нагрузка. Задание")]
|
||||
public float? AxialLoadSp { get; set; }
|
||||
@ -87,7 +87,7 @@ namespace AsbCloudDb.Model
|
||||
public float? AxialLoadLimitMax { get; set; }
|
||||
|
||||
[Column("hook_weight"), Comment("Вес на крюке")]
|
||||
public float? HookWeight { get; set; }
|
||||
public float HookWeight { get; set; }
|
||||
|
||||
[Column("hook_weight_idle"), Comment("Вес на крюке. Холостой ход")]
|
||||
public float? HookWeightIdle { get; set; }
|
||||
@ -99,7 +99,7 @@ namespace AsbCloudDb.Model
|
||||
public float? HookWeightLimitMax { get; set; }
|
||||
|
||||
[Column("rotor_torque"), Comment("Момент на роторе")]
|
||||
public float? RotorTorque { get; set; }
|
||||
public float RotorTorque { get; set; }
|
||||
|
||||
[Column("rotor_torque_idle"), Comment("Момент на роторе. Холостой ход")]
|
||||
public float? RotorTorqueIdle { get; set; }
|
||||
@ -111,7 +111,7 @@ namespace AsbCloudDb.Model
|
||||
public float? RotorTorqueLimitMax { get; set; }
|
||||
|
||||
[Column("rotor_speed"), Comment("Обороты ротора")]
|
||||
public float? RotorSpeed { get; set; }
|
||||
public float RotorSpeed { get; set; }
|
||||
|
||||
[Column("flow"), Comment("Расход")]
|
||||
public float? Flow { get; set; }
|
||||
|
265
AsbCloudDb/Setup db replication.md
Normal file
265
AsbCloudDb/Setup db replication.md
Normal file
@ -0,0 +1,265 @@
|
||||
# Репликация данных PostgreSQL
|
||||
## 1. Требования
|
||||
1. Primary и Replica сервера должны принадлежать одной версии postgreSQL
|
||||
2. Сервера должны иметь удаленный доступ
|
||||
|
||||
|
||||
## 2. Настройка Primary-сервера
|
||||
1. Открыть postgres.conf на редактирование
|
||||
|
||||
```
|
||||
cd /etc/postgresql/15/main/
|
||||
|
||||
sudo nano postgresql.conf
|
||||
```
|
||||
|
||||
2. В postgres.conf найти запись listen_addresses и добавить туда ip standby-сервера
|
||||
|
||||
> listen_addresses = '*, <ip standby-сервера>'
|
||||
|
||||
3. Открыть клиент для работы с postgres
|
||||
|
||||
```
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
4. Создать пользователя с атрибутом REPLICATION. <br />
|
||||
P.S: В данном примере создается пользователь с логином replicator и паролем q
|
||||
|
||||
```
|
||||
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'q';
|
||||
```
|
||||
|
||||
5. Открыть на редактирование файл pg_hba.conf
|
||||
```
|
||||
cd /etc/postgresql/15/main/
|
||||
|
||||
sudo nano pg_hba.conf
|
||||
```
|
||||
|
||||
6. Вставить в pg_hba.conf запись.
|
||||
Запись вставлять после комментария "Allow replication connections from localhost..." <br />
|
||||
Данные для вставки записи:<br />
|
||||
- replicator - имя пользователя, созданного на предыдущем шаге<br/>
|
||||
- <ip подсети>, например, 192.168.0.0/24
|
||||
```
|
||||
host replication replicator 192.168.0.0/24 md5
|
||||
```
|
||||
|
||||
7. Рестарт сервера
|
||||
```
|
||||
sudo systemctl restart postgresql
|
||||
```
|
||||
|
||||
## 3. Настройка replica-сервера
|
||||
1. Остановить сервер
|
||||
```
|
||||
sudo systemctl stop postgresql
|
||||
```
|
||||
|
||||
2. Важно! Зайти под пользователем postgres
|
||||
```
|
||||
sudo su - postgres
|
||||
```
|
||||
|
||||
3. Сделать резервную копию содержимого /var/lib/postgresql/15/main/ в папку main_old
|
||||
```
|
||||
cp -R /var/lib/postgresql/15/main/ /var/lib/postgresql/15/main_old/
|
||||
```
|
||||
|
||||
4. Удалить папку main
|
||||
```
|
||||
rm -rf /var/lib/postgresql/15/main/
|
||||
```
|
||||
|
||||
5. Используя утилиту basebackup создать базовую резервную копию с правами владения postgres (либо любого пользователя с соответствующими разрешениями).
|
||||
|
||||
```
|
||||
pg_basebackup -h <ip primary-сервера> -D /var/lib/postgresql/14/main/ -U replicator -P -v -R -X stream -C -S slaveslot1
|
||||
|
||||
где: /var/lib/postgresql/15/main/ - каталог replica-сервера
|
||||
```
|
||||
|
||||
6. Убедиться, что в папке main созданы файлы standby.signal и postgresql.auto.conf.
|
||||
```
|
||||
ls -ltrh /var/lib/postgresql/15/main/
|
||||
```
|
||||
|
||||
7. Запустить сервер
|
||||
```
|
||||
systemctl start postgresql
|
||||
```
|
||||
|
||||
## 4. Проверка настроек
|
||||
1. Подсоединиться к primary-серверу
|
||||
```
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
2. На primary-сервере выполнить команду
|
||||
```
|
||||
SELECT * FROM pg_replication_slots;
|
||||
```
|
||||
|
||||
3. Убедиться, что в представлении отображается слот репликации с именем slotslave1
|
||||
4. На standby-сервере выпонить команду
|
||||
```
|
||||
SELECT * FROM pg_stat_wal_receiver;
|
||||
```
|
||||
5. Убедиться, что появилась запись с ip primary-сервера
|
||||
|
||||
6. На primary - сервере проверить режим репликации. Он может быть синхронным или асинхронным. Для проверки необходимо выполнить команду
|
||||
```
|
||||
SELECT * FROM pg_stat_replication;
|
||||
```
|
||||
|
||||
7. Для включения синхронного режима необходимо выполнить следующую команду
|
||||
```
|
||||
ALTER SYSTEM SET synchronous_standby_names TO '*';
|
||||
```
|
||||
|
||||
8. Сделать рестарт primary-сервера.
|
||||
|
||||
9. Внести запись в любую таблицу базы данных primary-сервера
|
||||
10. Убедиться, что соответствующая запись появилась в таблице базы данных standby-сервера
|
||||
11. Попытаться внести запись в таблицу базы данных standby-сервера.
|
||||
12. Убедиться, что операция завершилась с ошибкой
|
||||
> cannot execute OPERATION in a read-only transaction
|
||||
|
||||
|
||||
|
||||
## 5. Установка PgPool-II
|
||||
|
||||
|
||||
1. Установить на primary-сервер pgpool2 и postgresql-14-pgpool2
|
||||
```
|
||||
apt-get -y install pgpool2 postgresql-15-pgpool2
|
||||
|
||||
```
|
||||
2. Установить на standby-сервер только postgresql-14-pgpool2
|
||||
```
|
||||
apt-get -y install postgresql-15-pgpool2
|
||||
```
|
||||
### Далее все настройки выполнить на primary-сервере
|
||||
3. Зайти на редактирование в конфигурационный файл pgpool2
|
||||
```
|
||||
sudo nano /etc/pgpool2/pgpool.conf
|
||||
```
|
||||
4. Задать параметры следующим образом:
|
||||
```
|
||||
backend_clustering_mode = 'streaming_replication'
|
||||
listen_addresses = '*, <ip standby-сервера>'
|
||||
port = 9999
|
||||
___
|
||||
backend_hostname0 = '<ip primary-сервера>'
|
||||
backend_port0 = '<порт primary-сервера>'
|
||||
backend_weight0 = 0
|
||||
backend_data_directory0 = '/var/lib/postgresql/14/main'
|
||||
___
|
||||
backend_hostname1 = '<ip replica-сервера>'
|
||||
backend_port1 = '<порт primary-сервера>'
|
||||
backend_weight1 = 1
|
||||
___
|
||||
enable_pool_hba = on
|
||||
log_statement = on
|
||||
log_per_node_statement = on
|
||||
pid_file_name = "pgpool.pid"
|
||||
load_balance_mode = on
|
||||
statement_level_load_balance = on
|
||||
sr_check_period = 1
|
||||
sr_check_user = '<имя пользователя>'
|
||||
sr_check_password = '<пароль пользователя>'
|
||||
health_check_period = 10
|
||||
health_check_user = '<имя пользователя>'
|
||||
health_check_password = '<пароль пользователя>'
|
||||
```
|
||||
5. Поскольку enable_pool_hba указан в режиме on, это значит, что Pgpool-II будет использовать pool_hba.conf для аутентификации клиента. Поэтому открываем на редактирование pool_hba.conf
|
||||
```
|
||||
sudo nano /etc/pgpool2/pool_hba.conf
|
||||
```
|
||||
6. Добавить строку
|
||||
```
|
||||
host all all <ip подсети> md5
|
||||
```
|
||||
7. Pgpool-II извлекает пароль пользователя из файла pool_passwd
|
||||
```
|
||||
sudo nano /etc/pgpool2/pool_passwd
|
||||
```
|
||||
Файл паролей представляет собой текстовый файл следующего формата:
|
||||
```
|
||||
пользователь1:пароль1
|
||||
пользователь2:пароль2
|
||||
```
|
||||
Файл может содержать 3 типа паролей. Pgpool-II идентифицирует тип формата пароля по его префиксу, поэтому каждая запись пароля в pool_passwd должна иметь префикс формата пароля.
|
||||
|
||||
- Обычный текст : пароль в текстовом формате с использованием префикса TEXT (например, TEXTmypassword ) .
|
||||
- Зашифрованный пароль AES256 : зашифрованный пароль AES256, используя префикс AES (например, AESmzVzywsN1Z5GABhSAhwLSA== ) .
|
||||
- Хешированный пароль MD5 : хешированный пароль MD5, используя префикс md5 (например, md5270e98c3db83dbc0e40f98d9bfe20972 ) .
|
||||
|
||||
8. В примере в качестве пароля используется обычный текст (пароль q)
|
||||
```
|
||||
postgres:TEXTq
|
||||
```
|
||||
9. Запустить pgpool
|
||||
```
|
||||
sudo pgpool -n
|
||||
```
|
||||
10. Убедиться, что процесс успешно запущен и подключены 2 ноды с разными индексами. В данном примере для primary node установлен индекс 0, а для standBy ноды установден индекс 1
|
||||
```
|
||||
|
||||
2023-09-14 06:08:08.339: main pid 3941: LOG: find_primary_node: primary node is 0
|
||||
2023-09-14 06:08:08.339: main pid 3941: LOG: find_primary_node: standby node is 1
|
||||
2023-09-14 06:08:08.343: pcp_main pid 3977: LOG: PCP process: 3977 started
|
||||
2023-09-14 06:08:08.343: sr_check_worker pid 3978: LOG: process started
|
||||
2023-09-14 06:08:08.345: health_check pid 3979: LOG: process started
|
||||
2023-09-14 06:08:08.349: health_check pid 3980: LOG: process started
|
||||
2023-09-14 06:08:08.559: main pid 3941: LOG: pgpool-II successfully started. version 4.3.5 (tamahomeboshi)
|
||||
2023-09-14 06:08:08.662: main pid 3941: LOG: node status[0]: 1
|
||||
2023-09-14 06:08:08.662: main pid 3941: LOG: node status[1]: 2
|
||||
|
||||
```
|
||||
11. При старте pgpool возможны следующие ошибки:
|
||||
- файл pgpool_status не найден / нет прав
|
||||
- pgpool стартует, но ноды имеют одинаковый индекс и балансировка идет только на первую ноду (как проверить балансировку указано ниже) <br/>
|
||||
|
||||
Проблема решилась удалением файла pgpool_status, откуда pgpool пытался считывать статусы для нод.
|
||||
|
||||
```
|
||||
cd /var/log/postgresql
|
||||
rm -rf pgpool_status
|
||||
sudo systemctl restart postgresql
|
||||
sudo pgpool -n
|
||||
```
|
||||
|
||||
## 6. Тестирование балансировки PgPool-II
|
||||
1. При запущенном pgpool (он должен выводить логи), открыть еще один терминал. Зайти в базу, используя Pgpool-II на 9999-порте, выполнив команду
|
||||
```
|
||||
psql -h <ip сервера, где установлен pgpool> -p 9999 -d postgres -U postgres
|
||||
```
|
||||
|
||||
2. Выполнить команду
|
||||
```
|
||||
show pool_nodes;
|
||||
```
|
||||
3. Убедиться, что обе ноды находятся в статусе up, а балансировка установлена на standBy-сервере (load_balance_node = true)
|
||||
```
|
||||
node_id | hostname | port | status | pg_status | lb_weight | role | pg_role | select_cnt | load_balance_node | replication_delay | replication_state | replication_sync_state | last_status_change
|
||||
---------+--------------+------+--------+-----------+-----------+---------+---------+------------+-------------------+-------------------+-------------------+------------------------+---------------------
|
||||
0 | 192.168.0.71 | 5432 | up | up | 0.000000 | primary | primary | 0 | false | 0 | | | 2023-09-14 06:36:16
|
||||
1 | 192.168.0.72 | 5432 | up | up | 1.000000 | standby | standby | 0 | true | 0 | | | 2023-09-14 06:36:16
|
||||
(2 rows)
|
||||
|
||||
```
|
||||
4. Выполнить команды Insert / Update / Delete (в качесве примера была внесена запись в таблицу public.t_company). Убедиться, что запрос приходит на primary-сервер (нода с индексом 0).
|
||||
|
||||
```
|
||||
2023-09-14 07:04:31.800: DBeaver 23.1.2 - Main <postgres> pid 4805: LOG: DB node id: 0 backend pid: 4814 statement: Execute: INSERT INTO public.t_company (id,caption,id_company_type)
|
||||
VALUES ($1,$2,$3)
|
||||
|
||||
```
|
||||
5. Выполинть команду Select. Убедиться, что запрос приходит на standBy-сервер (нода с индексом 1).
|
||||
```
|
||||
|
||||
2023-09-14 07:53:19.275: DBeaver 23.1.2 - Main <postgres> pid 5069: LOG: DB node id: 1 backend pid: 2745 statement: Execute: SELECT x.* FROM public.t_company x
|
||||
|
||||
```
|
@ -13,6 +13,7 @@
|
||||
<None Remove="CommonLibs\logo_720x404.png" />
|
||||
<None Remove="CommonLibs\Readme.md" />
|
||||
<None Remove="Services\DailyReport\DailyReportTemplate.xlsx" />
|
||||
<None Remove="Services\DrillTestReport\DrillTestReportTemplate.xlsx" />
|
||||
<None Remove="Services\Trajectory\PlannedTrajectoryTemplate.xlsx" />
|
||||
<None Remove="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
|
||||
<None Remove="Services\WellOperationService\WellOperationImportTemplate.xlsx" />
|
||||
@ -31,6 +32,7 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Services\DetectOperations\DetectOperations.xlsx" />
|
||||
<EmbeddedResource Include="Services\DailyReport\DailyReportTemplate.xlsx" />
|
||||
<EmbeddedResource Include="Services\DrillTestReport\DrillTestReportTemplate.xlsx" />
|
||||
<EmbeddedResource Include="Services\Trajectory\PlannedTrajectoryTemplate.xlsx" />
|
||||
<EmbeddedResource Include="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
|
||||
<EmbeddedResource Include="Services\AutoGeneratedDailyReports\AutogeneratedDailyReportTemplate.xlsx" />
|
||||
|
30
AsbCloudInfrastructure/AssemblyExtensions.cs
Normal file
30
AsbCloudInfrastructure/AssemblyExtensions.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure
|
||||
{
|
||||
public static class AssemblyExtensions
|
||||
{
|
||||
public static async Task<Stream> GetTemplateCopyStreamAsync(this Assembly assembly, string templateName, CancellationToken cancellationToken)
|
||||
{
|
||||
var resourceName = assembly
|
||||
.GetManifestResourceNames()
|
||||
.FirstOrDefault(n => n.EndsWith(templateName))!;
|
||||
|
||||
using var stream = Assembly.GetExecutingAssembly()
|
||||
.GetManifestResourceStream(resourceName)!;
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
await stream.CopyToAsync(memoryStream, cancellationToken);
|
||||
memoryStream.Position = 0;
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -11,13 +14,39 @@ namespace AsbCloudInfrastructure.Background;
|
||||
/// </summary>
|
||||
public class BackgroundWorker : BackgroundService
|
||||
{
|
||||
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
|
||||
private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public WorkStore WorkStore { get; } = new WorkStore();
|
||||
/// <summary>
|
||||
/// Очередь работ
|
||||
/// </summary>
|
||||
private Queue<Work> works = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// Список периодических работ
|
||||
/// </summary>
|
||||
public IEnumerable<Work> Works => works;
|
||||
|
||||
/// <summary>
|
||||
/// Работа выполняемая в данный момент
|
||||
/// </summary>
|
||||
public Work? CurrentWork;
|
||||
|
||||
/// <summary>
|
||||
/// последние 16 завершившиеся с ошибкой
|
||||
/// </summary>
|
||||
public CyclycArray<Work> Felled { get; } = new(16);
|
||||
|
||||
/// <summary>
|
||||
/// последние 16 успешно завершенных
|
||||
/// </summary>
|
||||
public CyclycArray<Work> Done { get; } = new(16);
|
||||
|
||||
/// <summary>
|
||||
/// Ошибка в главном цикле, никогда не должна появляться
|
||||
/// </summary>
|
||||
public string MainLoopLastException { get; private set; } = string.Empty;
|
||||
|
||||
public BackgroundWorker(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
@ -25,25 +54,65 @@ public class BackgroundWorker : BackgroundService
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
Trace.TraceInformation($"{GetType().Name} started");
|
||||
while (!token.IsCancellationRequested && works.TryDequeue(out CurrentWork))
|
||||
{
|
||||
var work = WorkStore.GetNext();
|
||||
if (work is null)
|
||||
try
|
||||
{
|
||||
await Task.Delay(executePeriod, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
CurrentWork = work;
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
|
||||
var result = await work.Start(scope.ServiceProvider, token);
|
||||
var result = await CurrentWork.Start(scope.ServiceProvider, token);
|
||||
|
||||
if (!result)
|
||||
WorkStore.Felled.Add(work);
|
||||
Felled.Add(CurrentWork);
|
||||
else
|
||||
Done.Add(CurrentWork);
|
||||
|
||||
CurrentWork = null;
|
||||
await Task.Delay(minDelay, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MainLoopLastException = $"BackgroundWorker " +
|
||||
$"MainLoopLastException: \r\n" +
|
||||
$"date: {DateTime.Now:O}\r\n" +
|
||||
$"message: {ex.Message}\r\n" +
|
||||
$"inner: {ex.InnerException?.Message}\r\n" +
|
||||
$"stackTrace: {ex.StackTrace}";
|
||||
Trace.TraceError(MainLoopLastException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить в очередь
|
||||
/// <para>
|
||||
/// work.Id может быть не уникальным,
|
||||
/// при этом метод TryRemoveFromQueue удалит все работы с совпадающими id
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="work"></param>
|
||||
public void Enqueue(Work work)
|
||||
{
|
||||
works.Enqueue(work);
|
||||
if (ExecuteTask is null || ExecuteTask.IsCompleted)
|
||||
StartAsync(CancellationToken.None).Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаление работы по ID из одноразовой очереди
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryRemoveFromQueue(string id)
|
||||
{
|
||||
var work = Works.FirstOrDefault(w => w.Id == id);
|
||||
if (work is not null)
|
||||
{
|
||||
works = new Queue<Work>(Works.Where(w => w.Id != id));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Collections.Generic
|
||||
{
|
||||
public class OrderedList<T>: IEnumerable<T>, ICollection<T>
|
||||
where T : notnull
|
||||
{
|
||||
private readonly List<T> list = new List<T>();
|
||||
|
||||
private readonly Func<T, object> keySelector;
|
||||
private readonly bool isDescending = false;
|
||||
|
||||
private IOrderedEnumerable<T> OrdredList => isDescending
|
||||
? list.OrderByDescending(keySelector)
|
||||
: list.OrderBy(keySelector);
|
||||
|
||||
public int Count => list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public OrderedList(Func<T, object> keySelector, bool isDescending = false)
|
||||
{
|
||||
this.keySelector = keySelector;
|
||||
this.isDescending = isDescending;
|
||||
}
|
||||
|
||||
public void Add(T item) => list.Add(item);
|
||||
|
||||
public void Clear()=> list.Clear();
|
||||
|
||||
public bool Contains(T item)=> list.Contains(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)=> list.CopyTo(array, arrayIndex);
|
||||
|
||||
public bool Remove(T item)=> list.Remove(item);
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => OrdredList.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
116
AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
Normal file
116
AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Background;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для фонового выполнения периодической работы
|
||||
/// </summary>
|
||||
public class PeriodicBackgroundWorker : BackgroundService
|
||||
{
|
||||
private readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
|
||||
private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private readonly List<WorkPeriodic> works = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// Список периодических работ
|
||||
/// </summary>
|
||||
public IEnumerable<WorkPeriodic> Works => works;
|
||||
|
||||
/// <summary>
|
||||
/// Работа выполняемая в данный момент
|
||||
/// </summary>
|
||||
public Work? CurrentWork;
|
||||
|
||||
/// <summary>
|
||||
/// Ошибка в главном цикле, никогда не должна появляться
|
||||
/// </summary>
|
||||
public string MainLoopLastException { get; private set; } = string.Empty;
|
||||
|
||||
public PeriodicBackgroundWorker(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken token)
|
||||
{
|
||||
Trace.TraceInformation($"{GetType().Name} started");
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var periodicWork = GetNext();
|
||||
if (periodicWork is null)
|
||||
{
|
||||
await Task.Delay(executePeriod, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
CurrentWork = periodicWork.Work;
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
|
||||
var result = await periodicWork.Work.Start(scope.ServiceProvider, token);
|
||||
|
||||
CurrentWork = null;
|
||||
await Task.Delay(minDelay, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MainLoopLastException = $"BackgroundWorker " +
|
||||
$"MainLoopLastException: \r\n" +
|
||||
$"date: {DateTime.Now:O}\r\n" +
|
||||
$"message: {ex.Message}\r\n" +
|
||||
$"inner: {ex.InnerException?.Message}\r\n" +
|
||||
$"stackTrace: {ex.StackTrace}";
|
||||
Trace.TraceError(MainLoopLastException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить фоновую работу выполняющуюся с заданным периодом
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="period"></param>
|
||||
public void Add<T>(TimeSpan period)
|
||||
where T : Work, new()
|
||||
{
|
||||
var work = new T();
|
||||
var periodic = new WorkPeriodic(work, period);
|
||||
works.Add(periodic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить фоновую работу выполняющуюся с заданным периодом
|
||||
/// </summary>
|
||||
/// <param name="work"></param>
|
||||
/// <param name="period"></param>
|
||||
public void Add(Work work, TimeSpan period)
|
||||
{
|
||||
var periodic = new WorkPeriodic(work, period);
|
||||
works.Add(periodic);
|
||||
if (ExecuteTask is null || ExecuteTask.IsCompleted)
|
||||
StartAsync(CancellationToken.None).Wait();
|
||||
}
|
||||
|
||||
private WorkPeriodic? GetNext()
|
||||
{
|
||||
var work = works
|
||||
.OrderBy(w => w.NextStart)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (work is null || work.NextStart > DateTime.Now)
|
||||
return null;
|
||||
|
||||
return work;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using AsbCloudApp.Data;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -12,6 +13,8 @@ namespace AsbCloudInfrastructure.Background;
|
||||
/// </summary>
|
||||
public abstract class Work : BackgroundWorkDto
|
||||
{
|
||||
private CancellationTokenSource? stoppingCts;
|
||||
|
||||
private sealed class WorkBase : Work
|
||||
{
|
||||
private Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> ActionAsync { get; }
|
||||
@ -68,8 +71,9 @@ public abstract class Work : BackgroundWorkDto
|
||||
SetStatusStart();
|
||||
try
|
||||
{
|
||||
var task = Action(Id, services, UpdateStatus, token);
|
||||
await task.WaitAsync(Timeout, token);
|
||||
stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||
var task = Action(Id, services, UpdateStatus, stoppingCts.Token);
|
||||
await task.WaitAsync(Timeout, stoppingCts.Token);
|
||||
SetStatusComplete();
|
||||
return true;
|
||||
}
|
||||
@ -78,16 +82,29 @@ public abstract class Work : BackgroundWorkDto
|
||||
var message = FormatExceptionMessage(exception);
|
||||
SetLastError(message);
|
||||
if (OnErrorAsync is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = Task.Run(
|
||||
async () => await OnErrorAsync(Id, exception, token),
|
||||
token);
|
||||
await task.WaitAsync(OnErrorHandlerTimeout, token);
|
||||
}
|
||||
catch (Exception onErrorAsyncException)
|
||||
{
|
||||
var message2 = FormatExceptionMessage(onErrorAsyncException);
|
||||
Trace.TraceError($"Backgroud work:\"{Id}\" OnError handler throws exception: {message2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
stoppingCts?.Cancel();
|
||||
}
|
||||
|
||||
private static string FormatExceptionMessage(Exception exception)
|
||||
{
|
||||
var firstException = FirstException(exception);
|
||||
|
@ -1,105 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AsbCloudInfrastructure.Background;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Очередь работ
|
||||
/// </para>
|
||||
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
|
||||
/// </summary>
|
||||
public class WorkStore
|
||||
{
|
||||
private readonly List<WorkPeriodic> periodics = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// Список периодических задач
|
||||
/// </summary>
|
||||
public IEnumerable<WorkPeriodic> Periodics => periodics;
|
||||
|
||||
/// <summary>
|
||||
/// Работы выполняемые один раз
|
||||
/// </summary>
|
||||
public Queue<Work> RunOnceQueue { get; private set; } = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// Завершившиеся с ошибкой
|
||||
/// </summary>
|
||||
public CyclycArray<Work> Felled { get; } = new(16);
|
||||
|
||||
/// <summary>
|
||||
/// Добавить фоновую работу выполняющуюся с заданным периодом
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="period"></param>
|
||||
public void AddPeriodic<T>(TimeSpan period)
|
||||
where T : Work, new()
|
||||
{
|
||||
var work = new T();
|
||||
var periodic = new WorkPeriodic(work, period);
|
||||
periodics.Add(periodic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить фоновую работу выполняющуюся с заданным периодом
|
||||
/// </summary>
|
||||
/// <param name="work"></param>
|
||||
/// <param name="period"></param>
|
||||
public void AddPeriodic(Work work, TimeSpan period)
|
||||
{
|
||||
var periodic = new WorkPeriodic(work, period);
|
||||
periodics.Add(periodic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаление работы по ID из одноразовой очереди
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryRemoveFromRunOnceQueue(string id)
|
||||
{
|
||||
var work = RunOnceQueue.FirstOrDefault(w => w.Id == id);
|
||||
if (work is not null)
|
||||
{
|
||||
RunOnceQueue = new Queue<Work>(RunOnceQueue.Where(w => w.Id != id));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Возвращает приоритетную задачу.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Если приоритетные закончились, то ищет ближайшую периодическую.
|
||||
/// Если до старта ближайшей периодической работы меньше 20 сек,
|
||||
/// то этой задаче устанавливается время последнего запуска в now и она возвращается.
|
||||
/// Если больше 20 сек, то возвращается null.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="maxTimeToNextWork"></param>
|
||||
/// <returns></returns>
|
||||
public Work? GetNext()
|
||||
{
|
||||
if (RunOnceQueue.Any())
|
||||
return RunOnceQueue.Dequeue();
|
||||
|
||||
var work = GetNextPeriodic();
|
||||
if (work is null || work.NextStart > DateTime.Now)
|
||||
return null;
|
||||
|
||||
return work.Work;
|
||||
}
|
||||
|
||||
private WorkPeriodic? GetNextPeriodic()
|
||||
{
|
||||
var work = Periodics
|
||||
.OrderBy(w => w.NextStart)
|
||||
.FirstOrDefault();
|
||||
return work;
|
||||
}
|
||||
}
|
34
AsbCloudInfrastructure/Background/WorkToDeleteOldReports.cs
Normal file
34
AsbCloudInfrastructure/Background/WorkToDeleteOldReports.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using AsbCloudApp.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Background
|
||||
{
|
||||
/// <summary>
|
||||
/// Задача по удалению загруженных отчетов
|
||||
/// </summary>
|
||||
public class WorkToDeleteOldReports : Work
|
||||
{
|
||||
public WorkToDeleteOldReports()
|
||||
: base("work to delete reports older than 30 days")
|
||||
{
|
||||
Timeout = TimeSpan.FromMinutes(10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаление отчетов, загруженных ранее 30 дней от текущей даты
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="onProgressCallback"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
|
||||
{
|
||||
var reportService = services.GetRequiredService<IReportService>();
|
||||
await reportService.DeleteAllOldReportsAsync(TimeSpan.FromDays(30), token);
|
||||
}
|
||||
}
|
||||
}
|
11
AsbCloudInfrastructure/Background/readme.md
Normal file
11
AsbCloudInfrastructure/Background/readme.md
Normal file
@ -0,0 +1,11 @@
|
||||
# BackgroundWorker
|
||||
Класс выполнения разовой фоновой работы.
|
||||
Для каждой работы создается свой scope.
|
||||
|
||||
# NotificationBackgroundWorker
|
||||
Предназначен для различных оповещений пользователей разными способами.
|
||||
Фактически это дополнительный экземпляр BackgroundWorker, чтобы оповещения не ждали завершения долгих операций из стандартного BackgroundWorker.
|
||||
Не должен давать большой нагрузки БД.
|
||||
|
||||
# PeriodicBackgroundWorker
|
||||
Класс выполнения периодической фоновой работы.
|
@ -1,12 +0,0 @@
|
||||
# Проблемы фонового сервиса
|
||||
- Нужно состояние по загрузки сервиса и очереди работ.
|
||||
- Все ли задачи укладываются в таймаут,
|
||||
- Сколько свободного времени остается,
|
||||
- Что делает текущая задача,
|
||||
- нет управления сервисом. Для исключения его влияния на другие процессы сервера.
|
||||
- отключать/включать целиком
|
||||
- отключать/включать отдельную периодическую задачу
|
||||
|
||||
# Сделать
|
||||
- Разработать dto статуса задачи
|
||||
- Отказаться от периодической задачи, при добавлении в хранилище задач период будет параметром метода добавления.
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Data.AutogeneratedDailyReport;
|
||||
using AsbCloudApp.Data.DrillTestReport;
|
||||
using AsbCloudApp.Data.Manuals;
|
||||
using AsbCloudApp.Data.ProcessMaps;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
@ -24,6 +26,7 @@ using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
|
||||
using AsbCloudInfrastructure.Services.DailyReport;
|
||||
using AsbCloudInfrastructure.Services.DetectOperations;
|
||||
using AsbCloudInfrastructure.Services.DrillingProgram;
|
||||
using AsbCloudInfrastructure.Services.DrillTestReport;
|
||||
using AsbCloudInfrastructure.Services.ProcessMaps.Report;
|
||||
using AsbCloudInfrastructure.Services.ProcessMaps.WellDrilling;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
@ -159,9 +162,12 @@ namespace AsbCloudInfrastructure
|
||||
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>());
|
||||
|
||||
services.AddSingleton(new WitsInfoService());
|
||||
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider));
|
||||
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
|
||||
services.AddSingleton<ITelemetryDataCache<TelemetryDataSaubDto>>(provider =>
|
||||
TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider));
|
||||
services.AddSingleton<ITelemetryDataCache<TelemetryDataSpinDto>>(provider =>
|
||||
TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
|
||||
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
|
||||
services.AddSingleton<PeriodicBackgroundWorker>();
|
||||
services.AddSingleton<BackgroundWorker>();
|
||||
services.AddSingleton<NotificationBackgroundWorker>();
|
||||
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
|
||||
@ -186,7 +192,6 @@ namespace AsbCloudInfrastructure
|
||||
services.AddTransient<IProcessMapReportWellDrillingExportService, ProcessMapReportWellDrillingExportService>();
|
||||
services.AddTransient<IPlannedTrajectoryImportService, PlannedTrajectoryImportService>();
|
||||
services.AddTransient<IWellOperationRepository, WellOperationRepository>();
|
||||
services.AddTransient<IScheduleReportService, ScheduleReportService>();
|
||||
services.AddTransient<IDailyReportService, DailyReportService>();
|
||||
services.AddTransient<IDetectedOperationService, DetectedOperationService>();
|
||||
services.AddTransient<ISubsystemOperationTimeService, SubsystemOperationTimeService>();
|
||||
@ -209,6 +214,7 @@ namespace AsbCloudInfrastructure
|
||||
services.AddTransient<INotificationRepository, NotificationRepository>();
|
||||
services.AddTransient<ICrudRepository<NotificationCategoryDto>, CrudCacheRepositoryBase<NotificationCategoryDto,
|
||||
NotificationCategory>>();
|
||||
services.AddTransient<IDrillTestRepository, DrillTestRepository>();
|
||||
|
||||
// admin crud services:
|
||||
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
|
||||
@ -274,7 +280,9 @@ namespace AsbCloudInfrastructure
|
||||
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record61Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record61Dto, AsbCloudDb.Model.WITS.Record61>>();
|
||||
|
||||
services.AddTransient<IAutoGeneratedDailyReportService, AutoGeneratedDailyReportService>();
|
||||
services.AddTransient<IAutoGeneratedDailyReportMakerService, AutoGeneratedDailyReportMakerService>();
|
||||
services.AddTransient<IReportMakerService<AutoGeneratedDailyReportDto>, AutoGeneratedDailyReportMakerService>();
|
||||
services.AddTransient<IDrillTestReportService, DrillTestReportService>();
|
||||
services.AddTransient<IReportMakerService<DrillTestReportDataDto>, DrillTestReportMakerService>();
|
||||
|
||||
services.AddTransient<IManualDirectoryRepository, ManualDirectoryRepository>();
|
||||
services.AddTransient<IManualCatalogService, ManualCatalogService>();
|
||||
|
82
AsbCloudInfrastructure/Repository/DrillTestRepository.cs
Normal file
82
AsbCloudInfrastructure/Repository/DrillTestRepository.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudApp.Exceptions;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Requests;
|
||||
using AsbCloudDb.Model;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Repository
|
||||
{
|
||||
public class DrillTestRepository : IDrillTestRepository
|
||||
{
|
||||
private readonly IAsbCloudDbContext db;
|
||||
|
||||
public DrillTestRepository(IAsbCloudDbContext db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DrillTestDto>> GetAllAsync(int idTelemetry, FileReportRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = db.DrillTests
|
||||
.Where(d => d.IdTelemetry == idTelemetry)
|
||||
.Include(d => d.Telemetry)
|
||||
.AsNoTracking();
|
||||
|
||||
if (request.GeDate.HasValue)
|
||||
{
|
||||
var startDateUTC = new DateTimeOffset(request.GeDate.Value.Year, request.GeDate.Value.Month, request.GeDate.Value.Day, 0, 0, 0, TimeSpan.Zero);
|
||||
query = query.Where(q => q.TimeStampStart >= startDateUTC);
|
||||
}
|
||||
if (request.LeDate.HasValue)
|
||||
{
|
||||
var finishDateUTC = new DateTimeOffset(request.LeDate.Value.Year, request.LeDate.Value.Month, request.LeDate.Value.Day, 0, 0, 0, TimeSpan.Zero);
|
||||
query = query.Where(q => q.TimeStampStart <= finishDateUTC);
|
||||
}
|
||||
|
||||
var entities = await query.ToListAsync(cancellationToken);
|
||||
var dtos = entities.Select(e => Convert(e));
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
|
||||
public async Task<DrillTestDto> GetAsync(int idTelemetry, int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var drillTest = await db.DrillTests
|
||||
.Where(d => d.Id == id)
|
||||
.Include(d => d.Telemetry)
|
||||
.Where(d => d.Telemetry.Id == idTelemetry)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (drillTest is null)
|
||||
throw new ArgumentInvalidException(new string[] { nameof(id), nameof(idTelemetry) }, $"Drill test with id: {id} and idTelemetry: {idTelemetry} does not exist.");
|
||||
|
||||
var dto = Convert(drillTest);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<int> SaveDataAsync(int idTelemetry, DrillTestDto dto, CancellationToken token)
|
||||
{
|
||||
var entity = dto.Adapt<DrillTest>();
|
||||
entity.IdTelemetry = idTelemetry;
|
||||
db.DrillTests.Add(entity);
|
||||
var result = await db.SaveChangesAsync(token);
|
||||
return result;
|
||||
}
|
||||
|
||||
private DrillTestDto Convert(DrillTest entity)
|
||||
{
|
||||
var dto = entity.Adapt<DrillTestDto>();
|
||||
dto.TimeStampStart = dto.TimeStampStart.ToRemoteDateTime(dto.Telemetry?.TimeZone?.Hours ?? 0);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
}
|
@ -147,7 +147,7 @@ namespace AsbCloudInfrastructure.Repository
|
||||
public async Task<IEnumerable<FileInfoDto>> DeleteAsync(IEnumerable<int> ids, CancellationToken token)
|
||||
{
|
||||
var query = dbSetConfigured
|
||||
.Where(f => ids.Contains(f.Id) && f.IsDeleted);
|
||||
.Where(f => ids.Contains(f.Id));
|
||||
|
||||
var files = await query.ToListAsync(token);
|
||||
|
||||
|
@ -128,12 +128,14 @@ public class WellOperationRepository : IWellOperationRepository
|
||||
operation.IdWell,
|
||||
operation.IdType,
|
||||
operation.IdWellSectionType,
|
||||
operation.WellSectionType.Caption,
|
||||
})
|
||||
.Select(group => new
|
||||
{
|
||||
group.Key.IdWell,
|
||||
group.Key.IdType,
|
||||
group.Key.IdWellSectionType,
|
||||
group.Key.Caption,
|
||||
|
||||
First = group
|
||||
.OrderBy(operation => operation.DateStart)
|
||||
@ -162,6 +164,8 @@ public class WellOperationRepository : IWellOperationRepository
|
||||
IdType = item.IdType,
|
||||
IdWellSectionType = item.IdWellSectionType,
|
||||
|
||||
Caption = item.Caption,
|
||||
|
||||
DateStart = item.First.DateStart,
|
||||
DepthStart = item.First.DepthStart,
|
||||
|
||||
|
@ -5,14 +5,16 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsbCloudApp.Data.AutogeneratedDailyReport;
|
||||
using AsbCloudApp.Services.AutoGeneratedDailyReports;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
|
||||
using ClosedXML.Excel;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
|
||||
|
||||
public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMakerService
|
||||
public class AutoGeneratedDailyReportMakerService : IReportMakerService<AutoGeneratedDailyReportDto>
|
||||
{
|
||||
private readonly string templateName = "AutogeneratedDailyReportTemplate.xlsx";
|
||||
|
||||
private readonly IEnumerable<IExcelBlockWriter> blockWriters = new List<IExcelBlockWriter>()
|
||||
{
|
||||
new HeadExcelBlockWriter(),
|
||||
@ -23,7 +25,9 @@ public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMak
|
||||
|
||||
public async Task<Stream> MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken)
|
||||
{
|
||||
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
|
||||
using var excelTemplateStream = await Assembly
|
||||
.GetExecutingAssembly()
|
||||
.GetTemplateCopyStreamAsync(templateName, cancellationToken);
|
||||
|
||||
using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
|
||||
|
||||
@ -36,22 +40,6 @@ public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMak
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
private async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var resourceName = Assembly.GetExecutingAssembly()
|
||||
.GetManifestResourceNames()
|
||||
.FirstOrDefault(n => n.EndsWith("AutogeneratedDailyReportTemplate.xlsx"))!;
|
||||
|
||||
using var stream = Assembly.GetExecutingAssembly()
|
||||
.GetManifestResourceStream(resourceName)!;
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
await stream.CopyToAsync(memoryStream, cancellationToken);
|
||||
memoryStream.Position = 0;
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
private void AddToWorkbook(XLWorkbook workbook, AutoGeneratedDailyReportDto report)
|
||||
{
|
||||
const string sheetName = "Рапорт";
|
||||
|
@ -26,14 +26,14 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
|
||||
private readonly ISubsystemOperationTimeService subsystemOperationTimeService;
|
||||
private readonly ICrudRepository<SubsystemDto> subsystemRepository;
|
||||
private readonly ILimitingParameterService limitingParameterService;
|
||||
private readonly IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService;
|
||||
private readonly IReportMakerService<AutoGeneratedDailyReportDto> autoGeneratedDailyReportMakerService;
|
||||
|
||||
public AutoGeneratedDailyReportService(IWellService wellService,
|
||||
IWellOperationRepository wellOperationRepository,
|
||||
ISubsystemOperationTimeService subsystemOperationTimeService,
|
||||
ICrudRepository<SubsystemDto> subsystemRepository,
|
||||
ILimitingParameterService limitingParameterService,
|
||||
IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService)
|
||||
IReportMakerService<AutoGeneratedDailyReportDto> autoGeneratedDailyReportMakerService)
|
||||
{
|
||||
this.wellOperationRepository = wellOperationRepository;
|
||||
this.wellService = wellService;
|
||||
@ -44,7 +44,7 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
|
||||
}
|
||||
|
||||
public async Task<PaginationContainer<AutoGeneratedDailyReportInfoDto>> GetListAsync(int idWell,
|
||||
AutoGeneratedDailyReportRequest request,
|
||||
FileReportRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new PaginationContainer<AutoGeneratedDailyReportInfoDto>
|
||||
@ -67,19 +67,19 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
|
||||
if (datesRange is null)
|
||||
return result;
|
||||
|
||||
if (request.StartDate.HasValue)
|
||||
if (request.GeDate.HasValue)
|
||||
{
|
||||
var startDate = new DateTime(request.StartDate.Value.Year, request.StartDate.Value.Month,
|
||||
request.StartDate.Value.Day);
|
||||
var startDate = new DateTime(request.GeDate.Value.Year, request.GeDate.Value.Month,
|
||||
request.GeDate.Value.Day);
|
||||
|
||||
if(startDate.Date >= datesRange.From.Date)
|
||||
datesRange.From = startDate;
|
||||
}
|
||||
|
||||
if (request.FinishDate.HasValue)
|
||||
if (request.LeDate.HasValue)
|
||||
{
|
||||
var finishDate = new DateTime(request.FinishDate.Value.Year, request.FinishDate.Value.Month,
|
||||
request.FinishDate.Value.Day);
|
||||
var finishDate = new DateTime(request.LeDate.Value.Year, request.LeDate.Value.Month,
|
||||
request.LeDate.Value.Day);
|
||||
|
||||
if (finishDate.Date <= datesRange.To.Date)
|
||||
datesRange.To = finishDate;
|
||||
|
@ -95,12 +95,12 @@ public class WorkOperationDetection: Work
|
||||
{
|
||||
DateTime = d.DateTime,
|
||||
IdUser = d.IdUser,
|
||||
WellDepth = d.WellDepth ?? float.NaN,
|
||||
Pressure = d.Pressure ?? float.NaN,
|
||||
HookWeight = d.HookWeight ?? float.NaN,
|
||||
BlockPosition = d.BlockPosition ?? float.NaN,
|
||||
BitDepth = d.BitDepth ?? float.NaN,
|
||||
RotorSpeed = d.RotorSpeed ?? float.NaN,
|
||||
WellDepth = d.WellDepth,
|
||||
Pressure = d.Pressure,
|
||||
HookWeight = d.HookWeight,
|
||||
BlockPosition = d.BlockPosition,
|
||||
BitDepth = d.BitDepth,
|
||||
RotorSpeed = d.RotorSpeed,
|
||||
})
|
||||
.OrderBy(d => d.DateTime);
|
||||
|
||||
|
@ -0,0 +1,90 @@
|
||||
using AsbCloudApp.Data.DrillTestReport;
|
||||
using AsbCloudApp.Services;
|
||||
using ClosedXML.Excel;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.DrillTestReport
|
||||
{
|
||||
public class DrillTestReportMakerService : IReportMakerService<DrillTestReportDataDto>
|
||||
{
|
||||
private readonly string templateName = "DrillTestReportTemplate.xlsx";
|
||||
private readonly string sheetName = "Лист1";
|
||||
private readonly int startRowNumber = 8;
|
||||
|
||||
public async Task<Stream> MakeReportAsync(DrillTestReportDataDto report, CancellationToken cancellationToken)
|
||||
{
|
||||
using var excelTemplateStream = await Assembly.GetExecutingAssembly().GetTemplateCopyStreamAsync(templateName, cancellationToken);
|
||||
|
||||
using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
|
||||
|
||||
AddToWorkbook(workbook, report);
|
||||
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
workbook.SaveAs(memoryStream, new SaveOptions { });
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
|
||||
private void AddToWorkbook(XLWorkbook workbook, DrillTestReportDataDto report)
|
||||
{
|
||||
var drillTestEntities = report.Data.Params;
|
||||
if (!drillTestEntities.Any())
|
||||
return;
|
||||
|
||||
var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName)
|
||||
?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}.");
|
||||
|
||||
sheet.Cell(4, 2).Value = report.Caption;
|
||||
sheet.Cell(5, 2)._SetValue(report.Date, setAllBorders: false);
|
||||
|
||||
var rowNumber = startRowNumber;
|
||||
|
||||
var stepWithMaxDepthSpeed = drillTestEntities.OrderByDescending(p => p.DepthSpeed).FirstOrDefault()!.Step;
|
||||
var startDepth = report.Data.DepthStart;
|
||||
var startDate = report.Data.TimeStampStart;
|
||||
|
||||
foreach (var drillTestEntity in drillTestEntities)
|
||||
{
|
||||
var endDepth = startDepth + (drillTestEntity.DepthDrillStep ?? 0);
|
||||
var endDateTime = startDate.AddSeconds(drillTestEntity.TimeDrillStep ?? 0);
|
||||
|
||||
sheet.Cell(rowNumber, 2).Value = startDepth;
|
||||
|
||||
sheet.Cell(rowNumber, 3).Value = endDepth;
|
||||
|
||||
sheet.Cell(rowNumber, 4).Value = drillTestEntity.DepthDrillStep;
|
||||
|
||||
sheet.Cell(rowNumber, 5).Value = drillTestEntity.Workload;
|
||||
|
||||
sheet.Cell(rowNumber, 6).Value = drillTestEntity.Speed;
|
||||
|
||||
var cell = sheet.Cell(rowNumber, 7);
|
||||
cell._SetValue(startDate.DateTime);
|
||||
|
||||
cell = sheet.Cell(rowNumber, 8);
|
||||
cell._SetValue(endDateTime.DateTime);
|
||||
|
||||
sheet.Cell(rowNumber, 9).Value = Math.Round((drillTestEntity.TimeDrillStep ?? 0) / (60 * 60), 2);
|
||||
|
||||
sheet.Cell(rowNumber, 10).Value = drillTestEntity.DepthSpeed;
|
||||
|
||||
if (drillTestEntity.Step == stepWithMaxDepthSpeed)
|
||||
{
|
||||
var currentCells = sheet.Row(rowNumber).Cells(1, 10);
|
||||
currentCells.Style.Fill.BackgroundColor = XLColor.Yellow;
|
||||
}
|
||||
|
||||
startDepth = endDepth;
|
||||
startDate = endDateTime;
|
||||
rowNumber++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Data.DrillTestReport;
|
||||
using AsbCloudApp.Exceptions;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Requests;
|
||||
using AsbCloudApp.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.DrillTestReport
|
||||
{
|
||||
public class DrillTestReportService : IDrillTestReportService
|
||||
{
|
||||
private readonly IWellService wellService;
|
||||
private readonly IDrillTestRepository drillTestRepository;
|
||||
private readonly ITelemetryService telemetryService;
|
||||
private readonly IReportMakerService<DrillTestReportDataDto> drillTestReportMakerService;
|
||||
|
||||
public DrillTestReportService(
|
||||
IWellService wellService,
|
||||
IDrillTestRepository drillTestRepository,
|
||||
ITelemetryService telemetryService,
|
||||
IReportMakerService<DrillTestReportDataDto> drillTestReportMakerService)
|
||||
{
|
||||
this.wellService = wellService;
|
||||
this.drillTestRepository = drillTestRepository;
|
||||
this.telemetryService = telemetryService;
|
||||
this.drillTestReportMakerService = drillTestReportMakerService;
|
||||
}
|
||||
|
||||
public async Task<(string fileName, Stream stream)> GenerateAsync(int idWell, int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var well = wellService.GetOrDefault(idWell);
|
||||
if (well is null)
|
||||
throw new ArgumentInvalidException(nameof(idWell), $"Well with id: {idWell} does not exist.");
|
||||
if (well.IdTelemetry is null)
|
||||
throw new ArgumentInvalidException(nameof(well.IdTelemetry), $"Well with id: {idWell} does not have telemetry.");
|
||||
|
||||
var dto = await drillTestRepository.GetAsync(well.IdTelemetry.Value, id, cancellationToken);
|
||||
|
||||
var report = new DrillTestReportDataDto()
|
||||
{
|
||||
Data = dto,
|
||||
Caption = string.Format("Месторождение: {0}, куст: {1}, скважина: {2}",
|
||||
well.Deposit ?? "-",
|
||||
well.Cluster ?? "-",
|
||||
well.Caption ?? "-"),
|
||||
Date = DateTime.Now,
|
||||
};
|
||||
|
||||
var fileName = string.Format("Drill_test_{0}.xlsx", dto.TimeStampStart.ToString("dd.mm.yyyy_HH_MM_ss"));
|
||||
var stream = await drillTestReportMakerService.MakeReportAsync(report, cancellationToken);
|
||||
|
||||
return (fileName, stream);
|
||||
}
|
||||
|
||||
|
||||
public async Task<PaginationContainer<DrillTestReportInfoDto>> GetListAsync(int idWell, FileReportRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
|
||||
if (telemetry is null)
|
||||
throw new Exception($"Telemetry with idWell: {idWell} does not exist.");
|
||||
|
||||
var result = new PaginationContainer<DrillTestReportInfoDto>
|
||||
{
|
||||
Skip = request.Skip ?? 0,
|
||||
Take = request.Take ?? 10,
|
||||
Items = Enumerable.Empty<DrillTestReportInfoDto>()
|
||||
};
|
||||
|
||||
var reports = new List<DrillTestReportInfoDto>();
|
||||
var timezone = telemetryService.GetTimezone(telemetry.Id);
|
||||
|
||||
var dtos = await drillTestRepository.GetAllAsync(telemetry.Id, request, cancellationToken);
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
var remoteDateTime = dto.TimeStampStart.ToRemoteDateTime(timezone.Hours);
|
||||
|
||||
reports.Add(new DrillTestReportInfoDto
|
||||
{
|
||||
FileName = string.Format("Drill_test_{0}", dto.TimeStampStart.DateTime),
|
||||
DrillDepth = (dto.Params
|
||||
.Where(p => p.DepthDrillStep.HasValue)
|
||||
.Sum(x => x.DepthDrillStep) ?? 0) + dto.DepthStart,
|
||||
DateTime = dto.TimeStampStart.DateTime,
|
||||
Id = dto.Id,
|
||||
});
|
||||
}
|
||||
|
||||
result.Items = reports;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@ -513,7 +513,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
if (state.IdState == idStateCreating)
|
||||
{
|
||||
var workId = MakeWorkId(idWell);
|
||||
if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
|
||||
if (!backgroundWorker.Works.Any(w => w.Id == workId))
|
||||
{
|
||||
var well = (await wellService.GetOrDefaultAsync(idWell, token))!;
|
||||
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf";
|
||||
@ -542,7 +542,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
|
||||
var work = Work.CreateByDelegate(workId, workAction);
|
||||
work.OnErrorAsync = onErrorAction;
|
||||
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
|
||||
backgroundWorker.Enqueue(work);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -556,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
var workId = MakeWorkId(idWell);
|
||||
backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId);
|
||||
backgroundWorker.TryRemoveFromQueue(workId);
|
||||
|
||||
var filesIds = await context.Files
|
||||
.Where(f => f.IdWell == idWell &&
|
||||
|
@ -52,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email
|
||||
}
|
||||
|
||||
var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
|
||||
if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId))
|
||||
if (!backgroundWorker.Works.Any(w=>w.Id==workId))
|
||||
{
|
||||
var workAction = MakeEmailSendWorkAction(notification);
|
||||
|
||||
var work = Work.CreateByDelegate(workId, workAction);
|
||||
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
|
||||
backgroundWorker.Enqueue(work);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -20,6 +20,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
{
|
||||
private readonly IAsbCloudDbContext db;
|
||||
private readonly ITelemetryService telemetryService;
|
||||
private readonly FileService fileService;
|
||||
private readonly IWellService wellService;
|
||||
private readonly BackgroundWorker backgroundWorkerService;
|
||||
|
||||
@ -28,12 +29,14 @@ namespace AsbCloudInfrastructure.Services
|
||||
public ReportService(IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
IWellService wellService,
|
||||
FileService fileService,
|
||||
BackgroundWorker backgroundWorkerService)
|
||||
{
|
||||
this.db = db;
|
||||
this.wellService = wellService;
|
||||
this.backgroundWorkerService = backgroundWorkerService;
|
||||
this.telemetryService = telemetryService;
|
||||
this.fileService = fileService;
|
||||
ReportCategoryId = db.FileCategories
|
||||
.AsNoTracking()
|
||||
.First(c => c.Name.Equals("Рапорт"))
|
||||
@ -95,7 +98,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
};
|
||||
|
||||
var work = Work.CreateByDelegate(workId, workAction);
|
||||
backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work);
|
||||
backgroundWorkerService.Enqueue(work);
|
||||
|
||||
progressHandler.Invoke(new ReportProgressDto
|
||||
{
|
||||
@ -186,6 +189,16 @@ namespace AsbCloudInfrastructure.Services
|
||||
|
||||
return generator;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAllOldReportsAsync(TimeSpan lifetime, CancellationToken token)
|
||||
{
|
||||
var lifeTimeStartDate = DateTime.UtcNow.Date - lifetime;
|
||||
var fileIds = await db.ReportProperties
|
||||
.Where(r => r.File.UploadDate.Date < lifeTimeStartDate)
|
||||
.Select(r => r.IdFile)
|
||||
.ToArrayAsync(token);
|
||||
|
||||
return await fileService.DeleteAsync(fileIds, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Exceptions;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb;
|
||||
using AsbCloudDb.Model;
|
||||
@ -18,12 +20,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
{
|
||||
protected readonly IAsbCloudDbContext db;
|
||||
protected readonly ITelemetryService telemetryService;
|
||||
protected readonly TelemetryDataCache<TDto> telemetryDataCache;
|
||||
protected readonly ITelemetryDataCache<TDto> telemetryDataCache;
|
||||
|
||||
public TelemetryDataBaseService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TDto> telemetryDataCache)
|
||||
ITelemetryDataCache<TDto> telemetryDataCache)
|
||||
{
|
||||
this.db = db;
|
||||
this.telemetryService = telemetryService;
|
||||
@ -203,41 +205,62 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<DatesRangeDto?> GetRangeAsync(
|
||||
int idWell,
|
||||
DateTimeOffset start,
|
||||
DateTimeOffset end,
|
||||
CancellationToken token)
|
||||
public async Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
|
||||
{
|
||||
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
|
||||
?? throw new ArgumentInvalidException(nameof(idWell), $"По скважине id:{idWell} нет телеметрии");
|
||||
|
||||
if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12))
|
||||
{
|
||||
// пробуем обойтись кешем
|
||||
var cechedRange = telemetryDataCache.GetOrDefaultCachedaDateRange(telemetry.Id);
|
||||
if (cechedRange?.From <= geDate)
|
||||
{
|
||||
var datesRange = new DatesRangeDto
|
||||
{
|
||||
From = geDate.DateTime,
|
||||
To = cechedRange.To
|
||||
};
|
||||
if (leDate.HasValue && leDate > geDate)
|
||||
datesRange.To = leDate.Value.Date;
|
||||
return datesRange;
|
||||
}
|
||||
}
|
||||
|
||||
var query = db.Set<TEntity>()
|
||||
.Where(entity => entity.IdTelemetry == telemetry.Id)
|
||||
.Where(entity => entity.DateTime >= geDate.ToUniversalTime());
|
||||
|
||||
if(leDate.HasValue)
|
||||
query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime());
|
||||
|
||||
var gquery = query
|
||||
.GroupBy(entity => entity.IdTelemetry)
|
||||
.Select(group => new
|
||||
{
|
||||
MinDate = group.Min(entity => entity.DateTime),
|
||||
MaxDate = group.Max(entity => entity.DateTime),
|
||||
});
|
||||
|
||||
var result = await gquery.FirstOrDefaultAsync(token);
|
||||
if (result is null)
|
||||
return null;
|
||||
|
||||
var range = new DatesRangeDto
|
||||
{
|
||||
From = result.MinDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime,
|
||||
To = result.MaxDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime,
|
||||
};
|
||||
return range;
|
||||
}
|
||||
|
||||
public DatesRangeDto? GetRange(int idWell)
|
||||
{
|
||||
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
|
||||
if (telemetry is null)
|
||||
return default;
|
||||
|
||||
var timezone = telemetryService.GetTimezone(telemetry.Id);
|
||||
var startUtc = start.ToOffset(TimeSpan.Zero);
|
||||
var endUtc = end.ToOffset(TimeSpan.Zero);
|
||||
|
||||
var dbSet = db.Set<TEntity>();
|
||||
var query = dbSet
|
||||
.Where(i => i.IdTelemetry == telemetry.Id)
|
||||
.Where(i => i.DateTime >= startUtc)
|
||||
.Where(i => i.DateTime <= endUtc)
|
||||
.GroupBy(i => i.IdTelemetry)
|
||||
.Select(g => new
|
||||
{
|
||||
DateStart = g.Min(i => i.DateTime),
|
||||
DateEnd = g.Max(i => i.DateTime),
|
||||
});
|
||||
|
||||
var data = await query.FirstOrDefaultAsync(token);
|
||||
if (data is null)
|
||||
return default;
|
||||
|
||||
return new DatesRangeDto
|
||||
{
|
||||
From = data.DateStart.ToRemoteDateTime(timezone.Hours),
|
||||
To = data.DateEnd.ToRemoteDateTime(timezone.Hours),
|
||||
};
|
||||
return telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id);
|
||||
}
|
||||
|
||||
public abstract TDto Convert(TEntity src, double timezoneOffset);
|
||||
|
@ -6,26 +6,24 @@ using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Mapster;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using AsbCloudInfrastructure.Background;
|
||||
using System.Threading;
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Requests;
|
||||
using AsbCloudApp.Repositories;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.SAUB
|
||||
{
|
||||
public class TelemetryDataCache<TDto>
|
||||
where TDto : AsbCloudApp.Data.ITelemetryData
|
||||
public class TelemetryDataCache<TDto> : ITelemetryDataCache<TDto> where TDto : AsbCloudApp.Data.ITelemetryData
|
||||
{
|
||||
class TelemetryDataCacheItem
|
||||
{
|
||||
public TDto? FirstByDate { get; init; }
|
||||
public TDto FirstByDate { get; init; } = default!;
|
||||
public CyclycArray<TDto> LastData { get; init; } = null!;
|
||||
public double TimezoneHours { get; init; } = 5;
|
||||
}
|
||||
|
||||
private IServiceProvider provider = null!;
|
||||
private const int activeWellCapacity = 12 * 60 * 60;
|
||||
private const int doneWellCapacity = 65 * 60;
|
||||
|
||||
@ -48,14 +46,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
instance = new TelemetryDataCache<TDto>();
|
||||
var worker = provider.GetRequiredService<BackgroundWorker>();
|
||||
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>();
|
||||
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
|
||||
});
|
||||
work.Timeout = TimeSpan.FromMinutes(15);
|
||||
worker.WorkStore.RunOnceQueue.Enqueue(work);
|
||||
worker.Enqueue(work);
|
||||
}
|
||||
instance.provider = provider;
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -69,10 +67,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
if (!range.Any())
|
||||
return;
|
||||
|
||||
var newItems = range
|
||||
.OrderBy(i => i.DateTime);
|
||||
range = range.OrderBy(x => x.DateTime);
|
||||
|
||||
foreach (var item in newItems)
|
||||
foreach (var item in range)
|
||||
item.IdTelemetry = idTelemetry;
|
||||
|
||||
TelemetryDataCacheItem cacheItem;
|
||||
@ -87,12 +84,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
{
|
||||
cacheItem = caches.GetOrAdd(idTelemetry, _ => new TelemetryDataCacheItem()
|
||||
{
|
||||
FirstByDate = newItems.ElementAt(0),
|
||||
FirstByDate = range.ElementAt(0),
|
||||
LastData = new CyclycArray<TDto>(activeWellCapacity)
|
||||
});
|
||||
}
|
||||
|
||||
cacheItem.LastData.AddRange(newItems);
|
||||
cacheItem.LastData.AddRange(range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -107,7 +104,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600d, int approxPointsCount = 1024)
|
||||
{
|
||||
if(!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||
return null;
|
||||
|
||||
var cacheLastData = cacheItem.LastData;
|
||||
@ -127,7 +124,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
return items;
|
||||
}
|
||||
|
||||
public TDto? GetLastOrDefault(int idTelemetry)
|
||||
public virtual TDto? GetLastOrDefault(int idTelemetry)
|
||||
{
|
||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||
return default;
|
||||
@ -141,7 +138,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
return null;
|
||||
|
||||
var from = cacheItem.FirstByDate?.DateTime;
|
||||
if(!cacheItem.LastData.Any())
|
||||
if (!cacheItem.LastData.Any())
|
||||
return null;
|
||||
|
||||
var to = cacheItem.LastData[^1].DateTime;
|
||||
@ -150,17 +147,46 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
return new DatesRangeDto { From = from.Value, To = to };
|
||||
}
|
||||
|
||||
public DatesRangeDto? GetOrDefaultCachedaDateRange(int idTelemetry)
|
||||
{
|
||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||
return null;
|
||||
|
||||
if (cacheItem.LastData.Count < 2)
|
||||
return null;
|
||||
|
||||
var to = cacheItem.LastData[^1].DateTime;
|
||||
var from = cacheItem.LastData[0].DateTime;
|
||||
|
||||
return new DatesRangeDto { From = from, 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)
|
||||
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
||||
{
|
||||
var defaultTimeout = db.Database.GetCommandTimeout();
|
||||
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
|
||||
|
||||
if (isLoading)
|
||||
throw new Exception("Multiple cache loading detected.");
|
||||
|
||||
try
|
||||
{
|
||||
isLoading = true;
|
||||
|
||||
var defaultTimeout = db.Database.GetCommandTimeout();
|
||||
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
|
||||
|
||||
Well[] wells = await db.Set<Well>()
|
||||
.Include(well => well.Telemetry)
|
||||
.Include(well => well.Cluster)
|
||||
@ -178,15 +204,18 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
var idTelemetry = well.IdTelemetry!.Value;
|
||||
var hoursOffset = well.Timezone.Hours;
|
||||
|
||||
onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++/count);
|
||||
onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++ / count);
|
||||
var cacheItem = await GetOrDefaultCacheDataFromDbAsync<TEntity>(db, idTelemetry, capacity, hoursOffset, token);
|
||||
if(cacheItem is not null)
|
||||
if (cacheItem is not null)
|
||||
caches.TryAdd(idTelemetry, cacheItem);
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
db.Database.SetCommandTimeout(defaultTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<TelemetryDataCacheItem?> GetOrDefaultCacheDataFromDbAsync<TEntity>(IAsbCloudDbContext db, int idTelemetry, int capacity, double hoursOffset, CancellationToken token)
|
||||
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
||||
@ -212,7 +241,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
var dtos = entities
|
||||
.AsEnumerable()
|
||||
.Reverse()
|
||||
.Select(entity => {
|
||||
.Select(entity =>
|
||||
{
|
||||
var dto = entity.Adapt<TDto>();
|
||||
dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset);
|
||||
return dto;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudApp.Exceptions;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using Mapster;
|
||||
@ -26,7 +27,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
ITelemetryUserService telemetryUserService,
|
||||
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
|
||||
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
|
||||
: base(db, telemetryService, telemetryDataCache)
|
||||
{
|
||||
this.telemetryUserService = telemetryUserService;
|
||||
@ -43,37 +44,37 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
.Where(t => t.BlockPosition > 0.0001)
|
||||
.Where(t => t.WellDepth > 0.0001)
|
||||
.Where(t => t.Mode != null)
|
||||
.Where(t => modes.Contains(t.Mode!.Value))
|
||||
.Where(t => modes.Contains(t.Mode))
|
||||
.Where(t => t.WellDepth - t.BitDepth < 0.01)
|
||||
.GroupBy(t => new {
|
||||
t.DateTime.Hour,
|
||||
WellDepthX10 = Math.Truncate(t.WellDepth!.Value * 10),
|
||||
WellDepthX10 = Math.Truncate(t.WellDepth * 10),
|
||||
t.Mode,
|
||||
t.IdFeedRegulator})
|
||||
.Select(g => new TelemetryDataSaubStatDto
|
||||
{
|
||||
Count = g.Count(),
|
||||
IdMode = g.Key.Mode??0,
|
||||
IdMode = g.Key.Mode,
|
||||
IdFeedRegulator = g.Key.IdFeedRegulator,
|
||||
|
||||
DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
|
||||
DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
|
||||
|
||||
WellDepthMin = g.Min(t => t.WellDepth!.Value),
|
||||
WellDepthMax = g.Max(t => t.WellDepth!.Value),
|
||||
WellDepthMin = g.Min(t => t.WellDepth),
|
||||
WellDepthMax = g.Max(t => t.WellDepth),
|
||||
|
||||
Pressure = g.Average(t => t.Pressure!.Value),
|
||||
Pressure = g.Average(t => t.Pressure),
|
||||
PressureSp = g.Average(t => t.PressureSp!.Value),
|
||||
PressureIdle = g.Average(t => t.PressureIdle!.Value),
|
||||
PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
|
||||
PressureDelta = g.Average(t => t.Pressure!.Value - t.PressureIdle!.Value),
|
||||
PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
|
||||
PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),
|
||||
|
||||
AxialLoad = g.Average(t => t.AxialLoad!.Value),
|
||||
AxialLoad = g.Average(t => t.AxialLoad),
|
||||
AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
|
||||
AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),
|
||||
|
||||
RotorTorque = g.Average(t => t.RotorTorque!.Value),
|
||||
RotorTorque = g.Average(t => t.RotorTorque),
|
||||
RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
|
||||
RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using Mapster;
|
||||
@ -11,7 +12,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
public TelemetryDataSpinService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
|
||||
ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
|
||||
: base(db, telemetryService, telemetryDataCache)
|
||||
{ }
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb;
|
||||
using AsbCloudDb.Model;
|
||||
@ -18,7 +19,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
{
|
||||
private readonly IAsbCloudDbContext db;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly TelemetryDataCache<TelemetryDataSaubDto> dataSaubCache;
|
||||
//TODO: методы использующие ITelemetryDataCache, скорее всего, тут не нужны
|
||||
private readonly ITelemetryDataCache<TelemetryDataSaubDto> dataSaubCache;
|
||||
private readonly ITimezoneService timezoneService;
|
||||
|
||||
public ITimezoneService TimeZoneService => timezoneService;
|
||||
@ -26,7 +28,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
public TelemetryService(
|
||||
IAsbCloudDbContext db,
|
||||
IMemoryCache memoryCache,
|
||||
TelemetryDataCache<TelemetryDataSaubDto> dataSaubCache,
|
||||
ITelemetryDataCache<TelemetryDataSaubDto> dataSaubCache,
|
||||
ITimezoneService timezoneService)
|
||||
{
|
||||
this.db = db;
|
||||
|
@ -26,7 +26,7 @@ public class WorkSubsystemOperationTimeCalc : Work
|
||||
public WorkSubsystemOperationTimeCalc()
|
||||
: base("Subsystem operation time calc")
|
||||
{
|
||||
Timeout = TimeSpan.FromMinutes(20);
|
||||
Timeout = TimeSpan.FromMinutes(30);
|
||||
}
|
||||
|
||||
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
|
||||
@ -55,10 +55,12 @@ public class WorkSubsystemOperationTimeCalc : Work
|
||||
{
|
||||
IdTelemetry = outer,
|
||||
inner.SingleOrDefault()?.LastDate,
|
||||
});
|
||||
})
|
||||
.OrderByDescending(i => i.IdTelemetry);
|
||||
|
||||
var count = telemetryLastDetectedDates.Count();
|
||||
var i = 0d;
|
||||
|
||||
foreach (var item in telemetryLastDetectedDates)
|
||||
{
|
||||
onProgressCallback($"Start handling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count);
|
||||
@ -277,11 +279,11 @@ public class WorkSubsystemOperationTimeCalc : Work
|
||||
.Where(d => d.DateTime <= dateEnd)
|
||||
.Where(d => d.WellDepth != null)
|
||||
.Where(d => d.WellDepth > 0)
|
||||
.GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10))
|
||||
.GroupBy(d => Math.Ceiling(d.WellDepth * 10))
|
||||
.Select(g => new
|
||||
{
|
||||
DateMin = g.Min(d => d.DateTime),
|
||||
DepthMin = g.Min(d => d.WellDepth) ?? 0,
|
||||
DepthMin = g.Min(d => d.WellDepth),
|
||||
})
|
||||
.OrderBy(i => i.DateMin)
|
||||
.ToArrayAsync(token);
|
||||
|
@ -36,7 +36,7 @@ public class WellInfoService
|
||||
var operationsStatService = services.GetRequiredService<IOperationsStatService>();
|
||||
var processMapPlanWellDrillingRepository = services.GetRequiredService<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>>();
|
||||
var subsystemOperationTimeService = services.GetRequiredService<ISubsystemOperationTimeService>();
|
||||
var telemetryDataSaubCache = services.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>();
|
||||
var telemetryDataSaubCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
|
||||
var messageHub = services.GetRequiredService<IIntegrationEventHandler<UpdateWellInfoEvent>>();
|
||||
|
||||
var wells = await wellService.GetAllAsync(token);
|
||||
@ -82,7 +82,7 @@ public class WellInfoService
|
||||
}
|
||||
|
||||
var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id);
|
||||
var wellLastFactSection = wellOperationsStat?.Sections.LastOrDefault(s => s.Fact is not null);
|
||||
var wellLastFactSection = wellOperationsStat?.Sections.OrderBy(s => s.Fact?.WellDepthStart).LastOrDefault(s => s.Fact is not null);
|
||||
currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd;
|
||||
|
||||
var wellProcessMaps = processMapPlanWellDrillings
|
||||
@ -180,16 +180,16 @@ public class WellInfoService
|
||||
public IEnumerable<int> IdsCompanies { get; set; } = null!;
|
||||
}
|
||||
|
||||
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
|
||||
private readonly TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
|
||||
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
|
||||
private readonly ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
|
||||
private readonly IWitsRecordRepository<Record7Dto> witsRecord7Repository;
|
||||
private readonly IWitsRecordRepository<Record1Dto> witsRecord1Repository;
|
||||
private readonly IGtrRepository gtrRepository;
|
||||
private static IEnumerable<WellMapInfoWithComanies> WellMapInfo = Enumerable.Empty<WellMapInfoWithComanies>();
|
||||
|
||||
public WellInfoService(
|
||||
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
|
||||
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache,
|
||||
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
|
||||
ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache,
|
||||
IWitsRecordRepository<Record7Dto> witsRecord7Repository,
|
||||
IWitsRecordRepository<Record1Dto> witsRecord1Repository,
|
||||
IGtrRepository gtrRepository)
|
||||
|
@ -11,6 +11,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
using AsbCloudApp.Repositories;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.WellOperationService;
|
||||
|
||||
@ -19,10 +20,10 @@ public class OperationsStatService : IOperationsStatService
|
||||
private readonly IAsbCloudDbContext db;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly IWellService wellService;
|
||||
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
|
||||
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
|
||||
|
||||
public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService,
|
||||
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
|
||||
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
|
||||
{
|
||||
this.db = db;
|
||||
this.memoryCache = memoryCache;
|
||||
|
@ -33,7 +33,8 @@ namespace AsbCloudInfrastructure.Services
|
||||
.Include(w => w.Telemetry)
|
||||
.Include(w => w.WellType)
|
||||
.Include(w => w.RelationCompaniesWells)
|
||||
.ThenInclude(r => r.Company);
|
||||
.ThenInclude(r => r.Company)
|
||||
.AsNoTracking();
|
||||
|
||||
public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService, WellInfoService wellInfoService)
|
||||
: base(db, memoryCache, MakeQueryWell)
|
||||
@ -105,19 +106,18 @@ namespace AsbCloudInfrastructure.Services
|
||||
|
||||
public async Task<WellMapInfoWithTelemetryStat?> GetOrDefaultStatAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
var dto = wellInfoService.FirstOrDefault(well => well.Id == idWell);
|
||||
if (dto is not null)
|
||||
return dto;
|
||||
var well = await GetOrDefaultAsync(idWell, token);
|
||||
|
||||
var request = new WellRequest{Ids = new[] { idWell }};
|
||||
var entities = await GetEntitiesAsync(request, token);
|
||||
var entity = entities.FirstOrDefault();
|
||||
if (entity is null)
|
||||
if (well is null)
|
||||
return null;
|
||||
|
||||
dto = entity.Adapt<WellMapInfoWithTelemetryStat>();
|
||||
var wellInfo = wellInfoService.FirstOrDefault(well => well.Id == idWell);
|
||||
|
||||
return dto;
|
||||
if (wellInfo is null)
|
||||
return well.Adapt<WellMapInfoWithTelemetryStat>();
|
||||
|
||||
wellInfo.IdState = well.IdState;
|
||||
return wellInfo;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WellDto>> GetAsync(WellRequest request, CancellationToken token)
|
||||
|
@ -1,91 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudApp.Repositories;
|
||||
using AsbCloudApp.Requests;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using Mapster;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services;
|
||||
|
||||
public class WellboreService : IWellboreService
|
||||
{
|
||||
const string WellboreNameFormat = "Ствол {0}";
|
||||
private readonly IWellService wellService;
|
||||
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.wellOperationRepository = wellOperationRepository;
|
||||
this.telemetryDataCache = telemetryDataCache;
|
||||
}
|
||||
|
||||
public async Task<WellboreDto?> GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken)
|
||||
{
|
||||
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,
|
||||
public async Task<IEnumerable<WellboreDto>> GetWellboresAsync(IEnumerable<int> idsWells,
|
||||
CancellationToken token)
|
||||
{
|
||||
var wellbores = new List<WellboreDto>(request.Ids.Count());
|
||||
var skip = request.Skip ?? 0;
|
||||
var take = request.Take ?? 10;
|
||||
var wellRequest = new WellRequest { Ids = idsWells };
|
||||
var wells = await wellService.GetAsync(wellRequest, token);
|
||||
|
||||
var sections = wellOperationRepository.GetSectionTypes()
|
||||
.ToDictionary(w => w.Id, w => w);
|
||||
var rowSections = await wellOperationRepository.GetSectionsAsync(idsWells, token);
|
||||
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);
|
||||
|
||||
foreach (var id in ids)
|
||||
private IEnumerable<WellboreDto> MakeWellboreDefault(WellDto well)
|
||||
{
|
||||
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)
|
||||
continue;
|
||||
if(well.IdTelemetry is not null)
|
||||
{
|
||||
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
|
||||
.Where(section => section.IdWell == id.Key)
|
||||
.Where(section => section.IdType == WellOperation.IdOperationTypeFact);
|
||||
return new[] { wellbore };
|
||||
}
|
||||
|
||||
var idsSections = id
|
||||
.Where(i => i.HasValue)
|
||||
.Select(i => i!.Value);
|
||||
private IEnumerable<WellboreDto> MakeWellboreBySections(IEnumerable<SectionByOperationsDto> sections, WellDto well)
|
||||
{
|
||||
var orderedSections = sections.OrderBy(s => s.DateStart);
|
||||
var wellbores = new List<WellboreDto>();
|
||||
int wellboreId = 1;
|
||||
|
||||
if (idsSections.Any())
|
||||
wellFactSections = wellFactSections
|
||||
.Where(section => idsSections.Contains(section.IdWellSectionType));
|
||||
SectionByOperationsDto? preSection = null;
|
||||
WellboreDto? wellbore = null;
|
||||
|
||||
var wellWellbores = wellFactSections.Select(section => new WellboreDto {
|
||||
Id = section.IdWellSectionType,
|
||||
Name = sections[section.IdWellSectionType].Caption,
|
||||
Well = well.Adapt<WellWithTimezoneDto>(),
|
||||
DateStart = section.DateStart.ToOffset(wellTimezoneOffset),
|
||||
DateEnd = section.DateEnd.ToOffset(wellTimezoneOffset),
|
||||
foreach (var section in orderedSections)
|
||||
{
|
||||
if (wellbore is null || wellbore.DepthEnd > section.DepthStart)
|
||||
{
|
||||
wellbore = new WellboreDto
|
||||
{
|
||||
Name = string.Format(WellboreNameFormat, wellboreId),
|
||||
Id = wellboreId,
|
||||
Well = well,
|
||||
|
||||
DateStart = section.DateStart,
|
||||
DateEnd = section.DateEnd,
|
||||
DepthStart = section.DepthStart,
|
||||
DepthEnd = section.DepthEnd,
|
||||
});
|
||||
};
|
||||
|
||||
wellbores.AddRange(wellWellbores);
|
||||
wellbores.Add(wellbore);
|
||||
wellboreId++;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
wellbore.DepthEnd = section.DepthEnd;
|
||||
wellbore.DateEnd = section.DateEnd;
|
||||
}
|
||||
|
||||
return wellbores
|
||||
.OrderBy(w => w.Well.Id).ThenBy(w => w.Id)
|
||||
.Skip(skip).Take(take);
|
||||
preSection = section;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.DetectOperations;
|
||||
using AsbCloudInfrastructure.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
@ -10,8 +8,9 @@ using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using AsbCloudInfrastructure.Background;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
using AsbCloudInfrastructure.Services.Subsystems;
|
||||
using AsbCloudDb;
|
||||
using AsbCloudApp.Repositories;
|
||||
|
||||
namespace AsbCloudInfrastructure
|
||||
{
|
||||
@ -23,19 +22,19 @@ namespace AsbCloudInfrastructure
|
||||
var provider = scope.ServiceProvider;
|
||||
|
||||
var context = provider.GetRequiredService<IAsbCloudDbContext>();
|
||||
context.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
|
||||
context.Database.Migrate();
|
||||
context.Database.EnshureCreatedAndMigrated();
|
||||
|
||||
// TODO: Сделать инициализацию кеша телеметрии более явной.
|
||||
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>();
|
||||
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSpinDto>>();
|
||||
_ = provider.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
|
||||
_ = provider.GetRequiredService<ITelemetryDataCache<TelemetryDataSpinDto>>();
|
||||
|
||||
var backgroundWorker = provider.GetRequiredService<BackgroundWorker>();
|
||||
backgroundWorker.WorkStore.AddPeriodic<WellInfoService.WorkWellInfoUpdate>(TimeSpan.FromMinutes(30));
|
||||
backgroundWorker.WorkStore.AddPeriodic<WorkOperationDetection>(TimeSpan.FromMinutes(15));
|
||||
backgroundWorker.WorkStore.AddPeriodic<WorkSubsystemOperationTimeCalc>(TimeSpan.FromMinutes(30));
|
||||
backgroundWorker.WorkStore.AddPeriodic<WorkLimitingParameterCalc>(TimeSpan.FromMinutes(30));
|
||||
backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
|
||||
var backgroundWorker = provider.GetRequiredService<PeriodicBackgroundWorker>();
|
||||
backgroundWorker.Add<WorkToDeleteOldReports>(TimeSpan.FromDays(1));
|
||||
backgroundWorker.Add<WellInfoService.WorkWellInfoUpdate>(TimeSpan.FromMinutes(30));
|
||||
backgroundWorker.Add<WorkOperationDetection>(TimeSpan.FromMinutes(15));
|
||||
backgroundWorker.Add<WorkSubsystemOperationTimeCalc>(TimeSpan.FromMinutes(30));
|
||||
backgroundWorker.Add<WorkLimitingParameterCalc>(TimeSpan.FromMinutes(30));
|
||||
backgroundWorker.Add(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
|
||||
|
||||
var notificationBackgroundWorker = provider.GetRequiredService<NotificationBackgroundWorker>();
|
||||
|
||||
|
@ -92,12 +92,16 @@ internal static class XLExtentions
|
||||
}
|
||||
|
||||
|
||||
internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS")
|
||||
internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS", bool setAllBorders = true)
|
||||
{
|
||||
cell.Value = value;
|
||||
if (setAllBorders == true)
|
||||
{
|
||||
cell.Style
|
||||
.SetAllBorders()
|
||||
.Alignment.WrapText = true;
|
||||
}
|
||||
|
||||
|
||||
cell.Value = value;
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="MockQueryable.Moq" Version="6.0.1" />
|
||||
<PackageReference Include="Moq" Version="4.18.2" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -48,11 +48,26 @@ namespace AsbCloudWebApi.Tests.Middlware
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DatesRangeDto? GetRange(int idWell, DateTimeOffset start, DateTimeOffset end)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DatesRangeDto? GetRange(int idWell)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) => throw new NotImplementedException();
|
||||
|
||||
public Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
|
||||
|
113
AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
Normal file
113
AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs
Normal file
@ -0,0 +1,113 @@
|
||||
using AsbCloudInfrastructure.Background;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace AsbCloudWebApi.Tests.Services;
|
||||
|
||||
public class BackgroundWorkerTest
|
||||
{
|
||||
private IServiceProvider provider;
|
||||
private BackgroundWorker service;
|
||||
|
||||
public BackgroundWorkerTest()
|
||||
{
|
||||
provider = Substitute.For<IServiceProvider, ISupportRequiredService>();
|
||||
var serviceScope = Substitute.For<IServiceScope>();
|
||||
var serviceScopeFactory = Substitute.For<IServiceScopeFactory>();
|
||||
serviceScopeFactory.CreateScope().Returns(serviceScope);
|
||||
((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
|
||||
|
||||
service = new BackgroundWorker(provider);
|
||||
typeof(BackgroundWorker)
|
||||
.GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.SetValue(service, TimeSpan.FromMilliseconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Enqueue_n_works()
|
||||
{
|
||||
var workCount = 10;
|
||||
var result = 0;
|
||||
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
{
|
||||
result++;
|
||||
return Task.Delay(1);
|
||||
}
|
||||
|
||||
//act
|
||||
for (int i = 0; i < workCount; i++)
|
||||
{
|
||||
var work = Work.CreateByDelegate(i.ToString(), workAction);
|
||||
service.Enqueue(work);
|
||||
}
|
||||
|
||||
await service.ExecuteTask;
|
||||
|
||||
//assert
|
||||
Assert.Equal(workCount, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Enqueue_continues_after_exceptions()
|
||||
{
|
||||
var expectadResult = 42;
|
||||
var result = 0;
|
||||
|
||||
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
{
|
||||
result = expectadResult;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var goodWork = Work.CreateByDelegate("", workAction);
|
||||
|
||||
Task failAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
=> throw new Exception();
|
||||
|
||||
var badWork = Work.CreateByDelegate("", failAction);
|
||||
badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
|
||||
|
||||
//act
|
||||
service.Enqueue(badWork);
|
||||
service.Enqueue(goodWork);
|
||||
|
||||
await service.ExecuteTask;
|
||||
|
||||
//assert
|
||||
Assert.Equal(expectadResult, result);
|
||||
Assert.Equal(1, service.Felled.Count);
|
||||
Assert.Equal(1, service.Done.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryRemove()
|
||||
{
|
||||
var workCount = 5;
|
||||
var result = 0;
|
||||
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
{
|
||||
result++;
|
||||
return Task.Delay(10);
|
||||
}
|
||||
|
||||
//act
|
||||
for (int i = 0; i < workCount; i++)
|
||||
{
|
||||
var work = Work.CreateByDelegate(i.ToString(), workAction);
|
||||
service.Enqueue(work);
|
||||
}
|
||||
|
||||
var removed = service.TryRemoveFromQueue((workCount - 1).ToString());
|
||||
|
||||
await service.ExecuteTask;
|
||||
|
||||
//assert
|
||||
Assert.True(removed);
|
||||
Assert.Equal(workCount - 1, result);
|
||||
Assert.Equal(workCount - 1, service.Done.Count);
|
||||
}
|
||||
}
|
@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
|
||||
|
||||
Assert.Equal(2, state.IdState);
|
||||
backgroundWorkerMock.Verify(s => s.Push(It.IsAny<Work>()));
|
||||
backgroundWorkerMock.Verify(s => s.Enqueue(It.IsAny<Work>()));
|
||||
}
|
||||
|
||||
[Fact]
|
@ -0,0 +1,97 @@
|
||||
using AsbCloudInfrastructure.Background;
|
||||
using DocumentFormat.OpenXml.Drawing.Charts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace AsbCloudWebApi.Tests.Services;
|
||||
|
||||
public class PeriodicBackgroundWorkerTest
|
||||
{
|
||||
private IServiceProvider provider;
|
||||
private PeriodicBackgroundWorker service;
|
||||
|
||||
public PeriodicBackgroundWorkerTest()
|
||||
{
|
||||
provider = Substitute.For<IServiceProvider, ISupportRequiredService>();
|
||||
var serviceScope = Substitute.For<IServiceScope>();
|
||||
var serviceScopeFactory = Substitute.For<IServiceScopeFactory>();
|
||||
serviceScopeFactory.CreateScope().Returns(serviceScope);
|
||||
((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
|
||||
|
||||
service = new PeriodicBackgroundWorker(provider);
|
||||
typeof(PeriodicBackgroundWorker)
|
||||
.GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)?
|
||||
.SetValue(service, TimeSpan.FromMilliseconds(1));
|
||||
|
||||
typeof(PeriodicBackgroundWorker)
|
||||
.GetField("executePeriod", BindingFlags.NonPublic | BindingFlags.Instance)?
|
||||
.SetValue(service, TimeSpan.FromMilliseconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WorkRunsTwice()
|
||||
{
|
||||
var workCount = 2;
|
||||
var periodMs = 100d;
|
||||
|
||||
var period = TimeSpan.FromMilliseconds(periodMs);
|
||||
|
||||
var result = 0;
|
||||
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
{
|
||||
result++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//act
|
||||
var work = Work.CreateByDelegate("", workAction);
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
service.Add(work, period);
|
||||
|
||||
var delay = (periodMs / 20) + (periodMs * workCount) - stopwatch.ElapsedMilliseconds;
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(delay));
|
||||
|
||||
//assert
|
||||
Assert.Equal(workCount, result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task Enqueue_continues_after_exceptions()
|
||||
{
|
||||
var expectadResult = 42;
|
||||
var result = 0;
|
||||
|
||||
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
{
|
||||
result = expectadResult;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var goodWork = Work.CreateByDelegate("", workAction);
|
||||
|
||||
Task failAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
|
||||
=> throw new Exception();
|
||||
|
||||
var badWork = Work.CreateByDelegate("", failAction);
|
||||
badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
|
||||
|
||||
//act
|
||||
service.Add(badWork, TimeSpan.FromSeconds(2));
|
||||
service.Add(goodWork, TimeSpan.FromSeconds(2));
|
||||
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(20));
|
||||
|
||||
//assert
|
||||
Assert.Equal(expectadResult, result);
|
||||
Assert.Equal(1, badWork.CountErrors);
|
||||
Assert.Equal(1, goodWork.CountComplete);
|
||||
Assert.Equal(1, goodWork.CountStart);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AsbCloudApp.Data.SAUB;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Background;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace AsbCloudWebApi.Tests.ServicesTests.SAUB;
|
||||
|
||||
public class TelemetryDataSaubCacheTests
|
||||
{
|
||||
private const int idTelemetry = 1;
|
||||
|
||||
private readonly IEnumerable<TelemetryDataSaubDto> fakeTelemetries = new[]
|
||||
{
|
||||
new TelemetryDataSaubDto()
|
||||
};
|
||||
|
||||
private readonly IServiceProvider serviceProviderMock = Substitute.For<IServiceProvider>();
|
||||
|
||||
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
|
||||
private readonly Type telemetryDataCacheType;
|
||||
|
||||
public TelemetryDataSaubCacheTests()
|
||||
{
|
||||
serviceProviderMock.GetService<BackgroundWorker>().Returns(new BackgroundWorker(serviceProviderMock));
|
||||
|
||||
telemetryDataCache = TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(serviceProviderMock);
|
||||
|
||||
telemetryDataCacheType = telemetryDataCache.GetType();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRange_ShouldReturn_AddedElementToCache()
|
||||
{
|
||||
//arrange
|
||||
telemetryDataCacheType.GetField("isLoading", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(telemetryDataCache, false);
|
||||
|
||||
//act
|
||||
telemetryDataCache.AddRange(idTelemetry, fakeTelemetries);
|
||||
var lastTelemetry = telemetryDataCache.GetLastOrDefault(idTelemetry);
|
||||
|
||||
//assert
|
||||
Assert.Equal(lastTelemetry, fakeTelemetries.Last());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRange_ShouldReturn_NotAddedToCache()
|
||||
{
|
||||
//arrange
|
||||
telemetryDataCacheType.GetField("isLoading", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(telemetryDataCache, true);
|
||||
|
||||
//act
|
||||
telemetryDataCache.AddRange(idTelemetry, fakeTelemetries);
|
||||
var lastTelemetry = telemetryDataCache.GetLastOrDefault(idTelemetry);
|
||||
|
||||
//assert
|
||||
Assert.NotEqual(lastTelemetry, fakeTelemetries.Last());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user