Merge branch 'dev' into WellOperationTree/refact_migration

This commit is contained in:
ngfrolov 2022-12-06 16:29:53 +05:00
commit 81d3ef3146
72 changed files with 1159 additions and 870 deletions

View File

@ -29,7 +29,7 @@ namespace AsbCloudApp.Data
/// <summary> /// <summary>
/// Включенные роли /// Включенные роли
/// </summary> /// </summary>
public virtual ICollection<UserRoleDto> Roles { get; set; } public virtual IEnumerable<UserRoleDto> Roles { get; set; }
/// <summary> /// <summary>
/// Пользователи в роли /// Пользователи в роли

View File

@ -11,7 +11,7 @@ namespace AsbCloudApp.Repositories
/// <summary> /// <summary>
/// Сервис доступа к файлам /// Сервис доступа к файлам
/// </summary> /// </summary>
public interface IFileRepository : ICrudService<FileInfoDto> public interface IFileRepository : ICrudRepository<FileInfoDto>
{ {
/// <summary> /// <summary>
/// Получение файлов по скважине /// Получение файлов по скважине

View File

@ -7,7 +7,7 @@ namespace AsbCloudApp.Repositories
/// <summary> /// <summary>
/// Репозиторий пользователей /// Репозиторий пользователей
/// </summary> /// </summary>
public interface IUserRepository : ICrudService<UserExtendedDto> public interface IUserRepository : ICrudRepository<UserExtendedDto>
{ {
/// <summary> /// <summary>
/// Получить список всех прав пользователя (включая наследование групп) /// Получить список всех прав пользователя (включая наследование групп)

View File

@ -10,7 +10,7 @@ namespace AsbCloudApp.Repositories
/// <summary> /// <summary>
/// Разрешения на доступ к данным /// Разрешения на доступ к данным
/// </summary> /// </summary>
public interface IUserRoleRepository : ICrudService<UserRoleDto> public interface IUserRoleRepository : ICrudRepository<UserRoleDto>
{ {
/// <summary> /// <summary>
/// получить dto по названиям /// получить dto по названиям

View File

@ -73,7 +73,7 @@ namespace AsbCloudApp.Services
/// <param name="fileStream"></param> /// <param name="fileStream"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
public async Task<FileInfoDto?> SaveAsync(int idWell, int? idUser, int idCategory, public async Task<FileInfoDto> SaveAsync(int idWell, int? idUser, int idCategory,
string fileFullName, Stream fileStream, CancellationToken token) string fileFullName, Stream fileStream, CancellationToken token)
{ {
//save info to db //save info to db
@ -93,7 +93,7 @@ namespace AsbCloudApp.Services
string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, fileFullName, fileId); string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, fileFullName, fileId);
await fileStorageRepository.SaveFileAsync(filePath, fileStream, token); await fileStorageRepository.SaveFileAsync(filePath, fileStream, token);
return await GetOrDefaultAsync(fileId, token); return (await GetOrDefaultAsync(fileId, token))!;
} }
/// <summary> /// <summary>

View File

@ -1,50 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
/// <summary>
/// Сервис выстраивает очередь из фоновых задач. Ограничивает количество одновременно выполняющихся задач.
/// </summary>
public interface IBackgroundWorkerService
{
/// <summary>
/// Проверка, есть ли задача в очереди
/// </summary>
/// <param name="id">идентификатор задачи</param>
/// <returns></returns>
bool Contains(string id);
/// <summary>
/// Добавляет в очередь задач новую задачу
/// </summary>
/// <param name="id">идентификатор задачи</param>
/// <param name="func">делегат</param>
/// <returns>id задачи в очереди</returns>
string Enqueue(string id, Func<string, CancellationToken, Task> func);
/// <summary>
/// Добавляет в очередь задач новую задачу
/// </summary>
/// <param name="func"></param>
/// <returns></returns>
string Enqueue(Func<string, CancellationToken, Task> func);
/// <summary>
/// Добавляет в очередь задач новую задачу
/// </summary>
/// <param name="id">идентификатор задачи</param>
/// <param name="func"></param>
/// <param name="onError"></param>
/// <returns></returns>
string Enqueue(string id, Func<string, CancellationToken, Task> func, Func<string, Exception, CancellationToken, Task> onError);
/// <summary>
/// Пробуем удалить задачу по идентификатору
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
bool TryRemove(string id);
}
}

View File

@ -10,7 +10,7 @@ namespace AsbCloudApp.Services
/// Сервис получения, добавления, изменения, удаления данных /// Сервис получения, добавления, изменения, удаления данных
/// </summary> /// </summary>
/// <typeparam name="TDto"></typeparam> /// <typeparam name="TDto"></typeparam>
public interface ICrudService<TDto> public interface ICrudRepository<TDto>
where TDto : Data.IId where TDto : Data.IId
{ {
/// <summary> /// <summary>

View File

@ -8,7 +8,7 @@ namespace AsbCloudApp.Services
/// <summary> /// <summary>
/// The параметры бурения service. /// The параметры бурения service.
/// </summary> /// </summary>
public interface IDrillParamsService : ICrudService<DrillParamsDto> public interface IDrillParamsService : ICrudRepository<DrillParamsDto>
{ {
/// <summary> /// <summary>
/// default параметры бурения /// default параметры бурения

View File

@ -19,6 +19,12 @@ namespace AsbCloudApp.Services
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<LimitingParameterDto>> GetStatAsync(LimitingParameterRequest request, CancellationToken token); Task<IEnumerable<LimitingParameterDto>> GetStatAsync(LimitingParameterRequest request, CancellationToken token);
/// <summary>
/// Получение списка ограничений
/// </summary>
/// <returns></returns>
Dictionary<int, string> GetLimitingParameteraNames();
} }
#nullable disable #nullable disable
} }

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
namespace AsbCloudApp.Services namespace AsbCloudApp.Services
{ {
#nullable enable
/// <summary> /// <summary>
/// Сервис рапортов /// Сервис рапортов
/// </summary> /// </summary>
@ -16,7 +17,6 @@ namespace AsbCloudApp.Services
/// </summary> /// </summary>
int ReportCategoryId { get; } int ReportCategoryId { get; }
// TODO: rename this method
/// <summary> /// <summary>
/// Поставить рапорт в очередь на формирование /// Поставить рапорт в очередь на формирование
/// </summary> /// </summary>
@ -28,7 +28,7 @@ namespace AsbCloudApp.Services
/// <param name="end"></param> /// <param name="end"></param>
/// <param name="handleReportProgress"></param> /// <param name="handleReportProgress"></param>
/// <returns></returns> /// <returns></returns>
string CreateReport(int idWell, int idUser, int stepSeconds, string EnqueueCreateReportWork(int idWell, int idUser, int stepSeconds,
int format, DateTime begin, DateTime end, int format, DateTime begin, DateTime end,
Action<object, string> handleReportProgress); Action<object, string> handleReportProgress);
@ -49,7 +49,7 @@ namespace AsbCloudApp.Services
/// </summary> /// </summary>
/// <param name="idWell"></param> /// <param name="idWell"></param>
/// <returns></returns> /// <returns></returns>
DatesRangeDto GetDatesRangeOrDefault(int idWell); DatesRangeDto? GetDatesRangeOrDefault(int idWell);
/// <summary> /// <summary>
/// Список готовых рапортов /// Список готовых рапортов
@ -58,5 +58,7 @@ namespace AsbCloudApp.Services
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<ReportPropertiesDto>> GetAllReportsByWellAsync(int idWell, CancellationToken token); Task<IEnumerable<ReportPropertiesDto>> GetAllReportsByWellAsync(int idWell, CancellationToken token);
#nullable disable
} }
} }

View File

@ -11,7 +11,7 @@ namespace AsbCloudApp.Services
/// Для сущностей относящихся к скважине /// Для сущностей относящихся к скважине
/// </summary> /// </summary>
/// <typeparam name="Tdto"></typeparam> /// <typeparam name="Tdto"></typeparam>
public interface IRepositoryWellRelated<Tdto> : ICrudService<Tdto> public interface IRepositoryWellRelated<Tdto> : ICrudRepository<Tdto>
where Tdto : IId, IWellRelated where Tdto : IId, IWellRelated
{ {
/// <summary> /// <summary>

View File

@ -9,7 +9,7 @@ namespace AsbCloudApp.Services
/// <summary> /// <summary>
/// сервис скважин /// сервис скважин
/// </summary> /// </summary>
public interface IWellService : ICrudService<WellDto> public interface IWellService : ICrudRepository<WellDto>
{ {
/// <summary> /// <summary>
/// сервис телеметрии /// сервис телеметрии

View File

@ -61,19 +61,31 @@ namespace AsbCloudDb.Model
public DbSet<WITS.Record60> Record60 => Set<WITS.Record60>(); public DbSet<WITS.Record60> Record60 => Set<WITS.Record60>();
public DbSet<WITS.Record61> Record61 => Set<WITS.Record61>(); public DbSet<WITS.Record61> Record61 => Set<WITS.Record61>();
public static int ReferenceCount { get; private set; }
public AsbCloudDbContext() : base() public AsbCloudDbContext() : base()
{ {
ReferenceCount++;
} }
public AsbCloudDbContext(DbContextOptions<AsbCloudDbContext> options) public AsbCloudDbContext(DbContextOptions<AsbCloudDbContext> options)
: base(options) : base(options)
{ {
ReferenceCount++;
}
~AsbCloudDbContext()
{
ReferenceCount--;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
if (!optionsBuilder.IsConfigured) if (!optionsBuilder.IsConfigured)
optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"); optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"
//, builder=>builder.EnableRetryOnFailure(2, System.TimeSpan.FromMinutes(1))
);
} }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -0,0 +1,98 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
{
# nullable enable
/// <summary>
/// Сервис для фонового выполнения работы
/// </summary>
public class BackgroundWorker : BackgroundService
{
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2);
private readonly IServiceProvider serviceProvider;
private readonly WorkQueue workQueue = new WorkQueue();
public string? CurrentWorkId;
public BackgroundWorker(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
/// <summary>
/// Добавление задачи в очередь.
/// Не периодические задачи будут выполняться вперед.
/// </summary>
/// <param name="work"></param>
/// <exception cref="ArgumentException">Id mast be unique</exception>
public void Push(WorkBase work)
{
workQueue.Push(work);
}
/// <summary>
/// Проверяет наличие работы с указанным Id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Contains(string id)
{
return workQueue.Contains(id);
}
/// <summary>
/// Удаление работы по ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Delete(string id)
{
return workQueue.Delete(id);
}
protected override async Task ExecuteAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var dateStart = DateTime.Now;
var work = workQueue.Pop();
if (work is null)
{
await Task.Delay(executePeriod, token);
continue;
}
CurrentWorkId = work.Id;
using var scope = serviceProvider.CreateScope();
try
{
Trace.TraceInformation($"Backgroud work:\"{work.Id}\" start.");
var task = work.ActionAsync(work.Id, scope.ServiceProvider, token);
await task.WaitAsync(work.Timeout, token);
work.ExecutionTime = DateTime.Now - dateStart;
Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}");
}
catch (Exception exception)
{
Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}");
if (work.OnErrorAsync is not null)
{
using var task = Task.Run(
async () => await work.OnErrorAsync(work.Id, exception, token),
token);
await task.WaitAsync(exceptionHandleTimeout, token);
}
}
CurrentWorkId = null;
await Task.Delay(minDelay, token);
}
}
}
#nullable disable
}

View File

@ -0,0 +1,69 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
{
#nullable enable
/// <summary>
/// Класс разовой работы.
/// Разовая работа приоритетнее периодической.
/// </summary>
public class WorkBase
{
/// <summary>
/// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки.
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Делегат работы.
/// <para>
/// Параметры:
/// <list type="number">
/// <item>
/// <term>string</term>
/// <description>Id Идентификатор работы</description>
/// </item>
/// <item>
/// <term>IServiceProvider</term>
/// <description>Поставщик сервисов</description>
/// </item>
/// <item>
/// <term>CancellationToken</term>
/// <description>Токен отмены задачи</description>
/// </item>
/// </list>
/// </para>
/// </summary>
internal Func<string, IServiceProvider, CancellationToken, Task> ActionAsync { get; set; }
/// <summary>
/// Делегат обработки ошибки.
/// Не должен выполняться долго.
/// </summary>
public Func<string, Exception, CancellationToken, Task>? OnErrorAsync { get; set; }
/// <summary>
/// максимально допустимое время выполнения работы
/// </summary>
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
/// <summary>
/// Фактическое время успешного выполнения работы
/// </summary>
public TimeSpan? ExecutionTime { get; internal set; }
/// <summary>
/// Время последнего запуска
/// </summary>
public DateTime LastStart { get; set; }
public WorkBase(string id, Func<string, IServiceProvider, CancellationToken, Task> actionAsync)
{
Id = id;
ActionAsync = actionAsync;
}
}
#nullable disable
}

View File

@ -0,0 +1,36 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
{
#nullable enable
/// <summary>
/// Класс периодической работы.
/// </summary>
public class WorkPeriodic : WorkBase
{
/// <summary>
/// Период выполнения задачи
/// </summary>
public TimeSpan Period { get; set; }
/// <summary>
/// Время следующего запуска
/// </summary>
public DateTime NextStart => LastStart + Period;
/// <summary>
/// Класс периодической работы
/// </summary>
/// <param name="id">Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки</param>
/// <param name="actionAsync">Делегат работы</param>
/// <param name="period">Период выполнения задачи</param>
public WorkPeriodic(string id, Func<string, IServiceProvider, CancellationToken, Task> actionAsync, TimeSpan period)
: base(id, actionAsync)
{
Period = period;
}
}
#nullable disable
}

