forked from ddrilling/AsbCloudServer
Add backgroundService.
ReportService uses backgroundService. Drilling program alpha
This commit is contained in:
parent
e4e906c8d7
commit
7a77ff7904
23
AsbCloudApp/Services/IBackgroundWorkerService.cs
Normal file
23
AsbCloudApp/Services/IBackgroundWorkerService.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using AsbCloudApp.Data;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudApp.Services
|
||||
{
|
||||
public interface IBackgroundWorkerService
|
||||
{
|
||||
bool Contains(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет в очередь задач новую задачу
|
||||
/// </summary>
|
||||
/// <param name="id">id задачи в очереди</param>
|
||||
/// <param name="func">делегат</param>
|
||||
/// <returns>id задачи в очереди</returns>
|
||||
string Enqueue(string id, Func<string, CancellationToken, Task> func);
|
||||
string Enqueue(Func<string, CancellationToken, Task> func);
|
||||
string Enqueue(string id, Func<string, CancellationToken, Task> func, Func<string, Exception, CancellationToken, Task> onError);
|
||||
bool TryRemove(string id);
|
||||
}
|
||||
}
|
@ -13,10 +13,11 @@ namespace AsbCloudApp.Services
|
||||
CancellationToken token = default);
|
||||
Task<int> AddOrReplaceFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token);
|
||||
Task<int> MarkAsDeletedFileMarkAsync(int idFileMark, CancellationToken token);
|
||||
Task<int> AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default);
|
||||
Task<int> RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default);
|
||||
Task<int> AddPartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default);
|
||||
Task<int> RemovePartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default);
|
||||
Task<int> AddUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default);
|
||||
Task<int> RemoveUserAsync(int idUser, int idPart, int idUserRole, CancellationToken token = default);
|
||||
Task<int> AddFile(int idPart, int idUser, string fileFullName, Stream fileStream, CancellationToken token = default);
|
||||
Task<IEnumerable<UserDto>> GetAvailableUsers(int idWell, CancellationToken token = default);
|
||||
}
|
||||
}
|
@ -9,9 +9,9 @@ namespace AsbCloudApp.Services
|
||||
public interface IReportService
|
||||
{
|
||||
int ReportCategoryId { get; }
|
||||
int CreateReport(int idWell, int idUser, int stepSeconds,
|
||||
string CreateReport(int idWell, int idUser, int stepSeconds,
|
||||
int format, DateTime begin, DateTime end,
|
||||
Action<object, int> handleReportProgress);
|
||||
Action<object, string> handleReportProgress);
|
||||
int GetReportPagesCount(int idWell, DateTime begin, DateTime end,
|
||||
int stepSeconds, int format);
|
||||
DatesRangeDto GetDatesRangeOrDefault(int idWell);
|
||||
|
@ -41,6 +41,7 @@ namespace AsbCloudDb.Model
|
||||
DatabaseFacade Database { get; }
|
||||
DbSet<DrillingProgramPart> DrillingProgramParts { get; set; }
|
||||
DbSet<RelationUserDrillingProgramPart> RelationDrillingProgramPartUsers { get; set; }
|
||||
DbSet<RelationCompanyWell> RelationCompaniesWells { get; set; }
|
||||
|
||||
int SaveChanges();
|
||||
int SaveChanges(bool acceptAllChangesOnSuccess);
|
||||
|
@ -51,13 +51,12 @@ namespace AsbCloudInfrastructure
|
||||
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
|
||||
services.AddScoped<IFileShareService, GoogleDriveService>();
|
||||
|
||||
services.AddHostedService<ReportsBackgroundService>();
|
||||
services.AddHostedService<TelemetryAnalyticsBackgroundService>();
|
||||
services.AddHostedService<TelemetryAnalyticsBackgroundService>();// replace by BackgroundWorkerService
|
||||
|
||||
services.AddSingleton(new CacheDb());
|
||||
services.AddSingleton<ITelemetryTracker, TelemetryTracker>();
|
||||
services.AddSingleton<IReportsBackgroundQueue, ReportsBackgroundQueue>();
|
||||
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
|
||||
services.AddSingleton<IBackgroundWorkerService, BackgroundWorkerService>();
|
||||
|
||||
services.AddTransient<IAuthService, AuthService>();
|
||||
services.AddTransient<IClusterService, ClusterService>();
|
||||
|
@ -45,8 +45,7 @@ namespace AsbCloudInfrastructure.Services.Analysis
|
||||
var analyticsService = new TelemetryAnalyticsService(context,
|
||||
telemetryService, cacheDb);
|
||||
|
||||
await analyticsService.AnalyzeAndSaveTelemetriesAsync(token).ConfigureAwait(false);
|
||||
context.ChangeTracker.Clear();
|
||||
await analyticsService.AnalyzeAndSaveTelemetriesAsync(token).ConfigureAwait(false);
|
||||
context.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
193
AsbCloudInfrastructure/Services/BackgroundWorkerService.cs
Normal file
193
AsbCloudInfrastructure/Services/BackgroundWorkerService.cs
Normal file
@ -0,0 +1,193 @@
|
||||
using AsbCloudApp.Data;
|
||||
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
|
||||
{
|
||||
await work.ActionAsync(work.Id, cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -66,7 +66,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
// return fileInfo;
|
||||
//}
|
||||
|
||||
private static void UniteExcelFiles(IEnumerable<string> excelFilesNames, string resultExcelPath)
|
||||
public static void UniteExcelFiles(IEnumerable<string> excelFilesNames, string resultExcelPath)
|
||||
{
|
||||
var resultExcelFile = new XLWorkbook(XLEventTracking.Disabled);
|
||||
|
||||
|
@ -4,8 +4,10 @@ using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -17,6 +19,9 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
private readonly IAsbCloudDbContext context;
|
||||
private readonly IFileService fileService;
|
||||
private readonly IUserService userService;
|
||||
private readonly IWellService wellService;
|
||||
private readonly IBackgroundWorkerService backgroundWorker;
|
||||
private readonly string connectionString;
|
||||
|
||||
private const int idFileCategoryDrillingProgram = 1000;
|
||||
private const int idFileCategoryDrillingProgramPartsStart = 1001;
|
||||
@ -37,11 +42,33 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
private const int idStateCreating = 2;
|
||||
private const int idStateReady = 3;
|
||||
|
||||
public DrillingProgramService(IAsbCloudDbContext context, IFileService fileService, IUserService userService)
|
||||
public DrillingProgramService(
|
||||
IAsbCloudDbContext context,
|
||||
IFileService fileService,
|
||||
IUserService userService,
|
||||
IWellService wellService,
|
||||
IConfiguration configuration,
|
||||
IBackgroundWorkerService backgroundWorker)
|
||||
{
|
||||
this.context = context;
|
||||
this.fileService = fileService;
|
||||
this.userService = userService;
|
||||
this.wellService = wellService;
|
||||
this.backgroundWorker = backgroundWorker;
|
||||
this.connectionString = configuration.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<UserDto>> GetAvailableUsers(int idWell, CancellationToken token = default)
|
||||
{
|
||||
var users = await context.RelationCompaniesWells
|
||||
.Include(r => r.Company)
|
||||
.ThenInclude(c => c.Users)
|
||||
.Where(r => r.IdWell == idWell)
|
||||
.SelectMany(r => r.Company.Users)
|
||||
.Where(u => u != null && !string.IsNullOrEmpty(u.Email))
|
||||
.ToListAsync(token);
|
||||
var usersDto = users.Adapt<UserDto>();
|
||||
return usersDto;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FileCategoryDto>> GetCategoriesAsync(CancellationToken token = default)
|
||||
@ -54,15 +81,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
|
||||
public async Task<DrillingProgramStateDto> GetStateAsync(int idWell, int idUser, CancellationToken token = default)
|
||||
{
|
||||
// Задание от геологов
|
||||
// Профиль ствола скважины(ННБ)
|
||||
// Технологические расчеты(ННБ)
|
||||
// Долотная программа
|
||||
// Программа по растворам
|
||||
// Программа геофизических исследований
|
||||
// Планы спусков обсадных колонн
|
||||
// Программы цементирования обсадных колонн
|
||||
|
||||
var fileCategories = await context.FileCategories
|
||||
.Where(c => c.Id >= idFileCategoryDrillingProgramPartsStart &&
|
||||
c.Id < idFileCategoryDrillingProgramPartsEnd)
|
||||
@ -94,10 +112,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
parts.Add(part);
|
||||
}
|
||||
|
||||
var state = new DrillingProgramStateDto();
|
||||
state.Parts = parts;
|
||||
state.Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram)
|
||||
.Adapt<FileInfoDto>();
|
||||
var state = new DrillingProgramStateDto
|
||||
{
|
||||
Parts = parts,
|
||||
Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram)
|
||||
.Adapt<FileInfoDto>()
|
||||
};
|
||||
|
||||
if (parts.Any())
|
||||
{
|
||||
@ -105,8 +125,8 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
{
|
||||
if (state.Program is not null)
|
||||
state.IdState = idStateReady;
|
||||
else
|
||||
state.IdState = idStateCreating;
|
||||
else
|
||||
state.IdState = idStateCreating;
|
||||
}
|
||||
else
|
||||
state.IdState = idStateApproving;
|
||||
@ -114,6 +134,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
else
|
||||
state.IdState = idStateNotInitialized;
|
||||
|
||||
await TryEnqueueMakeProgramAsync(idWell, state, token);
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -121,7 +142,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
{
|
||||
var part = await context.DrillingProgramParts
|
||||
.Include(p => p.RelatedUsers)
|
||||
.FirstOrDefaultAsync(p => p.Id == idPart);
|
||||
.FirstOrDefaultAsync(p => p.Id == idPart, token);
|
||||
|
||||
if (part == null)
|
||||
throw new ArgumentInvalidException($"DrillingProgramPart id == {idPart} does not exist", nameof(idPart));
|
||||
@ -141,25 +162,35 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
return result.Id;
|
||||
}
|
||||
|
||||
public async Task<int> AddPartAsync(int idWell, int idFileCategory, CancellationToken token = default)
|
||||
public async Task<int> AddPartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default)
|
||||
{
|
||||
var part = new DrillingProgramPart
|
||||
if (!idFileCategories.Any())
|
||||
return 0;
|
||||
|
||||
var existingCategories = await context.DrillingProgramParts
|
||||
.Where(p => p.IdWell == idWell)
|
||||
.Select(p => p.IdFileCategory)
|
||||
.ToListAsync(token);
|
||||
|
||||
var newParts = idFileCategories
|
||||
.Where(c => !existingCategories.Any(e => e == c))
|
||||
.Select(c => new DrillingProgramPart
|
||||
{
|
||||
IdWell = idWell,
|
||||
IdFileCategory = idFileCategory,
|
||||
};
|
||||
var entry = context.DrillingProgramParts.Add(part);
|
||||
await context.SaveChangesAsync(token);
|
||||
IdFileCategory = c,
|
||||
});
|
||||
|
||||
await RemoveDrillingProgramAsync(part.IdWell, token);
|
||||
return entry.Entity.Id;
|
||||
context.DrillingProgramParts.AddRange(newParts);
|
||||
var affected = await context.SaveChangesAsync(token);
|
||||
|
||||
await RemoveDrillingProgramAsync(idWell, token);
|
||||
return affected;
|
||||
}
|
||||
|
||||
public async Task<int> RemovePartAsync(int idWell, int idFileCategory, CancellationToken token = default)
|
||||
public async Task<int> RemovePartsAsync(int idWell, IEnumerable<int> idFileCategories, CancellationToken token = default)
|
||||
{
|
||||
var whereQuery = context.DrillingProgramParts
|
||||
.Where(r => r.IdWell == idWell &&
|
||||
r.IdFileCategory == idFileCategory);
|
||||
.Where(p => p.IdWell == idWell && idFileCategories.Contains(p.IdFileCategory));
|
||||
|
||||
context.DrillingProgramParts.RemoveRange(whereQuery);
|
||||
|
||||
@ -178,7 +209,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
throw new ArgumentInvalidException($"DrillingProgramPart id == {idPart} does not exist", nameof(idPart));
|
||||
|
||||
if (idUserRole != idUserRoleApprover && idUserRole != idUserRolePublisher)
|
||||
throw new ArgumentInvalidException($"idUserRole ({idPart}), should be approver ({idUserRoleApprover}) or publisher({idUserRolePublisher})", nameof(idPart));
|
||||
throw new ArgumentInvalidException($"idUserRole ({idPart}), should be approver ({idUserRoleApprover}) or publisher ({idUserRolePublisher})", nameof(idPart));
|
||||
|
||||
var newRelation = new RelationUserDrillingProgramPart
|
||||
{
|
||||
@ -204,7 +235,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
if (idUserRole == idUserRoleApprover)
|
||||
{
|
||||
var part = await context.DrillingProgramParts.FirstOrDefaultAsync(p => p.Id == idPart, token);
|
||||
await CheckAndEnqueueMakeProgramAsync(part.IdWell, token);
|
||||
}
|
||||
|
||||
return await context.SaveChangesAsync(token);
|
||||
@ -227,6 +257,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
throw new ArgumentInvalidException($"Этот метод допустим только для файлов-частей программы бурения.", nameof(fileMarkDto));
|
||||
|
||||
var part = await context.DrillingProgramParts
|
||||
.Include(p => p.RelatedUsers)
|
||||
.FirstOrDefaultAsync(p => p.IdWell == fileInfo.IdWell && p.IdFileCategory == fileInfo.IdCategory, token);
|
||||
|
||||
if (!part.RelatedUsers.Any(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover))
|
||||
@ -242,9 +273,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
var result = await fileService.CreateFileMarkAsync(fileMarkDto, idUser, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if(fileMarkDto.IdMarkType == idMarkTypeApprove)
|
||||
await CheckAndEnqueueMakeProgramAsync(fileInfo.IdWell, token);
|
||||
else
|
||||
if(fileMarkDto.IdMarkType == idMarkTypeReject)
|
||||
await RemoveDrillingProgramAsync(fileInfo.IdWell, token);
|
||||
|
||||
return result;
|
||||
@ -267,22 +296,22 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<FileInfo> files, DrillingProgramPart partEntity)
|
||||
private static DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> files, DrillingProgramPart partEntity)
|
||||
{
|
||||
var part = new DrillingProgramPartDto
|
||||
{
|
||||
IdFileCategory = partEntity.IdFileCategory,
|
||||
Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory).Name,
|
||||
Approvers = partEntity.RelatedUsers
|
||||
.Where(u => u.IdUserRole == idUserRoleApprover)
|
||||
.Select(u => u.Adapt<UserDto>()),
|
||||
.Where(r => r.IdUserRole == idUserRoleApprover)
|
||||
.Select(r => r.User.Adapt<UserDto>()),
|
||||
Publishers = partEntity.RelatedUsers
|
||||
.Where(u => u.IdUserRole == idUserRolePublisher)
|
||||
.Select(u => u.Adapt<UserDto>()),
|
||||
.Where(r => r.IdUserRole == idUserRolePublisher)
|
||||
.Select(r => r.User.Adapt<UserDto>()),
|
||||
PermissionToApprove = partEntity.RelatedUsers
|
||||
.Any(u => u.IdUserRole == idUserRoleApprover && u.IdUser == idUser),
|
||||
.Any(r => r.IdUserRole == idUserRoleApprover && r.IdUser == idUser),
|
||||
PermissionToUpload = partEntity.RelatedUsers
|
||||
.Any(u => u.IdUserRole == idUserRolePublisher && u.IdUser == idUser),
|
||||
.Any(r => r.IdUserRole == idUserRolePublisher && r.IdUser == idUser),
|
||||
};
|
||||
|
||||
var fileEntity = files.LastOrDefault(f => f.IdCategory == partEntity.IdFileCategory);
|
||||
@ -290,20 +319,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
if (fileEntity is not null)
|
||||
{
|
||||
part.File = fileEntity.Adapt<FileInfoDto>();
|
||||
var marks = fileEntity.FileMarks.Where(m => !m.IsDeleted);
|
||||
|
||||
if (marks.Any())
|
||||
part.IdState = idPartStateApproving;
|
||||
var marks = fileEntity.FileMarks.Where(m => !m.IsDeleted);
|
||||
var hasReject = marks.Any(m => m.IdMarkType == idMarkTypeReject);
|
||||
if (!hasReject)
|
||||
{
|
||||
var hasReject = marks.Any(m => m.IdMarkType == idMarkTypeReject);
|
||||
if (!hasReject)
|
||||
{
|
||||
var allAproved = part.Approvers.All(a => marks.Any(m => m.IdUser == a.Id && m.IdMarkType == idMarkTypeApprove));
|
||||
if (allAproved)
|
||||
part.IdState = idPartStateApproved;
|
||||
}
|
||||
var allAproved = part.Approvers.All(a => marks.Any(m => m.IdUser == a.Id && m.IdMarkType == idMarkTypeApprove));
|
||||
if (allAproved)
|
||||
part.IdState = idPartStateApproved;
|
||||
}
|
||||
else
|
||||
part.IdState = idPartStateApproving;
|
||||
}
|
||||
else
|
||||
part.IdState = idPartStateNoFile;
|
||||
@ -311,19 +335,38 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
return part;
|
||||
}
|
||||
|
||||
private async Task CheckAndEnqueueMakeProgramAsync(int idWell, CancellationToken token)
|
||||
private async Task TryEnqueueMakeProgramAsync(int idWell, DrillingProgramStateDto state, CancellationToken token)
|
||||
{
|
||||
var state = await GetStateAsync(idWell, 0, token);
|
||||
if(state.IdState == idStateCreating)
|
||||
if (state.IdState == idStateCreating)
|
||||
{
|
||||
// TODO: check if task is running else enqueue task
|
||||
throw new NotImplementedException();
|
||||
var workId = MakeWorkId(idWell);
|
||||
if (!backgroundWorker.Contains(workId))
|
||||
{
|
||||
var well = await wellService.GetAsync(idWell, token);
|
||||
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx";
|
||||
var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName);
|
||||
async Task funcProgramMake(string id, CancellationToken token)
|
||||
{
|
||||
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
.UseNpgsql(connectionString)
|
||||
.Options;
|
||||
using var context = new AsbCloudDbContext(contextOptions);
|
||||
var fileService = new FileService(context);
|
||||
var files = state.Parts.Select(p => fileService.GetUrl(p.File));
|
||||
DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath);
|
||||
await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token);
|
||||
}
|
||||
|
||||
backgroundWorker.Enqueue(funcProgramMake);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
// TODO: dequeue task if it exist
|
||||
var workId = MakeWorkId(idWell);
|
||||
backgroundWorker.TryRemove(workId);
|
||||
|
||||
var filesIds = await context.Files
|
||||
.Where(f => f.IdWell == idWell &&
|
||||
f.IdCategory == idFileCategoryDrillingProgram)
|
||||
@ -335,5 +378,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string MakeWorkId(int idWell)
|
||||
=> $"Make drilling program for wellId {idWell}";
|
||||
}
|
||||
}
|
@ -337,7 +337,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
newFileMark.Id = default;
|
||||
newFileMark.DateCreated = DateTime.UtcNow;
|
||||
newFileMark.IdUser = idUser;
|
||||
|
||||
|
||||
db.FileMarks.Add(newFileMark);
|
||||
return await db.SaveChangesAsync(token);
|
||||
}
|
||||
|
@ -19,18 +19,17 @@ namespace AsbCloudInfrastructure.Services
|
||||
private readonly IAsbCloudDbContext db;
|
||||
private readonly string connectionString;
|
||||
private readonly ITelemetryService telemetryService;
|
||||
private readonly IReportsBackgroundQueue queue;
|
||||
private readonly IWellService wellService;
|
||||
private readonly IBackgroundWorkerService backgroundWorkerService;
|
||||
|
||||
public ReportService(IAsbCloudDbContext db, IConfiguration configuration,
|
||||
ITelemetryService telemetryService,
|
||||
IReportsBackgroundQueue queue, IWellService wellService)
|
||||
ITelemetryService telemetryService, IWellService wellService, IBackgroundWorkerService backgroundWorkerService)
|
||||
{
|
||||
this.db = db;
|
||||
this.connectionString = configuration.GetConnectionString("DefaultConnection");
|
||||
this.wellService = wellService;
|
||||
this.backgroundWorkerService = backgroundWorkerService;
|
||||
this.telemetryService = telemetryService;
|
||||
this.queue = queue;
|
||||
ReportCategoryId = db.FileCategories.AsNoTracking()
|
||||
.FirstOrDefault(c =>
|
||||
c.Name.Equals("Рапорт")).Id;
|
||||
@ -38,8 +37,8 @@ namespace AsbCloudInfrastructure.Services
|
||||
|
||||
public int ReportCategoryId { get; private set; }
|
||||
|
||||
public int CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin,
|
||||
DateTime end, Action<object, int> progressHandler)
|
||||
public string CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin,
|
||||
DateTime end, Action<object, string> progressHandler)
|
||||
{
|
||||
var timezoneOffset = wellService.GetTimezone(idWell).Hours;
|
||||
var beginUtc = begin.ToUtcDateTimeOffset(timezoneOffset);
|
||||
@ -47,13 +46,14 @@ namespace AsbCloudInfrastructure.Services
|
||||
var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset);
|
||||
var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset);
|
||||
|
||||
var newReportId = queue.EnqueueTask((id) =>
|
||||
var newReportId = backgroundWorkerService.Enqueue(async (id, token) =>
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AsbCloudDbContext>();
|
||||
optionsBuilder.UseNpgsql(connectionString);
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "report");
|
||||
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
.UseNpgsql(connectionString)
|
||||
.Options;
|
||||
using var context = new AsbCloudDbContext(contextOptions);
|
||||
|
||||
using var context = new AsbCloudDbContext(optionsBuilder.Options);
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "report");
|
||||
|
||||
var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, context);
|
||||
var reportFileName = Path.Combine(tempDir, generator.GetReportDefaultFileName());
|
||||
@ -66,7 +66,7 @@ namespace AsbCloudInfrastructure.Services
|
||||
generator.Make(reportFileName);
|
||||
|
||||
var fileService = new FileService(context);
|
||||
var fileInfo = fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName).Result;
|
||||
var fileInfo = await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token);
|
||||
|
||||
progressHandler.Invoke(new
|
||||
{
|
||||
|
@ -1,28 +0,0 @@
|
||||
using AsbCloudApp.Services;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services
|
||||
{
|
||||
class ReportsBackgroundQueue : IReportsBackgroundQueue
|
||||
{
|
||||
private readonly ConcurrentQueue<(Action<int> action, int id)> tasks =
|
||||
new ConcurrentQueue<(Action<int> action, int id)>();
|
||||
|
||||
private int id = 0;
|
||||
|
||||
public int EnqueueTask(Action<int> action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
id++;
|
||||
|
||||
tasks.Enqueue(new(action, id));
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool TryDequeue(out (Action<int> action, int id) item)
|
||||
=> tasks.TryDequeue(out item);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
using AsbCloudApp.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services
|
||||
{
|
||||
public class ReportsBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly IReportsBackgroundQueue tasksQueue;
|
||||
|
||||
public ReportsBackgroundService(IReportsBackgroundQueue tasksQueue)
|
||||
{
|
||||
this.tasksQueue = tasksQueue;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (tasksQueue.TryDequeue(out var item))
|
||||
await Task.Run(() => item.action(item.id), token).ConfigureAwait(false);
|
||||
else
|
||||
await Task.Delay(100, token).ConfigureAwait(false);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Trace.TraceError(ex.Message);
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken token)
|
||||
{
|
||||
await base.StopAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -41,10 +41,10 @@ namespace AsbCloudInfrastructure.Services
|
||||
|
||||
public TelemetryTracker(CacheDb cacheDb, IConfiguration configuration)
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
|
||||
.Options;
|
||||
var db = new AsbCloudDbContext(options);
|
||||
var db = new AsbCloudDbContext(contextOptions);
|
||||
|
||||
var cacheTelemetry = cacheDb.GetCachedTable<Telemetry>(db);
|
||||
var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count());
|
||||
|
@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@ -18,14 +20,11 @@ namespace AsbCloudWebApi.Controllers
|
||||
public class DrillingProgramController : ControllerBase
|
||||
{
|
||||
private readonly IDrillingProgramService drillingProgramService;
|
||||
private readonly IFileService fileService;
|
||||
private readonly IWellService wellService;
|
||||
|
||||
public DrillingProgramController(IDrillingProgramService drillingProgramService,
|
||||
IFileService fileService, IWellService wellService)
|
||||
public DrillingProgramController(IDrillingProgramService drillingProgramService, IWellService wellService)
|
||||
{
|
||||
this.drillingProgramService = drillingProgramService;
|
||||
this.fileService = fileService;
|
||||
this.wellService = wellService;
|
||||
}
|
||||
|
||||
@ -41,7 +40,21 @@ namespace AsbCloudWebApi.Controllers
|
||||
var result = await drillingProgramService.GetCategoriesAsync(token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Список пользователей
|
||||
/// </summary>
|
||||
/// <param name="idWell">id скважины</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("availableUsers")]
|
||||
[ProducesResponseType(typeof(FileCategoryDto[]), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> GetAvailableUsers(int idWell, CancellationToken token)
|
||||
{
|
||||
var result = await drillingProgramService.GetAvailableUsers(idWell, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Состояние процесса согласования программы бурения
|
||||
/// </summary>
|
||||
@ -67,16 +80,16 @@ namespace AsbCloudWebApi.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загрузка файла программы бурения
|
||||
/// Загрузка файла для части программы бурения
|
||||
/// </summary>
|
||||
/// <param name="idPart">ID части программы. Не путать с категорией файла</param>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="files"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("{idPart}")]
|
||||
[HttpPost("part/{idPart}")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> AddFile(
|
||||
int idPart,
|
||||
int idWell,
|
||||
@ -100,6 +113,10 @@ namespace AsbCloudWebApi.Controllers
|
||||
return BadRequest(ArgumentInvalidException.MakeValidationError(nameof(files), "at list 1 file can be uploaded"));
|
||||
|
||||
var fileName = files[0].FileName;
|
||||
|
||||
if (!fileName.EndsWith(".xlsx"))
|
||||
return BadRequest(ArgumentInvalidException.MakeValidationError("file", "Файл должен быть xlsx"));
|
||||
|
||||
var fileStream = files[0].OpenReadStream();
|
||||
var result = await drillingProgramService.AddFile(idPart, (int)idUser, fileName, fileStream, token);
|
||||
|
||||
@ -107,16 +124,16 @@ namespace AsbCloudWebApi.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить раздел программы бурения
|
||||
/// Добавляет разделы программы бурения
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="idFileCategory"></param>
|
||||
/// <param name="idFileCategories">Список категорий файлов, по которым будут добавлены части ПБ</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("part")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> AddPartAsync(int idWell, int idFileCategory, CancellationToken token)
|
||||
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> AddPartsAsync(int idWell, [Required] IEnumerable<int> idFileCategories, CancellationToken token)
|
||||
{
|
||||
int? idCompany = User.GetCompanyId();
|
||||
int? idUser = User.GetUserId();
|
||||
@ -128,21 +145,21 @@ namespace AsbCloudWebApi.Controllers
|
||||
idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
||||
|
||||
var result = drillingProgramService.AddPartAsync(idWell, idFileCategory, token);
|
||||
var result = await drillingProgramService.AddPartsAsync(idWell, idFileCategories, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удалить раздел программы бурения
|
||||
/// Удаляет разделы программы бурения
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="idFileCategory"></param>
|
||||
/// <param name="idFileCategories">Список категорий файлов, по которым будут удалены части ПБ</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("part")]
|
||||
[HttpDelete("part")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> RemovePartAsync(int idWell, int idFileCategory, CancellationToken token)
|
||||
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> RemovePartsAsync(int idWell, [Required] IEnumerable<int> idFileCategories, CancellationToken token)
|
||||
{
|
||||
int? idCompany = User.GetCompanyId();
|
||||
int? idUser = User.GetUserId();
|
||||
@ -154,14 +171,23 @@ namespace AsbCloudWebApi.Controllers
|
||||
idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
||||
|
||||
var result = drillingProgramService.RemovePartAsync(idWell, idFileCategory, token);
|
||||
var result = await drillingProgramService.RemovePartsAsync(idWell, idFileCategories, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("part/{idPart}/user/{idUser}")]
|
||||
/// <summary>
|
||||
/// Добавить пользователя в список publishers или approvers части программы бурения
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="idUser"></param>
|
||||
/// <param name="idPart"></param>
|
||||
/// <param name="idUserRole"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("part/{idPart}/user")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> AddUserAsync(int idWell, int idUser, int idPart, int idUserRole, CancellationToken token = default)
|
||||
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> AddUserAsync(int idWell, [Required] int idUser, int idPart, [Required] int idUserRole, CancellationToken token = default)
|
||||
{
|
||||
int? idCompany = User.GetCompanyId();
|
||||
int? idUserEditor = User.GetUserId();
|
||||
@ -173,14 +199,23 @@ namespace AsbCloudWebApi.Controllers
|
||||
idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
||||
|
||||
var result = drillingProgramService.AddUserAsync(idUser, idPart, idUserRole, token);
|
||||
var result = await drillingProgramService.AddUserAsync(idUser, idPart, idUserRole, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удалить пользователя из списка publishers или approvers части программы бурения
|
||||
/// </summary>
|
||||
/// <param name="idWell"></param>
|
||||
/// <param name="idUser"></param>
|
||||
/// <param name="idPart"></param>
|
||||
/// <param name="idUserRole"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("part/{idPart}/user/{idUser}")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(Task<int>), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> RemoveUserAsync(int idWell, int idUser, int idPart, int idUserRole, CancellationToken token = default)
|
||||
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> RemoveUserAsync(int idWell, int idUser, int idPart, [Required]int idUserRole, CancellationToken token = default)
|
||||
{
|
||||
int? idCompany = User.GetCompanyId();
|
||||
int? idUserEditor = User.GetUserId();
|
||||
@ -192,7 +227,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
||||
|
||||
var result = drillingProgramService.RemoveUserAsync(idUser, idPart, idUserRole, token);
|
||||
var result = await drillingProgramService.RemoveUserAsync(idUser, idPart, idUserRole, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
@ -229,7 +264,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
/// <param name="idMark"> id метки </param>
|
||||
/// <param name="token"> Токен отмены задачи </param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("fileMark")]
|
||||
[HttpDelete("fileMark/{idMark}")]
|
||||
[Permission]
|
||||
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> MarkAsDeletedFileMarkAsync(int idWell, int idMark,
|
||||
|
@ -58,7 +58,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
||||
|
||||
void HandleReportProgressAsync(object progress, int id) =>
|
||||
void HandleReportProgressAsync(object progress, string id) =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
reportsHubContext.Clients.Group($"Report_{id}").SendAsync(
|
||||
|
Loading…
Reference in New Issue
Block a user