diff --git a/AsbCloudApp/Data/SAUB/TelemetryDataSpinDto.cs b/AsbCloudApp/Data/SAUB/TelemetryDataSpinDto.cs index 008bfeda..09eeff23 100644 --- a/AsbCloudApp/Data/SAUB/TelemetryDataSpinDto.cs +++ b/AsbCloudApp/Data/SAUB/TelemetryDataSpinDto.cs @@ -3,6 +3,9 @@ namespace AsbCloudApp.Data.SAUB { #nullable enable + /// + /// телеметрия спин мастер + /// public class TelemetryDataSpinDto : ITelemetryData { /// @@ -73,6 +76,16 @@ namespace AsbCloudApp.Data.SAUB /// Переменная этапа /// public short? State { get; set; } + + /// + /// Осцилляция включена + /// + public bool IsOscillating => State != 0 & State != 6 & State != 7; + + /// + /// Демпфирование включено + /// + public bool IsDampening => State == 7 && (Mode & 2) > 0; } #nullable disable } diff --git a/AsbCloudApp/Services/ITelemetryService.cs b/AsbCloudApp/Services/ITelemetryService.cs index 0975d0cf..c4e50b35 100644 --- a/AsbCloudApp/Services/ITelemetryService.cs +++ b/AsbCloudApp/Services/ITelemetryService.cs @@ -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 /// /// Сервис телеметрии /// @@ -43,21 +43,12 @@ namespace AsbCloudApp.Services /// SimpleTimezoneDto GetTimezone(int idTelemetry); - // TODO: вероятно лишнее - /// - /// Список передающих в данный момент телеметрий - /// - /// - IEnumerable GetTransmittingTelemetries(); - - // TODO: вероятно лишнее /// /// Получить дату получения последних данных /// /// - /// /// - DateTime GetLastTelemetryDate(int idTelemetry, bool useUtc = false); + DateTime GetLastTelemetryDate(int idTelemetry); /// /// получить idTelemetry по IdWell @@ -82,16 +73,6 @@ namespace AsbCloudApp.Services /// Task UpdateInfoAsync(string uid, TelemetryInfoDto info, CancellationToken token); - // TODO: вероятно лишнее - /// - /// обновить данные о временной зоне (используется панелью) - /// - /// - /// - /// - /// - Task UpdateTimezoneAsync(string uid, SimpleTimezoneDto telemetryTimeZoneInfo, CancellationToken token); - /// /// Слить данные телеметрии в одну /// @@ -100,13 +81,7 @@ namespace AsbCloudApp.Services /// /// Task MergeAsync(int from, int to, CancellationToken token); - - // TODO: вероятно лишнее - /// - /// сохранить данные о запросе - /// - /// - /// - void SaveRequestDate(string uid, DateTimeOffset remoteDate); } + +#nullable disable } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs index df762093..1ba5b36d 100644 --- a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs +++ b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs @@ -32,17 +32,33 @@ namespace AsbCloudInfrastructure.Services.DailyReport public async Task> 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)); } diff --git a/AsbCloudInfrastructure/Services/SAUB/MessageService.cs b/AsbCloudInfrastructure/Services/SAUB/MessageService.cs index 11db53f7..541003e5 100644 --- a/AsbCloudInfrastructure/Services/SAUB/MessageService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/MessageService.cs @@ -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(); diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs index 285ac3d8..a1881174 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs @@ -14,13 +14,11 @@ namespace AsbCloudInfrastructure.Services.SAUB { public abstract class TelemetryDataBaseService : ITelemetryDataService 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 cacheTelemetry; protected readonly CacheTable cacheTelemetryUsers; - protected readonly CacheTable cacheWells; public TelemetryDataBaseService( IAsbCloudDbContext db, @@ -29,9 +27,7 @@ namespace AsbCloudInfrastructure.Services.SAUB { this.db = db; this.telemetryService = telemetryService; - cacheTelemetry = cacheDb.GetCachedTable((AsbCloudDbContext)db); cacheTelemetryUsers = cacheDb.GetCachedTable((AsbCloudDbContext)db); - cacheWells = cacheDb.GetCachedTable((AsbCloudDbContext)db); } public virtual async Task UpdateDataAsync(string uid, IEnumerable 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(); 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; - } - - } } diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs index d327a02c..21390596 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs @@ -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 cacheTelemetry; - private readonly CacheTable 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((AsbCloudDbContext)db); - cacheWells = cacheDb.GetCachedTable( - (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 GetTransmittingTelemetries() + private Dictionary GetTelemetryCache() { - var telemetryDtos = new List(); - var activeTelemetriesUids = telemetryTracker.GetTransmittingTelemetriesUids(); - if (!activeTelemetriesUids.Any()) - return telemetryDtos; - var telemetries = cacheTelemetry - .Where(t => activeTelemetriesUids.Contains(t.RemoteUid)); - telemetryDtos = telemetries.Adapt>().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(); } 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(); } 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(); - 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 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 } diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs index ddcdbca1..7f241ca3 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs @@ -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; } diff --git a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs new file mode 100644 index 00000000..b3ea33f8 --- /dev/null +++ b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs @@ -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()) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .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; + } + } +} diff --git a/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs index 1a6ac59a..dc8b9acf 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs @@ -23,7 +23,7 @@ public class EventServiceTest cacheDb = new CacheDb(); var telemetryTracker = new Mock(); var imezoneServiceMock = new Mock(); - var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object, cacheDb); + var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object); service = new EventService(context, telemetryService); } diff --git a/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs index 5e61701d..830edb3a 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs @@ -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 { diff --git a/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs b/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs index 33750bdd..4a77088a 100644 --- a/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs @@ -54,23 +54,6 @@ namespace AsbCloudWebApi.Controllers.SAUB return Ok(); } - /// - /// Обновляет часовой пояс скважины - /// - /// Уникальный идентификатор отправителя - /// Информация о часовом поясе - /// Токен отмены задачи - /// - [HttpPost] - [Route("{uid}/timezone")] - public async Task UpdateTimeZoneAsync(string uid, SimpleTimezoneDto timezone, - CancellationToken token) - { - await telemetryService.UpdateTimezoneAsync(uid, timezone, token) - .ConfigureAwait(false); - return Ok(); - } - /// /// Принимает список новых сообщений от телеметрии /// diff --git a/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs b/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs index 5373cf64..00f7dd08 100644 --- a/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs +++ b/AsbCloudWebApi/Middlewares/PermissionsMiddlware.cs @@ -2,19 +2,23 @@ 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) @@ -30,11 +34,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() + ?.ControllerName; + bool isAuthorized; if (idUser == 1) isAuthorized = true; @@ -43,40 +50,34 @@ namespace AsbCloudWebApi.Middlewares var permissionName = permission.Name; if (string.IsNullOrEmpty(permissionName)) { - var controller = endpoint.Metadata - .GetMetadata() - ?.ControllerName; - var httpMethod = endpoint.Metadata .GetMetadata() - .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() - ?.ControllerName; - permissionName = permissionName.Replace("[controller]", controller); + permissionName = permissionName.Replace("[controller]", controllerName); PermissionAttribute.Registered.Add(permissionName); } var userService = context.RequestServices.GetRequiredService(); - 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 } diff --git a/AsbCloudWebApi/Middlewares/UserConnectionsLimitMiddlware.cs b/AsbCloudWebApi/Middlewares/UserConnectionsLimitMiddlware.cs new file mode 100644 index 00000000..96b036e2 --- /dev/null +++ b/AsbCloudWebApi/Middlewares/UserConnectionsLimitMiddlware.cs @@ -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 + /// + /// This is not real middleware it`s part of PermissionsMiddlware. + /// DO NOT register it in setup.cs as middleware. + /// + class UserConnectionsLimitMiddlware + { + private readonly RequestDelegate next; + private readonly int parallelRequestsToController; + private readonly byte[] body; + private readonly ConcurrentDictionary> stat = new (); + private readonly IEnumerable? controllerNames; + + public UserConnectionsLimitMiddlware(RequestDelegate next, IConfiguration configuration) + { + this.next = next; + + var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue("parallelRequestsToController") ?? 5; + this.parallelRequestsToController = parallelRequestsToController > 0 + ? parallelRequestsToController + : 5; + + controllerNames = configuration.GetSection("userLimits")?.GetValue>("controllerNames"); + + var bodyText = $"Too Many Requests

Too Many Requests

I only allow {parallelRequestsToController} parallel requests per user. Try again soon.

"; + 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 +} diff --git a/AsbCloudWebApi/Startup.cs b/AsbCloudWebApi/Startup.cs index 73410e21..8046d92c 100644 --- a/AsbCloudWebApi/Startup.cs +++ b/AsbCloudWebApi/Startup.cs @@ -117,7 +117,7 @@ namespace AsbCloudWebApi app.UseAuthentication(); app.UseAuthorization(); - app.UseMiddleware(); + app.UseMiddleware(Configuration); app.UseMiddleware(); app.UseMiddleware(); diff --git a/AsbCloudWebApi/appsettings.json b/AsbCloudWebApi/appsettings.json index 372f92e6..2a1fbd3b 100644 --- a/AsbCloudWebApi/appsettings.json +++ b/AsbCloudWebApi/appsettings.json @@ -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",