forked from ddrilling/AsbCloudServer
Рефакторинг транспорта уведомлений
1. Создал отдельный бекграунд сервис для уведомлений. 2. Сделал отправку уведомлений с помощью SignalR с использованием бекграунд сервиса. 3. Убрал из NotificationDto свойство User. Данное свойство избыточно в данном Dto. 4. В транспорте отправки уведомлений по e-mail добавил получение пользователя. 5. Поправил NotificationRepository, избавился от использования кэша.
This commit is contained in:
parent
4226d6366c
commit
ff65869341
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using AsbCloudApp.Data.User;
|
|
||||||
|
|
||||||
namespace AsbCloudApp.Data;
|
namespace AsbCloudApp.Data;
|
||||||
|
|
||||||
@ -79,9 +78,4 @@ public class NotificationDto : IId
|
|||||||
/// DTO категории уведомления
|
/// DTO категории уведомления
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NotificationCategoryDto NotificationCategory { get; set; } = null!;
|
public NotificationCategoryDto NotificationCategory { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DTO получателя уведомления
|
|
||||||
/// </summary>
|
|
||||||
public UserDto User { get; set; } = null!;
|
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ namespace AsbCloudApp.Services.Notifications;
|
|||||||
public class NotificationService
|
public class NotificationService
|
||||||
{
|
{
|
||||||
private readonly ICrudRepository<NotificationCategoryDto> notificationCategoryRepository;
|
private readonly ICrudRepository<NotificationCategoryDto> notificationCategoryRepository;
|
||||||
private readonly IUserRepository userRepository;
|
|
||||||
private readonly INotificationRepository notificationRepository;
|
private readonly INotificationRepository notificationRepository;
|
||||||
private readonly IEnumerable<INotificationTransportService> notificationTransportServices;
|
private readonly IEnumerable<INotificationTransportService> notificationTransportServices;
|
||||||
|
|
||||||
@ -28,11 +27,9 @@ public class NotificationService
|
|||||||
/// <param name="notificationRepository"></param>
|
/// <param name="notificationRepository"></param>
|
||||||
/// <param name="notificationTransportServices"></param>
|
/// <param name="notificationTransportServices"></param>
|
||||||
public NotificationService(ICrudRepository<NotificationCategoryDto> notificationCategoryRepository,
|
public NotificationService(ICrudRepository<NotificationCategoryDto> notificationCategoryRepository,
|
||||||
IUserRepository userRepository,
|
|
||||||
INotificationRepository notificationRepository,
|
INotificationRepository notificationRepository,
|
||||||
IEnumerable<INotificationTransportService> notificationTransportServices)
|
IEnumerable<INotificationTransportService> notificationTransportServices)
|
||||||
{
|
{
|
||||||
this.userRepository = userRepository;
|
|
||||||
this.notificationCategoryRepository = notificationCategoryRepository;
|
this.notificationCategoryRepository = notificationCategoryRepository;
|
||||||
this.notificationRepository = notificationRepository;
|
this.notificationRepository = notificationRepository;
|
||||||
this.notificationTransportServices = notificationTransportServices;
|
this.notificationTransportServices = notificationTransportServices;
|
||||||
@ -50,14 +47,11 @@ public class NotificationService
|
|||||||
.GetOrDefaultAsync(request.IdNotificationCategory, cancellationToken)
|
.GetOrDefaultAsync(request.IdNotificationCategory, cancellationToken)
|
||||||
?? throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(request.IdNotificationCategory));
|
?? throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(request.IdNotificationCategory));
|
||||||
|
|
||||||
var user = await userRepository.GetOrDefaultAsync(request.IdUser, cancellationToken)
|
|
||||||
?? throw new ArgumentInvalidException("Пользователь не найден" , nameof(request.IdUser));
|
|
||||||
|
|
||||||
var notification = new NotificationDto
|
var notification = new NotificationDto
|
||||||
{
|
{
|
||||||
IdUser = request.IdUser,
|
IdUser = request.IdUser,
|
||||||
RegistrationDate = DateTime.UtcNow,
|
RegistrationDate = DateTime.UtcNow,
|
||||||
IdNotificationCategory = request.IdNotificationCategory,
|
IdNotificationCategory = notificationCategory.Id,
|
||||||
Title = request.Title,
|
Title = request.Title,
|
||||||
Message = request.Message,
|
Message = request.Message,
|
||||||
IdTransportType = request.IdTransportType,
|
IdTransportType = request.IdTransportType,
|
||||||
@ -65,8 +59,7 @@ public class NotificationService
|
|||||||
|
|
||||||
notification.Id = await notificationRepository.InsertAsync(notification, cancellationToken);
|
notification.Id = await notificationRepository.InsertAsync(notification, cancellationToken);
|
||||||
notification.NotificationCategory = notificationCategory;
|
notification.NotificationCategory = notificationCategory;
|
||||||
notification.User = user;
|
|
||||||
|
|
||||||
var notificationTransportService = GetNotificationTransportService(request.IdTransportType);
|
var notificationTransportService = GetNotificationTransportService(request.IdTransportType);
|
||||||
|
|
||||||
await notificationTransportService.SendAsync(notification, cancellationToken);
|
await notificationTransportService.SendAsync(notification, cancellationToken);
|
||||||
|
@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace AsbCloudInfrastructure.Background
|
namespace AsbCloudInfrastructure.Background
|
||||||
{
|
{
|
||||||
# nullable enable
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Сервис для фонового выполнения работы
|
/// Сервис для фонового выполнения работы
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -94,5 +93,4 @@ namespace AsbCloudInfrastructure.Background
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace AsbCloudInfrastructure.Background;
|
||||||
|
|
||||||
|
public class NotificationBackgroundWorker : BackgroundWorker
|
||||||
|
{
|
||||||
|
public NotificationBackgroundWorker(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -111,6 +111,7 @@ namespace AsbCloudInfrastructure
|
|||||||
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
|
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
|
||||||
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
|
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
|
||||||
services.AddSingleton<BackgroundWorker>();
|
services.AddSingleton<BackgroundWorker>();
|
||||||
|
services.AddSingleton<NotificationBackgroundWorker>();
|
||||||
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
|
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
|
||||||
|
|
||||||
services.AddTransient<IAuthService, AuthService>();
|
services.AddTransient<IAuthService, AuthService>();
|
||||||
|
@ -8,21 +8,21 @@ using AsbCloudDb;
|
|||||||
using AsbCloudDb.Model;
|
using AsbCloudDb.Model;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure.Repository;
|
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)
|
private static IQueryable<Notification> MakeQueryNotification(DbSet<Notification> dbSet)
|
||||||
=> dbSet.Include(n => n.NotificationCategory)
|
=> dbSet.Include(n => n.NotificationCategory)
|
||||||
.Include(n => n.User)
|
.Include(n => n.User)
|
||||||
.AsNoTracking();
|
.AsNoTracking();
|
||||||
|
|
||||||
public NotificationRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
|
public NotificationRepository(IAsbCloudDbContext context)
|
||||||
: base(dbContext, memoryCache, MakeQueryNotification)
|
: base(context, MakeQueryNotification)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
|
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
|
||||||
NotificationRequest request,
|
NotificationRequest request,
|
||||||
@ -33,7 +33,7 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
|
|||||||
|
|
||||||
var query = BuildQuery(idUser, request);
|
var query = BuildQuery(idUser, request);
|
||||||
|
|
||||||
var result = new PaginationContainer<NotificationDto>()
|
var result = new PaginationContainer<NotificationDto>
|
||||||
{
|
{
|
||||||
Skip = skip,
|
Skip = skip,
|
||||||
Take = take,
|
Take = take,
|
||||||
@ -61,13 +61,12 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
|
|||||||
.Include(x => x.NotificationCategory)
|
.Include(x => x.NotificationCategory)
|
||||||
.Where(n => n.IdUser == idUser);
|
.Where(n => n.IdUser == idUser);
|
||||||
|
|
||||||
if (request.IsSent.HasValue)
|
if (request.IsSent.HasValue)
|
||||||
{
|
{
|
||||||
if(request.IsSent.Value)
|
query = request.IsSent.Value ?
|
||||||
query = query.Where(n => n.SentDate != null);
|
query.Where(n => n.SentDate != null)
|
||||||
else
|
: query.Where(n => n.SentDate == null);
|
||||||
query = query.Where(n => n.SentDate == null);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (request.IdTransportType.HasValue)
|
if (request.IdTransportType.HasValue)
|
||||||
query = query.Where(n => n.IdTransportType == request.IdTransportType);
|
query = query.Where(n => n.IdTransportType == request.IdTransportType);
|
||||||
|
@ -50,7 +50,7 @@ namespace AsbCloudInfrastructure.Services.Email
|
|||||||
return Task.CompletedTask;
|
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))
|
if (!backgroundWorker.Contains(workId))
|
||||||
{
|
{
|
||||||
var workAction = MakeEmailSendWorkAction(notification);
|
var workAction = MakeEmailSendWorkAction(notification);
|
||||||
@ -73,17 +73,23 @@ namespace AsbCloudInfrastructure.Services.Email
|
|||||||
|
|
||||||
private Func<string, IServiceProvider, CancellationToken, Task> MakeEmailSendWorkAction(NotificationDto notification)
|
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))
|
if (string.IsNullOrWhiteSpace(notification.Title))
|
||||||
throw new ArgumentInvalidException($"{nameof(notification.Title)} should be set", nameof(notification.Title));
|
throw new ArgumentInvalidException($"{nameof(notification.Title)} should be set", nameof(notification.Title));
|
||||||
|
|
||||||
return async (_, serviceProvider, token) =>
|
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 from = new MailAddress(sender);
|
||||||
var message = new MailMessage
|
var message = new MailMessage
|
||||||
{
|
{
|
||||||
@ -105,17 +111,15 @@ namespace AsbCloudInfrastructure.Services.Email
|
|||||||
|
|
||||||
notification.SentDate = DateTime.UtcNow;
|
notification.SentDate = DateTime.UtcNow;
|
||||||
|
|
||||||
var notificationRepository = serviceProvider.GetRequiredService<INotificationRepository>();
|
|
||||||
|
|
||||||
await notificationRepository.UpdateAsync(notification, token);
|
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 ^= subject.GetHashCode();
|
||||||
hash ^= content.GetHashCode();
|
hash ^= content.GetHashCode();
|
||||||
return hash.ToString("x");
|
return hash.ToString("x");
|
||||||
|
@ -40,8 +40,14 @@ namespace AsbCloudInfrastructure
|
|||||||
backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork());
|
backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork());
|
||||||
backgroundWorker.Push(MakeMemoryMonitoringWork());
|
backgroundWorker.Push(MakeMemoryMonitoringWork());
|
||||||
|
|
||||||
|
var notificationBackgroundWorker = provider.GetRequiredService<NotificationBackgroundWorker>();
|
||||||
|
|
||||||
Task.Delay(1_000)
|
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()
|
static WorkPeriodic MakeMemoryMonitoringWork()
|
||||||
|
@ -145,7 +145,6 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
|||||||
.Returns(1);
|
.Returns(1);
|
||||||
|
|
||||||
notificationService = new NotificationService(notificationCategoryRepositoryMock.Object,
|
notificationService = new NotificationService(notificationCategoryRepositoryMock.Object,
|
||||||
userRepositoryMock.Object,
|
|
||||||
new Mock<INotificationRepository>().Object,
|
new Mock<INotificationRepository>().Object,
|
||||||
new [] { notificationTransportServiceMock.Object });
|
new [] { notificationTransportServiceMock.Object });
|
||||||
|
|
||||||
|
@ -6,50 +6,47 @@ using System.Threading.Tasks;
|
|||||||
using AsbCloudApp.Data;
|
using AsbCloudApp.Data;
|
||||||
using AsbCloudApp.Repositories;
|
using AsbCloudApp.Repositories;
|
||||||
using AsbCloudApp.Services.Notifications;
|
using AsbCloudApp.Services.Notifications;
|
||||||
|
using AsbCloudInfrastructure.Background;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace AsbCloudWebApi.SignalR.Services;
|
namespace AsbCloudWebApi.SignalR.Services;
|
||||||
|
|
||||||
public class SignalRNotificationTransportService : INotificationTransportService
|
public class SignalRNotificationTransportService : INotificationTransportService
|
||||||
{
|
{
|
||||||
|
private readonly NotificationBackgroundWorker backgroundWorker;
|
||||||
private readonly ConnectionManagerService connectionManagerService;
|
private readonly ConnectionManagerService connectionManagerService;
|
||||||
private readonly IHubContext<NotificationHub> notificationHubContext;
|
private readonly IHubContext<NotificationHub> notificationHubContext;
|
||||||
private readonly INotificationRepository notificationRepository;
|
|
||||||
private readonly SemaphoreSlim semaphoreSlim = new (1, 1);
|
|
||||||
|
|
||||||
public SignalRNotificationTransportService(ConnectionManagerService connectionManagerService,
|
public SignalRNotificationTransportService(NotificationBackgroundWorker backgroundWorker,
|
||||||
IHubContext<NotificationHub> notificationHubContext,
|
ConnectionManagerService connectionManagerService,
|
||||||
INotificationRepository notificationRepository)
|
IHubContext<NotificationHub> notificationHubContext)
|
||||||
{
|
{
|
||||||
|
this.backgroundWorker = backgroundWorker;
|
||||||
this.connectionManagerService = connectionManagerService;
|
this.connectionManagerService = connectionManagerService;
|
||||||
this.notificationHubContext = notificationHubContext;
|
this.notificationHubContext = notificationHubContext;
|
||||||
this.notificationRepository = notificationRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int IdTransportType => 0;
|
public int IdTransportType => 0;
|
||||||
|
|
||||||
public async Task SendAsync(NotificationDto notification,
|
public Task SendAsync(NotificationDto notification,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
const string method = "receiveNotifications";
|
var workId = notification.Id.ToString();
|
||||||
|
|
||||||
var connectionId = connectionManagerService.GetConnectionIdByUserId(notification.IdUser);
|
if (!backgroundWorker.Contains(workId))
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(connectionId))
|
|
||||||
{
|
{
|
||||||
notification.SentDate = DateTime.UtcNow;
|
var connectionId = connectionManagerService.GetConnectionIdByUserId(notification.IdUser);
|
||||||
|
|
||||||
await notificationHubContext.Clients.Client(connectionId)
|
|
||||||
.SendAsync(method,
|
|
||||||
notification,
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
await semaphoreSlim.WaitAsync(cancellationToken);
|
if (!string.IsNullOrWhiteSpace(connectionId))
|
||||||
|
{
|
||||||
await notificationRepository.UpdateAsync(notification, cancellationToken);
|
var workAction = MakeSignalRSendWorkAction(notification, connectionId);
|
||||||
|
var work = new WorkBase(workId, workAction);
|
||||||
semaphoreSlim.Release();
|
backgroundWorker.Push(work);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
|
public Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
|
||||||
@ -60,4 +57,24 @@ public class SignalRNotificationTransportService : INotificationTransportService
|
|||||||
|
|
||||||
return Task.WhenAll(tasks);
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user