Merge branch 'dev' into feature/7887519

This commit is contained in:
ai.astrakhantsev 2022-11-18 14:30:34 +05:00
commit 49cc9c2342
52 changed files with 787 additions and 656 deletions

View File

@ -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
View 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

View File

@ -10,7 +10,7 @@ namespace AsbCloudApp.Data.Subsystems
/// <summary>
/// Активная скважина
/// </summary>
public WellDto Well { get; set; }
public WellDto Well { get; set; } = null!;
/// <summary>
/// Наработки подсистемы АКБ
/// </summary>

View File

@ -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);

View File

@ -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;
}

View File

@ -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
}

View File

@ -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>

View File

@ -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);
@ -36,3 +36,4 @@ namespace AsbCloudApp.Services
Task<int> UpdateDataAsync(string uid, IEnumerable<TDto> dtos, CancellationToken token = default);
}
}
#nullable disable

View File

@ -55,7 +55,7 @@ namespace AsbCloudApp.Services
/// </summary>
/// <param name="idWell"></param>
/// <returns></returns>
int? GetIdTelemetryByIdWell(int idWell);
int? GetOrDefaultIdTelemetryByIdWell(int idWell);
/// <summary>
/// получить диапазон дат за которые есть данные

View File

@ -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

View File

@ -61,14 +61,6 @@ namespace AsbCloudDb.Model
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();
}
}
}

View File

@ -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;
}
}

View File

@ -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>
/// Время работы АПД

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>();

View File

@ -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&lt;<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>&gt;. С тегом 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&lt;<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>&gt;. С тегом 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&lt;<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>&gt;.
/// </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&lt;<typeparamref name="TKey"/>, <typeparamref name="TModel"/>&gt;.
/// </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&lt;<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>&gt;. С тегом 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&lt;<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>&gt;. С тегом 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&lt;<typeparamref name="TKey"/>, <typeparamref name="TEntity"/>&gt;.
/// </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&lt;<typeparamref name="TKey"/>, <typeparamref name="TModel"/>&gt;.
/// </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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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;

View File

@ -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>();

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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

View 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

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View 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));
}
}
}

View File

@ -9,24 +9,8 @@ namespace AsbCloudWebApi.Tests.Middlware
{
public class UserConnectionsLimitMiddlwareTest
{
private readonly HttpClient httpClient;
const int iterations2Block = 8;
public UserConnectionsLimitMiddlwareTest()
{
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.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,
@ -48,8 +32,7 @@ namespace AsbCloudWebApi.Tests.Middlware
//where tw is not null
//order by t_stat.count
//limit 10;
var wells = new []
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)),
@ -63,23 +46,62 @@ namespace AsbCloudWebApi.Tests.Middlware
(112, new DateTime(2022, 04, 20, 16, 47, 51, DateTimeKind.Utc), new DateTime(2022, 04, 28, 15, 04, 33, DateTimeKind.Utc)),
};
public UserConnectionsLimitMiddlwareTest()
{
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
host.Start();
}
[Fact]
public async Task Send_n_requests_and_get_blocked()
{
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;
}
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -107,7 +107,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public DetectedOperationServiceTest()
{
context = TestHelpter.MakeTestContext();
context = TestHelpter.MakeRealTestContext();
context.SaveChanges();
context.Telemetries.Add(telemetry);

View File

@ -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();

View File

@ -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>();

View File

@ -17,7 +17,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public FileCategoryServiceTest()
{
context = TestHelpter.MakeTestContext();
context = TestHelpter.MakeRealTestContext();
context.SaveChanges();
service = new FileCategoryService(context);
}

View File

@ -58,7 +58,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public ScheduleServiceTest()
{
context = TestHelpter.MakeTestContext();
context = TestHelpter.MakeRealTestContext();
context.SaveChanges();
context.Deposits.Add(deposit);

View File

@ -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 = "Завулон";

View File

@ -43,7 +43,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public WellFinalDocumentsServiceTest()
{
context = TestHelpter.MakeTestContext();
context = TestHelpter.MakeRealTestContext();
context.SaveChanges();
fileServiceMock = new Mock<FileService>();

View File

@ -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;
}
}
}

View File

@ -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}")]

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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)
@ -44,11 +45,13 @@ namespace AsbCloudWebApi.Middlewares
}
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);
}
}
}

View File

@ -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>

View File

@ -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,7 +19,6 @@ using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
private static AsbCloudDbContext db = ServiceFactory.Context;
@ -25,14 +26,13 @@ namespace ConsoleApp1
// 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();
}
}