diff --git a/AsbCloudApp/Repositories/INotificationRepository.cs b/AsbCloudApp/Repositories/INotificationRepository.cs index 0bbdffbd..153eba58 100644 --- a/AsbCloudApp/Repositories/INotificationRepository.cs +++ b/AsbCloudApp/Repositories/INotificationRepository.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; @@ -11,6 +12,15 @@ namespace AsbCloudApp.Repositories; /// public interface INotificationRepository : ICrudRepository { + /// + /// Обновление изменённых уведомлений + /// + /// + /// + /// + Task UpdateRangeAsync(IEnumerable notifications, + CancellationToken cancellationToken); + /// /// Получение уведомлений по параметрам /// diff --git a/AsbCloudApp/Requests/NotificationRequest.cs b/AsbCloudApp/Requests/NotificationRequest.cs index 0124b744..955a6b8b 100644 --- a/AsbCloudApp/Requests/NotificationRequest.cs +++ b/AsbCloudApp/Requests/NotificationRequest.cs @@ -1,5 +1,3 @@ -using System; - namespace AsbCloudApp.Requests; /// @@ -8,9 +6,9 @@ namespace AsbCloudApp.Requests; public class NotificationRequest : RequestBase { /// - /// Дата отправки уведомления + /// Получение отправленных/не отправленных уведомлений /// - public DateTime? SentDate { get; set; } + public bool IsSent { get; set; } = false; /// /// Id типа доставки уведомления diff --git a/AsbCloudApp/Services/Notifications/INotificationTransportService.cs b/AsbCloudApp/Services/Notifications/INotificationTransportService.cs index dc4066bb..fba70d4b 100644 --- a/AsbCloudApp/Services/Notifications/INotificationTransportService.cs +++ b/AsbCloudApp/Services/Notifications/INotificationTransportService.cs @@ -30,6 +30,6 @@ public interface INotificationTransportService /// /// /// - Task SendRangeAsync(IEnumerable notifications, + Task SendRangeAsync(IEnumerable notifications, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 255d46a3..696a0439 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -84,6 +84,11 @@ namespace AsbCloudInfrastructure TypeAdapterConfig.GlobalSettings.Default.Config .ForType(); + + TypeAdapterConfig.GlobalSettings.Default.Config + .ForType() + .Ignore(dst => dst.NotificationCategory, + dst => dst.User); } public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) diff --git a/AsbCloudInfrastructure/Repository/NotificationRepository.cs b/AsbCloudInfrastructure/Repository/NotificationRepository.cs index 25e89ae0..cfc99cb9 100644 --- a/AsbCloudInfrastructure/Repository/NotificationRepository.cs +++ b/AsbCloudInfrastructure/Repository/NotificationRepository.cs @@ -1,9 +1,12 @@ +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; +using AsbCloudApp.Data.SAUB; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; +using AsbCloudApp.Services; using AsbCloudDb; using AsbCloudDb.Model; using Mapster; @@ -15,14 +18,37 @@ namespace AsbCloudInfrastructure.Repository; public class NotificationRepository : CrudCacheRepositoryBase, INotificationRepository { private static IQueryable MakeQueryNotification(DbSet dbSet) - => dbSet.AsNoTracking() - .Include(n => n.NotificationCategory); + => dbSet.Include(n => n.NotificationCategory) + .AsNoTracking(); public NotificationRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache) : base(dbContext, memoryCache, MakeQueryNotification) { } + public async Task UpdateRangeAsync(IEnumerable notifications, CancellationToken cancellationToken) + { + if (!notifications.Any()) + return 0; + + var ids = notifications.Select(d => d.Id).ToArray(); + + var existingEntities = await dbSet + .Where(d => ids.Contains(d.Id)) + .AsNoTracking() + .Select(d => d.Id) + .ToArrayAsync(cancellationToken); + + if (ids.Length > existingEntities.Length) + return ICrudRepository.ErrorIdNotFound; + + var entities = notifications.Select(Convert); + + dbContext.Notifications.UpdateRange(entities); + + return await dbContext.SaveChangesAsync(cancellationToken); + } + public async Task> GetNotificationsAsync(int idUser, NotificationRequest request, CancellationToken cancellationToken) @@ -41,28 +67,28 @@ public class NotificationRepository : CrudCacheRepositoryBase x.NotificationCategory) + .AsNoTracking() .Select(x => x.Adapt()) .ToListAsync(cancellationToken); return result; } - private IQueryable BuildQuery(int? idUser, + private IQueryable BuildQuery(int idUser, NotificationRequest request) { - var query = dbContext.Notifications.AsQueryable(); + var query = dbContext.Notifications + .Include(x => x.NotificationCategory) + .Where(n => n.IdUser == idUser) + .AsQueryable(); - if (!request.SentDate.HasValue) + if (!request.IsSent) query = query.Where(n => n.SentDate == null); - - if (idUser.HasValue) - query = query.Where(n => n.IdUser == idUser); if (request.IdTransportType.HasValue) query = query.Where(n => n.IdTransportType == request.IdTransportType); diff --git a/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs b/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs index fb6336ce..0406a3d4 100644 --- a/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs +++ b/AsbCloudInfrastructure/Services/Notifications/NotificationService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,7 +9,6 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudApp.Services.Notifications; -using Microsoft.Extensions.DependencyInjection; namespace AsbCloudInfrastructure.Services.Notifications; @@ -16,15 +16,15 @@ public class NotificationService : INotificationService { private readonly ICrudRepository notificationCategoryRepository; private readonly INotificationRepository notificationRepository; - private readonly IServiceProvider serviceProvider; + private readonly IEnumerable notificationTransportServices; public NotificationService(ICrudRepository notificationCategoryRepository, INotificationRepository notificationRepository, - IServiceProvider serviceProvider) + IEnumerable notificationTransportServices) { this.notificationCategoryRepository = notificationCategoryRepository; this.notificationRepository = notificationRepository; - this.serviceProvider = serviceProvider; + this.notificationTransportServices = notificationTransportServices; } public async Task NotifyAsync(int idUser, @@ -57,6 +57,9 @@ public class NotificationService : INotificationService var notificationTransportService = GetNotificationTransportService(idTransportType); await notificationTransportService.SendAsync(notification, cancellationToken); + + await notificationRepository.UpdateAsync(notification, + cancellationToken); } public async Task UpdateNotificationAsync(int idNotification, @@ -64,41 +67,47 @@ public class NotificationService : INotificationService CancellationToken cancellationToken) { var notification = await notificationRepository.GetOrDefaultAsync(idNotification, - cancellationToken) ?? throw new ArgumentInvalidException("Уведомление не найдено", - nameof(idNotification)); + cancellationToken) + ?? throw new ArgumentInvalidException("Уведомление не найдено", nameof(idNotification)); if (isRead) { if (notification.SentDate == null) throw new ArgumentInvalidException("Уведомление не может быть прочитано", nameof(isRead)); - + notification.SentDate = DateTime.UtcNow; } - await notificationRepository.UpdateAsync(notification, + await notificationRepository.UpdateAsync(notification, cancellationToken); } - public async Task ResendNotificationAsync(int idUser, + public async Task ResendNotificationAsync(int idUser, NotificationRequest request, CancellationToken cancellationToken) { + if (!request.IdTransportType.HasValue) + throw new ArgumentInvalidException("Id типа доставки уведомления должен иметь значение", + nameof(request.IdTransportType)); + var result = await notificationRepository.GetNotificationsAsync(idUser, request, cancellationToken); - var notificationTransportService = GetNotificationTransportService(request.IdTransportType!.Value); + var notificationTransportService = GetNotificationTransportService(request.IdTransportType.Value); - await notificationTransportService.SendRangeAsync(result.Items, cancellationToken); + await notificationTransportService.SendRangeAsync(result.Items, + cancellationToken); + + await notificationRepository.UpdateRangeAsync(result.Items, + cancellationToken); } private INotificationTransportService GetNotificationTransportService(int idTransportType) { - var notificationTransportService = serviceProvider.GetServices() - .FirstOrDefault(s => s.IdTransportType == idTransportType); - - if(notificationTransportService is null) - throw new ArgumentInvalidException("Доставщик уведомлений не найден", nameof(idTransportType)); + var notificationTransportService = notificationTransportServices + .FirstOrDefault(s => s.IdTransportType == idTransportType) + ?? throw new ArgumentInvalidException("Доставщик уведомлений не найден", nameof(idTransportType)); return notificationTransportService; } diff --git a/AsbCloudWebApi/DependencyInjection.cs b/AsbCloudWebApi/DependencyInjection.cs index 6ff597c4..9f15b707 100644 --- a/AsbCloudWebApi/DependencyInjection.cs +++ b/AsbCloudWebApi/DependencyInjection.cs @@ -138,7 +138,7 @@ namespace AsbCloudWebApi public static void AddNotificationTransportServices(this IServiceCollection services) { - services.AddSingleton(); + services.AddTransient(); } } } diff --git a/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs b/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs deleted file mode 100644 index 23c187bd..00000000 --- a/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Concurrent; - -namespace AsbCloudWebApi.SignalR.ConnectionManager; - -public class ConnectionManager : IConnectionManager -{ - private readonly ConcurrentDictionary _connections = new(); - - public void AddConnection(int userId, - string connectionId) - { - _connections.TryAdd(userId, connectionId); - } - - public void RemoveConnection(int userId) - { - _connections.TryRemove(userId, out _); - } - - public string? GetConnectionIdByUserId(int userId) - { - _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 deleted file mode 100644 index e8f34650..00000000 --- a/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace AsbCloudWebApi.SignalR.ConnectionManager; - -public interface IConnectionManager -{ - void AddConnection(int userId, - string connectionId); - - void RemoveConnection(int userId); - - string? GetConnectionIdByUserId(int userId); -} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/NotificationHub.cs b/AsbCloudWebApi/SignalR/NotificationHub.cs index 51b98f63..ea1f6885 100644 --- a/AsbCloudWebApi/SignalR/NotificationHub.cs +++ b/AsbCloudWebApi/SignalR/NotificationHub.cs @@ -3,37 +3,43 @@ using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Requests; using AsbCloudApp.Services.Notifications; -using AsbCloudWebApi.SignalR.ConnectionManager; +using AsbCloudWebApi.SignalR.Services; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace AsbCloudWebApi.SignalR; [Authorize] public class NotificationHub : BaseHub { - private readonly IConnectionManager connectionManager; + private readonly ConnectionManagerService connectionManagerService; private readonly INotificationService notificationService; - public NotificationHub(IConnectionManager connectionManager, + public NotificationHub(ConnectionManagerService connectionManagerService, INotificationService notificationService) { - this.connectionManager = connectionManager; + this.connectionManagerService = connectionManagerService; this.notificationService = notificationService; } - public async Task OnConnected(int idUser, NotificationRequest request) + public async Task OnConnected(NotificationRequest request) { try { + await base.OnConnectedAsync(); + + var idUser = Context.User?.GetUserId(); + + if (!idUser.HasValue) + return; + string connectionId = Context.ConnectionId; - connectionManager.AddConnection(idUser, connectionId); + connectionManagerService.AddOrUpdateConnection(idUser.Value, connectionId); - await notificationService.ResendNotificationAsync(idUser, + await notificationService.ResendNotificationAsync(idUser.Value, request, CancellationToken.None); - - await base.OnConnectedAsync(); } catch (Exception e) { @@ -41,10 +47,15 @@ public class NotificationHub : BaseHub } } - public Task OnDisconnected(int idUser) + public async Task OnDisconnected() { - connectionManager.RemoveConnection(idUser); + await base.OnDisconnectedAsync(null); + + var idUser = Context.User?.GetUserId(); - return base.OnDisconnectedAsync(null); + if (!idUser.HasValue) + return; + + connectionManagerService.RemoveConnection(idUser.Value); } } \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/Services/ConnectionManagerService.cs b/AsbCloudWebApi/SignalR/Services/ConnectionManagerService.cs new file mode 100644 index 00000000..b85700c3 --- /dev/null +++ b/AsbCloudWebApi/SignalR/Services/ConnectionManagerService.cs @@ -0,0 +1,25 @@ +using System.Collections.Concurrent; + +namespace AsbCloudWebApi.SignalR.Services; + +public class ConnectionManagerService +{ + private readonly ConcurrentDictionary connections = new(); + + public void AddOrUpdateConnection(int userId, string connectionId) + { + connections.AddOrUpdate(userId, connectionId, + (key, existingConnectionId) => connectionId); + } + + public void RemoveConnection(int userId) + { + connections.TryRemove(userId, out _); + } + + public string? GetConnectionIdByUserId(int userId) + { + connections.TryGetValue(userId, out var connectionId); + return connectionId; + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs index b596e84a..d8c10adc 100644 --- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs +++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs @@ -1,29 +1,24 @@ using System; using System.Collections.Generic; +using System.Linq; 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; -using Microsoft.Extensions.DependencyInjection; namespace AsbCloudWebApi.SignalR.Services; public class SignalRNotificationTransportService : INotificationTransportService { - private readonly IConnectionManager connectionManager; + private readonly ConnectionManagerService connectionManagerService; private readonly IHubContext notificationHubContext; - private readonly IServiceProvider serviceProvider; - public SignalRNotificationTransportService(IConnectionManager connectionManager, - IHubContext notificationHubContext, - IServiceProvider serviceProvider) + public SignalRNotificationTransportService(ConnectionManagerService connectionManagerService, + IHubContext notificationHubContext) { - this.connectionManager = connectionManager; + this.connectionManagerService = connectionManagerService; this.notificationHubContext = notificationHubContext; - this.serviceProvider = serviceProvider; } public int IdTransportType => 0; @@ -33,7 +28,7 @@ public class SignalRNotificationTransportService : INotificationTransportService { const string method = "notifications"; - var connectionId = connectionManager.GetConnectionIdByUserId(notification.IdUser); + var connectionId = connectionManagerService.GetConnectionIdByUserId(notification.IdUser); if (!string.IsNullOrWhiteSpace(connectionId)) { @@ -43,25 +38,15 @@ public class SignalRNotificationTransportService : INotificationTransportService .SendAsync(method, notification, cancellationToken); - - var scope = serviceProvider.CreateScope(); - - var notificationRepository = scope.ServiceProvider.GetService(); - - if (notificationRepository != null) - { - await notificationRepository.UpdateAsync(notification, cancellationToken); - } } } - public async Task SendRangeAsync(IEnumerable notifications, + public Task SendRangeAsync(IEnumerable notifications, CancellationToken cancellationToken) { - foreach (var notification in notifications) - { - await SendAsync(notification, - cancellationToken); - } + var tasks = Array.ConvertAll(notifications.ToArray(), notification => + SendAsync(notification, cancellationToken)); + + return Task.WhenAll(tasks); } } \ No newline at end of file diff --git a/AsbCloudWebApi/Startup.cs b/AsbCloudWebApi/Startup.cs index 7714fa4e..0fb3ba87 100644 --- a/AsbCloudWebApi/Startup.cs +++ b/AsbCloudWebApi/Startup.cs @@ -2,7 +2,7 @@ using AsbCloudInfrastructure; using AsbCloudWebApi.Converters; using AsbCloudWebApi.Middlewares; using AsbCloudWebApi.SignalR; -using AsbCloudWebApi.SignalR.ConnectionManager; +using AsbCloudWebApi.SignalR.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -48,7 +48,7 @@ namespace AsbCloudWebApi services.AddJWTAuthentication(); services.AddSignalR() - .Services.AddSingleton(); + .Services.AddSingleton(); services.AddCors(options => {