From a7e5c577cf9bf7fbb64bda14980451f976317ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Tue, 11 Jul 2023 19:07:57 +0500 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D1=83=20?= =?UTF-8?q?=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20+=20=D0=BD=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=BE?= =?UTF-8?q?=D0=B9=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Notifications/INotificationSender.cs | 35 ++++++++ .../INotificationSenderManager.cs | 16 ++++ .../INotificationService.cs | 23 +++--- AsbCloudInfrastructure/DependencyInjection.cs | 5 +- .../NotificationSenderManager.cs | 21 +++++ .../Notifications/NotificationService.cs | 82 +++++++++++++++++++ .../Controllers/NotificationController.cs | 41 ++++------ AsbCloudWebApi/DependencyInjection.cs | 9 ++ .../ConnectionManager/ConnectionManager.cs | 10 +-- .../ConnectionManager/IConnectionManager.cs | 6 +- AsbCloudWebApi/SignalR/NotificationHub.cs | 14 ++-- .../Services/SignalRNotificationSender.cs | 61 ++++++++++++++ AsbCloudWebApi/Startup.cs | 8 +- 13 files changed, 273 insertions(+), 58 deletions(-) create mode 100644 AsbCloudApp/Services/Notifications/INotificationSender.cs create mode 100644 AsbCloudApp/Services/Notifications/INotificationSenderManager.cs rename AsbCloudApp/Services/{ => Notifications}/INotificationService.cs (66%) create mode 100644 AsbCloudInfrastructure/Services/Notifications/NotificationSenderManager.cs create mode 100644 AsbCloudInfrastructure/Services/Notifications/NotificationService.cs create mode 100644 AsbCloudWebApi/SignalR/Services/SignalRNotificationSender.cs diff --git a/AsbCloudApp/Services/Notifications/INotificationSender.cs b/AsbCloudApp/Services/Notifications/INotificationSender.cs new file mode 100644 index 00000000..faa64b9e --- /dev/null +++ b/AsbCloudApp/Services/Notifications/INotificationSender.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; + +namespace AsbCloudApp.Services.Notifications; + +/// +/// Интерфейс для отправителя уведомлений +/// +public interface INotificationSender +{ + /// + /// Способ отправки уведомлений + /// + NotificationTransport NotificationTransport { get; } + + /// + /// Отправка одного уведомлений + /// + /// + /// + /// + Task SendAsync(NotificationDto notification, + CancellationToken cancellationToken); + + /// + /// Отправка нескольких уведомлений + /// + /// + /// + /// + Task SendRangeAsync(IEnumerable notifications, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudApp/Services/Notifications/INotificationSenderManager.cs b/AsbCloudApp/Services/Notifications/INotificationSenderManager.cs new file mode 100644 index 00000000..228308cd --- /dev/null +++ b/AsbCloudApp/Services/Notifications/INotificationSenderManager.cs @@ -0,0 +1,16 @@ +using AsbCloudApp.Data; + +namespace AsbCloudApp.Services.Notifications; + +/// +/// +/// +public interface INotificationSenderManager +{ + /// + /// + /// + /// + /// + INotificationSender? GetOrDefault(NotificationTransport notificationTransport); +} \ No newline at end of file diff --git a/AsbCloudApp/Services/INotificationService.cs b/AsbCloudApp/Services/Notifications/INotificationService.cs similarity index 66% rename from AsbCloudApp/Services/INotificationService.cs rename to AsbCloudApp/Services/Notifications/INotificationService.cs index 973e4c8d..746b33f9 100644 --- a/AsbCloudApp/Services/INotificationService.cs +++ b/AsbCloudApp/Services/Notifications/INotificationService.cs @@ -1,8 +1,9 @@ using System; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data; -namespace AsbCloudApp.Services; +namespace AsbCloudApp.Services.Notifications; /// /// Интерфейс для работы с уведомлениями @@ -10,26 +11,26 @@ namespace AsbCloudApp.Services; public interface INotificationService { /// - /// Метод отправки нового уведомления + /// Отправка нового уведомления /// /// - /// /// /// - /// + /// /// + /// /// /// - Task SendNotificationAsync(int idUser, - int idNotificationTransport, + Task NotifyAsync(int idUser, int idNotificationCategory, string title, - string subject, + string message, TimeSpan timeToLife, + NotificationTransport notificationTransport, CancellationToken cancellationToken); /// - /// Метод обновления уведомления + /// Обновление уведомления /// /// /// @@ -40,13 +41,13 @@ public interface INotificationService CancellationToken cancellationToken); /// - /// Метод отправки уведомлений, которые не были отправлены + /// Отправка уведомлений, которые не были отправлены /// /// - /// + /// /// /// Task ResendNotificationAsync(int idUser, - int idNotificationTransport, + NotificationTransport notificationTransport, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 01645d15..2d88349b 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -23,6 +23,8 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; +using AsbCloudApp.Services.Notifications; +using AsbCloudInfrastructure.Services.Notifications; namespace AsbCloudInfrastructure { @@ -145,8 +147,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); - services.AddSingleton(); - + // admin crud services: services.AddTransient, CrudCacheRepositoryBase>(s => new CrudCacheRepositoryBase( diff --git a/AsbCloudInfrastructure/Services/Notifications/NotificationSenderManager.cs b/AsbCloudInfrastructure/Services/Notifications/NotificationSenderManager.cs new file mode 100644 index 00000000..cd71ec79 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Notifications/NotificationSenderManager.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using AsbCloudApp.Data; +using AsbCloudApp.Services.Notifications; + +namespace AsbCloudInfrastructure.Services.Notifications; + +public class NotificationSenderManager : INotificationSenderManager +{ + private readonly IEnumerable notificationSenders; + + public NotificationSenderManager(IEnumerable notificationSenders) + { + this.notificationSenders = notificationSenders; + } + + public INotificationSender? GetOrDefault(NotificationTransport notificationTransport) + { + return notificationSenders.FirstOrDefault(x => x.NotificationTransport == notificationTransport); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs b/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs new file mode 100644 index 00000000..bfa055d6 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services.Notifications; + +namespace AsbCloudInfrastructure.Services.Notifications; + +public class NotificationService : INotificationService +{ + private readonly INotificationSenderManager notificationSenderManager; + private readonly INotificationRepository notificationRepository; + + public NotificationService(INotificationSenderManager notificationSenderManager, + INotificationRepository notificationRepository) + { + this.notificationSenderManager = notificationSenderManager; + this.notificationRepository = notificationRepository; + } + + public async Task NotifyAsync(int idUser, + int idNotificationCategory, + string title, + string message, + TimeSpan timeToLife, + NotificationTransport notificationTransport, + CancellationToken cancellationToken) + { + NotificationDto notification = new() + { + IdUser = idUser, + IdNotificationCategory = idNotificationCategory, + Title = title, + Message = message, + TimeToLife = timeToLife, + NotificationTransport = notificationTransport, + NotificationState = NotificationState.Registered + }; + + await notificationRepository.InsertAsync(notification, + cancellationToken); + + var notificationSender = notificationSenderManager.GetOrDefault(notificationTransport); + + if(notificationSender is null) + throw new ArgumentInvalidException("Метод отправки уведомления не найден", nameof(notificationTransport)); + + await notificationSender.SendAsync(notification, cancellationToken); + } + + public async Task UpdateNotificationAsync(int idNotification, + bool isRead, + CancellationToken cancellationToken) + { + var notification = await notificationRepository.GetOrDefaultAsync(idNotification, + cancellationToken) ?? throw new ArgumentInvalidException("Уведомление не найдено", + nameof(idNotification)); + + notification.NotificationState = isRead ? NotificationState.Read : NotificationState.Sent; + + await notificationRepository.UpdateAsync(notification, + cancellationToken); + } + + public async Task ResendNotificationAsync(int idUser, + NotificationTransport notificationTransport, + CancellationToken cancellationToken) + { + var notifications = await notificationRepository.GetUnsentNotificationsAsync(idUser, + notificationTransport, + cancellationToken); + + var notificationSender = notificationSenderManager.GetOrDefault(notificationTransport); + + if(notificationSender is null) + throw new ArgumentInvalidException("Отправитель уведомления не найден", nameof(notificationTransport)); + + await notificationSender.SendRangeAsync(notifications, cancellationToken); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/NotificationController.cs b/AsbCloudWebApi/Controllers/NotificationController.cs index 07fe4ace..d9834779 100644 --- a/AsbCloudWebApi/Controllers/NotificationController.cs +++ b/AsbCloudWebApi/Controllers/NotificationController.cs @@ -4,7 +4,8 @@ using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; using AsbCloudApp.Repositories; -using AsbCloudApp.Services; +using AsbCloudApp.Requests; +using AsbCloudApp.Services.Notifications; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -29,39 +30,39 @@ public class NotificationController : ControllerBase } /// - /// Метод отправки уведомления + /// Отправка уведомления /// /// Id пользователя - /// Id способа отправки уведомления /// Id категории уведомления /// Заголовок уведомления - /// Сообщение уведомления + /// Сообщение уведомления /// Время жизни уведомления + /// Способ отправки уведомления /// /// [HttpPost] [Route("send")] public async Task SendAsync([Required] int idUser, - [Required] int idNotificationTransport, [Required] int idNotificationCategory, [Required] string title, - [Required] string subject, + [Required] string message, [Required] TimeSpan timeToLife, + [Required] NotificationTransport notificationTransport, CancellationToken cancellationToken) { - await notificationService.SendNotificationAsync(idUser, - idNotificationTransport, + await notificationService.NotifyAsync(idUser, idNotificationCategory, title, - subject, + message, timeToLife, + notificationTransport, cancellationToken); return Ok(); } /// - /// Метод обновления уведомления + /// Обновление уведомления /// /// Id уведомления /// Прочитано ли уведомление @@ -79,21 +80,17 @@ public class NotificationController : ControllerBase return Ok(); } - + /// - /// Метод получения уведомлений по параметрам + /// Получение списка уведомлений /// - /// Кол-во пропускаемых записей - /// Кол-во выбираемых записей - /// Id способа доставки уведомления + /// Параметры запроса /// /// [HttpGet] [Route("getList")] [ProducesResponseType(typeof(PaginationContainer), (int)System.Net.HttpStatusCode.OK)] - public async Task GetListAsync(int? skip, - int? take, - [Required] int idNotificationTransport, + public async Task GetListAsync([FromQuery] NotificationRequest request, CancellationToken cancellationToken) { int? idUser = User.GetUserId(); @@ -101,17 +98,15 @@ public class NotificationController : ControllerBase if (!idUser.HasValue) return Forbid(); - var result = await notificationRepository.GetNotificationsAsync(skip, - take, - idUser.Value, - idNotificationTransport, + var result = await notificationRepository.GetNotificationsAsync(idUser.Value, + request, cancellationToken); return Ok(result); } /// - /// Метод удаления уведомления + /// Удаление уведомления /// /// Id уведомления /// diff --git a/AsbCloudWebApi/DependencyInjection.cs b/AsbCloudWebApi/DependencyInjection.cs index 6b1fc6b4..5d50561a 100644 --- a/AsbCloudWebApi/DependencyInjection.cs +++ b/AsbCloudWebApi/DependencyInjection.cs @@ -9,6 +9,9 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; +using AsbCloudApp.Services.Notifications; +using AsbCloudInfrastructure.Services.Notifications; +using AsbCloudWebApi.SignalR.Services; using Microsoft.OpenApi.Any; namespace AsbCloudWebApi @@ -105,5 +108,11 @@ namespace AsbCloudWebApi }; }); } + + public static void AddNotificationSenders(this IServiceCollection services) + { + services.AddScoped(); + services.AddTransient(); + } } } diff --git a/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs b/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs index 656c28ff..23c187bd 100644 --- a/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs +++ b/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs @@ -4,22 +4,22 @@ namespace AsbCloudWebApi.SignalR.ConnectionManager; public class ConnectionManager : IConnectionManager { - private readonly ConcurrentDictionary _connections = new(); + private readonly ConcurrentDictionary _connections = new(); - public void AddConnection(string userId, + public void AddConnection(int userId, string connectionId) { _connections.TryAdd(userId, connectionId); } - public void RemoveConnection(string userId) + public void RemoveConnection(int userId) { _connections.TryRemove(userId, out _); } - public string? GetConnectionIdByUserId(string userId) + public string? GetConnectionIdByUserId(int userId) { - _connections.TryGetValue(userId, out string? connectionId); + _connections.TryGetValue(userId, out var connectionId); return connectionId; } } \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs b/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs index 9584d32b..e8f34650 100644 --- a/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs +++ b/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs @@ -2,10 +2,10 @@ namespace AsbCloudWebApi.SignalR.ConnectionManager; public interface IConnectionManager { - void AddConnection(string userId, + void AddConnection(int userId, string connectionId); - void RemoveConnection(string userId); + void RemoveConnection(int userId); - string? GetConnectionIdByUserId(string userId); + string? GetConnectionIdByUserId(int userId); } \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/NotificationHub.cs b/AsbCloudWebApi/SignalR/NotificationHub.cs index 322808cc..99983f14 100644 --- a/AsbCloudWebApi/SignalR/NotificationHub.cs +++ b/AsbCloudWebApi/SignalR/NotificationHub.cs @@ -1,10 +1,9 @@ using System.Threading; using System.Threading.Tasks; -using AsbCloudApp.Services; -using AsbCloudWebApi.Options.Notifications; +using AsbCloudApp.Data; +using AsbCloudApp.Services.Notifications; using AsbCloudWebApi.SignalR.ConnectionManager; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Options; namespace AsbCloudWebApi.SignalR; @@ -13,25 +12,22 @@ public class NotificationHub : BaseHub { private readonly IConnectionManager connectionManager; private readonly INotificationService notificationService; - private readonly NotificationsOptionsSignalR notificationsOptionsSignalR; public NotificationHub(IConnectionManager connectionManager, - INotificationService notificationService, - IOptions notificationsOptionsSignalR) + INotificationService notificationService) { this.connectionManager = connectionManager; this.notificationService = notificationService; - this.notificationsOptionsSignalR = notificationsOptionsSignalR.Value; } public async Task OnConnected(int idUser) { string connectionId = Context.ConnectionId; - connectionManager.AddConnection(idUser.ToString(), connectionId); + connectionManager.AddConnection(idUser, connectionId); await notificationService.ResendNotificationAsync(idUser, - notificationsOptionsSignalR.IdTransport, + NotificationTransport.SignalR, CancellationToken.None); await base.OnConnectedAsync(); diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationSender.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationSender.cs new file mode 100644 index 00000000..bf7c226e --- /dev/null +++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationSender.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services.Notifications; +using AsbCloudWebApi.SignalR.ConnectionManager; +using Microsoft.AspNetCore.SignalR; + +namespace AsbCloudWebApi.SignalR.Services; + +public class SignalRNotificationSender : INotificationSender +{ + private readonly IConnectionManager connectionManager; + private readonly IHubContext notificationHubContext; + private readonly INotificationRepository notificationRepository; + + public SignalRNotificationSender(IConnectionManager connectionManager, + IHubContext notificationHubContext, + INotificationRepository notificationRepository) + { + this.connectionManager = connectionManager; + this.notificationHubContext = notificationHubContext; + this.notificationRepository = notificationRepository; + } + + public NotificationTransport NotificationTransport => NotificationTransport.SignalR; + + public async Task SendAsync(NotificationDto notification, + CancellationToken cancellationToken) + { + const string method = "notifications"; + + var connectionId = connectionManager.GetConnectionIdByUserId(notification.IdUser); + + if (!string.IsNullOrWhiteSpace(connectionId)) + { + await notificationHubContext.Clients.Client(connectionId) + .SendAsync(method, + notification, + cancellationToken); + + notification.SentDate = DateTime.UtcNow; + notification.NotificationState = NotificationState.Sent; + } + + await notificationRepository.UpdateAsync(notification, + cancellationToken); + } + + public async Task SendRangeAsync(IEnumerable notifications, + CancellationToken cancellationToken) + { + foreach (var notification in notifications) + { + await SendAsync(notification, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Startup.cs b/AsbCloudWebApi/Startup.cs index 3e1c56c3..bda1e1ff 100644 --- a/AsbCloudWebApi/Startup.cs +++ b/AsbCloudWebApi/Startup.cs @@ -1,9 +1,7 @@ using AsbCloudInfrastructure; using AsbCloudWebApi.Converters; using AsbCloudWebApi.Middlewares; -using AsbCloudWebApi.Options.Notifications; using AsbCloudWebApi.SignalR; -using AsbCloudWebApi.SignalR.BackgroundServices; using AsbCloudWebApi.SignalR.ConnectionManager; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -44,13 +42,13 @@ namespace AsbCloudWebApi services.AddSwagger(); services.AddInfrastructure(Configuration); + + services.AddNotificationSenders(); services.AddJWTAuthentication(); services.AddSignalR() - .Services.AddSingleton() - .AddHostedService() - .Configure(Configuration.GetSection("NotificationsOptionsSignalR")); + .Services.AddSingleton(); services.AddCors(options => {