View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudInfrastructure.Background
{
#nullable enable
/// <summary>
/// <para>
/// Очередь работ
/// </para>
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
/// </summary>
class WorkQueue
{
private Queue<WorkBase> Primary = new(8);
private readonly List<WorkPeriodic> Periodic = new(8);
/// <summary>
/// Добавление работы.
/// </summary>
/// <param name="work"></param>
/// <exception cref="ArgumentException">Id mast be unique</exception>
public void Push(WorkBase work)
{
if (Periodic.Any(w => w.Id == work.Id))
throw new ArgumentException("work.Id is not unique", nameof(work));
if (Primary.Any(w => w.Id == work.Id))
throw new ArgumentException("work.Id is not unique", nameof(work));
if (work is WorkPeriodic workPeriodic)
{
Periodic.Add(workPeriodic);
return;
}
Primary.Enqueue(work);
}
/// <summary>
/// Удаление работы по ID
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Delete(string id)
{
var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id);
if (workPeriodic is not null)
{
Periodic.Remove(workPeriodic);
return true;
}
var work = Primary.FirstOrDefault(w => w.Id == id);
if (work is not null)
{
Primary = new Queue<WorkBase>(Primary.Where(w => w.Id != id));
return true;
}
return false;
}
public bool Contains(string id)
{
var result = Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id);
return result;
}
/// <summary>
/// <para>
/// Возвращает приоритетную задачу.
/// </para>
/// <para>
/// Если приоритетные закончились, то ищет ближайшую периодическую.
/// Если до старта ближайшей периодической работы меньше 20 сек,
/// то этой задаче устанавливается время последнего запуска в now и она возвращается.
/// Если больше 20 сек, то возвращается null.
/// </para>
/// </summary>
/// <param name="maxTimeToNextWork"></param>
/// <returns></returns>
public WorkBase? Pop()
{
if (Primary.Any())
return Primary.Dequeue();
var work = GetNextPeriodic();
if (work is null || work.NextStart > DateTime.Now)
return null;
work.LastStart = DateTime.Now;
return work;
}
private WorkPeriodic? GetNextPeriodic()
{
var work = Periodic
.OrderBy(w => w.NextStart)
.ThenByDescending(w => w.Period)
.FirstOrDefault();
return work;
}
}
#nullable disable
}

View File

