merge dev to user_settings

This commit is contained in:
ngfrolov 2022-11-03 15:34:03 +05:00
commit b5650dcd51
15 changed files with 295 additions and 172 deletions

View File

@ -3,6 +3,9 @@
namespace AsbCloudApp.Data.SAUB
{
#nullable enable
/// <summary>
/// телеметрия спин мастер
/// </summary>
public class TelemetryDataSpinDto : ITelemetryData
{
/// <summary>
@ -73,6 +76,16 @@ namespace AsbCloudApp.Data.SAUB
/// Переменная этапа
/// </summary>
public short? State { get; set; }
/// <summary>
/// Осцилляция включена
/// </summary>
public bool IsOscillating => State != 0 & State != 6 & State != 7;
/// <summary>
/// Демпфирование включено
/// </summary>
public bool IsDampening => State == 7 && (Mode & 2) > 0;
}
#nullable disable
}

View File

@ -1,12 +1,12 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
#nullable enable
/// <summary>
/// Сервис телеметрии
/// </summary>
@ -43,21 +43,12 @@ namespace AsbCloudApp.Services
/// <returns></returns>
SimpleTimezoneDto GetTimezone(int idTelemetry);
// TODO: вероятно лишнее
/// <summary>
/// Список передающих в данный момент телеметрий
/// </summary>
/// <returns></returns>
IEnumerable<TelemetryDto> GetTransmittingTelemetries();
// TODO: вероятно лишнее
/// <summary>
/// Получить дату получения последних данных
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="useUtc"></param>
/// <returns></returns>
DateTime GetLastTelemetryDate(int idTelemetry, bool useUtc = false);
DateTime GetLastTelemetryDate(int idTelemetry);
/// <summary>
/// получить idTelemetry по IdWell
@ -82,16 +73,6 @@ namespace AsbCloudApp.Services
/// <returns></returns>
Task UpdateInfoAsync(string uid, TelemetryInfoDto info, CancellationToken token);
// TODO: вероятно лишнее
/// <summary>
/// обновить данные о временной зоне (используется панелью)
/// </summary>
/// <param name="uid"></param>
/// <param name="telemetryTimeZoneInfo"></param>
/// <param name="token"></param>
/// <returns></returns>
Task UpdateTimezoneAsync(string uid, SimpleTimezoneDto telemetryTimeZoneInfo, CancellationToken token);
/// <summary>
/// Слить данные телеметрии в одну
/// </summary>
@ -100,13 +81,7 @@ namespace AsbCloudApp.Services
/// <param name="token"></param>
/// <returns></returns>
Task<int> MergeAsync(int from, int to, CancellationToken token);
// TODO: вероятно лишнее
/// <summary>
/// сохранить данные о запросе
/// </summary>
/// <param name="uid"></param>
/// <param name="remoteDate"></param>
void SaveRequestDate(string uid, DateTimeOffset remoteDate);
}
#nullable disable
}

View File

@ -32,17 +32,33 @@ namespace AsbCloudInfrastructure.Services.DailyReport
public async Task<IEnumerable<DailyReportDto>> GetListAsync(int idWell, DateTime? begin, DateTime? end, CancellationToken token)
{
var well = wellService.GetOrDefault(idWell);
if (well is null || well.Timezone is null)
return null;
var query = db.DailyReports.Where(r => r.IdWell == idWell);
DateTimeOffset ExtractDate(DateTime dateTime)
{
var dateTimeOffset = dateTime.ToUtcDateTimeOffset(well!.Timezone.Hours);
var date = new DateTimeOffset(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day, 0,0,0,TimeSpan.Zero);
return date;
}
if (begin is not null)
query = query.Where(d => d.StartDate >= begin.Value.Date);
{
var beginUTC = ExtractDate(begin.Value);
query = query.Where(d => d.StartDate >= beginUTC);
}
if (end is not null)
query = query.Where(d => d.StartDate <= end.Value.Date);
{
var endUTC = ExtractDate(end.Value);
query = query.Where(d => d.StartDate <= endUTC);
}
var entities = await query
.OrderBy(e => e.StartDate)
.ToListAsync(token);
return entities.Select(r => Convert(r));
}

