Рефакторинг транспорта уведомлений

1. Создал отдельный бекграунд сервис для уведомлений.
2. Сделал отправку уведомлений с помощью SignalR с использованием бекграунд сервиса.
3. Убрал из NotificationDto свойство User. Данное свойство избыточно в данном Dto.
4. В транспорте отправки уведомлений по e-mail добавил получение пользователя.
5. Поправил NotificationRepository, избавился от использования кэша.
This commit is contained in:
parent 4226d6366c
commit ff65869341
10 changed files with 85 additions and 64 deletions

View File

@ -1,5 +1,4 @@
using System;
using AsbCloudApp.Data.User;
namespace AsbCloudApp.Data;
@ -79,9 +78,4 @@ public class NotificationDto : IId
/// DTO категории уведомления
/// </summary>
public NotificationCategoryDto NotificationCategory { get; set; } = null!;
/// <summary>
/// DTO получателя уведомления
/// </summary>
public UserDto User { get; set; } = null!;
}

View File

@ -16,7 +16,6 @@ namespace AsbCloudApp.Services.Notifications;
public class NotificationService
{
private readonly ICrudRepository<NotificationCategoryDto> notificationCategoryRepository;
private readonly IUserRepository userRepository;
private readonly INotificationRepository notificationRepository;
private readonly IEnumerable<INotificationTransportService> notificationTransportServices;
@ -28,11 +27,9 @@ public class NotificationService
/// <param name="notificationRepository"></param>
/// <param name="notificationTransportServices"></param>
public NotificationService(ICrudRepository<NotificationCategoryDto> notificationCategoryRepository,
IUserRepository userRepository,
INotificationRepository notificationRepository,
IEnumerable<INotificationTransportService> notificationTransportServices)
{
this.userRepository = userRepository;
this.notificationCategoryRepository = notificationCategoryRepository;
this.notificationRepository = notificationRepository;
this.notificationTransportServices = notificationTransportServices;
@ -50,14 +47,11 @@ public class NotificationService
.GetOrDefaultAsync(request.IdNotificationCategory, cancellationToken)
?? throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(request.IdNotificationCategory));
var user = await userRepository.GetOrDefaultAsync(request.IdUser, cancellationToken)
?? throw new ArgumentInvalidException("Пользователь не найден" , nameof(request.IdUser));
var notification = new NotificationDto
{
IdUser = request.IdUser,
RegistrationDate = DateTime.UtcNow,
IdNotificationCategory = request.IdNotificationCategory,
IdNotificationCategory = notificationCategory.Id,
Title = request.Title,
Message = request.Message,
IdTransportType = request.IdTransportType,
@ -65,8 +59,7 @@ public class NotificationService
notification.Id = await notificationRepository.InsertAsync(notification, cancellationToken);
notification.NotificationCategory = notificationCategory;
notification.User = user;
var notificationTransportService = GetNotificationTransportService(request.IdTransportType);
await notificationTransportService.SendAsync(notification, cancellationToken);

View File

@ -7,7 +7,6 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
{
# nullable enable
/// <summary>
/// Сервис для фонового выполнения работы
/// </summary>
@ -94,5 +93,4 @@ namespace AsbCloudInfrastructure.Background
}
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace AsbCloudInfrastructure.Background;
public class NotificationBackgroundWorker : BackgroundWorker
{
public NotificationBackgroundWorker(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}

View File

@ -111,6 +111,7 @@ namespace AsbCloudInfrastructure
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
services.AddSingleton<BackgroundWorker>();
services.AddSingleton<NotificationBackgroundWorker>();
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
services.AddTransient<IAuthService, AuthService>();

View File

@ -8,21 +8,21 @@ using AsbCloudDb;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
namespace AsbCloudInfrastructure.Repository;
public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, Notification>, INotificationRepository
public class NotificationRepository : CrudRepositoryBase<NotificationDto, Notification>, INotificationRepository
{
private static IQueryable<Notification> MakeQueryNotification(DbSet<Notification> dbSet)
=> dbSet.Include(n => n.NotificationCategory)
.Include(n => n.User)
.AsNoTracking();
public NotificationRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
: base(dbContext, memoryCache, MakeQueryNotification)
public NotificationRepository(IAsbCloudDbContext context)
: base(context, MakeQueryNotification)
{
}
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
NotificationRequest request,
@ -33,7 +33,7 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
var query = BuildQuery(idUser, request);
var result = new PaginationContainer<NotificationDto>()
var result = new PaginationContainer<NotificationDto>
{
Skip = skip,
Take = take,
@ -61,13 +61,12 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
.Include(x => x.NotificationCategory)
.Where(n => n.IdUser == idUser);
if (request.IsSent.HasValue)
if (request.IsSent.HasValue)
{
if(request.IsSent.Value)
query = query.Where(n => n.SentDate != null);
else
query = query.Where(n => n.SentDate == null);
}
query = request.IsSent.Value ?
query.Where(n => n.SentDate != null)
: query.Where(n => n.SentDate == null);
}
if (request.IdTransportType.HasValue)
query = query.Where(n => n.IdTransportType == request.IdTransportType);

View File

@ -50,7 +50,7 @@ namespace AsbCloudInfrastructure.Services.Email
return Task.CompletedTask;
}
var workId = MakeWorkId(notification.User.Email, notification.Title, notification.Message);
var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
if (!backgroundWorker.Contains(workId))
{
var workAction = MakeEmailSendWorkAction(notification);
@ -73,17 +73,23 @@ namespace AsbCloudInfrastructure.Services.Email
private Func<string, IServiceProvider, CancellationToken, Task> MakeEmailSendWorkAction(NotificationDto notification)
{
if(!MailAddress.TryCreate(notification.User.Email, out var mailAddress))
Trace.TraceWarning($"Mail {notification.User.Email} is not correct.");
if (mailAddress is null)
throw new ArgumentInvalidException($"Mail {notification.User.Email} is not null.", nameof(notification.User.Email));
if (string.IsNullOrWhiteSpace(notification.Title))
throw new ArgumentInvalidException($"{nameof(notification.Title)} should be set", nameof(notification.Title));
return async (_, serviceProvider, token) =>
{
var notificationRepository = serviceProvider.GetRequiredService<INotificationRepository>();
var userRepository = serviceProvider.GetRequiredService<IUserRepository>();
var user = await userRepository.GetOrDefaultAsync(notification.IdUser, token)
?? throw new ArgumentInvalidException("Пользователь не найден" , nameof(notification.IdUser));
if(!MailAddress.TryCreate(user.Email, out var mailAddress))
Trace.TraceWarning($"Mail {user.Email} is not correct.");
if (mailAddress is null)
throw new ArgumentInvalidException($"Mail {user.Email} is not null.", nameof(user.Email));
var from = new MailAddress(sender);
var message = new MailMessage
{
@ -105,17 +111,15 @@ namespace AsbCloudInfrastructure.Services.Email
notification.SentDate = DateTime.UtcNow;
var notificationRepository = serviceProvider.GetRequiredService<INotificationRepository>();
await notificationRepository.UpdateAsync(notification, token);
Trace.TraceInformation($"Send email to {notification.User.Email} subj:{notification.Title} html body count {notification.Message.Length}");
Trace.TraceInformation($"Send email to {user.Email} subj:{notification.Title} html body count {notification.Message.Length}");
};
}
private static string MakeWorkId(string address, string subject, string content)
private static string MakeWorkId(int idUser, string subject, string content)
{
var hash = address.GetHashCode();
var hash = idUser.GetHashCode();
hash ^= subject.GetHashCode();
hash ^= content.GetHashCode();
return hash.ToString("x");

View File

@ -40,8 +40,14 @@ namespace AsbCloudInfrastructure
backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork());
backgroundWorker.Push(MakeMemoryMonitoringWork());
var notificationBackgroundWorker = provider.GetRequiredService<NotificationBackgroundWorker>();
Task.Delay(1_000)
.ContinueWith(async (_) => await backgroundWorker.StartAsync(CancellationToken.None));
.ContinueWith(async (_) =>
{
await backgroundWorker.StartAsync(CancellationToken.None);
await notificationBackgroundWorker.StartAsync(CancellationToken.None);
});
}
static WorkPeriodic MakeMemoryMonitoringWork()

View File

@ -145,7 +145,6 @@ namespace AsbCloudWebApi.Tests.ServicesTests
.Returns(1);
notificationService = new NotificationService(notificationCategoryRepositoryMock.Object,
userRepositoryMock.Object,
new Mock<INotificationRepository>().Object,
new [] { notificationTransportServiceMock.Object });

View File

@ -6,50 +6,47 @@ using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Background;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudWebApi.SignalR.Services;
public class SignalRNotificationTransportService : INotificationTransportService
{
private readonly NotificationBackgroundWorker backgroundWorker;
private readonly ConnectionManagerService connectionManagerService;
private readonly IHubContext<NotificationHub> notificationHubContext;
private readonly INotificationRepository notificationRepository;
private readonly SemaphoreSlim semaphoreSlim = new (1, 1);
public SignalRNotificationTransportService(ConnectionManagerService connectionManagerService,
IHubContext<NotificationHub> notificationHubContext,
INotificationRepository notificationRepository)
public SignalRNotificationTransportService(NotificationBackgroundWorker backgroundWorker,
ConnectionManagerService connectionManagerService,
IHubContext<NotificationHub> notificationHubContext)
{
this.backgroundWorker = backgroundWorker;
this.connectionManagerService = connectionManagerService;
this.notificationHubContext = notificationHubContext;
this.notificationRepository = notificationRepository;
}
public int IdTransportType => 0;
public async Task SendAsync(NotificationDto notification,
public Task SendAsync(NotificationDto notification,
CancellationToken cancellationToken)
{
const string method = "receiveNotifications";
var workId = notification.Id.ToString();
var connectionId = connectionManagerService.GetConnectionIdByUserId(notification.IdUser);
if (!string.IsNullOrWhiteSpace(connectionId))
if (!backgroundWorker.Contains(workId))
{
notification.SentDate = DateTime.UtcNow;
await notificationHubContext.Clients.Client(connectionId)
.SendAsync(method,
notification,
cancellationToken);
var connectionId = connectionManagerService.GetConnectionIdByUserId(notification.IdUser);
await semaphoreSlim.WaitAsync(cancellationToken);
await notificationRepository.UpdateAsync(notification, cancellationToken);
semaphoreSlim.Release();
if (!string.IsNullOrWhiteSpace(connectionId))
{
var workAction = MakeSignalRSendWorkAction(notification, connectionId);
var work = new WorkBase(workId, workAction);
backgroundWorker.Push(work);
}
}
return Task.CompletedTask;
}
public Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
@ -60,4 +57,24 @@ public class SignalRNotificationTransportService : INotificationTransportService
return Task.WhenAll(tasks);
}
private Func<string, IServiceProvider, CancellationToken, Task> MakeSignalRSendWorkAction(NotificationDto notification,
string connectionId)
{
const string method = "receiveNotifications";
return async (_, serviceProvider, cancellationToken) =>
{
notification.SentDate = DateTime.UtcNow;
await notificationHubContext.Clients.Client(connectionId)
.SendAsync(method,
notification,
cancellationToken);
var notificationRepository = serviceProvider.GetRequiredService<INotificationRepository>();
await notificationRepository.UpdateAsync(notification, cancellationToken);
};
}
}