@ -6,6 +6,7 @@ using AsbCloudApp.Services;
using AsbCloudApp.Services.Subsystems; using AsbCloudApp.Services.Subsystems;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudDb.Model.Subsystems; using AsbCloudDb.Model.Subsystems;
using AsbCloudInfrastructure.Background;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.DailyReport; using AsbCloudInfrastructure.Services.DailyReport;
@ -97,16 +98,13 @@ namespace AsbCloudInfrastructure
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>()); services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
services.AddScoped<IEmailService, EmailService>(); services.AddScoped<IEmailService, EmailService>();
services.AddHostedService<OperationDetectionBackgroundService>();
services.AddHostedService<SubsystemOperationTimeBackgroundService>();
services.AddHostedService<LimitingParameterBackgroundService>();
services.AddSingleton(new WitsInfoService()); services.AddSingleton(new WitsInfoService());
services.AddSingleton(new InstantDataRepository()); services.AddSingleton(new InstantDataRepository());
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration)); services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration));
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration)); services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration));
services.AddSingleton<ITelemetryTracker, TelemetryTracker>(); services.AddSingleton<ITelemetryTracker, TelemetryTracker>();
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>(); services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
services.AddSingleton<IBackgroundWorkerService, BackgroundWorkerService>(); services.AddSingleton<BackgroundWorker>();
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration)); services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
services.AddTransient<IAuthService, AuthService>(); services.AddTransient<IAuthService, AuthService>();
@ -139,33 +137,33 @@ namespace AsbCloudInfrastructure
services.AddTransient<ILimitingParameterService, LimitingParameterService>(); services.AddTransient<ILimitingParameterService, LimitingParameterService>();
// admin crud services: // admin crud services:
services.AddTransient<ICrudService<TelemetryDto>, CrudServiceBase<TelemetryDto, Telemetry>>(s => services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheServiceBase<TelemetryDto, Telemetry>( new CrudCacheRepositoryBase<TelemetryDto, Telemetry>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(), s.GetService<IMemoryCache>(),
dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService
services.AddTransient<ICrudService<DrillParamsDto>, DrillParamsService>(); services.AddTransient<ICrudRepository<DrillParamsDto>, DrillParamsService>();
services.AddTransient<ICrudService<DepositDto>, CrudCacheServiceBase<DepositDto, Deposit>>(s => services.AddTransient<ICrudRepository<DepositDto>, CrudCacheRepositoryBase<DepositDto, Deposit>>(s =>
new CrudCacheServiceBase<DepositDto, Deposit>( new CrudCacheRepositoryBase<DepositDto, Deposit>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(), s.GetService<IMemoryCache>(),
dbSet => dbSet.Include(d => d.Clusters))); dbSet => dbSet.Include(d => d.Clusters)));
services.AddTransient<ICrudService<CompanyDto>, CrudCacheServiceBase<CompanyDto, Company>>(s => services.AddTransient<ICrudRepository<CompanyDto>, CrudCacheRepositoryBase<CompanyDto, Company>>(s =>
new CrudCacheServiceBase<CompanyDto, Company>( new CrudCacheRepositoryBase<CompanyDto, Company>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(), s.GetService<IMemoryCache>(),
dbSet => dbSet.Include(c => c.CompanyType))); dbSet => dbSet.Include(c => c.CompanyType)));
services.AddTransient<ICrudService<CompanyTypeDto>, CrudCacheServiceBase<CompanyTypeDto, CompanyType>>(); services.AddTransient<ICrudRepository<CompanyTypeDto>, CrudCacheRepositoryBase<CompanyTypeDto, CompanyType>>();
services.AddTransient<ICrudService<ClusterDto>, CrudCacheServiceBase<ClusterDto, Cluster>>(s => services.AddTransient<ICrudRepository<ClusterDto>, CrudCacheRepositoryBase<ClusterDto, Cluster>>(s =>
new CrudCacheServiceBase<ClusterDto, Cluster>( new CrudCacheRepositoryBase<ClusterDto, Cluster>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(), s.GetService<IMemoryCache>(),
dbSet => dbSet dbSet => dbSet
.Include(c => c.Wells) .Include(c => c.Wells)
.Include(c => c.Deposit))); // может быть включен в сервис ClusterService .Include(c => c.Deposit))); // может быть включен в сервис ClusterService
services.AddTransient<ICrudService<DrillerDto>, CrudCacheServiceBase<DrillerDto, Driller>>(); services.AddTransient<ICrudRepository<DrillerDto>, CrudCacheRepositoryBase<DrillerDto, Driller>>();
services.AddTransient<IFileRepository, FileRepository>(); services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<IFileStorageRepository, FileStorageRepository>(); services.AddTransient<IFileStorageRepository, FileStorageRepository>();
@ -174,10 +172,10 @@ namespace AsbCloudInfrastructure
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ILimitingParameterRepository, LimitingParameterRepository>(); services.AddTransient<ILimitingParameterRepository, LimitingParameterRepository>();
// Subsystem service // Subsystem service
services.AddTransient<ICrudService<SubsystemDto>, CrudCacheServiceBase<SubsystemDto, Subsystem>>(); services.AddTransient<ICrudRepository<SubsystemDto>, CrudCacheRepositoryBase<SubsystemDto, Subsystem>>();
services.AddTransient<ISubsystemService, SubsystemService>(); services.AddTransient<ISubsystemService, SubsystemService>();
services.AddTransient<ICrudService<PermissionDto>, CrudCacheServiceBase<PermissionDto, Permission>>(); services.AddTransient<ICrudRepository<PermissionDto>, CrudCacheRepositoryBase<PermissionDto, Permission>>();
// TelemetryData services // TelemetryData services
services.AddTransient<ITelemetryDataService<TelemetryDataSaubDto>, TelemetryDataSaubService>(); services.AddTransient<ITelemetryDataService<TelemetryDataSaubDto>, TelemetryDataSaubService>();

View File

@ -1,55 +0,0 @@
using System;
namespace AsbCloudInfrastructure
{
public static class Helper
{
public static T Max<T>(params T[] items)
where T : IComparable
{
var count = items.Length;
if (count < 1)
throw new ArgumentException("Count of params must be greater than 1");
var max = items[0];
for (var i = 1; i < count; i++)
if (max.CompareTo(items[i]) < 0)
max = items[i];
return max;
}
public static T Min<T>(params T[] items)
where T : IComparable
{
var count = items.Length;
if (count < 1)
throw new ArgumentException("Count of params must be greater than 1");
var min = items[0];
for (var i = 1; i < count; i++)
if (min.CompareTo(items[i]) > 0)
min = items[i];
return min;
}
public static (T min, T max) MinMax<T>(params T[] items)
where T : IComparable
{
var count = items.Length;
if (count < 1)
throw new ArgumentException("Count of params must be greater than 1");
var min = items[0];
var max = items[0];
for (var i = 1; i < count; i++)
if (max.CompareTo(items[i]) < 0)
max = items[i];
else if (min.CompareTo(items[i]) > 0)
min = items[i];
return (min, max);
}
}
}

View File

@ -1,12 +0,0 @@
namespace Mapster
{
public static class MapsterExtension
{
//public static IEnumerable<TDestination> Adapt<TDestination>(this IEnumerable<object> sourceList)
//{
// return sourceList.Select(item => item.Adapt<TDestination>());
//}
}
}

View File

@ -10,7 +10,7 @@ namespace AsbCloudInfrastructure
{ {
public class ReportDataSourcePgCloud : IReportDataSource public class ReportDataSourcePgCloud : IReportDataSource
{ {
private readonly AsbCloudDbContext context; private readonly IAsbCloudDbContext context;
private readonly int? idTelemetry; private readonly int? idTelemetry;
private readonly WellInfoReport info; private readonly WellInfoReport info;
@ -25,7 +25,7 @@ namespace AsbCloudInfrastructure
{3, "Информация"}, {3, "Информация"},
}; };
public ReportDataSourcePgCloud(AsbCloudDbContext context, int idWell) public ReportDataSourcePgCloud(IAsbCloudDbContext context, int idWell)
{ {
this.context = context; this.context = context;
@ -65,6 +65,7 @@ namespace AsbCloudInfrastructure
public AnalyzeResult Analyze() public AnalyzeResult Analyze()
{ {
// TODO: Replace by linq methods.
var messagesStat = (from item in context.TelemetryMessages var messagesStat = (from item in context.TelemetryMessages
where item.IdTelemetry == idTelemetry where item.IdTelemetry == idTelemetry
group item.DateTime by item.IdTelemetry into g group item.DateTime by item.IdTelemetry into g

View File

@ -0,0 +1,62 @@
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository
{
#nullable enable
public class CacheBase<TEntity> : QueryContainer<TEntity>
where TEntity : class, AsbCloudDb.Model.IId
{
protected readonly IMemoryCache memoryCache;
protected string CacheTag = typeof(TEntity).Name;
protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
public CacheBase(IAsbCloudDbContext context, IMemoryCache memoryCache)
: base(context)
{
this.memoryCache = memoryCache;
}
public CacheBase(IAsbCloudDbContext context, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(context, makeQuery)
{
this.memoryCache = memoryCache;
}
protected virtual void DropCache()
=> memoryCache.Remove(CacheTag);
protected virtual IEnumerable<TEntity> GetCache()
{
var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence;
var entities = this.GetQuery().ToArray();
return entities;
});
return cache;
}
protected virtual Task<IEnumerable<TEntity>> GetCacheAsync(CancellationToken token)
{
var cache = memoryCache.GetOrCreateAsync(CacheTag, async (cacheEntry) =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence;
var entities = await this.GetQuery().ToArrayAsync(token);
return entities.AsEnumerable();
});
return cache;
}
}
#nullable disable
}

View File

@ -0,0 +1,114 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository
{
#nullable enable
/// <summary>
/// CRUD ñåðâèñ ñ êåøåì â îïåðàòèâêå
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <typeparam name="TEntity"></typeparam>
public class CrudCacheRepositoryBase<TDto, TEntity> : CacheBase<TEntity>, ICrudRepository<TDto>
where TDto : AsbCloudApp.Data.IId
where TEntity : class, IId
{
protected int KeySelector(TEntity entity) => entity.Id;
protected readonly ICrudRepository<TDto> crudServiceBase;
public CrudCacheRepositoryBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
: base(dbContext, memoryCache)
{
crudServiceBase = new CrudRepositoryBase<TDto, TEntity>(dbContext);
}
public CrudCacheRepositoryBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(dbContext, memoryCache, makeQuery)
{
crudServiceBase = new CrudRepositoryBase<TDto, TEntity>(dbContext, makeQuery);
}
/// <inheritdoc/>
public virtual async Task<int> InsertAsync(TDto newItem, CancellationToken token)
{
var result = await crudServiceBase.InsertAsync(newItem, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public virtual async Task<int> InsertRangeAsync(IEnumerable<TDto> dtos, CancellationToken token)
{
var result = await crudServiceBase.InsertRangeAsync(dtos, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public virtual async Task<int> UpdateAsync(TDto dto, CancellationToken token)
{
var result = await crudServiceBase.UpdateAsync(dto, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TDto>> GetAllAsync(CancellationToken token)
{
var cache = await GetCacheAsync(token);
var dtos = cache.Select(Convert);
return dtos;
}
/// <summary>
/// Ñèíõðîííî ïîëó÷èòü çàïèñü ïî ÈÄ
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public TDto? GetOrDefault(int id)
{
var cache = GetCache();
var cacheItem = cache.FirstOrDefault(d => d.Id == id);
if (cacheItem is null)
return default;
var dto = Convert(cacheItem);
return dto;
}
/// <inheritdoc/>
public async Task<TDto?> GetOrDefaultAsync(int id, CancellationToken token)
{
var cache = await GetCacheAsync(token);
var cacheItem = cache.FirstOrDefault(d => d.Id == id);
if (cacheItem is null)
return default;
var dto = Convert(cacheItem);
return dto;
}
/// <inheritdoc/>
public virtual async Task<int> DeleteAsync(int id, CancellationToken token)
{
var result = await crudServiceBase.DeleteAsync(id, token);
if (result > 0)
DropCache();
return result;
}
protected virtual TDto Convert(TEntity src) => src.Adapt<TDto>();
protected virtual TEntity Convert(TDto src) => src.Adapt<TEntity>();
}
#nullable disable
}

View File

@ -1,140 +0,0 @@
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository
{
#nullable enable
/// <summary>
/// CRUD ñåðâèñ ñ êåøåì â îïåðàòèâêå
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <typeparam name="TEntity"></typeparam>
public class CrudCacheServiceBase<TDto, TEntity> : CrudServiceBase<TDto, TEntity>
where TDto : AsbCloudApp.Data.IId
where TEntity : class, IId
{
protected string CacheTag = typeof(TDto).Name;
protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
private readonly IMemoryCache memoryCache;
protected int KeySelector(TEntity entity) => entity.Id;
public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
: base(dbContext)
{
this.memoryCache = memoryCache;
}
public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(dbContext, makeQuery)
{
this.memoryCache = memoryCache;
}
/// <inheritdoc/>
public override async Task<int> InsertAsync(TDto newItem, CancellationToken token)
{
var result = await base.InsertAsync(newItem, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public override async Task<int> InsertRangeAsync(IEnumerable<TDto> dtos, CancellationToken token)
{
var result = await base.InsertRangeAsync(dtos, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public override async Task<IEnumerable<TDto>> GetAllAsync(CancellationToken token)
{
var cache = await GetCacheAsync(token);
return cache;
}
/// <summary>
/// Ñèíõðîííî ïîëó÷èòü çàïèñü ïî ÈÄ
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public override TDto? GetOrDefault(int id)
{
var cache = GetCache();
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.FirstOrDefault(d => d.Id == id);
}
/// <inheritdoc/>
public override async Task<int> UpdateAsync(TDto dto, CancellationToken token)
{
var result = await base.UpdateAsync(dto, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public override async Task<int> UpdateRangeAsync(IEnumerable<TDto> dtos, CancellationToken token)
{
var result = await base.UpdateRangeAsync(dtos, token);
if (result > 0)
DropCache();
return result;
}
/// <inheritdoc/>
public override async Task<int> DeleteAsync(int id, CancellationToken token)
{
var result = await base.DeleteAsync(id, token);
if (result > 0)
DropCache();
return result;
}
protected virtual Task<IEnumerable<TDto>> GetCacheAsync(CancellationToken token)
{
var cache = memoryCache.GetOrCreateAsync(CacheTag, async (cacheEntry) => {
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence;
var entities = await GetQuery().ToArrayAsync(token);
var dtos = entities.Select(Convert);
return dtos.ToArray().AsEnumerable();
});
return cache;
}
protected virtual IEnumerable<TDto> GetCache()
{
var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => {
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration= CacheOlescence;
var entities = GetQuery().ToArray();
var dtos = entities.Select(Convert);
return dtos.ToArray();
});
return cache;
}
protected virtual void DropCache()
=> memoryCache.Remove(CacheTag);
}
#nullable disable
}

View File

@ -16,33 +16,22 @@ namespace AsbCloudInfrastructure.Repository
/// </summary> /// </summary>
/// <typeparam name="TDto"></typeparam> /// <typeparam name="TDto"></typeparam>
/// <typeparam name="TEntity"></typeparam> /// <typeparam name="TEntity"></typeparam>
public class CrudServiceBase<TDto, TEntity> : ICrudService<TDto> public class CrudRepositoryBase<TDto, TEntity> : QueryContainer<TEntity>, ICrudRepository<TDto>
where TDto : AsbCloudApp.Data.IId where TDto : AsbCloudApp.Data.IId
where TEntity : class, IId where TEntity : class, IId
{ {
protected readonly IAsbCloudDbContext dbContext; public CrudRepositoryBase(IAsbCloudDbContext context)
protected readonly DbSet<TEntity> dbSet; : base(context)
protected readonly Func<IQueryable<TEntity>> GetQuery; { }
public CrudServiceBase(IAsbCloudDbContext context) public CrudRepositoryBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
{ : base(context, makeQuery)
dbContext = context; { }
dbSet = context.Set<TEntity>();
GetQuery = () => dbSet;
}
public CrudServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
{
dbContext = context;
dbSet = context.Set<TEntity>();
GetQuery = () => makeQuery(dbSet);
}
/// <inheritdoc/> /// <inheritdoc/>
public virtual async Task<IEnumerable<TDto>> GetAllAsync(CancellationToken token = default) public virtual async Task<IEnumerable<TDto>> GetAllAsync(CancellationToken token = default)
{ {
var entities = await GetQuery() var entities = await GetQuery()
//.OrderBy(e => e.Id)
.AsNoTracking() .AsNoTracking()
.ToListAsync(token) .ToListAsync(token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -117,7 +106,7 @@ namespace AsbCloudInfrastructure.Repository
.ConfigureAwait(false); .ConfigureAwait(false);
if (existingEntity is null) if (existingEntity is null)
return ICrudService<TDto>.ErrorIdNotFound; return ICrudRepository<TDto>.ErrorIdNotFound;
var entity = Convert(item); var entity = Convert(item);
var entry = dbSet.Update(entity); var entry = dbSet.Update(entity);
@ -126,29 +115,6 @@ namespace AsbCloudInfrastructure.Repository
return entry.Entity.Id; return entry.Entity.Id;
} }
public virtual async Task<int> UpdateRangeAsync(IEnumerable<TDto> dtos, CancellationToken token)
{
var ids = dtos.Select(d => d.Id);
var existingEntities = await dbSet
.AsNoTracking()
.Where(d => ids.Contains(d.Id))
.Select(d => d.Id)
.ToListAsync(token)
.ConfigureAwait(false);
if (ids.Count() > existingEntities.Count)
return ICrudService<TDto>.ErrorIdNotFound;
foreach (var dto in dtos)
{
var entity = Convert(dto);
var entry = dbSet.Update(entity);
}
var affected = await dbContext.SaveChangesAsync(token);
return affected;
}
/// <inheritdoc/> /// <inheritdoc/>
public virtual Task<int> DeleteAsync(int id, CancellationToken token = default) public virtual Task<int> DeleteAsync(int id, CancellationToken token = default)
{ {
@ -156,7 +122,7 @@ namespace AsbCloudInfrastructure.Repository
.AsNoTracking() .AsNoTracking()
.FirstOrDefault(e => e.Id == id); .FirstOrDefault(e => e.Id == id);
if (entity == default) if (entity == default)
return Task.FromResult(ICrudService<TDto>.ErrorIdNotFound); return Task.FromResult(ICrudRepository<TDto>.ErrorIdNotFound);
var entry = dbSet.Remove(entity); var entry = dbSet.Remove(entity);
var affected = dbContext.SaveChangesAsync(token); var affected = dbContext.SaveChangesAsync(token);
entry.State = EntityState.Detached; entry.State = EntityState.Detached;

View File

@ -11,7 +11,7 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository namespace AsbCloudInfrastructure.Repository
{ {
#nullable enable #nullable enable
public class CrudWellRelatedCacheServiceBase<TDto, TEntity> : CrudCacheServiceBase<TDto, TEntity>, IRepositoryWellRelated<TDto> public class CrudWellRelatedCacheServiceBase<TDto, TEntity> : CrudCacheRepositoryBase<TDto, TEntity>, IRepositoryWellRelated<TDto>
where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated
where TEntity : class, IId, IWellRelated where TEntity : class, IId, IWellRelated
{ {
@ -27,7 +27,7 @@ namespace AsbCloudInfrastructure.Repository
var dtos = cache var dtos = cache
.Where(e => e.IdWell == idWell) .Where(e => e.IdWell == idWell)
.ToList(); .Select(Convert);
return dtos; return dtos;
} }
@ -41,7 +41,8 @@ namespace AsbCloudInfrastructure.Repository
var dtos = cache var dtos = cache
.Where(e => idsWells.Contains(e.IdWell)) .Where(e => idsWells.Contains(e.IdWell))
.ToList(); .Select(Convert);
return dtos; return dtos;
} }
} }

View File

@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository namespace AsbCloudInfrastructure.Repository
{ {
#nullable enable #nullable enable
public class CrudWellRelatedServiceBase<TDto, TEntity> : CrudServiceBase<TDto, TEntity>, IRepositoryWellRelated<TDto> public class CrudWellRelatedServiceBase<TDto, TEntity> : CrudRepositoryBase<TDto, TEntity>, IRepositoryWellRelated<TDto>
where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated
where TEntity : class, IId, IWellRelated where TEntity : class, IId, IWellRelated
{ {

View File

@ -1,15 +1,10 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Requests; using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services;
using DocumentFormat.OpenXml.Drawing.Charts;
using DocumentFormat.OpenXml.Wordprocessing;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Org.BouncyCastle.Asn1.Ocsp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

View File

@ -0,0 +1,28 @@
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace AsbCloudInfrastructure.Repository
{
public class QueryContainer<TEntity> where TEntity : class, IId
{
protected readonly IAsbCloudDbContext dbContext;
protected readonly DbSet<TEntity> dbSet;
protected readonly Func<IQueryable<TEntity>> GetQuery;
public QueryContainer(IAsbCloudDbContext context)
{
dbContext = context;
dbSet = context.Set<TEntity>();
GetQuery = () => dbSet;
}
public QueryContainer(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
{
dbContext = context;
dbSet = context.Set<TEntity>();
GetQuery = () => makeQuery(dbSet);
}
}
}

View File

@ -4,6 +4,10 @@ using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
namespace AsbCloudInfrastructure.Repository namespace AsbCloudInfrastructure.Repository
{ {
@ -18,6 +22,29 @@ namespace AsbCloudInfrastructure.Repository
this.wellService = wellService; this.wellService = wellService;
} }
public virtual async Task<int> UpdateRangeAsync(IEnumerable<SetpointsRequestDto> dtos, CancellationToken token)
{
var ids = dtos.Select(d => d.Id);
var existingEntities = await dbSet
.AsNoTracking()
.Where(d => ids.Contains(d.Id))
.Select(d => d.Id)
.ToListAsync(token)
.ConfigureAwait(false);
if (ids.Count() > existingEntities.Count)
return ICrudRepository<SetpointsRequestDto>.ErrorIdNotFound;
foreach (var dto in dtos)
{
var entity = Convert(dto);
var entry = dbSet.Update(entity);
}
var affected = await dbContext.SaveChangesAsync(token);
return affected;
}
protected override SetpointsRequestDto Convert(SetpointsRequest src) protected override SetpointsRequestDto Convert(SetpointsRequest src)
{ {
var result = base.Convert(src); var result = base.Convert(src);

View File

@ -1,193 +0,0 @@
using AsbCloudApp.Services;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services
{
/// <summary>
/// Сервис выстраивает очередь из фоновых задач. Ограничивает количество одновременно выполняющихся задач.
/// </summary>
public class BackgroundWorkerService : IDisposable, IBackgroundWorkerService
{
private readonly Worker[] workers;
private readonly Dictionary<string, Work> works = new Dictionary<string, Work>();
private bool isRunning = false;
private CancellationTokenSource cts;
private Task task;
public BackgroundWorkerService(IConfiguration configuration)
{
var workersCount = configuration.GetValue("BackgroundWorkersCount", 4);
workers = new Worker[workersCount];
for (int i = 0; i < workers.Length; i++)
workers[i] = new Worker();
}
~BackgroundWorkerService()
{
Dispose();
}
public string Enqueue(Func<string, CancellationToken, Task> func)
{
var work = new Work
{
ActionAsync = func
};
return Enqueue(work);
}
public string Enqueue(string id, Func<string, CancellationToken, Task> func)
{
var work = new Work(id, func);
return Enqueue(work);
}
public string Enqueue(string id, Func<string, CancellationToken, Task> func, Func<string, Exception, CancellationToken, Task> onError)
{
var work = new Work(id, func)
{
OnErrorAsync = onError
};
return Enqueue(work);
}
string Enqueue(Work work)
{
works[work.Id] = work;
if (!isRunning)
{
isRunning = true;
cts = new CancellationTokenSource();
task = Task.Run(() => ExecuteAsync(cts.Token), cts.Token);
}
return work.Id;
}
private Work Dequeue()
{
var item = works.First();
works.Remove(item.Key);
return item.Value;
}
public bool TryRemove(string id)
=> works.Remove(id);
public bool Contains(string id)
=> works.ContainsKey(id);
protected async Task ExecuteAsync(CancellationToken token)
{
while (works.Any() && !token.IsCancellationRequested)
{
var freeworker = workers.FirstOrDefault(w => !w.IsBusy);
if (freeworker is not null)
{
var work = Dequeue();
freeworker.Start(work);
}
else
await Task.Delay(10, token).ConfigureAwait(false);
}
isRunning = false;
}
public void Dispose()
{
cts?.Cancel();
task?.Wait(1);
task?.Dispose();
cts?.Dispose();
task = null;
cts = null;
GC.SuppressFinalize(this);
}
}
class Worker : IDisposable
{
private CancellationTokenSource cts;
private Task task;
public bool IsBusy { get; private set; }
~Worker()
{
Dispose();
}
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
public void Start(Work work)
{
IsBusy = true;
cts = new CancellationTokenSource();
task = Task.Run(async () =>
{
try
{
var actionTask = work.ActionAsync(work.Id, cts.Token);
await actionTask.WaitAsync(TimeSpan.FromMinutes(2), cts.Token);
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
if (work.OnErrorAsync is not null)
{
try
{
await work.OnErrorAsync(work.Id, ex, cts.Token).ConfigureAwait(false);
}
catch (Exception exOnErrorHandler)
{
Trace.TraceError(exOnErrorHandler.Message);
}
}
}
finally
{
cts?.Dispose();
cts = null;
IsBusy = false;
}
}, cts.Token);
}
public void Stop()
{
cts?.Cancel();
task?.Wait(1);
task = null;
cts?.Dispose();
cts = null;
IsBusy = false;
}
}
class Work
{
public string Id { get; private set; }
public Func<string, CancellationToken, Task> ActionAsync { get; set; }
public Func<string, Exception, CancellationToken, Task> OnErrorAsync { get; set; }
public Work()
{
Id = Guid.NewGuid().ToString();
}
public Work(string id, Func<string, CancellationToken, Task> actionAsync)
{
Id = id;
ActionAsync = actionAsync;
}
}
}

View File

@ -268,7 +268,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
return query; return query;
} }
private DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules) private static DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules)
{ {
var dto = operation.Adapt<DetectedOperationDto>(); var dto = operation.Adapt<DetectedOperationDto>();
dto.IdWell = well.Id; dto.IdWell = well.Id;

View File

@ -1,7 +1,5 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -9,14 +7,16 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors; using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudInfrastructure.Services.DetectOperations namespace AsbCloudInfrastructure.Services.DetectOperations
{ {
#nullable enable #nullable enable
public class OperationDetectionBackgroundService : BackgroundService public static class OperationDetectionWorkFactory
{ {
private readonly string connectionString; private const string workId = "Operation detection";
private readonly TimeSpan period = TimeSpan.FromHours(1); private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
{ {
@ -31,49 +31,18 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
new DetectorTemplatingWhileDrilling(), new DetectorTemplatingWhileDrilling(),
}; };
public OperationDetectionBackgroundService(IConfiguration configuration) public static WorkPeriodic MakeWork()
{ {
connectionString = configuration.GetConnectionString("DefaultConnection"); var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod);
workPeriodic.Timeout = TimeSpan.FromMinutes(30);
return workPeriodic;
} }
protected override async Task ExecuteAsync(CancellationToken token = default) // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token)
{ {
var timeToStartAnalysis = DateTime.Now; using var db = serviceProvider.GetRequiredService<IAsbCloudDbContext>();
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(connectionString)
.Options;
while (!token.IsCancellationRequested)
{
if (DateTime.Now > timeToStartAnalysis)
{
timeToStartAnalysis = DateTime.Now + period;
try
{
using var context = new AsbCloudDbContext(options);
var added = await DetectedAllTelemetriesAsync(context, token);
Trace.TraceInformation($"Total detection complete. Added {added} operations.");
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
}
GC.Collect();
}
var ms = (int)(timeToStartAnalysis - DateTime.Now).TotalMilliseconds;
ms = ms > 100 ? ms : 100;
await Task.Delay(ms, token).ConfigureAwait(false);
}
}
public override async Task StopAsync(CancellationToken token)
{
await base.StopAsync(token).ConfigureAwait(false);
}
private static async Task<int> DetectedAllTelemetriesAsync(IAsbCloudDbContext db, CancellationToken token)
{
var lastDetectedDates = await db.DetectedOperations var lastDetectedDates = await db.DetectedOperations
.GroupBy(o => o.IdTelemetry) .GroupBy(o => o.IdTelemetry)
.Select(g => new .Select(g => new
@ -88,7 +57,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
.Select(t => t.Id) .Select(t => t.Id)
.ToListAsync(token); .ToListAsync(token);
var JounedlastDetectedDates = telemetryIds var joinedlastDetectedDates = telemetryIds
.GroupJoin(lastDetectedDates, .GroupJoin(lastDetectedDates,
t => t, t => t,
o => o.IdTelemetry, o => o.IdTelemetry,
@ -97,8 +66,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
IdTelemetry = outer, IdTelemetry = outer,
inner.SingleOrDefault()?.LastDate, inner.SingleOrDefault()?.LastDate,
}); });
var affected = 0; var affected = 0;
foreach (var item in JounedlastDetectedDates) foreach (var item in joinedlastDetectedDates)
{ {
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
@ -109,7 +79,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations
affected += await db.SaveChangesAsync(token); affected += await db.SaveChangesAsync(token);
} }
} }
return affected;
} }
private static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) private static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)

View File

@ -12,7 +12,7 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
#nullable enable #nullable enable
public class DrillParamsService : CrudServiceBase<DrillParamsDto, DrillParams>, IDrillParamsService public class DrillParamsService : CrudRepositoryBase<DrillParamsDto, DrillParams>, IDrillParamsService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;

View File

@ -3,10 +3,11 @@ using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Background;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -16,6 +17,7 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.DrillingProgram namespace AsbCloudInfrastructure.Services.DrillingProgram
{ {
# nullable enable
public class DrillingProgramService : IDrillingProgramService public class DrillingProgramService : IDrillingProgramService
{ {
private static readonly Dictionary<string, DrillingProgramCreateError> drillingProgramCreateErrors = new Dictionary<string, DrillingProgramCreateError>(); private static readonly Dictionary<string, DrillingProgramCreateError> drillingProgramCreateErrors = new Dictionary<string, DrillingProgramCreateError>();
@ -25,9 +27,8 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private readonly IUserRepository userRepository; private readonly IUserRepository userRepository;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IConfiguration configuration; private readonly IConfiguration configuration;
private readonly IBackgroundWorkerService backgroundWorker; private readonly BackgroundWorker backgroundWorker;
private readonly IEmailService emailService; private readonly IEmailService emailService;
private readonly string connectionString;
private const int idFileCategoryDrillingProgram = 1000; private const int idFileCategoryDrillingProgram = 1000;
private const int idFileCategoryDrillingProgramPartsStart = 1001; private const int idFileCategoryDrillingProgramPartsStart = 1001;
@ -55,7 +56,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
IUserRepository userRepository, IUserRepository userRepository,
IWellService wellService, IWellService wellService,
IConfiguration configuration, IConfiguration configuration,
IBackgroundWorkerService backgroundWorker, BackgroundWorker backgroundWorker,
IEmailService emailService) IEmailService emailService)
{ {
this.context = context; this.context = context;
@ -64,7 +65,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
this.wellService = wellService; this.wellService = wellService;
this.configuration = configuration; this.configuration = configuration;
this.backgroundWorker = backgroundWorker; this.backgroundWorker = backgroundWorker;
this.connectionString = configuration.GetConnectionString("DefaultConnection");
this.emailService = emailService; this.emailService = emailService;
} }
@ -127,7 +127,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
{ {
Parts = parts, Parts = parts,
Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram) Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram)
.Adapt<FileInfoDto>(), ?.Adapt<FileInfoDto>(),
PermissionToEdit = userRepository.HasPermission(idUser, "DrillingProgram.edit"), PermissionToEdit = userRepository.HasPermission(idUser, "DrillingProgram.edit"),
}; };
@ -157,7 +157,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
else else
state.IdState = idStateNotInitialized; state.IdState = idStateNotInitialized;
await TryEnqueueMakeProgramAsync(idWell, state, token); await EnqueueMakeProgramWorkAsync(idWell, state, token);
return state; return state;
} }
@ -299,7 +299,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(p => p.IdWell == fileInfo.IdWell && p.IdFileCategory == fileInfo.IdCategory, token); .FirstOrDefaultAsync(p => p.IdWell == fileInfo.IdWell && p.IdFileCategory == fileInfo.IdCategory, token);
var user = part.RelatedUsers.FirstOrDefault(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover)?.User; var user = part?.RelatedUsers.FirstOrDefault(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover)?.User;
if (user is null) if (user is null)
throw new ForbidException($"User {idUser} is not in the approvers list."); throw new ForbidException($"User {idUser} is not in the approvers list.");
@ -323,11 +323,11 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
else else
{ {
// если все согласованты согласовали - оповещаем публикатора // если все согласованты согласовали - оповещаем публикатора
var approvers = part.RelatedUsers var approvers = part!.RelatedUsers
.Where(u => u.IdUserRole == idUserRoleApprover); .Where(u => u.IdUserRole == idUserRoleApprover);
if (approvers if (approvers
.All(user => fileInfo.FileMarks .All(user => fileInfo.FileMarks
.Any(mark => (mark.IdMarkType == idMarkTypeApprove && mark.User.Id == user.IdUser && !mark.IsDeleted)) || ?.Any(mark => (mark.IdMarkType == idMarkTypeApprove && mark.User.Id == user.IdUser && !mark.IsDeleted)) == true ||
(fileMarkDto.IdMarkType == idMarkTypeApprove && user.IdUser == idUser))) (fileMarkDto.IdMarkType == idMarkTypeApprove && user.IdUser == idUser)))
{ {
await NotifyPublisherOnFullAccepAsync(fileMarkDto, token); await NotifyPublisherOnFullAccepAsync(fileMarkDto, token);
@ -359,7 +359,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task NotifyPublisherOnFullAccepAsync(FileMarkDto fileMark, CancellationToken token) private async Task NotifyPublisherOnFullAccepAsync(FileMarkDto fileMark, CancellationToken token)
{ {
var file = await fileService.GetOrDefaultAsync(fileMark.IdFile, token); var file = await fileService.GetOrDefaultAsync(fileMark.IdFile, token);
var well = await wellService.GetOrDefaultAsync(file.IdWell, token); var well = await wellService.GetOrDefaultAsync(file!.IdWell, token);
var user = file.Author; var user = file.Author;
var factory = new DrillingMailBodyFactory(configuration); var factory = new DrillingMailBodyFactory(configuration);
var subject = factory.MakeSubject(well, "Загруженный вами документ полностью согласован"); var subject = factory.MakeSubject(well, "Загруженный вами документ полностью согласован");
@ -371,7 +371,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task NotifyPublisherOnRejectAsync(FileMarkDto fileMark, CancellationToken token) private async Task NotifyPublisherOnRejectAsync(FileMarkDto fileMark, CancellationToken token)
{ {
var file = await fileService.GetOrDefaultAsync(fileMark.IdFile, token); var file = await fileService.GetOrDefaultAsync(fileMark.IdFile, token);
var well = await wellService.GetOrDefaultAsync(file.IdWell, token); var well = await wellService.GetOrDefaultAsync(file!.IdWell, token);
var user = file.Author; var user = file.Author;
var factory = new DrillingMailBodyFactory(configuration); var factory = new DrillingMailBodyFactory(configuration);
var subject = factory.MakeSubject(well, "Загруженный вами документ отклонен"); var subject = factory.MakeSubject(well, "Загруженный вами документ отклонен");
@ -405,12 +405,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
emailService.EnqueueSend(user.Email, subject, body); emailService.EnqueueSend(user.Email, subject, body);
} }
private DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> files, DrillingProgramPart partEntity, double timezoneOffset) private static DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> files, DrillingProgramPart partEntity, double timezoneOffset)
{ {
var part = new DrillingProgramPartDto var part = new DrillingProgramPartDto
{ {
IdFileCategory = partEntity.IdFileCategory, IdFileCategory = partEntity.IdFileCategory,
Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory).Name, Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory)!.Name,
Approvers = partEntity.RelatedUsers Approvers = partEntity.RelatedUsers
.Where(r => r.IdUserRole == idUserRoleApprover) .Where(r => r.IdUserRole == idUserRoleApprover)
.Select(r => r.User.Adapt<UserDto>()), .Select(r => r.User.Adapt<UserDto>()),
@ -464,31 +464,27 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
return part; return part;
} }
private async Task TryEnqueueMakeProgramAsync(int idWell, DrillingProgramStateDto state, CancellationToken token) private async Task EnqueueMakeProgramWorkAsync(int idWell, DrillingProgramStateDto state, CancellationToken token)
{ {
if (state.IdState == idStateCreating) if (state.IdState == idStateCreating)
{ {
var workId = MakeWorkId(idWell); var workId = MakeWorkId(idWell);
if (!backgroundWorker.Contains(workId)) if (!backgroundWorker.Contains(workId))
{ {
var well = await wellService.GetOrDefaultAsync(idWell, token); var well = (await wellService.GetOrDefaultAsync(idWell, token))!;
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx";
var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName); var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName);
async Task funcProgramMake(string id, CancellationToken token)
var workAction = async (string workId, IServiceProvider serviceProvider, CancellationToken token) =>
{ {
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>() var context = serviceProvider.GetRequiredService<IAsbCloudDbContext>();
.UseNpgsql(connectionString) var fileService = serviceProvider.GetRequiredService<FileService>();
.Options;
using var context = new AsbCloudDbContext(contextOptions);
var fileRepository = new FileRepository(context);
var fileStorageRepository = new FileStorageRepository();
var fileService = new FileService(fileRepository, fileStorageRepository);
var files = state.Parts.Select(p => fileService.GetUrl(p.File)); var files = state.Parts.Select(p => fileService.GetUrl(p.File));
DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath, state.Parts, well); DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath, state.Parts, well);
await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token); await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token);
} };
Task funcOnErrorProgramMake(string workId, Exception exception, CancellationToken token) var onErrorAction = (string workId, Exception exception, CancellationToken token) =>
{ {
var message = $"Не удалось сформировать программу бурения по скважине {well?.Caption}"; var message = $"Не удалось сформировать программу бурения по скважине {well?.Caption}";
drillingProgramCreateErrors[workId] = new() drillingProgramCreateErrors[workId] = new()
@ -497,9 +493,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
Exception = exception.Message, Exception = exception.Message,
}; };
return Task.CompletedTask; return Task.CompletedTask;
} };
backgroundWorker.Enqueue(workId, funcProgramMake, funcOnErrorProgramMake); var work = new WorkBase(workId, workAction)
{
ExecutionTime = TimeSpan.FromMinutes(1),
OnErrorAsync = onErrorAction
};
backgroundWorker.Push(work);
} }
} }
} }
@ -513,7 +515,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token) private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{ {
var workId = MakeWorkId(idWell); var workId = MakeWorkId(idWell);
backgroundWorker.TryRemove(workId); backgroundWorker.Delete(workId);
var filesIds = await context.Files var filesIds = await context.Files
.Where(f => f.IdWell == idWell && .Where(f => f.IdWell == idWell &&
@ -529,4 +531,5 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private static string MakeWorkId(int idWell) private static string MakeWorkId(int idWell)
=> $"Make drilling program for wellId {idWell}"; => $"Make drilling program for wellId {idWell}";
} }
#nullable disable
} }

View File

@ -8,26 +8,28 @@ using System.Linq;
using System.Net.Mail; using System.Net.Mail;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudInfrastructure.Background;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
#nullable enable
public class EmailService : IEmailService public class EmailService : IEmailService
{ {
private readonly IBackgroundWorkerService backgroundWorker; private readonly BackgroundWorker backgroundWorker;
private readonly bool IsConfigured; private readonly bool IsConfigured;
private readonly string sender; private readonly string sender;
private readonly string smtpServer; private readonly string smtpServer;
private readonly string smtpPassword; private readonly string smtpPassword;
public EmailService(IBackgroundWorkerService backgroundWorker, IConfiguration configuration) public EmailService(BackgroundWorker backgroundWorker, IConfiguration configuration)
{ {
sender = configuration.GetValue<string>("email:sender", null); sender = configuration.GetValue("email:sender", string.Empty);
smtpPassword = configuration.GetValue<string>("email:password", null); smtpPassword = configuration.GetValue("email:password", string.Empty);
smtpServer = configuration.GetValue<string>("email:smtpServer", null); smtpServer = configuration.GetValue("email:smtpServer", string.Empty);
var configError = (string.IsNullOrEmpty(sender) || var configError = string.IsNullOrEmpty(sender) ||
string.IsNullOrEmpty(smtpPassword) || string.IsNullOrEmpty(smtpPassword) ||
string.IsNullOrEmpty(smtpServer)); string.IsNullOrEmpty(smtpServer);
IsConfigured = !configError; IsConfigured = !configError;
@ -44,20 +46,21 @@ namespace AsbCloudInfrastructure.Services
Trace.TraceWarning("smtp is not configured"); Trace.TraceWarning("smtp is not configured");
return; return;
} }
var jobId = CalcJobId(addresses, subject, htmlBody); var workId = MakeWorkId(addresses, subject, htmlBody);
if (!backgroundWorker.Contains(jobId)) if (!backgroundWorker.Contains(workId))
{ {
var action = MakeEmailSendJobAsync(addresses, subject, htmlBody); var workAction = MakeEmailSendWorkAction(addresses, subject, htmlBody);
backgroundWorker.Enqueue(jobId, action); var work = new WorkBase(workId, workAction);
backgroundWorker.Push(work);
} }
} }
private Func<string, CancellationToken, Task> MakeEmailSendJobAsync(IEnumerable<string> addresses, string subject, string htmlBody) private Func<string, IServiceProvider, CancellationToken, Task> MakeEmailSendWorkAction(IEnumerable<string> addresses, string subject, string htmlBody)
{ {
var mailAddresses = new List<MailAddress>(); var mailAddresses = new List<MailAddress>();
foreach (var address in addresses) foreach (var address in addresses)
{ {
if (MailAddress.TryCreate(address, out MailAddress mailAddress)) if (MailAddress.TryCreate(address, out MailAddress? mailAddress))
mailAddresses.Add(mailAddress); mailAddresses.Add(mailAddress);
else else
Trace.TraceWarning($"Mail {address} is not correct."); Trace.TraceWarning($"Mail {address} is not correct.");
@ -69,16 +72,16 @@ namespace AsbCloudInfrastructure.Services
if (string.IsNullOrEmpty(subject)) if (string.IsNullOrEmpty(subject))
throw new ArgumentInvalidException($"{nameof(subject)} should be set", nameof(subject)); throw new ArgumentInvalidException($"{nameof(subject)} should be set", nameof(subject));
var func = async (string id, CancellationToken token) => var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) =>
{ {
var from = new MailAddress(sender); var from = new MailAddress(sender);
var message = new MailMessage
var message = new MailMessage(); {
message.From = from; From = from
};
foreach (var mailAddress in mailAddresses) foreach (var mailAddress in mailAddresses)
message.To.Add(mailAddress); message.To.Add(mailAddress);
//message.To.Add("support@digitaldrilling.ru");
message.BodyEncoding = System.Text.Encoding.UTF8; message.BodyEncoding = System.Text.Encoding.UTF8;
message.Body = htmlBody; message.Body = htmlBody;
@ -91,12 +94,12 @@ namespace AsbCloudInfrastructure.Services
client.Credentials = new System.Net.NetworkCredential(sender, smtpPassword); client.Credentials = new System.Net.NetworkCredential(sender, smtpPassword);
await client.SendMailAsync(message, token); await client.SendMailAsync(message, token);
Trace.TraceInformation($"Send email to {string.Join(',', addresses)} subj:{subject} html body count {htmlBody.Count()}"); Trace.TraceInformation($"Send email to {string.Join(',', addresses)} subj:{subject} html body count {htmlBody.Length}");
}; };
return func; return workAction;
} }
private string CalcJobId(IEnumerable<string> addresses, string subject, string content) private static string MakeWorkId(IEnumerable<string> addresses, string subject, string content)
{ {
var hash = GetHashCode(addresses); var hash = GetHashCode(addresses);
hash ^= subject.GetHashCode(); hash ^= subject.GetHashCode();
@ -114,4 +117,5 @@ namespace AsbCloudInfrastructure.Services
return hash; return hash;
} }
} }
#nullable disable
} }

View File

@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
public class FileCategoryService : CrudCacheServiceBase<FileCategoryDto, FileCategory>, IFileCategoryService public class FileCategoryService : CrudCacheRepositoryBase<FileCategoryDto, FileCategory>, IFileCategoryService
{ {
public FileCategoryService(IAsbCloudDbContext context, IMemoryCache memoryCache) public FileCategoryService(IAsbCloudDbContext context, IMemoryCache memoryCache)
: base(context, memoryCache) { } : base(context, memoryCache) { }
@ -22,7 +22,8 @@ namespace AsbCloudInfrastructure.Services
.ConfigureAwait(false); .ConfigureAwait(false);
var dtos = cache var dtos = cache
.Where(f => f.Id >= 10000) .Where(f => f.Id >= 10000)
.Where(f => f.Id <= 20000); .Where(f => f.Id <= 20000)
.Select(Convert);
return dtos; return dtos;
} }

View File

@ -1,61 +1,37 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System; using System;
using System.Data.Common; using System.Data.Common;
using System.Data; using System.Data;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
#nullable enable #nullable enable
internal class LimitingParameterBackgroundService : BackgroundService internal static class LimitingParameterCalcWorkFactory
{ {
private readonly string connectionString; private const string workId = "Limiting parameter calc";
private readonly TimeSpan period = TimeSpan.FromHours(1); private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
public LimitingParameterBackgroundService(IConfiguration configuration) public static WorkPeriodic MakeWork()
{ {
connectionString = configuration.GetConnectionString("DefaultConnection"); var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
}
protected override async Task ExecuteAsync(CancellationToken token)
{
var timeToStart = DateTime.Now;
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(connectionString)
.Options;
while (!token.IsCancellationRequested)
{ {
if (DateTime.Now > timeToStart) Timeout = TimeSpan.FromMinutes(30)
{ };
timeToStart = DateTime.Now + period; return workPeriodic;
try
{
using var context = new AsbCloudDbContext(options);
var added = await LimitingParameterAsync(context, token);
Trace.TraceInformation($"Total limiting parameter complete. Added {added} limiting parameters.");
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
}
GC.Collect();
}
var ms = (int)(timeToStart - DateTime.Now).TotalMilliseconds;
ms = ms > 100 ? ms : 100;
await Task.Delay(ms, token).ConfigureAwait(false);
}
} }
private static async Task<int> LimitingParameterAsync(IAsbCloudDbContext context, CancellationToken token) // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token)
{ {
var lastDetectedDates = await context.LimitingParameter using var db = serviceProvider.GetRequiredService<IAsbCloudDbContext>();
var lastDetectedDates = await db.LimitingParameter
.GroupBy(o => o.IdTelemetry) .GroupBy(o => o.IdTelemetry)
.Select(g => new .Select(g => new
{ {
@ -64,7 +40,7 @@ namespace AsbCloudInfrastructure.Services
}) })
.ToListAsync(token); .ToListAsync(token);
var telemetryIds = await context.Telemetries var telemetryIds = await db.Telemetries
.Where(t => t.Info != null && t.TimeZone != null) .Where(t => t.Info != null && t.TimeZone != null)
.Select(t => t.Id) .Select(t => t.Id)
.ToListAsync(token); .ToListAsync(token);
@ -79,17 +55,15 @@ namespace AsbCloudInfrastructure.Services
inner.SingleOrDefault()?.LastDate, inner.SingleOrDefault()?.LastDate,
}); });
var affected = 0;
foreach (var item in telemetryLastDetectedDates) foreach (var item in telemetryLastDetectedDates)
{ {
var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, context, token); var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
if (newLimitingParameters?.Any() == true) if (newLimitingParameters?.Any() == true)
{ {
context.LimitingParameter.AddRange(newLimitingParameters); db.LimitingParameter.AddRange(newLimitingParameters);
affected += await context.SaveChangesAsync(token); await db.SaveChangesAsync(token);
} }
} }
return affected;
} }
private static async Task<IEnumerable<LimitingParameter>> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) private static async Task<IEnumerable<LimitingParameter>> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)

View File

@ -15,7 +15,7 @@ namespace AsbCloudInfrastructure.Services
{ {
private readonly ILimitingParameterRepository limitingParameterRepository; private readonly ILimitingParameterRepository limitingParameterRepository;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly Dictionary<int, string> feedRegulatorData = new Dictionary<int, string>() private readonly Dictionary<int, string> feedRegulatorData = new ()
{ {
{ 0, "Нет ограничения" }, { 0, "Нет ограничения" },
{ 1, "МСП" }, { 1, "МСП" },
@ -63,6 +63,11 @@ namespace AsbCloudInfrastructure.Services
return result; return result;
} }
public Dictionary<int, string> GetLimitingParameteraNames() //TODO: Перенести получение ограничений в репозиторий
{
return feedRegulatorData;
}
private IEnumerable<LimitingParameterDataDto> TrimLimitingParameters(IEnumerable<LimitingParameterDataDto> data, LimitingParameterRequest request) private IEnumerable<LimitingParameterDataDto> TrimLimitingParameters(IEnumerable<LimitingParameterDataDto> data, LimitingParameterRequest request)
{ {
var result = data.Select((x) => var result = data.Select((x) =>

View File

@ -1,11 +1,11 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Background;
using AsbSaubReport; using AsbSaubReport;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -15,30 +15,32 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
#nullable enable
public class ReportService : IReportService public class ReportService : IReportService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly string connectionString;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IBackgroundWorkerService backgroundWorkerService; private readonly BackgroundWorker backgroundWorkerService;
public ReportService(IAsbCloudDbContext db, IConfiguration configuration,
ITelemetryService telemetryService, IWellService wellService, IBackgroundWorkerService backgroundWorkerService)
{
this.db = db;
this.connectionString = configuration.GetConnectionString("DefaultConnection");
this.wellService = wellService;
this.backgroundWorkerService = backgroundWorkerService;
this.telemetryService = telemetryService;
ReportCategoryId = db.FileCategories.AsNoTracking()
.FirstOrDefault(c =>
c.Name.Equals("Рапорт")).Id;
}
public int ReportCategoryId { get; private set; } public int ReportCategoryId { get; private set; }
public string CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin, public ReportService(IAsbCloudDbContext db,
ITelemetryService telemetryService,
IWellService wellService,
BackgroundWorker backgroundWorkerService)
{
this.db = db;
this.wellService = wellService;
this.backgroundWorkerService = backgroundWorkerService;
this.telemetryService = telemetryService;
ReportCategoryId = db.FileCategories
.AsNoTracking()
.First(c => c.Name.Equals("Рапорт"))
.Id;
}
public string EnqueueCreateReportWork(int idWell, int idUser, int stepSeconds, int format, DateTime begin,
DateTime end, Action<object, string> progressHandler) DateTime end, Action<object, string> progressHandler)
{ {
var timezoneOffset = wellService.GetTimezone(idWell).Hours; var timezoneOffset = wellService.GetTimezone(idWell).Hours;
@ -47,12 +49,12 @@ namespace AsbCloudInfrastructure.Services
var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset); var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset);
var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset); var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset);
var newReportId = backgroundWorkerService.Enqueue(async (id, token) => var workId = $"create report by wellid:{idWell} for userid:{idUser} requested at {DateTime.Now}";
var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) =>
{ {
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>() using var context = serviceProvider.GetRequiredService<IAsbCloudDbContext>();
.UseNpgsql(connectionString) var fileService = serviceProvider.GetRequiredService<FileService>();
.Options;
using var context = new AsbCloudDbContext(contextOptions);
var tempDir = Path.Combine(Path.GetTempPath(), "report"); var tempDir = Path.Combine(Path.GetTempPath(), "report");
@ -66,10 +68,7 @@ namespace AsbCloudInfrastructure.Services
}; };
generator.Make(reportFileName); generator.Make(reportFileName);
var fileRepository = new FileRepository(context); var fileInfo = (await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token))!;
var fileStorageRepository = new FileStorageRepository();
var fileService = new FileService(fileRepository, fileStorageRepository);
var fileInfo = await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token);
progressHandler.Invoke(new progressHandler.Invoke(new
{ {
@ -91,13 +90,17 @@ namespace AsbCloudInfrastructure.Services
}; };
context.ReportProperties.Add(newReportProperties); context.ReportProperties.Add(newReportProperties);
context.SaveChanges(); context.SaveChanges();
}); };
var work = new WorkBase(workId, workAction);
backgroundWorkerService.Push(work);
progressHandler.Invoke(new ReportProgressDto progressHandler.Invoke(new ReportProgressDto
{ {
Operation = "Ожидает начала в очереди.", Operation = "Ожидает начала в очереди.",
Progress = 0f, Progress = 0f,
}, newReportId); }, workId);
return newReportId; return workId;
} }
public int GetReportPagesCount(int idWell, DateTime begin, DateTime end, int stepSeconds, int format) public int GetReportPagesCount(int idWell, DateTime begin, DateTime end, int stepSeconds, int format)
@ -106,12 +109,12 @@ namespace AsbCloudInfrastructure.Services
var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset); var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset);
var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset); var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset);
var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, (AsbCloudDbContext)db); var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, db);
var pagesCount = generator.GetPagesCount(); var pagesCount = generator.GetPagesCount();
return pagesCount; return pagesCount;
} }
public DatesRangeDto GetDatesRangeOrDefault(int idWell) public DatesRangeDto? GetDatesRangeOrDefault(int idWell)
{ {
var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell); var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell);
if (idTelemetry is null) if (idTelemetry is null)
@ -128,8 +131,8 @@ namespace AsbCloudInfrastructure.Services
.OrderBy(o => o.File.UploadDate) .OrderBy(o => o.File.UploadDate)
.AsNoTracking() .AsNoTracking()
.Take(1024); .Take(1024);
var properties = await propertiesQuery.ToListAsync(token); var entities = await propertiesQuery.ToListAsync(token);
return properties.Select(p => new ReportPropertiesDto var dtos = entities.Select(p => new ReportPropertiesDto
{ {
Id = p.Id, Id = p.Id,
Name = p.File.Name, Name = p.File.Name,
@ -151,10 +154,11 @@ namespace AsbCloudInfrastructure.Services
Step = p.Step, Step = p.Step,
Format = p.Format == 0 ? ".pdf" : ".las" Format = p.Format == 0 ? ".pdf" : ".las"
}); });
return dtos;
} }
private static IReportGenerator GetReportGenerator(int idWell, DateTime begin, private static IReportGenerator GetReportGenerator(int idWell, DateTime begin,
DateTime end, int stepSeconds, int format, AsbCloudDbContext context) DateTime end, int stepSeconds, int format, IAsbCloudDbContext context)
{ {
var dataSource = new ReportDataSourcePgCloud(context, idWell); var dataSource = new ReportDataSourcePgCloud(context, idWell);
IReportGenerator generator = format switch IReportGenerator generator = format switch
@ -173,4 +177,5 @@ namespace AsbCloudInfrastructure.Services
return generator; return generator;
} }
} }
#nullable disable
} }