View File

@ -141,10 +141,6 @@ namespace AsbCloudInfrastructure.Services.SAUB
var telemetryId = telemetryService.GetOrCreateTelemetryIdByUid(uid);
var timezone = telemetryService.GetTimezone(telemetryId);
var maxDateDto = dtos.Max(m => m.Date);
var maxDateUtc = maxDateDto.ToUtcDateTimeOffset(timezone.Hours);
telemetryService.SaveRequestDate(uid, maxDateDto);
foreach (var dto in dtos)
{
var entity = dto.Adapt<TelemetryMessage>();

View File

@ -14,13 +14,11 @@ namespace AsbCloudInfrastructure.Services.SAUB
{
public abstract class TelemetryDataBaseService<TDto, TModel> : ITelemetryDataService<TDto>
where TDto : AsbCloudApp.Data.ITelemetryData
where TModel : class, AsbCloudDb.Model.ITelemetryData
where TModel : class, ITelemetryData
{
protected readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService;
protected readonly CacheTable<Telemetry> cacheTelemetry;
protected readonly CacheTable<TelemetryUser> cacheTelemetryUsers;
protected readonly CacheTable<Well> cacheWells;
public TelemetryDataBaseService(
IAsbCloudDbContext db,
@ -29,9 +27,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
{
this.db = db;
this.telemetryService = telemetryService;
cacheTelemetry = cacheDb.GetCachedTable<Telemetry>((AsbCloudDbContext)db);
cacheTelemetryUsers = cacheDb.GetCachedTable<TelemetryUser>((AsbCloudDbContext)db);
cacheWells = cacheDb.GetCachedTable<Well>((AsbCloudDbContext)db);
}
public virtual async Task<int> UpdateDataAsync(string uid, IEnumerable<TDto> dtos, CancellationToken token = default)
@ -65,7 +61,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
});
var entityMaxDate = entities.Max(e => e.DateTime);
telemetryService.SaveRequestDate(uid, entityMaxDate);
telemetryService.TelemetryTracker.SaveRequestDate(uid, entityMaxDate);
var dbset = db.Set<TModel>();
var stopwatch = Stopwatch.StartNew();
@ -91,11 +87,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
DateTime dateBegin = default, double intervalSec = 600d,
int approxPointsCount = 1024, CancellationToken token = default)
{
var well = cacheWells.FirstOrDefault(w => w.Id == idWell);
if (well?.IdTelemetry is null)
return default;
var idTelemetry = well?.IdTelemetry ?? default;
var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell) ?? -1;
if (idTelemetry == -1)
return null;
var timezone = telemetryService.GetTimezone(idTelemetry);
@ -103,7 +97,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
DateTimeOffset dateBeginUtc;
if (dateBegin == default)
{
dateBeginUtc = telemetryService.GetLastTelemetryDate(idTelemetry, true);
dateBeginUtc = telemetryService.GetLastTelemetryDate(idTelemetry)
.ToUtcDateTimeOffset(timezone.Hours);
if (dateBeginUtc != default)
dateBeginUtc = dateBeginUtc.AddSeconds(-intervalSec);
}
@ -153,24 +148,5 @@ namespace AsbCloudInfrastructure.Services.SAUB
public abstract TModel Convert(TDto src, double timezoneOffset);
private static double AssumeTimezoneOffset(DateTime nearToCurrentDate)
{
var offset = 5d;
if (nearToCurrentDate.Kind == DateTimeKind.Unspecified)
{
var now = DateTime.UtcNow;
var minutes = 60 * (now.Hour - nearToCurrentDate.Hour) + now.Minute - nearToCurrentDate.Minute;
var minutesPositive = (1440_0000 + minutes) % 1440; //60*24
var halfsHours = Math.Round(1d * minutesPositive / 30d); // quarters are ignored
var hours = halfsHours / 2;
offset = hours < 12 ? hours : 24 - hours;
}
if (nearToCurrentDate.Kind == DateTimeKind.Local)
offset = TimeZoneInfo.Local.BaseUtcOffset.TotalHours;
return offset;
}
}
}

