Правки после ревью

1. Поправил обновление отправленных уведомлений.
2. Поправил ConnectionManager. Переименовал сервис, удалил абстракцию, так как предполагается только одна реализация.
3. При маппинге из dto в сущность для уведомления добавил игнорирование категории и пользователя.
4. Изменил жизенный цикл зависимости для отправителя.
5. Добавил в репозиторий метод для обновления коллекции уведомлений.
6. Поправил Hub, Id пользователя можно получать внутри хаба.
7. Поправил NotificationRequest + метод BuildQuery в NotificationRepository
This commit is contained in:
parent 4ae769adcc
commit 635e4cd7fc
13 changed files with 141 additions and 108 deletions

View File

@ -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;
/// </summary>
public interface INotificationRepository : ICrudRepository<NotificationDto>
{
/// <summary>
/// Обновление изменённых уведомлений
/// </summary>
/// <param name="notifications"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<int> UpdateRangeAsync(IEnumerable<NotificationDto> notifications,
CancellationToken cancellationToken);
/// <summary>
/// Получение уведомлений по параметрам
/// </summary>

View File

@ -1,5 +1,3 @@
using System;
namespace AsbCloudApp.Requests;
/// <summary>
@ -8,9 +6,9 @@ namespace AsbCloudApp.Requests;
public class NotificationRequest : RequestBase
{
/// <summary>
/// Дата отправки уведомления
/// Получение отправленных/не отправленных уведомлений
/// </summary>
public DateTime? SentDate { get; set; }
public bool IsSent { get; set; } = false;
/// <summary>
/// Id типа доставки уведомления

View File

@ -30,6 +30,6 @@ public interface INotificationTransportService
/// <param name="notifications"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
CancellationToken cancellationToken);
}

View File

@ -84,6 +84,11 @@ namespace AsbCloudInfrastructure
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<WellFinalDocumentDto, WellFinalDocument>();
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<NotificationDto, Notification>()
.Ignore(dst => dst.NotificationCategory,
dst => dst.User);
}
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)

View File

@ -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<NotificationDto, Notification>, INotificationRepository
{
private static IQueryable<Notification> MakeQueryNotification(DbSet<Notification> 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<int> UpdateRangeAsync(IEnumerable<NotificationDto> 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<SetpointsRequestDto>.ErrorIdNotFound;
var entities = notifications.Select(Convert);
dbContext.Notifications.UpdateRange(entities);
return await dbContext.SaveChangesAsync(cancellationToken);
}
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
NotificationRequest request,
CancellationToken cancellationToken)
@ -41,28 +67,28 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
if (result.Count == 0)
return result;
result.Items = await query
.SortBy(request.SortFields)
.Skip(skip)
.Take(take)
.Include(x => x.NotificationCategory)
.AsNoTracking()
.Select(x => x.Adapt<NotificationDto>())
.ToListAsync(cancellationToken);
return result;
}
private IQueryable<Notification> BuildQuery(int? idUser,
private IQueryable<Notification> 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);

View File

@ -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<NotificationCategoryDto> notificationCategoryRepository;
private readonly INotificationRepository notificationRepository;
private readonly IServiceProvider serviceProvider;
private readonly IEnumerable<INotificationTransportService> notificationTransportServices;
public NotificationService(ICrudRepository<NotificationCategoryDto> notificationCategoryRepository,
INotificationRepository notificationRepository,
IServiceProvider serviceProvider)
IEnumerable<INotificationTransportService> 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<INotificationTransportService>()
.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;
}

View File

@ -138,7 +138,7 @@ namespace AsbCloudWebApi
public static void AddNotificationTransportServices(this IServiceCollection services)
{
services.AddSingleton<INotificationTransportService, SignalRNotificationTransportService>();
services.AddTransient<INotificationTransportService, SignalRNotificationTransportService>();
}
}
}

View File

@ -1,25 +0,0 @@
using System.Collections.Concurrent;
namespace AsbCloudWebApi.SignalR.ConnectionManager;
public class ConnectionManager : IConnectionManager
{
private readonly ConcurrentDictionary<int, string> _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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace AsbCloudWebApi.SignalR.Services;
public class ConnectionManagerService
{
private readonly ConcurrentDictionary<int, string> 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;
}
}

View File

@ -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<NotificationHub> notificationHubContext;
private readonly IServiceProvider serviceProvider;
public SignalRNotificationTransportService(IConnectionManager connectionManager,
IHubContext<NotificationHub> notificationHubContext,
IServiceProvider serviceProvider)
public SignalRNotificationTransportService(ConnectionManagerService connectionManagerService,
IHubContext<NotificationHub> 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<INotificationRepository>();
if (notificationRepository != null)
{
await notificationRepository.UpdateAsync(notification, cancellationToken);
}
}
}
public async Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
public Task SendRangeAsync(IEnumerable<NotificationDto> 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);
}
}

View File

@ -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<IConnectionManager, ConnectionManager>();
.Services.AddSingleton<ConnectionManagerService>();
services.AddCors(options =>
{