View File

@ -14,7 +14,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
public class TelemetryDataCache<TDto> public class TelemetryDataCache<TDto>
where TDto : AsbCloudApp.Data.ITelemetryData where TDto : AsbCloudApp.Data.ITelemetryData
{ {
private const int activeWellCapacity = 24 * 60 * 60; private const int activeWellCapacity = 12 * 60 * 60;
private const int doneWellCapacity = 65 * 60; private const int doneWellCapacity = 65 * 60;
private readonly ConcurrentDictionary<int, CyclycArray<TDto>> caches; private readonly ConcurrentDictionary<int, CyclycArray<TDto>> caches;

View File

@ -41,6 +41,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache) public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache)
{ {
// TODO: make this background work
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>() var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(configuration.GetConnectionString("DefaultConnection")) .UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
.Options; .Options;

View File

@ -1,9 +1,9 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudDb.Model.Subsystems; using AsbCloudDb.Model.Subsystems;
using AsbCloudInfrastructure.Background;
using AsbCloudInfrastructure.Services.Subsystems.Utils; using AsbCloudInfrastructure.Services.Subsystems.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
@ -16,56 +16,30 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.Subsystems namespace AsbCloudInfrastructure.Services.Subsystems
{ {
#nullable enable #nullable enable
internal class SubsystemOperationTimeBackgroundService : BackgroundService internal static class SubsystemOperationTimeCalcWorkFactory
{ {
private readonly string connectionString; private const string workId = "Subsystem operation time calc";
private readonly TimeSpan period = TimeSpan.FromHours(1); private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
private const int idSubsytemTorqueMaster = 65537; private const int idSubsytemTorqueMaster = 65537;
private const int idSubsytemSpinMaster = 65536; private const int idSubsytemSpinMaster = 65536;
private const int idSubsytemAkb = 1; private const int idSubsytemAkb = 1;
private const int idSubsytemMse = 2; private const int idSubsytemMse = 2;
public SubsystemOperationTimeBackgroundService(IConfiguration configuration) public static WorkPeriodic MakeWork()
{ {
connectionString = configuration.GetConnectionString("DefaultConnection"); var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
}
protected override async Task ExecuteAsync(CancellationToken token)
{
var timeToStart = DateTime.Now;
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(connectionString)
.Options;
while (!token.IsCancellationRequested)
{ {
if (DateTime.Now > timeToStart) Timeout = TimeSpan.FromMinutes(30)
{ };
timeToStart = DateTime.Now + period; return workPeriodic;
try
{
using var context = new AsbCloudDbContext(options);
var added = await OperationTimeAllTelemetriesAsync(context, token);
Trace.TraceInformation($"Total subsystem operation time complete. Added {added} operations time.");
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
}
GC.Collect();
}
var ms = (int)(timeToStart - DateTime.Now).TotalMilliseconds;
ms = ms > 100 ? ms : 100;
await Task.Delay(ms, token).ConfigureAwait(false);
}
} }
public override async Task StopAsync(CancellationToken token) // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД.
private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token)
{ {
await base.StopAsync(token).ConfigureAwait(false); using var db = serviceProvider.GetRequiredService<IAsbCloudDbContext>();
}
private static async Task<int> OperationTimeAllTelemetriesAsync(IAsbCloudDbContext db, CancellationToken token)
{
var lastDetectedDates = await db.SubsystemOperationTimes var lastDetectedDates = await db.SubsystemOperationTimes
.GroupBy(o => o.IdTelemetry) .GroupBy(o => o.IdTelemetry)
.Select(g => new .Select(g => new
@ -90,23 +64,21 @@ namespace AsbCloudInfrastructure.Services.Subsystems
inner.SingleOrDefault()?.LastDate, inner.SingleOrDefault()?.LastDate,
}); });
var affected = 0;
foreach (var item in telemetryLastDetectedDates) foreach (var item in telemetryLastDetectedDates)
{ {
var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
if (newOperationsSaub?.Any() == true) if (newOperationsSaub?.Any() == true)
{ {
db.SubsystemOperationTimes.AddRange(newOperationsSaub); db.SubsystemOperationTimes.AddRange(newOperationsSaub);
affected += await db.SaveChangesAsync(token); await db.SaveChangesAsync(token);
} }
var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token);
if (newOperationsSpin?.Any() == true) if (newOperationsSpin?.Any() == true)
{ {
db.SubsystemOperationTimes.AddRange(newOperationsSpin); db.SubsystemOperationTimes.AddRange(newOperationsSpin);
affected += await db.SaveChangesAsync(token); await db.SaveChangesAsync(token);
} }
} }
return affected;
} }
private static async Task<DbDataReader> ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) private static async Task<DbDataReader> ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token)