View File

@ -3,7 +3,7 @@ using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.EfCache;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
@ -14,10 +14,12 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB
{
#nullable enable
public class TelemetryService : ITelemetryService
{
private readonly CacheTable<Telemetry> cacheTelemetry;
private readonly CacheTable<Well> cacheWells;//TODO: use wellService instead of this
private const string telemetryCacheTag = "telemetryCache";
private static readonly TimeSpan telemetryCacheObsolescence = TimeSpan.FromMinutes(5);
private readonly IAsbCloudDbContext db;
private readonly ITelemetryTracker telemetryTracker;
private readonly ITimezoneService timezoneService;
@ -28,59 +30,42 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryService(
IAsbCloudDbContext db,
ITelemetryTracker telemetryTracker,
ITimezoneService timezoneService,
CacheDb cacheDb)
ITimezoneService timezoneService)
{
cacheTelemetry = cacheDb.GetCachedTable<Telemetry>((AsbCloudDbContext)db);
cacheWells = cacheDb.GetCachedTable<Well>(
(AsbCloudDbContext)db,
$"{nameof(Well.Cluster)}.{nameof(Cluster.Deposit)}",
nameof(Well.Telemetry),
$"{nameof(Well.RelationCompaniesWells)}.{nameof(RelationCompanyWell.Company)}",
nameof(Well.WellType));
this.db = db;
this.telemetryTracker = telemetryTracker;
this.timezoneService = timezoneService;
}
public IEnumerable<TelemetryDto> GetTransmittingTelemetries()
private Dictionary<int, Telemetry> GetTelemetryCache()
{
var telemetryDtos = new List<TelemetryDto>();
var activeTelemetriesUids = telemetryTracker.GetTransmittingTelemetriesUids();
if (!activeTelemetriesUids.Any())
return telemetryDtos;
var telemetries = cacheTelemetry
.Where(t => activeTelemetriesUids.Contains(t.RemoteUid));
telemetryDtos = telemetries.Adapt<IEnumerable<TelemetryDto>>().ToList();
return telemetryDtos;
var cache = db.Telemetries
.Include(t => t.Well)
.FromCacheDictionary(telemetryCacheTag, telemetryCacheObsolescence, t => t.Id);
return cache;
}
public void SaveRequestDate(string uid, DateTimeOffset remoteDate) =>
telemetryTracker.SaveRequestDate(uid, remoteDate);
public DateTime GetLastTelemetryDate(int idTelemetry, bool useUtc = false)
private void DropTelemetryCache()
{
var lastTelemetryDate = DateTimeOffset.MinValue;
var telemetry = cacheTelemetry.FirstOrDefault(t => t.Id == idTelemetry);
db.Telemetries.DropCacheDictionary(telemetryCacheTag);
}
public DateTime GetLastTelemetryDate(int idTelemetry)
{
var telemetry = GetTelemetryCache().GetValueOrDefault(idTelemetry);
if (telemetry is null)
throw new Exception($"Telemetry id:{idTelemetry} does not exist");
var uid = telemetry.RemoteUid;
var timzone = GetTimezone(idTelemetry);
lastTelemetryDate = telemetryTracker.GetLastTelemetryDateByUid(uid);
if (useUtc)
return lastTelemetryDate.UtcDateTime;
var lastTelemetryDate = telemetryTracker.GetLastTelemetryDateByUid(uid);
return lastTelemetryDate.ToRemoteDateTime(timzone.Hours);
}
public DatesRangeDto GetDatesRange(int idTelemetry)
{
var telemetry = cacheTelemetry.FirstOrDefault(t => t.Id == idTelemetry);
var telemetry = GetTelemetryCache().GetValueOrDefault(idTelemetry);
if (telemetry is null)
throw new Exception($"Telemetry id:{idTelemetry} does not exist");
@ -115,13 +100,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
TimezoneId = info.TimeZoneId
};
await cacheTelemetry.UpsertAsync(telemetry, token)
.ConfigureAwait(false);
db.Telemetries.Upsert(telemetry);
await db.SaveChangesAsync(token);
DropTelemetryCache();
}
public SimpleTimezoneDto GetTimezone(int idTelemetry)
{
var telemetry = cacheTelemetry.FirstOrDefault(t => t.Id == idTelemetry);
var telemetry = GetTelemetryCache().GetValueOrDefault(idTelemetry);
if (telemetry is null)
throw new Exception($"Telemetry id: {idTelemetry} does not exist.");
@ -137,58 +123,65 @@ namespace AsbCloudInfrastructure.Services.SAUB
IsOverride = false,
TimezoneId = telemetry.Info.TimeZoneId,
};
cacheTelemetry.Upsert(telemetry);
db.Telemetries.Upsert(telemetry);
db.SaveChanges();
DropTelemetryCache();
return telemetry.TimeZone.Adapt<SimpleTimezoneDto>();
}
if (telemetry.Well?.Timezone is not null)
{
telemetry.TimeZone = telemetry.Well.Timezone;
cacheTelemetry.Upsert(telemetry);
db.Telemetries.Upsert(telemetry);
db.SaveChanges();
DropTelemetryCache();
return telemetry.TimeZone.Adapt<SimpleTimezoneDto>();
}
throw new Exception($"Telemetry id: {idTelemetry} can't find timezone.");
}
public async Task UpdateTimezoneAsync(string uid, SimpleTimezoneDto timeone,
CancellationToken token)
{
var telemetry = GetOrCreateTelemetryByUid(uid);
var newTelemetryTimeZone = timeone.Adapt<SimpleTimezone>();
if (newTelemetryTimeZone?.Equals(telemetry.TimeZone) == true)
return;
await cacheTelemetry.UpsertAsync(telemetry, token)
.ConfigureAwait(false);
}
public int? GetIdTelemetryByIdWell(int idWell)
{
var well = cacheWells.FirstOrDefault(w => w.Id == idWell);
return well?.IdTelemetry;
var telemetry = GetTelemetryCache()
.FirstOrDefault(t => t.Value.Well?.Id == idWell).Value;
return telemetry?.Id;
}
private Well GetWellByTelemetryUid(string uid)
private Well? GetWellByTelemetryUid(string uid)
{
var tele = cacheTelemetry.FirstOrDefault(t => t.RemoteUid == uid);
if (tele is null)
return null;
var telemetry = GetOrDefaultTelemetryByUid(uid);
return telemetry?.Well;
}
var well = cacheWells.FirstOrDefault(w => w?.IdTelemetry == tele.Id);
return well;
private Telemetry? GetOrDefaultTelemetryByUid(string uid)
{
var telemetry = GetTelemetryCache().FirstOrDefault(kv => kv.Value.RemoteUid == uid).Value;
return telemetry;
}
private Telemetry GetOrCreateTelemetryByUid(string uid)
=> cacheTelemetry.GetOrCreate(t => t.RemoteUid == uid, () => new Telemetry
{
var telemetry = GetOrDefaultTelemetryByUid(uid);
if (telemetry is null)
{
RemoteUid = uid,
TimeZone = new SimpleTimezone
var newTelemetry = new Telemetry
{
Hours = 5,
IsOverride = false,
TimezoneId = "default",
}
});
RemoteUid = uid,
TimeZone = new SimpleTimezone
{
Hours = 5,
IsOverride = false,
TimezoneId = "default",
}
};
var entry = db.Telemetries.Add(newTelemetry);
db.SaveChanges();
DropTelemetryCache();
return entry.Entity;
}
return telemetry;
}
public async Task<int> MergeAsync(int from, int to, CancellationToken token)
{
@ -229,12 +222,15 @@ namespace AsbCloudInfrastructure.Services.SAUB
stopwath.Stop();
Console.WriteLine($"Successfully committed in {1d * stopwath.ElapsedMilliseconds / 1000d: #0.00} sec. Affected {affected} rows.");
DropTelemetryCache();
return affected;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine($"Merge() Fail. Rollback. Reason is:{ex.Message}");
await transaction.RollbackAsync(token).ConfigureAwait(false);
#pragma warning disable CA2016 // Перенаправьте параметр "CancellationToken" в методы
await transaction.RollbackAsync();
#pragma warning restore CA2016 // Перенаправьте параметр "CancellationToken" в методы
return -1;
}
}
@ -352,4 +348,5 @@ namespace AsbCloudInfrastructure.Services.SAUB
return affected;
}
}
#nullable disable
}

