forked from ddrilling/AsbCloudServer
Merge branch 'dev' into feature/7887519
This commit is contained in:
commit
49cc9c2342
@ -18,10 +18,6 @@
|
||||
<EditorConfigFiles Remove="D:\Source\AsbCloudApp\Services\.editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="D:\Source\AsbCloudApp\Services\.editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AsbCloudDb\AsbCloudDb.csproj" />
|
||||
</ItemGroup>
|
||||
|
198
AsbCloudApp/CyclycArray.cs
Normal file
198
AsbCloudApp/CyclycArray.cs
Normal file
@ -0,0 +1,198 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Collections.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// Цикличный массив
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class CyclycArray<T> : IEnumerable<T>
|
||||
{
|
||||
readonly T[] array;
|
||||
int used, current = -1;
|
||||
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public CyclycArray(int capacity)
|
||||
{
|
||||
array = new T[capacity];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Количество элементов в массиве
|
||||
/// </summary>
|
||||
public int Count => used;
|
||||
|
||||
/// <summary>
|
||||
/// Добавить новый элемент<br/>
|
||||
/// Если capacity достигнуто, то вытеснит самый первый элемент
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Add(T item)
|
||||
{
|
||||
current = (++current) % array.Length;
|
||||
array[current] = item;
|
||||
if (used < array.Length)
|
||||
used++;
|
||||
UpdatedInvoke(current, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить новые элементы.<br/>
|
||||
/// Если capacity достигнуто, то вытеснит самые первые элементы.<br/>
|
||||
/// Не вызывает Updated!
|
||||
/// </summary>
|
||||
/// <param name="items"></param>
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
var capacity = array.Length;
|
||||
var newItems = items.TakeLast(capacity).ToArray();
|
||||
if (newItems.Length == capacity)
|
||||
{
|
||||
Array.Copy(newItems, array, capacity);
|
||||
current = capacity - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = (++current) % capacity;
|
||||
var countToEndOfArray = capacity - current;
|
||||
if (newItems.Length <= countToEndOfArray)
|
||||
{
|
||||
Array.Copy(newItems, 0, array, current, newItems.Length);
|
||||
current += newItems.Length - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstStepLength = countToEndOfArray;
|
||||
Array.Copy(newItems, 0, array, current, firstStepLength);
|
||||
var secondStepCount = newItems.Length - firstStepLength;
|
||||
Array.Copy(newItems, firstStepLength, array, 0, secondStepCount);
|
||||
current = secondStepCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (used < capacity)
|
||||
{
|
||||
used += newItems.Length;
|
||||
used = used > capacity ? capacity : used;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Индекс
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (used == 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
var i = (current + 1 + index) % used;
|
||||
return array[i];
|
||||
}
|
||||
set
|
||||
{
|
||||
var devider = used > 0 ? used : array.Length;
|
||||
var i = (current + 1 + index) % devider;
|
||||
array[i] = value;
|
||||
UpdatedInvoke(current, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// событие на изменение элемента в массиве
|
||||
/// </summary>
|
||||
public event EventHandler<(int index, T value)>? Updated;
|
||||
private void UpdatedInvoke(int index, T value)
|
||||
{
|
||||
Updated?.Invoke(this, (index, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Агрегирование значения по всему массиву
|
||||
/// </summary>
|
||||
/// <typeparam name="Tout"></typeparam>
|
||||
/// <param name="func"></param>
|
||||
/// <param name="startValue"></param>
|
||||
/// <returns></returns>
|
||||
public Tout Aggregate<Tout>(Func<T, Tout, Tout> func, Tout startValue)
|
||||
{
|
||||
Tout result = startValue;
|
||||
for (int i = 0; i < used; i++)
|
||||
result = func(this[i], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
=> new CyclycListEnumerator<T>(array, current, used);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
class CyclycListEnumerator<Te> : IEnumerator<Te>
|
||||
{
|
||||
private readonly Te[] array;
|
||||
private readonly int used;
|
||||
private readonly int first;
|
||||
private int current = -1;
|
||||
|
||||
public CyclycListEnumerator(Te[] array, int first, int used)
|
||||
{
|
||||
this.array = new Te[array.Length];
|
||||
array.CopyTo(this.array, 0);
|
||||
this.used = used;
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public Te Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsCurrentOk())
|
||||
{
|
||||
var i = (current + first + 1) % used;
|
||||
return array[i];
|
||||
}
|
||||
else
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose() {; }
|
||||
|
||||
private bool IsCurrentOk() => current >= 0 && current < used;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (current < used)
|
||||
current++;
|
||||
return IsCurrentOk();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
current = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очистить весь массив
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
used = 0;
|
||||
current = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#nullable disable
|
@ -10,7 +10,7 @@ namespace AsbCloudApp.Data.Subsystems
|
||||
/// <summary>
|
||||
/// Активная скважина
|
||||
/// </summary>
|
||||
public WellDto Well { get; set; }
|
||||
public WellDto Well { get; set; } = null!;
|
||||
/// <summary>
|
||||
/// Наработки подсистемы АКБ
|
||||
/// </summary>
|
||||
|
@ -51,7 +51,6 @@ namespace AsbCloudApp.Repositories
|
||||
/// <summary>
|
||||
/// Вывод списка всех файлов из базы, для которых нет файла на диске
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="files"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<FileInfoDto> GetListFilesNotDisc(IEnumerable<FileInfoDto> files);
|
||||
|
@ -165,15 +165,6 @@ namespace AsbCloudApp.Services
|
||||
public async Task<IEnumerable<FileInfoDto>> GetInfoByIdsAsync(IEnumerable<int> idsFile, CancellationToken token)
|
||||
{
|
||||
var result = await fileRepository.GetInfoByIdsAsync(idsFile, token).ConfigureAwait(false);
|
||||
|
||||
foreach (var entity in result)
|
||||
{
|
||||
|
||||
var ext = Path.GetExtension(entity.Name);
|
||||
var relativePath = GetUrl(entity.IdWell, entity.IdCategory, entity.Id, ext);
|
||||
var fullPath = Path.GetFullPath(relativePath);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudApp.Services
|
||||
{
|
||||
#nullable enable
|
||||
/// <summary>
|
||||
/// Сервис авторизации
|
||||
/// </summary>
|
||||
@ -33,13 +34,14 @@ namespace AsbCloudApp.Services
|
||||
/// <param name="password"></param>
|
||||
/// <param name="token">токен отмены задачи</param>
|
||||
/// <returns></returns>
|
||||
Task<UserTokenDto> LoginAsync(string login,
|
||||
Task<UserTokenDto?> LoginAsync(string login,
|
||||
string password, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Обновление токена авторизации
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="identity"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<UserTokenDto?> RefreshAsync(ClaimsPrincipal identity,
|
||||
CancellationToken token);
|
||||
@ -51,4 +53,5 @@ namespace AsbCloudApp.Services
|
||||
/// <returns></returns>
|
||||
int Register(UserRegistrationDto userDto);
|
||||
}
|
||||
#nullable disable
|
||||
}
|
@ -10,6 +10,12 @@ namespace AsbCloudApp.Services
|
||||
/// </summary>
|
||||
public interface IFileCategoryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить категории файлов
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<FileCategoryDto> GetOrDefaultAsync(int id, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
|
@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
namespace AsbCloudApp.Services
|
||||
{
|
||||
/// <summary>
|
||||
@ -22,7 +22,7 @@ namespace AsbCloudApp.Services
|
||||
/// <param name="approxPointsCount">кол-во элементов до которых эти данные прореживаются</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<TDto>> GetAsync(int idWell,
|
||||
Task<IEnumerable<TDto>?> GetOrDefaultAsync(int idWell,
|
||||
DateTime dateBegin = default, double intervalSec = 600d,
|
||||
int approxPointsCount = 1024, CancellationToken token = default);
|
||||
|
||||
@ -35,4 +35,5 @@ namespace AsbCloudApp.Services
|
||||
/// <returns></returns>
|
||||
Task<int> UpdateDataAsync(string uid, IEnumerable<TDto> dtos, CancellationToken token = default);
|
||||
}
|
||||
}
|
||||
}
|
||||
#nullable disable
|
@ -55,7 +55,7 @@ namespace AsbCloudApp.Services
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <returns></returns>
|
||||
int? GetIdTelemetryByIdWell(int idWell);
|
||||
int? GetOrDefaultIdTelemetryByIdWell(int idWell);
|
||||
|
||||
/// <summary>
|
||||
/// получить диапазон дат за которые есть данные
|
||||
|
@ -6,8 +6,19 @@ using System.Threading.Tasks;
|
||||
namespace AsbCloudApp.Services.Subsystems
|
||||
{
|
||||
#nullable enable
|
||||
// TODO: move this to repositories
|
||||
|
||||
/// <summary>
|
||||
/// репозиторий получения подсистем
|
||||
/// </summary>
|
||||
public interface ISubsystemService
|
||||
{
|
||||
/// <summary>
|
||||
/// получение списка подсистем. Если скважина указана, то получим только использованные в скважине подсистемы.
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<SubsystemDto>?> GetSubsystemAsync(int? idWell, CancellationToken token);
|
||||
}
|
||||
#nullable disable
|
||||
|
@ -60,15 +60,7 @@ namespace AsbCloudDb.Model
|
||||
public DbSet<WITS.Record50> Record50 => Set<WITS.Record50>();
|
||||
public DbSet<WITS.Record60> Record60 => Set<WITS.Record60>();
|
||||
public DbSet<WITS.Record61> Record61 => Set<WITS.Record61>();
|
||||
|
||||
private System.Text.Json.JsonSerializerOptions jsonSerializerOptions = new()
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
WriteIndented = true,
|
||||
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString |
|
||||
System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
};
|
||||
|
||||
|
||||
public AsbCloudDbContext() : base()
|
||||
{
|
||||
}
|
||||
@ -369,10 +361,5 @@ namespace AsbCloudDb.Model
|
||||
var sql = $"REFRESH MATERIALIZED VIEW {materializedViewName};";
|
||||
return Database.ExecuteSqlRawAsync(sql, token);
|
||||
}
|
||||
|
||||
public Task<int> RefreshMaterializedViewAsync<TEntity>(string? mwName = null, CancellationToken token = default) where TEntity : class
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,58 +5,57 @@
|
||||
/// <summary>
|
||||
/// КНБК описание
|
||||
/// </summary>
|
||||
public string BHADescription { get; set; }
|
||||
public string BHADescription { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Бурение с наращиваниями в инт. 2195-2763м. Время начала
|
||||
/// </summary>
|
||||
public string ExtensionDrillingOneBegin { get; set; }
|
||||
public string ExtensionDrillingOneBegin { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Бурение с наращиваниями в инт. 2195-2763м. Время окончания
|
||||
/// </summary>
|
||||
public string ExtensionDrillingOneFinish { get; set; }
|
||||
public string ExtensionDrillingOneFinish { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Промывка. Время начала
|
||||
/// </summary>
|
||||
public string SluiceBegin { get; set; }
|
||||
public string SluiceBegin { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Промывка. Время окончания
|
||||
/// </summary>
|
||||
public string SluiceFinish { get; set; }
|
||||
public string SluiceFinish { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Подьем КНБК. Время начала
|
||||
/// </summary>
|
||||
public string ClimbBegin { get; set; }
|
||||
public string ClimbBegin { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Подьем КНБК. Время окончания
|
||||
/// </summary>
|
||||
public string ClimbFinish { get; set; }
|
||||
public string ClimbFinish { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Спуск КНБК. Время начала
|
||||
/// </summary>
|
||||
public string DescentBegin { get; set; }
|
||||
public string DescentBegin { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Спуск КНБК. Время окончания
|
||||
/// </summary>
|
||||
public string DescentFinish { get; set; }
|
||||
public string DescentFinish { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Бурение с наращиваниями в инт. 2763-2850м. Время начала
|
||||
/// </summary>
|
||||
public string ExtensionDrillingTwoBegin { get; set; }
|
||||
public string ExtensionDrillingTwoBegin { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Бурение с наращиваниями в инт. 2763-2850м. Время окончания
|
||||
/// </summary>
|
||||
public string ExtensionDrillingTwoFinish { get; set; }
|
||||
public string ExtensionDrillingTwoFinish { get; set; } = string.Empty;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,22 +6,22 @@ namespace AsbCloudDb.Model.DailyReport
|
||||
/// <summary>
|
||||
/// название скважины
|
||||
/// </summary>
|
||||
public string WellName { get; set; }
|
||||
public string WellName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// название куста
|
||||
/// </summary>
|
||||
public string ClusterName { get; set; }
|
||||
public string ClusterName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// заказчик
|
||||
/// </summary>
|
||||
public string Customer { get; set; }
|
||||
public string Customer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// подрядчик
|
||||
/// </summary>
|
||||
public string Contractor { get; set; }
|
||||
public string Contractor { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// дата рапорта
|
||||
@ -61,12 +61,12 @@ namespace AsbCloudDb.Model.DailyReport
|
||||
/// <summary>
|
||||
/// ФИО бурильщиков
|
||||
/// </summary>
|
||||
public string FirstDriller { get; set; }
|
||||
public string FirstDriller { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ФИО бурильщиков
|
||||
/// </summary>
|
||||
public string SecondDriller { get; set; }
|
||||
public string SecondDriller { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Время работы АПД
|
||||
|
@ -72,32 +72,32 @@ namespace AsbCloudDb.Model.DailyReport
|
||||
/// <summary>
|
||||
/// указываются все причины, которые влияют на снижение МСП.
|
||||
/// </summary>
|
||||
public string DeclinesReasonsROP { get; set; }
|
||||
public string DeclinesReasonsROP { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Увеличение мех скорости за секцию %
|
||||
/// </summary>
|
||||
public string IncreaseSpeedSection { get; set; }
|
||||
public string IncreaseSpeedSection { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Увеличение мех скорости за сутки %
|
||||
/// </summary>
|
||||
public string IncreaseSpeedDay { get; set; }
|
||||
public string IncreaseSpeedDay { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Сокращение времени бурения за секцию, ч
|
||||
/// </summary>
|
||||
public string ReductionTimeDrilling { get; set; }
|
||||
public string ReductionTimeDrilling { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Ротор/Слайд %
|
||||
/// </summary>
|
||||
public string RotorSlidePercent { get; set; }
|
||||
public string RotorSlidePercent { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// МСП
|
||||
/// </summary>
|
||||
public string MspSection { get; set; }
|
||||
public string MspSection { get; set; } = string.Empty;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,12 @@
|
||||
/// <summary>
|
||||
/// ФИО Мастера буровой
|
||||
/// </summary>
|
||||
public string DrillingMaster { get; set; }
|
||||
public string DrillingMaster { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ФИО супервайзера
|
||||
/// </summary>
|
||||
public string Supervisor { get; set; }
|
||||
public string Supervisor { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,92 +5,92 @@
|
||||
/// <summary>
|
||||
/// Бурение
|
||||
/// </summary>
|
||||
public string Drilling { get; set; }
|
||||
public string Drilling { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Промывка
|
||||
/// </summary>
|
||||
public string Flushing { get; set; }
|
||||
public string Flushing { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Наращивание
|
||||
/// </summary>
|
||||
public string Building { get; set; }
|
||||
public string Building { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Проработка
|
||||
/// </summary>
|
||||
public string Elaboration { get; set; }
|
||||
public string Elaboration { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Расширка
|
||||
/// </summary>
|
||||
public string Extension { get; set; }
|
||||
public string Extension { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Ремонт
|
||||
/// </summary>
|
||||
public string Repair { get; set; }
|
||||
public string Repair { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// КНБК
|
||||
/// </summary>
|
||||
public string Knbk { get; set; }
|
||||
public string Knbk { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// СПО
|
||||
/// </summary>
|
||||
public string Spo { get; set; }
|
||||
public string Spo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ПЗР
|
||||
/// </summary>
|
||||
public string Pzr { get; set; }
|
||||
public string Pzr { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ПВО
|
||||
/// </summary>
|
||||
public string Pvo { get; set; }
|
||||
public string Pvo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ПГР
|
||||
/// </summary>
|
||||
public string Pgr { get; set; }
|
||||
public string Pgr { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ГИС
|
||||
/// </summary>
|
||||
public string Gis { get; set; }
|
||||
public string Gis { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ОЗЦ
|
||||
/// </summary>
|
||||
public string Ozc { get; set; }
|
||||
public string Ozc { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Тех. работы
|
||||
/// </summary>
|
||||
public string EngineeringWorks { get; set; }
|
||||
public string EngineeringWorks { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Снятие замера
|
||||
/// </summary>
|
||||
public string TakingMeasure { get; set; }
|
||||
public string TakingMeasure { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Цементирование
|
||||
/// </summary>
|
||||
public string Cementing { get; set; }
|
||||
public string Cementing { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Простой
|
||||
/// </summary>
|
||||
public string Simple { get; set; }
|
||||
public string Simple { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// НПВ
|
||||
/// </summary>
|
||||
public string Npv { get; set; }
|
||||
public string Npv { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ using AsbCloudInfrastructure.Services.SAUB;
|
||||
using AsbCloudInfrastructure.Services.Subsystems;
|
||||
using AsbCloudInfrastructure.Services.WellOperationService;
|
||||
using AsbCloudInfrastructure.Validators;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using FluentValidation.AspNetCore;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -90,7 +91,8 @@ namespace AsbCloudInfrastructure
|
||||
services.AddDbContext<AsbCloudDbContext>(options =>
|
||||
options.UseNpgsql(configuration.GetConnectionString(connectionStringName)));
|
||||
|
||||
services.AddFluentValidation();
|
||||
// TODO: переместить FluentValidation в описание моделей
|
||||
services.AddFluentValidationClientsideAdapters();
|
||||
|
||||
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
|
||||
services.AddScoped<IEmailService, EmailService>();
|
||||
@ -101,6 +103,8 @@ namespace AsbCloudInfrastructure
|
||||
services.AddSingleton(new WitsInfoService());
|
||||
services.AddSingleton(new CacheDb());
|
||||
services.AddSingleton(new InstantDataRepository());
|
||||
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration));
|
||||
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration));
|
||||
services.AddSingleton<ITelemetryTracker, TelemetryTracker>();
|
||||
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
|
||||
services.AddSingleton<IBackgroundWorkerService, BackgroundWorkerService>();
|
||||
|
@ -1,439 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.EfCache
|
||||
{
|
||||
#nullable enable
|
||||
/// <summary>
|
||||
/// Кеширование запросов EF.
|
||||
/// Кеш не отслеживается ChangeTracker.
|
||||
/// </summary>
|
||||
public static class EfCacheDictionaryExtensions
|
||||
{
|
||||
private static readonly Dictionary<string, CacheItem> caches = new(16);
|
||||
private static readonly TimeSpan semaphoreTimeout = TimeSpan.FromSeconds(25);
|
||||
private static readonly SemaphoreSlim semaphore = new(1);
|
||||
private static readonly TimeSpan minCacheTime = TimeSpan.FromSeconds(2);
|
||||
private static readonly TimeSpan defaultObsolescence = TimeSpan.FromMinutes(4);
|
||||
|
||||
private class CacheItem
|
||||
{
|
||||
internal IDictionary? Data;
|
||||
internal DateTime DateObsolete;
|
||||
internal DateTime DateObsoleteTotal;
|
||||
internal readonly SemaphoreSlim semaphore = new(1);
|
||||
|
||||
|
||||
internal Dictionary<TKey, TEntity> GetData<TKey, TEntity>()
|
||||
where TKey : notnull
|
||||
{
|
||||
if (Data is Dictionary<TKey, TEntity> typedData)
|
||||
return typedData;
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
|
||||
internal Dictionary<TKey, TModel> GetData<TKey, TEntity, TModel>(Func<TEntity, TModel> convert, int attempt = 1)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (Data is Dictionary<TKey, TModel> typedData)
|
||||
return typedData;
|
||||
if (Data is Dictionary<TKey, TEntity> typedEntityData)
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
var convertedData = typedEntityData.ToDictionary(i => i.Key, i => convert(i.Value));
|
||||
Data = convertedData;
|
||||
return convertedData;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (semaphore.Wait(semaphoreTimeout))
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
semaphore.Release();
|
||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while converting cache data");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attempt > 0)
|
||||
return GetData<TKey, TEntity, TModel>(convert, --attempt);
|
||||
throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique.");
|
||||
}
|
||||
}
|
||||
|
||||
private static CacheItem GetOrAddCache(string tag, Func<IDictionary> valueFactory, TimeSpan obsolete)
|
||||
{
|
||||
CacheItem cache;
|
||||
while (!caches.ContainsKey(tag))
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
cache = new CacheItem();
|
||||
caches.Add(tag, cache);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (semaphore.Wait(semaphoreTimeout))
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
semaphore.Release();
|
||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache = caches[tag];
|
||||
|
||||
if (cache.DateObsolete < DateTime.Now)
|
||||
{
|
||||
if (cache.semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dateObsolete = DateTime.Now + obsolete;
|
||||
var dateQueryStart = DateTime.Now;
|
||||
var data = valueFactory();
|
||||
var queryTime = DateTime.Now - dateQueryStart;
|
||||
|
||||
if (dateObsolete - DateTime.Now < minCacheTime)
|
||||
dateObsolete = DateTime.Now + minCacheTime;
|
||||
|
||||
cache.Data = data;
|
||||
cache.DateObsolete = dateObsolete;
|
||||
cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cache.semaphore.Release();
|
||||
}
|
||||
}
|
||||
else if (cache.DateObsoleteTotal < DateTime.Now)
|
||||
{
|
||||
if (cache.semaphore.Wait(semaphoreTimeout))
|
||||
{
|
||||
cache.semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.semaphore.Release();
|
||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static async Task<CacheItem> GetOrAddCacheAsync(string tag, Func<CancellationToken, Task<IDictionary>> valueFactoryAsync, TimeSpan obsolete, CancellationToken token)
|
||||
{
|
||||
CacheItem cache;
|
||||
while (!caches.ContainsKey(tag))
|
||||
{
|
||||
if (semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
cache = new CacheItem();
|
||||
caches.Add(tag, cache);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await semaphore.WaitAsync(semaphoreTimeout, token))
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
semaphore.Release();
|
||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache = caches[tag];
|
||||
|
||||
if (cache.DateObsolete < DateTime.Now)
|
||||
{
|
||||
if (cache.semaphore.Wait(0))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dateObsolete = DateTime.Now + obsolete;
|
||||
var dateQueryStart = DateTime.Now;
|
||||
var data = await valueFactoryAsync(token);
|
||||
var queryTime = DateTime.Now - dateQueryStart;
|
||||
|
||||
if (dateObsolete - DateTime.Now < minCacheTime)
|
||||
dateObsolete = DateTime.Now + minCacheTime;
|
||||
|
||||
cache.Data = data;
|
||||
cache.DateObsolete = dateObsolete;
|
||||
cache.DateObsoleteTotal = dateObsolete + queryTime + minCacheTime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cache.semaphore.Release();
|
||||
}
|
||||
}
|
||||
else if (cache.DateObsoleteTotal < DateTime.Now)
|
||||
{
|
||||
if (await cache.semaphore.WaitAsync(semaphoreTimeout, token))
|
||||
{
|
||||
cache.semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
cache.semaphore.Release();
|
||||
throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while getting updated cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>. С тегом typeof(TEntity).Name и ключом int Id
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<int, TEntity> FromCacheDictionary<TEntity>(
|
||||
this IQueryable<TEntity> query)
|
||||
where TEntity : class, AsbCloudDb.Model.IId
|
||||
{
|
||||
var tag = typeof(TEntity).Name;
|
||||
return FromCacheDictionary(query, tag, defaultObsolescence, e => e.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>. С тегом typeof(TEntity).Name
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<TKey, TEntity> FromCacheDictionary<TKey, TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
Func<TEntity, TKey> keySelector)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
var tag = typeof(TEntity).Name;
|
||||
return FromCacheDictionary(query, tag, defaultObsolescence, keySelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<TKey, TEntity> FromCacheDictionary<TKey, TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
string tag,
|
||||
TimeSpan obsolescence,
|
||||
Func<TEntity, TKey> keySelector)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
IDictionary factory()
|
||||
=> query.AsNoTracking().ToDictionary(keySelector);
|
||||
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||
return cache.GetData<TKey, TEntity>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
|
||||
/// Преобразование выполняется после получения из БД, результат кешируется в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TModel"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <typeparam name="TModel"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="convert">Преобразование данных БД в DTO</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<TKey, TModel> FromCacheDictionary<TKey, TEntity, TModel>(
|
||||
this IQueryable<TEntity> query,
|
||||
string tag,
|
||||
TimeSpan obsolescence,
|
||||
Func<TEntity, TKey> keySelector,
|
||||
Func<TEntity, TModel> convert)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
IDictionary factory()
|
||||
=> query.AsNoTracking().ToDictionary(keySelector);
|
||||
var cache = GetOrAddCache(tag, factory, obsolescence);
|
||||
return cache.GetData<TKey, TEntity, TModel>(convert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>. С тегом typeof(TEntity).Name и ключом int Id
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<Dictionary<int, TEntity>> FromCacheDictionaryAsync<TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
CancellationToken token = default)
|
||||
where TEntity : class, AsbCloudDb.Model.IId
|
||||
{
|
||||
var tag = typeof(TEntity).Name;
|
||||
return FromCacheDictionaryAsync(query, tag, defaultObsolescence, e => e.Id, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>. С тегом typeof(TEntity).Name
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<Dictionary<TKey, TEntity>> FromCacheDictionaryAsync<TKey, TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
Func<TEntity, TKey> keySelector,
|
||||
CancellationToken token = default)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
var tag = typeof(TEntity).Name;
|
||||
return FromCacheDictionaryAsync(query, tag, defaultObsolescence, keySelector, token);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Dictionary<TKey, TEntity>> FromCacheDictionaryAsync<TKey, TEntity>(
|
||||
this IQueryable<TEntity> query,
|
||||
string tag,
|
||||
TimeSpan obsolescence,
|
||||
Func<TEntity, TKey> keySelector,
|
||||
CancellationToken token = default)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
async Task<IDictionary> factory(CancellationToken token)
|
||||
=> await query.AsNoTracking().ToDictionaryAsync(keySelector, token);
|
||||
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||
return cache.GetData<TKey, TEntity>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Асинхронно кешировать запрос с последующим преобразованием из <typeparamref name="TEntity"/> в <typeparamref name="TModel"/>.<br/>
|
||||
/// Преобразование выполняется после получения из БД, результат кешируется в Dictionary<<typeparamref name="TKey"/>, <typeparamref name="TModel"/>>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">тип ключа</typeparam>
|
||||
/// <typeparam name="TEntity">тип значения</typeparam>
|
||||
/// <typeparam name="TModel"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="keySelector">Делегат получения ключа из записи</param>
|
||||
/// <param name="convert">Преобразование данных БД в DTO</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Dictionary<TKey, TModel>> FromCacheDictionaryAsync<TKey, TEntity, TModel>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TKey> keySelector, Func<TEntity, TModel> convert, CancellationToken token = default)
|
||||
where TEntity : class
|
||||
where TKey : notnull
|
||||
{
|
||||
async Task<IDictionary> factory(CancellationToken token)
|
||||
=> await query.AsNoTracking().ToDictionaryAsync(keySelector, token);
|
||||
var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token);
|
||||
return cache.GetData<TKey, TEntity, TModel>(convert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// drops cache with tag = typeof(T).Name
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
public static void DropCacheDictionary<T>(this IQueryable<T> query)
|
||||
{
|
||||
var tag = typeof(T).Name;
|
||||
DropCacheDictionary<T>(query, tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очистить кеш
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="tag">Метка кеша</param>
|
||||
public static void DropCacheDictionary<T>(this IQueryable<T> query, string tag)
|
||||
{
|
||||
caches.Remove(tag, out var _);
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
}
|
@ -349,7 +349,7 @@ namespace AsbCloudInfrastructure.EfCache
|
||||
return cache.GetData(convert);
|
||||
}
|
||||
|
||||
public static Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(this IQueryable<TEntity> query, CancellationToken token = default)
|
||||
public static Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(this IQueryable<TEntity> query, CancellationToken token)
|
||||
where TEntity : class
|
||||
{
|
||||
var tag = typeof(TEntity).Name;
|
||||
@ -365,7 +365,7 @@ namespace AsbCloudInfrastructure.EfCache
|
||||
/// <param name="obsolescence">Период устаревания данных</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, CancellationToken token = default)
|
||||
public static async Task<IEnumerable<TEntity>> FromCacheAsync<TEntity>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, CancellationToken token)
|
||||
where TEntity : class
|
||||
{
|
||||
async Task<object[]> factory(CancellationToken token)
|
||||
@ -386,7 +386,7 @@ namespace AsbCloudInfrastructure.EfCache
|
||||
/// <param name="convert">Преобразование данных БД в DTO</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IEnumerable<TModel>> FromCacheAsync<TEntity, TModel>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TModel> convert, CancellationToken token = default)
|
||||
public static async Task<IEnumerable<TModel>> FromCacheAsync<TEntity, TModel>(this IQueryable<TEntity> query, string tag, TimeSpan obsolescence, Func<TEntity, TModel> convert, CancellationToken token)
|
||||
where TEntity : class
|
||||
{
|
||||
async Task<object[]> factory(CancellationToken token)
|
||||
|
@ -54,7 +54,7 @@ namespace AsbCloudInfrastructure.Repository
|
||||
public override async Task<IEnumerable<TDto>> GetAllAsync(CancellationToken token)
|
||||
{
|
||||
var cache = await GetCacheAsync(token);
|
||||
return cache.Values;
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -65,14 +65,14 @@ namespace AsbCloudInfrastructure.Repository
|
||||
public override TDto? GetOrDefault(int id)
|
||||
{
|
||||
var cache = GetCache();
|
||||
return cache.GetValueOrDefault(id);
|
||||
return cache.FirstOrDefault(d => d.Id == id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<TDto?> GetOrDefaultAsync(int id, CancellationToken token)
|
||||
{
|
||||
var cache = await GetCacheAsync(token);
|
||||
return cache.GetValueOrDefault(id);
|
||||
return cache.FirstOrDefault(d => d.Id == id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -102,17 +102,17 @@ namespace AsbCloudInfrastructure.Repository
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual Task<Dictionary<int, TDto>> GetCacheAsync(CancellationToken token)
|
||||
protected virtual Task<IEnumerable<TDto>> GetCacheAsync(CancellationToken token)
|
||||
=> GetQuery()
|
||||
.FromCacheDictionaryAsync(CacheTag, CacheOlescence, KeySelector, Convert, token);
|
||||
.FromCacheAsync(CacheTag, CacheOlescence, Convert, token);
|
||||
|
||||
|
||||
protected virtual Dictionary<int, TDto> GetCache()
|
||||
protected virtual IEnumerable<TDto> GetCache()
|
||||
=> GetQuery()
|
||||
.FromCacheDictionary(CacheTag, CacheOlescence, KeySelector, Convert);
|
||||
.FromCache(CacheTag, CacheOlescence, Convert);
|
||||
|
||||
protected virtual void DropCache()
|
||||
=> dbSet.DropCacheDictionary(CacheTag);
|
||||
=> dbSet.DropCache(CacheTag);
|
||||
}
|
||||
#nullable disable
|
||||
}
|
@ -23,25 +23,25 @@ namespace AsbCloudInfrastructure.Repository
|
||||
public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(context, makeQuery) { }
|
||||
|
||||
public async Task<IEnumerable<TDto>> GetByIdWellAsync(int idWell, CancellationToken token)
|
||||
public async Task<IEnumerable<TDto>?> GetByIdWellAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
var cache = await GetCacheAsync(token);
|
||||
|
||||
var dtos = cache.Values
|
||||
var dtos = cache
|
||||
.Where(e => e.IdWell == idWell)
|
||||
.ToList();
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TDto>> GetByIdWellAsync(IEnumerable<int> idsWells, CancellationToken token)
|
||||
public async Task<IEnumerable<TDto>?> GetByIdWellAsync(IEnumerable<int> idsWells, CancellationToken token)
|
||||
{
|
||||
if (!idsWells.Any())
|
||||
return Enumerable.Empty<TDto>();
|
||||
|
||||
var cache = await GetCacheAsync(token);
|
||||
|
||||
var dtos = cache.Values
|
||||
var dtos = cache
|
||||
.Where(e => idsWells.Contains(e.IdWell))
|
||||
.ToList();
|
||||
return dtos;
|
||||
|
@ -23,7 +23,7 @@ namespace AsbCloudInfrastructure.Repository
|
||||
public CrudWellRelatedServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(context, makeQuery) { }
|
||||
|
||||
public async Task<IEnumerable<TDto>> GetByIdWellAsync(int idWell, CancellationToken token)
|
||||
public async Task<IEnumerable<TDto>?> GetByIdWellAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
var entities = await GetQuery()
|
||||
.Where(e => e.IdWell == idWell)
|
||||
@ -32,7 +32,7 @@ namespace AsbCloudInfrastructure.Repository
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TDto>> GetByIdWellAsync(IEnumerable<int> idsWells, CancellationToken token)
|
||||
public async Task<IEnumerable<TDto>?> GetByIdWellAsync(IEnumerable<int> idsWells, CancellationToken token)
|
||||
{
|
||||
if (!idsWells.Any())
|
||||
return Enumerable.Empty<TDto>();
|
||||
|
@ -12,6 +12,7 @@ using AsbCloudApp.Data.DailyReport;
|
||||
using AsbCloudApp.Requests;
|
||||
using AsbCloudInfrastructure.Services.DetectOperations;
|
||||
using AsbCloudApp.Data.DetectedOperation;
|
||||
using AsbCloudApp.Exceptions;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services.DailyReport
|
||||
{
|
||||
@ -34,7 +35,8 @@ namespace AsbCloudInfrastructure.Services.DailyReport
|
||||
{
|
||||
var well = wellService.GetOrDefault(idWell);
|
||||
if (well is null || well.Timezone is null)
|
||||
return null;
|
||||
throw new ArgumentInvalidException("idWell doesn`t exist", nameof(idWell));
|
||||
|
||||
var query = db.DailyReports.Where(r => r.IdWell == idWell);
|
||||
|
||||
DateTimeOffset ExtractDate(DateTime dateTime)
|
||||
@ -65,9 +67,8 @@ namespace AsbCloudInfrastructure.Services.DailyReport
|
||||
public async Task<DailyReportDto> GetOrGenerateAsync(int idWell, DateTime date, CancellationToken token)
|
||||
{
|
||||
var dateOnly = DateTime.SpecifyKind(date.Date, DateTimeKind.Utc);
|
||||
var dailyReportDto = await GetAsync(idWell, dateOnly, token);
|
||||
if (dailyReportDto is null)
|
||||
dailyReportDto = await MakeDefaultDailyReportAsync(idWell, dateOnly, token);
|
||||
var dailyReportDto = await GetOrDefaultAsync(idWell, dateOnly, token);
|
||||
dailyReportDto ??= await MakeDefaultDailyReportAsync(idWell, dateOnly, token);
|
||||
return dailyReportDto;
|
||||
}
|
||||
|
||||
@ -105,7 +106,7 @@ namespace AsbCloudInfrastructure.Services.DailyReport
|
||||
|
||||
public async Task<Stream?> MakeReportAsync(int idWell, DateTime date, CancellationToken token = default)
|
||||
{
|
||||
var dailyReportDto = await GetAsync(idWell, date, token);
|
||||
var dailyReportDto = await GetOrDefaultAsync(idWell, date, token);
|
||||
if (dailyReportDto is null)
|
||||
return null;
|
||||
|
||||
@ -113,7 +114,7 @@ namespace AsbCloudInfrastructure.Services.DailyReport
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
private async Task<DailyReportDto?> GetAsync(int idWell, DateTime date, CancellationToken token)
|
||||
private async Task<DailyReportDto?> GetOrDefaultAsync(int idWell, DateTime date, CancellationToken token)
|
||||
{
|
||||
var dateOffset = date.Date;
|
||||
var entity = await db.DailyReports
|
||||
|
@ -27,7 +27,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
public async Task<DrillParamsDto?> GetDefaultDrillParamsAsync(int idWell,
|
||||
double startDepth, double endDepth, CancellationToken token = default)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
|
||||
if (idTelemetry is null)
|
||||
return null;
|
||||
|
@ -15,15 +15,15 @@ namespace AsbCloudInfrastructure.Services
|
||||
: base(context)
|
||||
{
|
||||
}
|
||||
|
||||
//TODO: Перенести в сервис "дело скважины"
|
||||
public async Task<IEnumerable<FileCategoryDto>> GetWellCaseCategoriesAsync(CancellationToken token)
|
||||
{
|
||||
var cache = await GetCacheAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
var dtos = cache
|
||||
.Where(kv => kv.Key >= 10000)
|
||||
.Where(kv => kv.Key <= 20000)
|
||||
.Select(kv => kv.Value);
|
||||
.Where(f => f.Id >= 10000)
|
||||
.Where(f => f.Id <= 20000);
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
|
||||
public DatesRangeDto GetDatesRangeOrDefault(int idWell)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
if (idTelemetry is null)
|
||||
return null;
|
||||
var range = telemetryService.GetDatesRange((int)idTelemetry);
|
||||
|
@ -34,7 +34,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
int take = 32,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
if (idTelemetry is null)
|
||||
return null;
|
||||
|
||||
|
@ -10,26 +10,31 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
namespace AsbCloudInfrastructure.Services.SAUB
|
||||
{
|
||||
public abstract class TelemetryDataBaseService<TDto, TModel> : ITelemetryDataService<TDto>
|
||||
public abstract class TelemetryDataBaseService<TDto, TEntity> : ITelemetryDataService<TDto>
|
||||
where TDto : AsbCloudApp.Data.ITelemetryData
|
||||
where TModel : class, ITelemetryData
|
||||
where TEntity : class, ITelemetryData
|
||||
{
|
||||
protected readonly IAsbCloudDbContext db;
|
||||
private readonly ITelemetryService telemetryService;
|
||||
protected readonly CacheTable<TelemetryUser> cacheTelemetryUsers;
|
||||
private readonly TelemetryDataCache<TDto> telemetryDataCache;
|
||||
|
||||
public TelemetryDataBaseService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TDto> telemetryDataCache,
|
||||
CacheDb cacheDb)
|
||||
{
|
||||
this.db = db;
|
||||
this.telemetryService = telemetryService;
|
||||
cacheTelemetryUsers = cacheDb.GetCachedTable<TelemetryUser>((AsbCloudDbContext)db);
|
||||
this.telemetryDataCache = telemetryDataCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<int> UpdateDataAsync(string uid, IEnumerable<TDto> dtos, CancellationToken token = default)
|
||||
{
|
||||
if (dtos == default || !dtos.Any())
|
||||
@ -53,6 +58,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
var idTelemetry = telemetryService.GetOrCreateTelemetryIdByUid(uid);
|
||||
var timezone = telemetryService.GetTimezone(idTelemetry);
|
||||
|
||||
telemetryDataCache.AddRange(idTelemetry, dtos);
|
||||
|
||||
var entities = dtosList.Select(dto =>
|
||||
{
|
||||
var entity = Convert(dto, timezone.Hours);
|
||||
@ -63,7 +70,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
var entityMaxDate = entities.Max(e => e.DateTime);
|
||||
telemetryService.TelemetryTracker.SaveRequestDate(uid, entityMaxDate);
|
||||
|
||||
var dbset = db.Set<TModel>();
|
||||
var dbset = db.Set<TEntity>();
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
@ -83,11 +90,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<TDto>> GetAsync(int idWell,
|
||||
|
||||
// TODO: It shouldn`t be nullable. Throw exceptions instead and return empty.
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<IEnumerable<TDto>?> GetOrDefaultAsync(int idWell,
|
||||
DateTime dateBegin = default, double intervalSec = 600d,
|
||||
int approxPointsCount = 1024, CancellationToken token = default)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell) ?? -1;
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell) ?? -1;
|
||||
if (idTelemetry == -1)
|
||||
return null;
|
||||
|
||||
@ -110,8 +120,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
if (dateBeginUtc == default)
|
||||
dateBeginUtc = DateTime.UtcNow.AddSeconds(-intervalSec);
|
||||
|
||||
var cacheData = telemetryDataCache.GetOrDefault(idTelemetry, dateBeginUtc.ToRemoteDateTime(timezone.Hours), intervalSec, approxPointsCount);
|
||||
if (cacheData is not null)
|
||||
return cacheData;
|
||||
|
||||
var dateEnd = dateBeginUtc.AddSeconds(intervalSec);
|
||||
var dbSet = db.Set<TModel>();
|
||||
var dbSet = db.Set<TEntity>();
|
||||
|
||||
var query = dbSet
|
||||
.Where(d => d.IdTelemetry == idTelemetry
|
||||
@ -144,9 +158,10 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public abstract TDto Convert(TModel src, double timezoneOffset);
|
||||
public abstract TDto Convert(TEntity src, double timezoneOffset);
|
||||
|
||||
public abstract TModel Convert(TDto src, double timezoneOffset);
|
||||
public abstract TEntity Convert(TDto src, double timezoneOffset);
|
||||
|
||||
}
|
||||
}
|
||||
#nullable disable
|
184
AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
Normal file
184
AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs
Normal file
@ -0,0 +1,184 @@
|
||||
using AsbCloudDb.Model;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Mapster;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
#nullable enable
|
||||
namespace AsbCloudInfrastructure.Services.SAUB
|
||||
{
|
||||
public class TelemetryDataCache<TDto>
|
||||
where TDto : AsbCloudApp.Data.ITelemetryData
|
||||
{
|
||||
private const int activeWellCapacity = 24 * 60 * 60;
|
||||
private const int doneWellCapacity = 65 * 60;
|
||||
|
||||
private readonly ConcurrentDictionary<int, CyclycArray<TDto>> caches;
|
||||
private bool isLoading = false;
|
||||
|
||||
private TelemetryDataCache()
|
||||
{
|
||||
caches = new();
|
||||
}
|
||||
|
||||
private static TelemetryDataCache<TDto>? instance;
|
||||
|
||||
//TODO: Move initialize fromDB to bacground service task
|
||||
public static TelemetryDataCache<TDto> GetInstance<TEntity>(IConfiguration configuration)
|
||||
where TEntity : class, ITelemetryData
|
||||
{
|
||||
if (instance is null)
|
||||
{
|
||||
instance = new TelemetryDataCache<TDto>();
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
using var db = MakeContext(configuration);
|
||||
instance.InitializeCacheFromDB<TEntity>(db);
|
||||
});
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public static TelemetryDataCache<TDto> GetInstance<TEntity>(IAsbCloudDbContext db, out Task initializationTask)
|
||||
where TEntity : class, ITelemetryData
|
||||
{
|
||||
if (instance is null)
|
||||
{
|
||||
instance = new TelemetryDataCache<TDto>();
|
||||
initializationTask = Task.Run(() =>
|
||||
{
|
||||
instance.InitializeCacheFromDB<TEntity>(db);
|
||||
});
|
||||
}
|
||||
else
|
||||
initializationTask = Task.CompletedTask;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить новые элементы в кеш
|
||||
/// </summary>
|
||||
/// <param name="idTelemetry"></param>
|
||||
/// <param name="range"></param>
|
||||
public void AddRange(int idTelemetry, IEnumerable<TDto> range)
|
||||
{
|
||||
CyclycArray<TDto> cacheItem;
|
||||
if (isLoading)
|
||||
{
|
||||
if (caches.TryGetValue(idTelemetry, out CyclycArray<TDto>? localCacheItem))
|
||||
cacheItem = localCacheItem;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
cacheItem = caches.GetOrAdd(idTelemetry, _ => new CyclycArray<TDto>(activeWellCapacity));
|
||||
}
|
||||
|
||||
var newItems = range
|
||||
.OrderBy(i => i.DateTime);
|
||||
foreach (var item in newItems)
|
||||
item.IdTelemetry = idTelemetry;
|
||||
cacheItem.AddRange(newItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить данные из кеша. <br/>
|
||||
/// Если dateBegin меньше минимального элемента в кеше, то вернется null.
|
||||
/// Даже если intervalSec частично перекрыт данными из кеша.
|
||||
/// </summary>
|
||||
/// <param name="idTelemetry"></param>
|
||||
/// <param name="dateBegin"></param>
|
||||
/// <param name="intervalSec"></param>
|
||||
/// <param name="approxPointsCount">кол-во элементов до которых эти данные прореживаются</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600d, int approxPointsCount = 1024)
|
||||
{
|
||||
if(!caches.TryGetValue(idTelemetry, out CyclycArray<TDto>? cacheItem))
|
||||
return null;
|
||||
|
||||
if (cacheItem is null || !cacheItem.Any() || cacheItem[0].DateTime > dateBegin)
|
||||
return null;
|
||||
|
||||
var dateEnd = dateBegin.AddSeconds(intervalSec);
|
||||
var items = cacheItem
|
||||
.Where(i => i.DateTime >= dateBegin && i.DateTime <= dateEnd);
|
||||
|
||||
var ratio = items.Count() / approxPointsCount;
|
||||
if (ratio > 1)
|
||||
items = items
|
||||
.Where((_, index) => index % ratio == 0);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private void InitializeCacheFromDB<TEntity>(IAsbCloudDbContext db)
|
||||
where TEntity : class, ITelemetryData
|
||||
{
|
||||
if (isLoading)
|
||||
throw new Exception("Multiple cache loading detected.");
|
||||
|
||||
isLoading = true;
|
||||
Well[] wells = Array.Empty<Well>();
|
||||
wells = db.Set<Well>()
|
||||
.Include(well => well.Telemetry)
|
||||
.Include(well => well.Cluster)
|
||||
.Where(well => well.IdTelemetry != null)
|
||||
.ToArray();
|
||||
|
||||
foreach (Well well in wells)
|
||||
{
|
||||
var capacity = well.IdState == 1
|
||||
? activeWellCapacity
|
||||
: doneWellCapacity;
|
||||
|
||||
var idTelemetry = well.IdTelemetry!.Value;
|
||||
var hoursOffset = well.Timezone.Hours;
|
||||
|
||||
IEnumerable<TDto> cacheItemData = GetCacheDataFromDb<TEntity>(db, idTelemetry, capacity, hoursOffset);
|
||||
var cacheItem = new CyclycArray<TDto>(capacity);
|
||||
cacheItem.AddRange(cacheItemData);
|
||||
caches.TryAdd(idTelemetry, cacheItem);
|
||||
|
||||
System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} loaded");
|
||||
}
|
||||
|
||||
System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> load complete");
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
private static IAsbCloudDbContext MakeContext(IConfiguration configuration)
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString("DefaultConnection");
|
||||
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
.UseNpgsql(connectionString)
|
||||
.Options;
|
||||
var db = new AsbCloudDbContext(options);
|
||||
return db;
|
||||
}
|
||||
|
||||
private static IEnumerable<TDto> GetCacheDataFromDb<TEntity>(IAsbCloudDbContext db, int idTelemetry, int capacity, double hoursOffset)
|
||||
where TEntity : class, ITelemetryData
|
||||
{
|
||||
var entities = db.Set<TEntity>()
|
||||
.Where(i => i.IdTelemetry == idTelemetry)
|
||||
.OrderByDescending(i => i.DateTime)
|
||||
.Take(capacity)
|
||||
.ToArray()
|
||||
.AsEnumerable()
|
||||
.Reverse();
|
||||
|
||||
var dtos = entities.Select(entity => {
|
||||
var dto = entity.Adapt<TDto>();
|
||||
dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset);
|
||||
return dto;
|
||||
});
|
||||
|
||||
return dtos;
|
||||
}
|
||||
}
|
||||
}
|
||||
#nullable disable
|
@ -16,8 +16,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
public TelemetryDataSaubService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache,
|
||||
CacheDb cacheDb)
|
||||
: base(db, telemetryService, cacheDb)
|
||||
: base(db, telemetryService, telemetryDataCache, cacheDb)
|
||||
{ }
|
||||
|
||||
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
|
||||
|
@ -13,8 +13,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
public TelemetryDataSpinService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache,
|
||||
CacheDb cacheDb)
|
||||
: base(db, telemetryService, cacheDb)
|
||||
: base(db, telemetryService, telemetryDataCache, cacheDb)
|
||||
{ }
|
||||
|
||||
public override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset)
|
||||
|
@ -37,22 +37,22 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
this.timezoneService = timezoneService;
|
||||
}
|
||||
|
||||
private Dictionary<int, Telemetry> GetTelemetryCache()
|
||||
private IEnumerable<Telemetry> GetTelemetryCache()
|
||||
{
|
||||
var cache = db.Telemetries
|
||||
.Include(t => t.Well)
|
||||
.FromCacheDictionary(telemetryCacheTag, telemetryCacheObsolescence, t => t.Id);
|
||||
.FromCache(telemetryCacheTag, telemetryCacheObsolescence);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void DropTelemetryCache()
|
||||
{
|
||||
db.Telemetries.DropCacheDictionary(telemetryCacheTag);
|
||||
db.Telemetries.DropCache(telemetryCacheTag);
|
||||
}
|
||||
|
||||
public DateTime GetLastTelemetryDate(int idTelemetry)
|
||||
{
|
||||
var telemetry = GetTelemetryCache().GetValueOrDefault(idTelemetry);
|
||||
var telemetry = GetTelemetryCache().FirstOrDefault(t => t.Id == idTelemetry);
|
||||
|
||||
if (telemetry is null)
|
||||
throw new Exception($"Telemetry id:{idTelemetry} does not exist");
|
||||
@ -65,7 +65,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
|
||||
public DatesRangeDto GetDatesRange(int idTelemetry)
|
||||
{
|
||||
var telemetry = GetTelemetryCache().GetValueOrDefault(idTelemetry);
|
||||
var telemetry = GetTelemetryCache().FirstOrDefault(t => t.Id == idTelemetry);
|
||||
if (telemetry is null)
|
||||
throw new Exception($"Telemetry id:{idTelemetry} does not exist");
|
||||
|
||||
@ -107,7 +107,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
|
||||
public SimpleTimezoneDto GetTimezone(int idTelemetry)
|
||||
{
|
||||
var telemetry = GetTelemetryCache().GetValueOrDefault(idTelemetry);
|
||||
var telemetry = GetTelemetryCache().FirstOrDefault(t => t.Id == idTelemetry);
|
||||
|
||||
if (telemetry is null)
|
||||
throw new Exception($"Telemetry id: {idTelemetry} does not exist.");
|
||||
@ -141,10 +141,10 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
throw new Exception($"Telemetry id: {idTelemetry} can't find timezone.");
|
||||
}
|
||||
|
||||
public int? GetIdTelemetryByIdWell(int idWell)
|
||||
public int? GetOrDefaultIdTelemetryByIdWell(int idWell)
|
||||
{
|
||||
var telemetry = GetTelemetryCache()
|
||||
.FirstOrDefault(t => t.Value.Well?.Id == idWell).Value;
|
||||
.FirstOrDefault(t => t.Well?.Id == idWell);
|
||||
return telemetry?.Id;
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
||||
|
||||
private Telemetry? GetOrDefaultTelemetryByUid(string uid)
|
||||
{
|
||||
var telemetry = GetTelemetryCache().FirstOrDefault(kv => kv.Value.RemoteUid == uid).Value;
|
||||
var telemetry = GetTelemetryCache().FirstOrDefault(t => t.RemoteUid == uid);
|
||||
return telemetry;
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
.Select(r => r.IdWell);
|
||||
|
||||
var wellsDtos = (await GetCacheAsync(token))
|
||||
.Where(kv => wellsIds.Contains(kv.Key))
|
||||
.Select(kv => kv.Value);
|
||||
.Where(w => wellsIds.Contains(w.Id));
|
||||
|
||||
return wellsDtos.ToList();
|
||||
}
|
||||
@ -95,7 +94,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
if (dto.IdState is < 0 or > 2)
|
||||
throw new ArgumentInvalidException("Текущее состояние работы скважины указано неправильно.", nameof(dto));
|
||||
|
||||
if (dto.Id != 0 && (await GetCacheAsync(token)).ContainsKey(dto.Id))
|
||||
if (dto.Id != 0 && (await GetCacheAsync(token)).Any(w => w.Id == dto.Id))
|
||||
throw new ArgumentInvalidException($"Нельзя повторно добавить скважину с id: {dto.Id}", nameof(dto));
|
||||
|
||||
var entity = Convert(dto);
|
||||
@ -194,7 +193,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
|
||||
var cache = await GetCacheAsync(token);
|
||||
|
||||
var clusterWellsIds = cache.Values
|
||||
var clusterWellsIds = cache
|
||||
.Where((w) => w.IdCluster == well.IdCluster)
|
||||
.Select(w => w.Id);
|
||||
|
||||
@ -247,7 +246,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
public async Task EnshureTimezonesIsSetAsync(CancellationToken token)
|
||||
{
|
||||
var cache = await GetCacheAsync(token);
|
||||
if (!cache.Values.Any(w => w.Timezone is null))
|
||||
if (!cache.Any(w => w.Timezone is null))
|
||||
return;
|
||||
|
||||
var defaultTimeZone = new SimpleTimezone
|
||||
|
93
AsbCloudWebApi.Tests/CacheTests/TelemetryDataCacheTest.cs
Normal file
93
AsbCloudWebApi.Tests/CacheTests/TelemetryDataCacheTest.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace AsbCloudWebApi.Tests.CacheTests
|
||||
{
|
||||
public class TelemetryDataCacheTest
|
||||
{
|
||||
private const int IdTelemetryOk = 10;
|
||||
private static readonly DateTime baseDate = DateTime.Now;
|
||||
private static readonly SimpleTimezone timezone = new() { Hours = TimeZoneInfo.Local.BaseUtcOffset.TotalHours };
|
||||
|
||||
private static readonly Well[] wellData = new Well[]{
|
||||
new(){ Id = 1, IdTelemetry = IdTelemetryOk, IdState = 1, Caption = "", Timezone = timezone}
|
||||
};
|
||||
private static readonly TTelemetryData[] telemetryData = new TTelemetryData[]{
|
||||
new ( IdTelemetryOk, baseDate.AddSeconds(1), timezone.Hours ),
|
||||
new ( IdTelemetryOk, baseDate.AddSeconds(2), timezone.Hours ),
|
||||
new ( IdTelemetryOk, baseDate.AddSeconds(3), timezone.Hours ),
|
||||
new ( IdTelemetryOk, baseDate.AddSeconds(4), timezone.Hours ),
|
||||
};
|
||||
private readonly Mock<IAsbCloudDbContext> dbMock;
|
||||
|
||||
private TelemetryDataCache<TTelemetryData> cacheTest;
|
||||
|
||||
public class TTelemetryData : ITelemetryData, AsbCloudApp.Data.ITelemetryData
|
||||
{
|
||||
private DateTimeOffset _dateTime;
|
||||
|
||||
public TTelemetryData(int idTelemetry, DateTime dateTime, double hoursOffset)
|
||||
{
|
||||
IdTelemetry = idTelemetry;
|
||||
DateTime = dateTime;
|
||||
var offset = TimeSpan.FromHours(hoursOffset);
|
||||
var dateTimeUTC = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
|
||||
_dateTime = new DateTimeOffset(dateTimeUTC, offset)
|
||||
.ToUniversalTime();
|
||||
}
|
||||
|
||||
public int IdTelemetry { get; set; }
|
||||
public DateTime DateTime { get; set; }
|
||||
DateTimeOffset ITelemetryData.DateTime { get => _dateTime; set => _dateTime = value; }
|
||||
}
|
||||
|
||||
public TelemetryDataCacheTest()
|
||||
{
|
||||
dbMock = new Mock<IAsbCloudDbContext>();
|
||||
dbMock
|
||||
.AddDbSetMock(wellData)
|
||||
.AddDbSetMock(telemetryData);
|
||||
|
||||
cacheTest = TelemetryDataCache<TTelemetryData>.GetInstance<TTelemetryData>(dbMock.Object, out Task cacheInitialization);
|
||||
cacheInitialization.Wait();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_existing_cache_returns_some()
|
||||
{
|
||||
var startDate = baseDate.AddSeconds(3);
|
||||
var data = cacheTest.GetOrDefault(IdTelemetryOk, startDate, 600, 600);
|
||||
Assert.NotNull(data);
|
||||
Assert.NotEmpty(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_non_existing_cache_returns_null()
|
||||
{
|
||||
var startDate = baseDate.AddSeconds(-1);
|
||||
var data = cacheTest.GetOrDefault(IdTelemetryOk, startDate, 600, 600);
|
||||
Assert.Null(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_new_data_should_replace_IdTelemetry()
|
||||
{
|
||||
var startDate = baseDate.AddSeconds(4);
|
||||
var newTelemetryData = new TTelemetryData[]{
|
||||
new ( 0, startDate, timezone.Hours),
|
||||
new ( 0, startDate.AddSeconds(1), timezone.Hours),
|
||||
};
|
||||
cacheTest.AddRange(IdTelemetryOk, newTelemetryData);
|
||||
var data = cacheTest.GetOrDefault(IdTelemetryOk, startDate, 600, 600);
|
||||
|
||||
Assert.NotNull(data);
|
||||
Assert.True(data?.Count() > 2);
|
||||
Assert.All(data!, p => Assert.Equal(IdTelemetryOk, p.IdTelemetry));
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,42 @@ namespace AsbCloudWebApi.Tests.Middlware
|
||||
{
|
||||
public class UserConnectionsLimitMiddlwareTest
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
const int iterations2Block = 8;
|
||||
|
||||
//Данные в тестовой БД
|
||||
//select
|
||||
// tw.id,
|
||||
// t_stat.minDate,
|
||||
// t_stat.maxDate
|
||||
//from(
|
||||
// select
|
||||
|
||||
// id_telemetry,
|
||||
// count(1) as count,
|
||||
// min("date") as minDate,
|
||||
// max("date") as maxDate
|
||||
|
||||
// from t_telemetry_data_saub
|
||||
|
||||
// group by id_telemetry
|
||||
//) as t_stat
|
||||
//join t_well tw on tw.id_telemetry = t_stat.id_telemetry
|
||||
//where tw is not null
|
||||
//order by t_stat.count
|
||||
//limit 10;
|
||||
private readonly (int, DateTime, DateTime)[] wells = new[]
|
||||
{
|
||||
(191, new DateTime(2022, 09, 01, 21, 43, 00, DateTimeKind.Utc), new DateTime(2022, 09, 04, 07, 37, 31, DateTimeKind.Utc)),
|
||||
(3 , new DateTime(2021, 09, 16, 06, 13, 33, DateTimeKind.Utc), new DateTime(2021, 09, 20, 00, 29, 28, DateTimeKind.Utc)),
|
||||
(199, new DateTime(2022, 09, 15, 11, 27, 18, DateTimeKind.Utc), new DateTime(2022, 09, 20, 14, 00, 23, DateTimeKind.Utc)),
|
||||
(6 , new DateTime(2021, 09, 20, 00, 35, 03, DateTimeKind.Utc), new DateTime(2021, 09, 25, 06, 46, 17, DateTimeKind.Utc)),
|
||||
(41 , new DateTime(2021, 12, 10, 00, 59, 52, DateTimeKind.Utc), new DateTime(2022, 10, 31, 15, 29, 24, DateTimeKind.Utc)),
|
||||
(100, new DateTime(2022, 04, 24, 03, 04, 05, DateTimeKind.Utc), new DateTime(2022, 04, 29, 11, 38, 36, DateTimeKind.Utc)),
|
||||
(154, new DateTime(2022, 03, 28, 10, 09, 14, DateTimeKind.Utc), new DateTime(2022, 06, 14, 15, 01, 12, DateTimeKind.Utc)),
|
||||
(5 , new DateTime(2021, 09, 25, 08, 09, 37, DateTimeKind.Utc), new DateTime(2021, 10, 01, 14, 39, 51, DateTimeKind.Utc)),
|
||||
(1 , new DateTime(2021, 09, 10, 01, 32, 42, DateTimeKind.Utc), new DateTime(2021, 09, 18, 00, 35, 22, DateTimeKind.Utc)),
|
||||
(112, new DateTime(2022, 04, 20, 16, 47, 51, DateTimeKind.Utc), new DateTime(2022, 04, 28, 15, 04, 33, DateTimeKind.Utc)),
|
||||
};
|
||||
|
||||
public UserConnectionsLimitMiddlwareTest()
|
||||
{
|
||||
@ -20,66 +55,53 @@ namespace AsbCloudWebApi.Tests.Middlware
|
||||
})
|
||||
.Build();
|
||||
host.Start();
|
||||
httpClient = new ();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2NjY1ODY2MjAsImV4cCI6MTY5ODE0NDIyMCwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zqBdR4nYB87-Xyzv025waasN47i43c9FJ23RfzIvUsM");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Send_n_requests_and_get_blocked()
|
||||
{
|
||||
//Данные в тестовой БД
|
||||
//select
|
||||
// tw.id,
|
||||
// t_stat.minDate,
|
||||
// t_stat.maxDate
|
||||
//from(
|
||||
// select
|
||||
|
||||
// id_telemetry,
|
||||
// count(1) as count,
|
||||
// min("date") as minDate,
|
||||
// max("date") as maxDate
|
||||
|
||||
// from t_telemetry_data_saub
|
||||
|
||||
// group by id_telemetry
|
||||
//) as t_stat
|
||||
//join t_well tw on tw.id_telemetry = t_stat.id_telemetry
|
||||
//where tw is not null
|
||||
//order by t_stat.count
|
||||
//limit 10;
|
||||
|
||||
var wells = new []
|
||||
{
|
||||
(191, new DateTime(2022, 09, 01, 21, 43, 00, DateTimeKind.Utc), new DateTime(2022, 09, 04, 07, 37, 31, DateTimeKind.Utc)),
|
||||
(3 , new DateTime(2021, 09, 16, 06, 13, 33, DateTimeKind.Utc), new DateTime(2021, 09, 20, 00, 29, 28, DateTimeKind.Utc)),
|
||||
(199, new DateTime(2022, 09, 15, 11, 27, 18, DateTimeKind.Utc), new DateTime(2022, 09, 20, 14, 00, 23, DateTimeKind.Utc)),
|
||||
(6 , new DateTime(2021, 09, 20, 00, 35, 03, DateTimeKind.Utc), new DateTime(2021, 09, 25, 06, 46, 17, DateTimeKind.Utc)),
|
||||
(41 , new DateTime(2021, 12, 10, 00, 59, 52, DateTimeKind.Utc), new DateTime(2022, 10, 31, 15, 29, 24, DateTimeKind.Utc)),
|
||||
(100, new DateTime(2022, 04, 24, 03, 04, 05, DateTimeKind.Utc), new DateTime(2022, 04, 29, 11, 38, 36, DateTimeKind.Utc)),
|
||||
(154, new DateTime(2022, 03, 28, 10, 09, 14, DateTimeKind.Utc), new DateTime(2022, 06, 14, 15, 01, 12, DateTimeKind.Utc)),
|
||||
(5 , new DateTime(2021, 09, 25, 08, 09, 37, DateTimeKind.Utc), new DateTime(2021, 10, 01, 14, 39, 51, DateTimeKind.Utc)),
|
||||
(1 , new DateTime(2021, 09, 10, 01, 32, 42, DateTimeKind.Utc), new DateTime(2021, 09, 18, 00, 35, 22, DateTimeKind.Utc)),
|
||||
(112, new DateTime(2022, 04, 20, 16, 47, 51, DateTimeKind.Utc), new DateTime(2022, 04, 28, 15, 04, 33, DateTimeKind.Utc)),
|
||||
};
|
||||
|
||||
var i = 0;
|
||||
for (; i < 5; i++)
|
||||
for (; i < iterations2Block; i++)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var well = wells[i];
|
||||
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
|
||||
var response = await httpClient.GetAsync(url);
|
||||
var response = await MakeHttpClient().GetAsync(url);
|
||||
//await response.Content.ReadAsStringAsync();
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
|
||||
var well = wells[i];
|
||||
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
|
||||
var response = await httpClient.GetAsync(url);
|
||||
var response = await MakeHttpClient().GetAsync(url);
|
||||
Assert.Equal(System.Net.HttpStatusCode.TooManyRequests, response.StatusCode);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task Send_n_requests_and_get_blocked_then_restored()
|
||||
{
|
||||
var i = 0;
|
||||
var tasks = new Task[iterations2Block];
|
||||
for (; i < iterations2Block; i++)
|
||||
tasks[i] = Task.Run(async () =>
|
||||
{
|
||||
var well = wells[i];
|
||||
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
|
||||
var response = await MakeHttpClient().GetAsync(url);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
|
||||
var well = wells[i];
|
||||
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
|
||||
var response = await MakeHttpClient().GetAsync(url);
|
||||
Assert.Equal(System.Net.HttpStatusCode.TooManyRequests, response.StatusCode);
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
response = await MakeHttpClient().GetAsync(url);
|
||||
Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
private static string MakeUrl(int idWell, DateTime dateBegin, DateTime dateEnd)
|
||||
{
|
||||
var interval = (dateEnd - dateBegin).TotalSeconds;
|
||||
@ -87,5 +109,13 @@ namespace AsbCloudWebApi.Tests.Middlware
|
||||
var url = $"http://127.0.0.1:5000/api/TelemetryDataSaub/{idWell}?begin={dateBeginString}&intervalSec={interval}&approxPointsCount={interval}";
|
||||
return url;
|
||||
}
|
||||
|
||||
private static HttpClient MakeHttpClient()
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2NjY1ODY2MjAsImV4cCI6MTY5ODE0NDIyMCwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zqBdR4nYB87-Xyzv025waasN47i43c9FJ23RfzIvUsM");
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public class ClusterServiceTest
|
||||
|
||||
public ClusterServiceTest()
|
||||
{
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
wellService = new Mock<IWellService>();
|
||||
context.Deposits.RemoveRange(context.Deposits);
|
||||
context.Clusters.RemoveRange(context.Clusters);
|
||||
|
@ -21,7 +21,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
|
||||
protected override ICrudService<DepositDto> MakeService()
|
||||
{
|
||||
var dbContext = TestHelpter.MakeTestContext();
|
||||
var dbContext = TestHelpter.MakeRealTestContext();
|
||||
return new CrudCacheServiceBase<DepositDto, Deposit>(dbContext);
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
|
||||
public DetectedOperationServiceTest()
|
||||
{
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
context.SaveChanges();
|
||||
|
||||
context.Telemetries.Add(telemetry);
|
||||
|
@ -90,7 +90,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
{
|
||||
AsbCloudInfrastructure.DependencyInjection.MapsterSetup();
|
||||
|
||||
db = TestHelpter.MakeTestContext();
|
||||
db = TestHelpter.MakeRealTestContext();
|
||||
db.Wells.AddRange(wells);
|
||||
db.Companies.AddRange(companies);
|
||||
db.SaveChanges();
|
||||
|
@ -19,7 +19,7 @@ public class EventServiceTest
|
||||
|
||||
public EventServiceTest()
|
||||
{
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
cacheDb = new CacheDb();
|
||||
var telemetryTracker = new Mock<ITelemetryTracker>();
|
||||
var imezoneServiceMock = new Mock<ITimezoneService>();
|
||||
|
@ -17,7 +17,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
public FileCategoryServiceTest()
|
||||
{
|
||||
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
context.SaveChanges();
|
||||
service = new FileCategoryService(context);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
|
||||
public ScheduleServiceTest()
|
||||
{
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
context.SaveChanges();
|
||||
|
||||
context.Deposits.Add(deposit);
|
||||
|
@ -40,7 +40,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
timezoneService.Setup(s => s.GetByCoordinates(It.IsAny<double>(), It.IsAny<double>()))
|
||||
.Returns(timezone);
|
||||
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
cacheDb = new CacheDb();
|
||||
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object);
|
||||
|
||||
@ -64,7 +64,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
public async Task UpdateDataAsync()
|
||||
{
|
||||
// Arrange
|
||||
var telemetryDataSaubService = new TelemetryDataSaubService(context, telemetryService, cacheDb);
|
||||
var telemetryDataSaubService = new TelemetryDataSaubService(context, telemetryService, null, cacheDb);
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(timezone.Hours)).DateTime;
|
||||
var tuser = "Завулон";
|
||||
|
@ -43,7 +43,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
|
||||
public WellFinalDocumentsServiceTest()
|
||||
{
|
||||
context = TestHelpter.MakeTestContext();
|
||||
context = TestHelpter.MakeRealTestContext();
|
||||
context.SaveChanges();
|
||||
|
||||
fileServiceMock = new Mock<FileService>();
|
||||
|
@ -1,7 +1,8 @@
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudDb.Model;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AsbCloudWebApi.Tests
|
||||
{
|
||||
@ -9,7 +10,7 @@ namespace AsbCloudWebApi.Tests
|
||||
{
|
||||
// Попробовать когда-нибудь https://github.com/MichalJankowskii/Moq.EntityFrameworkCore
|
||||
|
||||
public static AsbCloudDbContext MakeTestContext()
|
||||
public static AsbCloudDbContext MakeRealTestContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
//.UseInMemoryDatabase(System.Guid.NewGuid().ToString())
|
||||
@ -22,5 +23,38 @@ namespace AsbCloudWebApi.Tests
|
||||
context.Database.EnsureCreated();
|
||||
return context;
|
||||
}
|
||||
|
||||
public static Mock<IAsbCloudDbContext> AddDbSetMock<T>(this Mock<IAsbCloudDbContext> contextMock, IEnumerable<T> dbSetData)
|
||||
where T : class
|
||||
{
|
||||
var dbSetMock = MakeDbSetMock(dbSetData);
|
||||
contextMock.Setup(o => o.Set<T>())
|
||||
.Returns(() => dbSetMock.Object);
|
||||
|
||||
return contextMock;
|
||||
}
|
||||
|
||||
public static Mock<DbSet<T>> MakeDbSetMock<T>()
|
||||
where T : class
|
||||
{
|
||||
var dbSetData = new List<T>();
|
||||
return MakeDbSetMock(dbSetData);
|
||||
}
|
||||
|
||||
public static Mock<DbSet<T>> MakeDbSetMock<T>(IEnumerable<T> dbSetData)
|
||||
where T : class
|
||||
{
|
||||
var dbSetDataQueriable = dbSetData
|
||||
.ToList()
|
||||
.AsQueryable();
|
||||
|
||||
Mock<DbSet<T>> dbSetMock = new();
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.Provider).Returns(() => dbSetDataQueriable.Provider);
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.Expression).Returns(() => dbSetDataQueriable.Expression);
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.ElementType).Returns(() => dbSetDataQueriable.ElementType);
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.GetEnumerator()).Returns(() => dbSetDataQueriable.GetEnumerator());
|
||||
|
||||
return dbSetMock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
/// <param name="idWell">id скважины</param>
|
||||
/// <param name="idCategory">id категории файла</param>
|
||||
/// <param name="files">Коллекция файлов</param>
|
||||
/// <param name="userService">dependency</param>
|
||||
/// <param name="userRepository">dependency</param>
|
||||
/// <param name="token"> Токен отмены задачи </param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
@ -100,7 +100,6 @@ namespace AsbCloudWebApi.Controllers
|
||||
/// <summary>
|
||||
/// Возвращает файл с диска на сервере
|
||||
/// </summary>
|
||||
/// <param name="idWell">id скважины</param>
|
||||
/// <param name="idFile">id запрашиваемого файла</param>
|
||||
/// <param name="token"> Токен отмены задачи </param>
|
||||
/// <returns>Запрашиваемый файл</returns>
|
||||
@ -108,7 +107,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
[Route("{idFile}")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> GetFileAsync([FromRoute] int idWell,
|
||||
public async Task<IActionResult> GetFileAsync(
|
||||
int idFile, CancellationToken token = default)
|
||||
{
|
||||
int? idCompany = User.GetCompanyId();
|
||||
@ -135,7 +134,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
/// </summary>
|
||||
/// <param name="idWell">id скважины</param>
|
||||
/// <param name="idFile">id запрашиваемого файла</param>
|
||||
/// <param name="userService">dependency</param>
|
||||
/// <param name="userRepository">dependency</param>
|
||||
/// <param name="token">Токен отмены задачи </param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("{idFile}")]
|
||||
|
@ -86,7 +86,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
|
||||
if (!isCompanyOwnsWell)
|
||||
return Forbid();
|
||||
|
||||
var content = await telemetryDataService.GetAsync(idWell, begin,
|
||||
var content = await telemetryDataService.GetOrDefaultAsync(idWell, begin,
|
||||
intervalSec, approxPointsCount, token).ConfigureAwait(false);
|
||||
|
||||
return Ok(content);
|
||||
|
@ -89,7 +89,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
|
||||
if (!isCompanyOwnsWell)
|
||||
return Forbid();
|
||||
|
||||
int? idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
int? idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
if (idTelemetry is null)
|
||||
return NoContent();
|
||||
|
||||
|
@ -72,7 +72,7 @@ namespace AsbCloudWebApi.Controllers.WITS
|
||||
[FromServices] IWitsRecordRepository<TDto> witsRecordRepository,
|
||||
CancellationToken token)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
if (idTelemetry is null)
|
||||
return NoContent();
|
||||
var dtos = await witsRecordRepository.GetLastAsync((int)idTelemetry, token);
|
||||
@ -96,7 +96,7 @@ namespace AsbCloudWebApi.Controllers.WITS
|
||||
[FromServices] IWitsRecordRepository<TDto> witsRecordRepository,
|
||||
CancellationToken token)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
if (idTelemetry is null)
|
||||
return NoContent();
|
||||
var dtos = await witsRecordRepository.GetAsync((int)idTelemetry, begin, end, token);
|
||||
@ -118,7 +118,7 @@ namespace AsbCloudWebApi.Controllers.WITS
|
||||
[FromServices] IWitsRecordRepository<TDto> witsRecordRepository,
|
||||
CancellationToken token)
|
||||
{
|
||||
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);
|
||||
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
|
||||
if (idTelemetry is null)
|
||||
return NoContent();
|
||||
var dtos = await witsRecordRepository.GetStatAsync((int)idTelemetry, token);
|
||||
|
@ -14,25 +14,26 @@ namespace AsbCloudWebApi.Middlewares
|
||||
/// </summary>
|
||||
class UserConnectionsLimitMiddlware
|
||||
{
|
||||
private readonly RequestDelegate next;
|
||||
private readonly int parallelRequestsToController;
|
||||
private readonly byte[] body;
|
||||
private readonly RequestDelegate next;
|
||||
private readonly byte[] responseBody;
|
||||
private readonly ConcurrentDictionary<int, ConcurrentDictionary<string, int>> stat = new ();
|
||||
private readonly IEnumerable<string>? controllerNames;
|
||||
|
||||
public UserConnectionsLimitMiddlware(RequestDelegate next, IConfiguration configuration)
|
||||
{
|
||||
const int parallelRequestsToControllerDefault = 8;
|
||||
this.next = next;
|
||||
|
||||
var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue<int>("parallelRequestsToController") ?? 5;
|
||||
var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue<int>("parallelRequestsToController") ?? parallelRequestsToControllerDefault;
|
||||
this.parallelRequestsToController = parallelRequestsToController > 0
|
||||
? parallelRequestsToController
|
||||
: 5;
|
||||
: parallelRequestsToControllerDefault;
|
||||
|
||||
controllerNames = configuration.GetSection("userLimits")?.GetValue<IEnumerable<string>>("controllerNames");
|
||||
|
||||
var bodyText = $"<html><head><title>Too Many Requests</title></head><body><h1>Too Many Requests</h1><p>I only allow {parallelRequestsToController} parallel requests per user. Try again soon.</p></body></html>";
|
||||
body = System.Text.Encoding.UTF8.GetBytes(bodyText);
|
||||
responseBody = System.Text.Encoding.UTF8.GetBytes(bodyText);
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, int idUser, string controllerName)
|
||||
@ -41,14 +42,16 @@ namespace AsbCloudWebApi.Middlewares
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var userStat = stat.GetOrAdd(idUser, idUser => new());
|
||||
var count = userStat.AddOrUpdate(controllerName, 1, (key, value) => value + 1);
|
||||
if(count < parallelRequestsToController)
|
||||
var count = userStat.AddOrUpdate(controllerName, 0, (k, v) => v);
|
||||
|
||||
if(count + 1 < parallelRequestsToController)
|
||||
{
|
||||
try
|
||||
{
|
||||
userStat[controllerName]++;
|
||||
await next(context);
|
||||
}
|
||||
finally
|
||||
@ -60,10 +63,9 @@ namespace AsbCloudWebApi.Middlewares
|
||||
{
|
||||
context.Response.Clear();
|
||||
context.Response.StatusCode = (int)System.Net.HttpStatusCode.TooManyRequests;
|
||||
|
||||
context.Response.Headers.RetryAfter = "1000";
|
||||
context.Response.Headers.ContentType = "text/html";
|
||||
await context.Response.BodyWriter.WriteAsync(body);
|
||||
await context.Response.BodyWriter.WriteAsync(responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,17 @@
|
||||
<!doctype html><html lang="ru"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="white"/><meta name="theme-color" media="(prefers-color-scheme: light)" content="white"/><meta name="theme-color" media="(prefers-color-scheme: dark)" content="black"/><meta name="description" content="Онлайн мониторинг процесса бурения в реальном времени в офисе заказчика"/><title>DDrilling</title><script defer="defer" src="/vendors.1920da1d.js"></script><script defer="defer" src="/main.101601f2.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="white" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black" />
|
||||
<meta name="description" content="Онлайн мониторинг процесса бурения в реальном времени в офисе заказчика" />
|
||||
<title>DDrilling</title>
|
||||
<script defer src="/runtime~main.5cad8e9a.js"></script><script defer src="/vendors.0c60e3f8.js"></script><script defer src="/main.3e1e062c.js"></script><link href="/main.e200693d.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,7 +2,9 @@
|
||||
using AsbCloudApp.Data.DailyReport;
|
||||
using AsbCloudDb;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure;
|
||||
using AsbCloudInfrastructure.Services.DailyReport;
|
||||
using AsbCloudInfrastructure.Services.SAUB;
|
||||
using ClosedXML.Excel;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -17,22 +19,20 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ConsoleApp1
|
||||
{
|
||||
|
||||
class Program
|
||||
{
|
||||
private static AsbCloudDbContext db = ServiceFactory.Context;
|
||||
|
||||
|
||||
// use ServiceFactory to make services
|
||||
static void Main(/*string[] args*/)
|
||||
{
|
||||
var h = new Hashtable();
|
||||
h.Add("name", 1);
|
||||
h.Add("name2", "66");
|
||||
var v = h["v"];
|
||||
DependencyInjection.MapsterSetup();
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
var s = System.Text.Json.JsonSerializer.Serialize(h);
|
||||
|
||||
Console.WriteLine($"total time: ms");
|
||||
|
||||
sw.Stop();
|
||||
Console.WriteLine($"total time: {sw.ElapsedMilliseconds} ms");
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user