View File

@ -23,13 +23,13 @@ namespace AsbCloudInfrastructure.Services.Subsystems
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly ICrudService<SubsystemDto> subsystemService; private readonly ICrudRepository<SubsystemDto> subsystemService;
private readonly IDetectedOperationService detectedOperationService; private readonly IDetectedOperationService detectedOperationService;
public const int IdSubsystemAKB = 1; public const int IdSubsystemAKB = 1;
public const int IdSubsystemMSE = 2; public const int IdSubsystemMSE = 2;
public const int IdSubsystemSpin = 65536; public const int IdSubsystemSpin = 65536;
public const int IdSubsystemTorque = 65537; public const int IdSubsystemTorque = 65537;
public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudService<SubsystemDto> subsystemService, IDetectedOperationService detectedOperationService) public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudRepository<SubsystemDto> subsystemService, IDetectedOperationService detectedOperationService)
{ {
this.db = db; this.db = db;
this.wellService = wellService; this.wellService = wellService;

View File

@ -15,7 +15,7 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.Subsystems namespace AsbCloudInfrastructure.Services.Subsystems
{ {
#nullable enable #nullable enable
internal class SubsystemService : CrudCacheServiceBase<SubsystemDto, Subsystem>, ISubsystemService internal class SubsystemService : CrudCacheRepositoryBase<SubsystemDto, Subsystem>, ISubsystemService
{ {
private readonly IWellService wellService; private readonly IWellService wellService;
public SubsystemService(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, IWellService wellService) : base(dbContext, memoryCache) public SubsystemService(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, IWellService wellService) : base(dbContext, memoryCache)

View File

@ -15,13 +15,13 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
public class WellService : CrudCacheServiceBase<WellDto, Well>, IWellService public class WellService : CrudCacheRepositoryBase<WellDto, Well>, IWellService
{ {
private const string relationCompaniesWellsCacheTag = "RelationCompaniesWells"; private const string relationCompaniesWellsCacheTag = "RelationCompaniesWells";
private static readonly TimeSpan relationCompaniesWellsCacheObsolence = TimeSpan.FromMinutes(15); private static readonly TimeSpan relationCompaniesWellsCacheObsolence = TimeSpan.FromMinutes(15);
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
private readonly ICrudService<CompanyTypeDto> companyTypesService; private readonly ICrudRepository<CompanyTypeDto> companyTypesService;
private readonly ITimezoneService timezoneService; private readonly ITimezoneService timezoneService;
private readonly IWellOperationService wellOperationService; private readonly IWellOperationService wellOperationService;
@ -43,7 +43,7 @@ namespace AsbCloudInfrastructure.Services
this.timezoneService = timezoneService; this.timezoneService = timezoneService;
this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this); this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this);
companyTypesService = new CrudCacheServiceBase<CompanyTypeDto, CompanyType>(dbContext, memoryCache); companyTypesService = new CrudCacheRepositoryBase<CompanyTypeDto, CompanyType>(dbContext, memoryCache);
} }
private IEnumerable<RelationCompanyWell> GetCacheRelationCompanyWell() private IEnumerable<RelationCompanyWell> GetCacheRelationCompanyWell()
@ -81,9 +81,10 @@ namespace AsbCloudInfrastructure.Services
.Select(r => r.IdWell); .Select(r => r.IdWell);
var wellsDtos = (await GetCacheAsync(token)) var wellsDtos = (await GetCacheAsync(token))
.Where(w => wellsIds.Contains(w.Id)); .Where(w => wellsIds.Contains(w.Id))
.Select(Convert);
return wellsDtos.ToList(); return wellsDtos;
} }
public override async Task<int> InsertAsync(WellDto dto, CancellationToken token = default) public override async Task<int> InsertAsync(WellDto dto, CancellationToken token = default)
@ -155,8 +156,7 @@ namespace AsbCloudInfrastructure.Services
public async Task<string> GetWellCaptionByIdAsync(int idWell, CancellationToken token) public async Task<string> GetWellCaptionByIdAsync(int idWell, CancellationToken token)
{ {
var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false); var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false);
var dto = Convert(entity); return entity!.Caption;
return dto.Caption;
} }
public async Task<IEnumerable<CompanyDto>> GetCompaniesAsync(int idWell, CancellationToken token) public async Task<IEnumerable<CompanyDto>> GetCompaniesAsync(int idWell, CancellationToken token)