View File

@ -229,11 +229,12 @@ namespace AsbCloudInfrastructure.Services.Subsystems
{
static int? GetSubsytemId(short? mode, int? state)
{
// При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital
if (state == 7 && (mode & 2) > 0)
return idSubsytemTorqueMaster;
return idSubsytemTorqueMaster;// демпфер
if (state != 0 & state != 6 & state != 7)
return idSubsytemSpinMaster;
return idSubsytemSpinMaster;// осцилляция
return null;
}

View File

@ -0,0 +1,91 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace AsbCloudWebApi.Tests.Middlware
{
public class UserConnectionsLimitMiddlwareTest
{
private readonly HttpClient httpClient;
public UserConnectionsLimitMiddlwareTest()
{
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
host.Start();
httpClient = new ();
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2NjY1ODY2MjAsImV4cCI6MTY5ODE0NDIyMCwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zqBdR4nYB87-Xyzv025waasN47i43c9FJ23RfzIvUsM");
}
[Fact]
public async Task Send_n_requests_and_get_blocked()
{
//Данные в тестовой БД
//select
// tw.id,
// t_stat.minDate,
// t_stat.maxDate
//from(
// select
// id_telemetry,
// count(1) as count,
// min("date") as minDate,
// max("date") as maxDate
// from t_telemetry_data_saub
// group by id_telemetry
//) as t_stat
//join t_well tw on tw.id_telemetry = t_stat.id_telemetry
//where tw is not null
//order by t_stat.count
//limit 10;
var wells = new []
{
(191, new DateTime(2022, 09, 01, 21, 43, 00, DateTimeKind.Utc), new DateTime(2022, 09, 04, 07, 37, 31, DateTimeKind.Utc)),
(3 , new DateTime(2021, 09, 16, 06, 13, 33, DateTimeKind.Utc), new DateTime(2021, 09, 20, 00, 29, 28, DateTimeKind.Utc)),
(199, new DateTime(2022, 09, 15, 11, 27, 18, DateTimeKind.Utc), new DateTime(2022, 09, 20, 14, 00, 23, DateTimeKind.Utc)),
(6 , new DateTime(2021, 09, 20, 00, 35, 03, DateTimeKind.Utc), new DateTime(2021, 09, 25, 06, 46, 17, DateTimeKind.Utc)),
(41 , new DateTime(2021, 12, 10, 00, 59, 52, DateTimeKind.Utc), new DateTime(2022, 10, 31, 15, 29, 24, DateTimeKind.Utc)),
(100, new DateTime(2022, 04, 24, 03, 04, 05, DateTimeKind.Utc), new DateTime(2022, 04, 29, 11, 38, 36, DateTimeKind.Utc)),
(154, new DateTime(2022, 03, 28, 10, 09, 14, DateTimeKind.Utc), new DateTime(2022, 06, 14, 15, 01, 12, DateTimeKind.Utc)),
(5 , new DateTime(2021, 09, 25, 08, 09, 37, DateTimeKind.Utc), new DateTime(2021, 10, 01, 14, 39, 51, DateTimeKind.Utc)),
(1 , new DateTime(2021, 09, 10, 01, 32, 42, DateTimeKind.Utc), new DateTime(2021, 09, 18, 00, 35, 22, DateTimeKind.Utc)),
(112, new DateTime(2022, 04, 20, 16, 47, 51, DateTimeKind.Utc), new DateTime(2022, 04, 28, 15, 04, 33, DateTimeKind.Utc)),
};
var i = 0;
for (; i < 5; i++)
_ = Task.Run(async () =>
{
var well = wells[i];
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
var response = await httpClient.GetAsync(url);
//await response.Content.ReadAsStringAsync();
await Task.Delay(1000);
});
var well = wells[i];
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
var response = await httpClient.GetAsync(url);
Assert.Equal(System.Net.HttpStatusCode.TooManyRequests, response.StatusCode);
}
private static string MakeUrl(int idWell, DateTime dateBegin, DateTime dateEnd)
{
var interval = (dateEnd - dateBegin).TotalSeconds;
var dateBeginString = dateBegin.ToString("yyyy-MM-ddZ");
var url = $"http://127.0.0.1:5000/api/TelemetryDataSaub/{idWell}?begin={dateBeginString}&intervalSec={interval}&approxPointsCount={interval}";
return url;
}
}
}

View File

@ -23,7 +23,7 @@ public class EventServiceTest
cacheDb = new CacheDb();
var telemetryTracker = new Mock<ITelemetryTracker>();
var imezoneServiceMock = new Mock<ITimezoneService>();
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object, cacheDb);
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object);
service = new EventService(context, telemetryService);
}

View File

@ -42,7 +42,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
context = TestHelpter.MakeTestContext();
cacheDb = new CacheDb();
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object, cacheDb);
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object);
var info = new TelemetryInfoDto
{

View File

@ -54,23 +54,6 @@ namespace AsbCloudWebApi.Controllers.SAUB
return Ok();
}
/// <summary>
/// Обновляет часовой пояс скважины
/// </summary>
/// <param name="uid">Уникальный идентификатор отправителя</param>
/// <param name="timezone">Информация о часовом поясе</param>
/// <param name="token">Токен отмены задачи</param>
/// <returns></returns>
[HttpPost]
[Route("{uid}/timezone")]
public async Task<IActionResult> UpdateTimeZoneAsync(string uid, SimpleTimezoneDto timezone,
CancellationToken token)
{
await telemetryService.UpdateTimezoneAsync(uid, timezone, token)
.ConfigureAwait(false);
return Ok();
}
/// <summary>
/// Принимает список новых сообщений от телеметрии
/// </summary>

View File

@ -3,19 +3,23 @@ using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace AsbCloudWebApi.Middlewares
{
#nullable enable
public class PermissionsMiddlware
{
private readonly RequestDelegate next;
private readonly UserConnectionsLimitMiddlware userConnectionsLimitMiddlware;
public PermissionsMiddlware(RequestDelegate next)
public PermissionsMiddlware(RequestDelegate next, IConfiguration configuration)
{
this.next = next;
userConnectionsLimitMiddlware = new UserConnectionsLimitMiddlware(next, configuration);
}
public async Task InvokeAsync(HttpContext context)
@ -31,11 +35,14 @@ namespace AsbCloudWebApi.Middlewares
var idUser = context.User.GetUserId();
if (idUser is null)
{
context.User = null;
await context.ForbidAsync();
return;
}
var controllerName = endpoint!.Metadata
.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
?.ControllerName;
bool isAuthorized;
if (idUser == 1)
isAuthorized = true;
@ -44,40 +51,34 @@ namespace AsbCloudWebApi.Middlewares
var permissionName = permission.Name;
if (string.IsNullOrEmpty(permissionName))
{
var controller = endpoint.Metadata
.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
?.ControllerName;
var httpMethod = endpoint.Metadata
.GetMetadata<Microsoft.AspNetCore.Routing.HttpMethodMetadata>()
.HttpMethods[0]
?.HttpMethods[0]
.ToLower();
permissionName = httpMethod switch
{
"get" or "delete" => $"{controller}.{httpMethod}",
"post" or "put" or "patch" => $"{controller}.edit",
"get" or "delete" => $"{controllerName}.{httpMethod}",
"post" or "put" or "patch" => $"{controllerName}.edit",
_ => throw new NotImplementedException(),
};
PermissionAttribute.Registered.Add(permissionName);
}
else if (permissionName.Contains("[controller]"))
{
var controller = endpoint.Metadata
.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
?.ControllerName;
permissionName = permissionName.Replace("[controller]", controller);
permissionName = permissionName.Replace("[controller]", controllerName);
PermissionAttribute.Registered.Add(permissionName);
}
var userService = context.RequestServices.GetRequiredService<IUserRepository>();
isAuthorized = userService.HasPermission((int)idUser, permissionName);
isAuthorized = userService.HasPermission(idUser.Value, permissionName);
}
if (isAuthorized)
await next?.Invoke(context);
await userConnectionsLimitMiddlware.InvokeAsync(context, idUser!.Value, controllerName!);
else
await context.ForbidAsync();
}
}
#nullable disable
}