View File

@ -1,26 +1,72 @@
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services.Subsystems;
using AsbCloudInfrastructure.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using System; using System;
using System.Linq; using System.Threading.Tasks;
using System.Threading;
using AsbCloudInfrastructure.Background;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure
{ {
public class Startup public class Startup
{ {
public static void BeforeRunHandler(IHost host, IConfigurationRoot configuration) public static void BeforeRunHandler(IHost host)
{ {
using var scope = host.Services.CreateScope(); using var scope = host.Services.CreateScope();
var context = scope.ServiceProvider.GetService<IAsbCloudDbContext>(); var provider = scope.ServiceProvider;
context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60));
var context = provider.GetService<IAsbCloudDbContext>();
context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60));
context.Database.Migrate(); context.Database.Migrate();
var wellService = scope.ServiceProvider.GetService<IWellService>(); var wellService = provider.GetRequiredService<IWellService>();
wellService.EnshureTimezonesIsSetAsync(System.Threading.CancellationToken.None).Wait(); wellService.EnshureTimezonesIsSetAsync(CancellationToken.None).Wait();// TODO: make this background work
var backgroundWorker = provider.GetRequiredService<Background.BackgroundWorker>();
backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork());
backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork());
backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork());
backgroundWorker.Push(MakeMemoryMonitoringWork());
Task.Delay(1_000)
.ContinueWith(async (_) => await backgroundWorker.StartAsync(CancellationToken.None));
}
static WorkPeriodic MakeMemoryMonitoringWork()
{
var workId = "Memory monitoring";
var workAction = (string _, IServiceProvider _, CancellationToken _) => {
var bytes = GC.GetTotalMemory(false);
var bytesString = FromatBytes(bytes);
System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDb.Model.AsbCloudDbContext.ReferenceCount}");
return Task.CompletedTask;
};
var workPeriod = TimeSpan.FromMinutes(1);
var work = new WorkPeriodic(workId, workAction, workPeriod);
return work;
}
static string FromatBytes(long bytes)
{
const double gigaByte = 1024 * 1024 * 1024;
const double megaByte = 1024 * 1024;
const double kiloByte = 1024;
if (bytes > 10 * gigaByte)
return (bytes / gigaByte).ToString("### ### ###.## Gb");
if (bytes > 10 * megaByte)
return (bytes / megaByte).ToString("### ### ###.## Mb");
if (bytes > 10 * kiloByte)
return (bytes / megaByte).ToString("### ### ###.## Kb");
return bytes.ToString("### ### ###");
} }
} }
} }