View File

@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AsbCloudWebApi.Middlewares
{
#nullable enable
/// <summary>
/// This is not real middleware it`s part of PermissionsMiddlware.
/// DO NOT register it in setup.cs as middleware.
/// </summary>
class UserConnectionsLimitMiddlware
{
private readonly RequestDelegate next;
private readonly int parallelRequestsToController;
private readonly byte[] body;
private readonly ConcurrentDictionary<int, ConcurrentDictionary<string, int>> stat = new ();
private readonly IEnumerable<string>? controllerNames;
public UserConnectionsLimitMiddlware(RequestDelegate next, IConfiguration configuration)
{
this.next = next;
var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue<int>("parallelRequestsToController") ?? 5;
this.parallelRequestsToController = parallelRequestsToController > 0
? parallelRequestsToController
: 5;
controllerNames = configuration.GetSection("userLimits")?.GetValue<IEnumerable<string>>("controllerNames");
var bodyText = $"<html><head><title>Too Many Requests</title></head><body><h1>Too Many Requests</h1><p>I only allow {parallelRequestsToController} parallel requests per user. Try again soon.</p></body></html>";
body = System.Text.Encoding.UTF8.GetBytes(bodyText);
}
public async Task InvokeAsync(HttpContext context, int idUser, string controllerName)
{
if(controllerNames?.Any(n => controllerName.StartsWith(n)) == false)
{
await next(context);
return;
}
var userStat = stat.GetOrAdd(idUser, idUser => new());
var count = userStat.AddOrUpdate(controllerName, 1, (key, value) => value + 1);
if(count < parallelRequestsToController)
{
try
{
await next(context);
}
finally
{
userStat[controllerName]--;
}
}
else
{
context.Response.Clear();
context.Response.StatusCode = (int)System.Net.HttpStatusCode.TooManyRequests;
context.Response.Headers.RetryAfter = "1000";
context.Response.Headers.ContentType = "text/html";
await context.Response.BodyWriter.WriteAsync(body);
}
}
}
#nullable disable
}

View File

@ -117,7 +117,7 @@ namespace AsbCloudWebApi
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<PermissionsMiddlware>();
app.UseMiddleware<PermissionsMiddlware>(Configuration);
app.UseMiddleware<SimplifyExceptionsMiddleware>();
app.UseMiddleware<RequerstTrackerMiddleware>();

View File

@ -13,6 +13,9 @@
"LocalConnection": "Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"
},
"AllowedHosts": "*",
"userLimits": {
"parallelRequestsToController": 5
},
"email": {
"smtpServer": "smtp.timeweb.ru",
"sender": "bot@autodrilling.ru",