View File

@ -11,7 +11,7 @@ namespace AsbCloudWebApi.Tests
{ {
public static Mock<TRepository> Make<TRepository, TDto>(ICollection<TDto> data) public static Mock<TRepository> Make<TRepository, TDto>(ICollection<TDto> data)
where TDto : AsbCloudApp.Data.IId where TDto : AsbCloudApp.Data.IId
where TRepository : class, ICrudService<TDto> where TRepository : class, ICrudRepository<TDto>
{ {
var repositoryMock = new Mock<TRepository>(); var repositoryMock = new Mock<TRepository>();

View File

@ -0,0 +1,244 @@
using Microsoft.Extensions.DependencyInjection;
using Moq;
using System;
using AsbCloudInfrastructure.Background;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace AsbCloudWebApi.Tests.ServicesTests
{
public class BackgroundWorkerTest
{
private readonly Mock<IServiceProvider> mockServiceProvider;
private readonly Mock<IServiceScopeFactory> mockServiceScopeFactory;
private readonly Func<string, IServiceProvider, CancellationToken, Task> someAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask;
public BackgroundWorkerTest()
{
var mockServiceScope = new Mock<IServiceScope>();
mockServiceScopeFactory = new Mock<IServiceScopeFactory>();
mockServiceProvider = new Mock<IServiceProvider>();
mockServiceScope.SetReturnsDefault(mockServiceProvider.Object);
mockServiceProvider.SetReturnsDefault(mockServiceScopeFactory.Object);
mockServiceProvider.Setup(s => s.GetService(It.IsAny<Type>()))
.Returns(mockServiceScopeFactory.Object);
mockServiceScopeFactory.SetReturnsDefault(mockServiceScope.Object);
}
[Fact]
public void Contains_returns_true()
{
mockServiceScopeFactory.Invocations.Clear();
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
const string work1Id = "long name 1";
const string work2Id = "long name 2";
var work1 = new WorkBase(work1Id, someAction);
var work2 = new WorkPeriodic(work2Id, someAction, TimeSpan.Zero);
BackgroundWorker.Push(work1);
BackgroundWorker.Push(work2);
Assert.True(BackgroundWorker.Contains(work1Id));
Assert.True(BackgroundWorker.Contains(work2Id));
Assert.False(BackgroundWorker.Contains(work2Id + work1Id));
Assert.False(BackgroundWorker.Contains(string.Empty));
}
[Fact]
public async Task Push_makes_new_scope_after_start()
{
mockServiceScopeFactory.Invocations.Clear();
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var work = new WorkBase("", someAction);
BackgroundWorker.Push(work);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(10);
mockServiceScopeFactory.Verify(f => f.CreateScope());
}
[Fact]
public async Task Makes_primary_work_done()
{
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var workDone = false;
var work = new WorkBase("", (_, _, _) =>
{
workDone = true;
return Task.CompletedTask;
});
BackgroundWorker.Push(work);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(10);
Assert.True(workDone);
}
[Fact]
public async Task Sets_ExecutionTime_after_work_done()
{
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var work = new WorkBase("", someAction);
BackgroundWorker.Push(work);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(10);
Assert.True(work.ExecutionTime > TimeSpan.Zero);
}
[Fact]
public async Task Makes_periodic_work_done()
{
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var workDone = false;
var work = new WorkPeriodic("", (_, _, _) =>
{
workDone = true;
return Task.CompletedTask;
},
TimeSpan.FromMilliseconds(10));
BackgroundWorker.Push(work);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(20);
Assert.True(workDone);
}
[Fact]
public async Task Does_not_start_periodic_work()
{
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var workDone = false;
var work = new WorkPeriodic("", (_, _, _) =>
{
workDone = true;
return Task.CompletedTask;
},
TimeSpan.FromSeconds(30))
{
LastStart = DateTime.Now
};
BackgroundWorker.Push(work);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(20);
Assert.False(workDone);
}
[Fact]
public async Task Follows_work_priority()
{
var order = 0;
var work1Order = -1;
var work2Order = -1;
var work1 = new WorkPeriodic("1", (_, _, _) =>
{
work1Order = order++;
return Task.CompletedTask;
},
TimeSpan.FromMilliseconds(1)
);
var work2 = new WorkBase("2", (_, _, _) =>
{
work2Order = order++;
return Task.CompletedTask;
});
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
BackgroundWorker.Push(work2);
BackgroundWorker.Push(work1);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(2_100);
Assert.True(work2Order < work1Order);
}
[Fact]
public async Task Runs_second_after_delete_first()
{
var workDone = false;
var work1 = new WorkBase("1", someAction);
var work2 = new WorkPeriodic("2", (_, _, _) =>
{
workDone = true;
return Task.CompletedTask;
}, TimeSpan.FromMilliseconds(1));
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
BackgroundWorker.Push(work1);
BackgroundWorker.Push(work2);
BackgroundWorker.Delete("1");
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(10);
Assert.True(workDone);
}
[Fact]
public async Task Aborts_long_work()
{
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var workCanceled = false;
var work = new WorkBase("", async (_, _, token) => await Task.Delay(1000000, token))
{
Timeout = TimeSpan.FromMilliseconds(1),
OnErrorAsync = async (id, ex, token) =>
{
workCanceled = ex is System.TimeoutException;
await Task.CompletedTask;
}
};
BackgroundWorker.Push(work);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(20 * 4);
Assert.True(workCanceled);
}
[Fact]
public async Task Execution_continues_after_work_exception()
{
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
var work2done = false;
var work1 = new WorkBase("1", (_, _, _) => throw new Exception());
var work2 = new WorkBase("2", (_, _, _) =>
{
work2done = true;
return Task.CompletedTask;
});
BackgroundWorker.Push(work1);
BackgroundWorker.Push(work2);
await BackgroundWorker.StartAsync(CancellationToken.None);
await Task.Delay(2_100);
Assert.True(work2done);
}
[Fact]
public void Push_not_unique_id_should_throw()
{
var work1 = new WorkPeriodic("1", someAction, TimeSpan.FromSeconds(30));
var work2 = new WorkBase("1", someAction);
var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object);
BackgroundWorker.Push(work1);
Assert.Throws<ArgumentException>(
() => BackgroundWorker.Push(work2));
}
}
}

View File

@ -9,7 +9,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public abstract class CrudServiceTestAbstract<TDto> public abstract class CrudServiceTestAbstract<TDto>
where TDto : AsbCloudApp.Data.IId where TDto : AsbCloudApp.Data.IId
{ {
private readonly ICrudService<TDto> service; private readonly ICrudRepository<TDto> service;
public CrudServiceTestAbstract() public CrudServiceTestAbstract()
{ {
@ -17,7 +17,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
service = MakeService(); service = MakeService();
} }
protected abstract ICrudService<TDto> MakeService(); protected abstract ICrudRepository<TDto> MakeService();
protected abstract TDto MakeNewItem(); protected abstract TDto MakeNewItem();
[Fact] [Fact]

View File

@ -19,10 +19,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests
return item; return item;
} }
protected override ICrudService<DepositDto> MakeService() protected override ICrudRepository<DepositDto> MakeService()
{ {
var dbContext = TestHelpter.MakeRealTestContext(); var dbContext = TestHelpter.MakeRealTestContext();
return new CrudCacheServiceBase<DepositDto, Deposit>(dbContext, TestHelpter.MemoryCache); return new CrudCacheRepositoryBase<DepositDto, Deposit>(dbContext, TestHelpter.MemoryCache);
} }
} }
} }

View File

@ -27,11 +27,11 @@ namespace AsbCloudWebApi.Tests.ServicesTests
Surname = "Тестович" Surname = "Тестович"
} }
}; };
private ICrudService<DrillerDto> service; private ICrudRepository<DrillerDto> service;
public DrillerServiceTest() public DrillerServiceTest()
{ {
var repositoryMock = RepositoryFactory.Make<ICrudService<DrillerDto>, DrillerDto>(Drillers); var repositoryMock = RepositoryFactory.Make<ICrudRepository<DrillerDto>, DrillerDto>(Drillers);
repositoryMock.Setup(x => x.GetAllAsync(It.IsAny<CancellationToken>())) repositoryMock.Setup(x => x.GetAllAsync(It.IsAny<CancellationToken>()))
.Returns(() => { .Returns(() => {

View File

@ -2,7 +2,7 @@
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Background;
using AsbCloudInfrastructure.Services.DrillingProgram; using AsbCloudInfrastructure.Services.DrillingProgram;
using Mapster; using Mapster;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -83,8 +83,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests
private readonly Mock<IUserRepository> userRepositoryMock; private readonly Mock<IUserRepository> userRepositoryMock;
private readonly Mock<IWellService> wellServiceMock; private readonly Mock<IWellService> wellServiceMock;
private readonly Mock<IConfiguration> configurationMock; private readonly Mock<IConfiguration> configurationMock;
private readonly Mock<IBackgroundWorkerService> backgroundWorkerMock; private readonly Mock<BackgroundWorker> backgroundWorkerMock;
private readonly Mock<IEmailService> emailService; private readonly Mock<IEmailService> emailServiceMock;
public DrillingProgramServiceTest() public DrillingProgramServiceTest()
{ {
@ -102,7 +102,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests
userRepositoryMock = new Mock<IUserRepository>(); userRepositoryMock = new Mock<IUserRepository>();
wellServiceMock = new Mock<IWellService>(); wellServiceMock = new Mock<IWellService>();
configurationMock = new Mock<IConfiguration>(); configurationMock = new Mock<IConfiguration>();
backgroundWorkerMock = new Mock<IBackgroundWorkerService>(); backgroundWorkerMock = new Mock<BackgroundWorker>();
emailServiceMock = new Mock<IEmailService>();
} }
[Fact] [Fact]
@ -115,7 +116,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var users = await service.GetAvailableUsers(idWell, CancellationToken.None); var users = await service.GetAvailableUsers(idWell, CancellationToken.None);
@ -132,7 +133,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var result = await service.AddPartsAsync(idWell, new int[] { 1001, 1002 }, CancellationToken.None); var result = await service.AddPartsAsync(idWell, new int[] { 1001, 1002 }, CancellationToken.None);
@ -151,7 +152,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var result = await service.RemovePartsAsync(idWell, new int[] { 1005 }, CancellationToken.None); var result = await service.RemovePartsAsync(idWell, new int[] { 1005 }, CancellationToken.None);
@ -174,7 +175,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var result = await service.AddUserAsync(idWell, 1001, publisher1.Id, 1, CancellationToken.None); var result = await service.AddUserAsync(idWell, 1001, publisher1.Id, 1, CancellationToken.None);
@ -209,7 +210,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var result = await service.RemoveUserAsync(idWell, idFileCategory, publisher1.Id, idUserRole, CancellationToken.None); var result = await service.RemoveUserAsync(idWell, idFileCategory, publisher1.Id, idUserRole, CancellationToken.None);
@ -235,7 +236,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var fileMark = new FileMarkDto var fileMark = new FileMarkDto
{ {
@ -266,7 +267,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var fileMark = new FileMarkDto var fileMark = new FileMarkDto
{ {
IdFile = file1001.Id, IdFile = file1001.Id,
@ -304,7 +305,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var fileMark = new FileMarkDto var fileMark = new FileMarkDto
{ {
@ -331,7 +332,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
@ -358,12 +359,12 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
Assert.Equal(2, state.IdState); Assert.Equal(2, state.IdState);
backgroundWorkerMock.Verify(s => s.Enqueue(It.IsAny<Func<string, CancellationToken, Task>>())); backgroundWorkerMock.Verify(s => s.Push(It.IsAny<WorkBase>()));
} }
[Fact] [Fact]
@ -388,7 +389,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailService.Object); emailServiceMock.Object);
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);

View File

@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/cluster")] [Route("api/admin/cluster")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminClusterController : CrudController<ClusterDto, ICrudService<ClusterDto>> public class AdminClusterController : CrudController<ClusterDto, ICrudRepository<ClusterDto>>
{ {
public AdminClusterController(ICrudService<ClusterDto> service) public AdminClusterController(ICrudRepository<ClusterDto> service)
: base(service) : base(service)
{ } { }
} }

View File

@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/company")] [Route("api/admin/company")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminCompanyController : CrudController<CompanyDto, ICrudService<CompanyDto>> public class AdminCompanyController : CrudController<CompanyDto, ICrudRepository<CompanyDto>>
{ {
public AdminCompanyController(ICrudService<CompanyDto> service) public AdminCompanyController(ICrudRepository<CompanyDto> service)
: base(service) : base(service)
{ {
} }

View File

@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/companyType")] [Route("api/admin/companyType")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminCompanyTypeController : CrudController<CompanyTypeDto, ICrudService<CompanyTypeDto>> public class AdminCompanyTypeController : CrudController<CompanyTypeDto, ICrudRepository<CompanyTypeDto>>
{ {
public AdminCompanyTypeController(ICrudService<CompanyTypeDto> service) public AdminCompanyTypeController(ICrudRepository<CompanyTypeDto> service)
: base(service) : base(service)
{ } { }
} }

View File

@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/deposit")] [Route("api/admin/deposit")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminDepositController : CrudController<DepositDto, ICrudService<DepositDto>> public class AdminDepositController : CrudController<DepositDto, ICrudRepository<DepositDto>>
{ {
public AdminDepositController(ICrudService<DepositDto> service) public AdminDepositController(ICrudRepository<DepositDto> service)
: base(service) : base(service)
{ {
} }

View File

@ -15,9 +15,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/permission")] [Route("api/admin/permission")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminPermissionController : CrudController<PermissionDto, ICrudService<PermissionDto>> public class AdminPermissionController : CrudController<PermissionDto, ICrudRepository<PermissionDto>>
{ {
public AdminPermissionController(ICrudService<PermissionDto> service) public AdminPermissionController(ICrudRepository<PermissionDto> service)
: base(service) : base(service)
{ } { }

View File

@ -13,11 +13,11 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/telemetry")] [Route("api/admin/telemetry")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminTelemetryController : CrudController<TelemetryDto, ICrudService<TelemetryDto>> public class AdminTelemetryController : CrudController<TelemetryDto, ICrudRepository<TelemetryDto>>
{ {
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
public AdminTelemetryController(ICrudService<TelemetryDto> service, public AdminTelemetryController(ICrudRepository<TelemetryDto> service,
ITelemetryService telemetryService) ITelemetryService telemetryService)
: base(service) : base(service)
{ {

View File

@ -13,7 +13,7 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/user")] [Route("api/admin/user")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminUserController : CrudController<UserExtendedDto, ICrudService<UserExtendedDto>> public class AdminUserController : CrudController<UserExtendedDto, ICrudRepository<UserExtendedDto>>
{ {
public AdminUserController(IUserRepository service) public AdminUserController(IUserRepository service)
: base(service) : base(service)

View File

@ -13,7 +13,7 @@ namespace AsbCloudWebApi.Controllers
[Route("api/admin/well")] [Route("api/admin/well")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminWellController : CrudController<WellDto, ICrudService<WellDto>> public class AdminWellController : CrudController<WellDto, ICrudRepository<WellDto>>
{ {
public AdminWellController(IWellService service) public AdminWellController(IWellService service)
: base(service) : base(service)

View File

@ -21,7 +21,7 @@ namespace AsbCloudWebApi.Controllers
[Authorize] [Authorize]
public abstract class CrudController<T, TService> : ControllerBase public abstract class CrudController<T, TService> : ControllerBase
where T : IId where T : IId
where TService : ICrudService<T> where TService : ICrudRepository<T>
{ {
protected readonly TService service; protected readonly TService service;
@ -115,7 +115,7 @@ namespace AsbCloudWebApi.Controllers
return Forbid(); return Forbid();
var result = await service.UpdateAsync(value, token).ConfigureAwait(false); var result = await service.UpdateAsync(value, token).ConfigureAwait(false);
if (result == ICrudService<T>.ErrorIdNotFound) if (result == ICrudRepository<T>.ErrorIdNotFound)
return BadRequest($"id:{value.Id} does not exist in the db"); return BadRequest($"id:{value.Id} does not exist in the db");
return Ok(result); return Ok(result);
} }
@ -134,7 +134,7 @@ namespace AsbCloudWebApi.Controllers
return Forbid(); return Forbid();
var result = await service.DeleteAsync(id, token).ConfigureAwait(false); var result = await service.DeleteAsync(id, token).ConfigureAwait(false);
if (result == ICrudService<T>.ErrorIdNotFound) if (result == ICrudRepository<T>.ErrorIdNotFound)
return NoContent(); return NoContent();
return Ok(result); return Ok(result);
} }

View File

@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/driller")] [Route("api/driller")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class DrillerController : CrudController<DrillerDto, ICrudService<DrillerDto>> public class DrillerController : CrudController<DrillerDto, ICrudRepository<DrillerDto>>
{ {
public DrillerController(ICrudService<DrillerDto> service) public DrillerController(ICrudRepository<DrillerDto> service)
: base(service) : base(service)
{ } { }
} }

View File

@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers
[Route("api/[Controller]")] [Route("api/[Controller]")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class FileCategoryController : CrudController<FileCategoryDto, ICrudService<FileCategoryDto>> public class FileCategoryController : CrudController<FileCategoryDto, ICrudRepository<FileCategoryDto>>
{ {
public FileCategoryController(ICrudService<FileCategoryDto> service, IFileCategoryService fileCategoryService) public FileCategoryController(ICrudRepository<FileCategoryDto> service, IFileCategoryService fileCategoryService)
: base(service) : base(service)
{ {
} }

View File

@ -40,6 +40,18 @@ namespace AsbCloudWebApi.Controllers
return Ok(subsystemResult); return Ok(subsystemResult);
} }
/// <summary>
/// Получение словаря названий ограничений
/// </summary>
/// <returns></returns>
[HttpGet("names")]
[ProducesResponseType(typeof(Dictionary<int, string>), (int)System.Net.HttpStatusCode.OK)]
public IActionResult GetLimitingParameteraNames()
{
var feedRegulatorData = limitingParameterService.GetLimitingParameteraNames();
return Ok(feedRegulatorData);
}
protected async Task<bool> UserHasAccesToWellAsync(int idWell, CancellationToken token) protected async Task<bool> UserHasAccesToWellAsync(int idWell, CancellationToken token)
{ {
var idCompany = User.GetCompanyId(); var idCompany = User.GetCompanyId();

View File

@ -68,7 +68,7 @@ namespace AsbCloudWebApi.Controllers
).ConfigureAwait(false); ).ConfigureAwait(false);
}, token); }, token);
var id = reportService.CreateReport(idWell, (int)idUser, var id = reportService.EnqueueCreateReportWork(idWell, (int)idUser,
stepSeconds, format, begin, end, HandleReportProgressAsync); stepSeconds, format, begin, end, HandleReportProgressAsync);
return Ok(id); return Ok(id);

View File

@ -12,9 +12,9 @@ namespace AsbCloudWebApi.Controllers.Subsystems
[Route("api/admin/subsystem")] [Route("api/admin/subsystem")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class AdminSubsystemController : CrudController<SubsystemDto, ICrudService<SubsystemDto>> public class AdminSubsystemController : CrudController<SubsystemDto, ICrudRepository<SubsystemDto>>
{ {
public AdminSubsystemController(ICrudService<SubsystemDto> service) public AdminSubsystemController(ICrudRepository<SubsystemDto> service)
: base(service) : base(service)
{ {
} }

View File

@ -3,7 +3,6 @@ using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudApp.Services.Subsystems; using AsbCloudApp.Services.Subsystems;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -25,6 +24,14 @@ namespace AsbCloudWebApi.Controllers.Subsystems
private readonly ISubsystemOperationTimeService subsystemOperationTimeService; private readonly ISubsystemOperationTimeService subsystemOperationTimeService;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly ISubsystemService subsystemService; private readonly ISubsystemService subsystemService;
private readonly Dictionary<int, string> subsystemNames = new()
{
{ 1, "SAUB" },
{ 65537, "Torque Master" },
{ 65536, "Spin Master" }
};
public SubsystemOperationTimeController(ISubsystemOperationTimeService subsystemOperationTimeService, IWellService wellService, ISubsystemService subsystemService) public SubsystemOperationTimeController(ISubsystemOperationTimeService subsystemOperationTimeService, IWellService wellService, ISubsystemService subsystemService)
{ {
this.subsystemOperationTimeService = subsystemOperationTimeService; this.subsystemOperationTimeService = subsystemOperationTimeService;
@ -131,6 +138,17 @@ namespace AsbCloudWebApi.Controllers.Subsystems
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получение словаря названий подсистем
/// </summary>
/// <returns></returns>
[HttpGet("names")]
[ProducesResponseType(typeof(Dictionary<int, string>), (int)System.Net.HttpStatusCode.OK)]
public IActionResult GetSubsystemsNames()
{
return Ok(subsystemNames);
}
protected async Task<bool> UserHasAccesToWellAsync(int idWell, CancellationToken token) protected async Task<bool> UserHasAccesToWellAsync(int idWell, CancellationToken token)
{ {
var idCompany = User.GetCompanyId(); var idCompany = User.GetCompanyId();

View File

@ -43,6 +43,7 @@ namespace AsbCloudWebApi.Middlewares
sw.Stop(); sw.Stop();
requestLog.ElapsedMilliseconds = sw.ElapsedMilliseconds; requestLog.ElapsedMilliseconds = sw.ElapsedMilliseconds;
requestLog.Status = context.Response.StatusCode; requestLog.Status = context.Response.StatusCode;
// TODO: Add request params and body size.
service.RegisterRequestError(requestLog, ex); service.RegisterRequestError(requestLog, ex);
throw; throw;
} }

View File

@ -40,7 +40,7 @@ namespace AsbCloudWebApi.Middlewares
{ {
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
} }
catch (Exception ex) catch (Exception ex) // TODO: find explicit exception. Use Trace. Add body size to message.
{ {
if (ex.Message.Contains("Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.")) if (ex.Message.Contains("Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate."))
Console.WriteLine("Reading the request body timed out due to data arriving too slowly."); Console.WriteLine("Reading the request body timed out due to data arriving too slowly.");

View File

@ -1,10 +1,5 @@
using DocumentFormat.OpenXml.InkML;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using System;
using System.Linq;
namespace AsbCloudWebApi namespace AsbCloudWebApi
{ {
@ -15,30 +10,8 @@ namespace AsbCloudWebApi
public static void Main(string[] args) public static void Main(string[] args)
{ {
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
if (args?.Length > 0)
{
if (args.Contains("db_init"))
{
var connectionStringName = "DefaultConnection";
var context = AsbCloudInfrastructure.DependencyInjection.MakeContext(configuration.GetConnectionString(connectionStringName));
context.Database.SetCommandTimeout(TimeSpan.FromSeconds(5 * 60));
context.Database.Migrate();
Console.WriteLine("Óñïåøíî âûïîëíåíî.");
return;
}
WriteHelp();
return;
}
var host = CreateHostBuilder(args).Build(); var host = CreateHostBuilder(args).Build();
AsbCloudInfrastructure.Startup.BeforeRunHandler(host, configuration); AsbCloudInfrastructure.Startup.BeforeRunHandler(host);
host.Run(); host.Run();
} }
@ -48,17 +21,5 @@ namespace AsbCloudWebApi
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();
}); });
private static void WriteHelp()
{
Console.WriteLine("Ïðè çàïóñêå áåç êëþ÷åé ïðîãðàììà ïðîñòî ñòàðòóåò â îáû÷íîì ðåæèìå.");
Console.WriteLine("Êëþ÷è äëÿ çàïóñêà:");
Console.WriteLine("db_init - ñîçäàòü êîíòåêñò ÁÄ è âûéòè.");
Console.WriteLine("Êîíòåêñò ñîçäàñòñÿ äëÿ ñòðîêè ïîäêëþ÷åíèÿ \"DefaultConnection\"");
Console.WriteLine("Ñîçäàíèå êîíòåêñòà ïðèâåäåò ê ñîçäàíèþ ÁÄ, åñëè åé íåò");
Console.WriteLine("è ïðèìåíåíèþ âñåõ ìèãðàöèé, åñëè ÁÄ óæå åñòü.");
Console.WriteLine("Äëÿ ñîçäàíèÿ êîíòåêñòà â ÁÄ äîëæíà áûòü ñîçäàíà ñõåìà public");
Console.WriteLine("");
}
} }
} }