Исправил отправку уведомлений + небольшой рефакторинг

This commit is contained in:
parent 2232061f95
commit a7e5c577cf
13 changed files with 273 additions and 58 deletions

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
namespace AsbCloudApp.Services.Notifications;
/// <summary>
/// Интерфейс для отправителя уведомлений
/// </summary>
public interface INotificationSender
{
/// <summary>
/// Способ отправки уведомлений
/// </summary>
NotificationTransport NotificationTransport { get; }
/// <summary>
/// Отправка одного уведомлений
/// </summary>
/// <param name="notification"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SendAsync(NotificationDto notification,
CancellationToken cancellationToken);
/// <summary>
/// Отправка нескольких уведомлений
/// </summary>
/// <param name="notifications"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
CancellationToken cancellationToken);
}

View File

@ -0,0 +1,16 @@
using AsbCloudApp.Data;
namespace AsbCloudApp.Services.Notifications;
/// <summary>
/// Ñåðâèñ äëÿ ðàáîòû ñ îòïðàâèòåëÿìè óâåäîìëåíèé
/// </summary>
public interface INotificationSenderManager
{
/// <summary>
/// Ìåòîä ïîëó÷åíèÿ íóæíîãî îòïðàâèòåëÿ óâåäîìëåíèé
/// </summary>
/// <param name="notificationTransport"></param>
/// <returns></returns>
INotificationSender? GetOrDefault(NotificationTransport notificationTransport);
}

View File

@ -1,8 +1,9 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data;
namespace AsbCloudApp.Services; namespace AsbCloudApp.Services.Notifications;
/// <summary> /// <summary>
/// Интерфейс для работы с уведомлениями /// Интерфейс для работы с уведомлениями
@ -10,26 +11,26 @@ namespace AsbCloudApp.Services;
public interface INotificationService public interface INotificationService
{ {
/// <summary> /// <summary>
/// Метод отправки нового уведомления /// Отправка нового уведомления
/// </summary> /// </summary>
/// <param name="idUser"></param> /// <param name="idUser"></param>
/// <param name="idNotificationTransport"></param>
/// <param name="idNotificationCategory"></param> /// <param name="idNotificationCategory"></param>
/// <param name="title"></param> /// <param name="title"></param>
/// <param name="subject"></param> /// <param name="message"></param>
/// <param name="timeToLife"></param> /// <param name="timeToLife"></param>
/// <param name="notificationTransport"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task SendNotificationAsync(int idUser, Task NotifyAsync(int idUser,
int idNotificationTransport,
int idNotificationCategory, int idNotificationCategory,
string title, string title,
string subject, string message,
TimeSpan timeToLife, TimeSpan timeToLife,
NotificationTransport notificationTransport,
CancellationToken cancellationToken); CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Метод обновления уведомления /// Обновление уведомления
/// </summary> /// </summary>
/// <param name="idNotification"></param> /// <param name="idNotification"></param>
/// <param name="isRead"></param> /// <param name="isRead"></param>
@ -40,13 +41,13 @@ public interface INotificationService
CancellationToken cancellationToken); CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Метод отправки уведомлений, которые не были отправлены /// Отправка уведомлений, которые не были отправлены
/// </summary> /// </summary>
/// <param name="idUser"></param> /// <param name="idUser"></param>
/// <param name="idNotificationTransport"></param> /// <param name="notificationTransport"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task ResendNotificationAsync(int idUser, Task ResendNotificationAsync(int idUser,
int idNotificationTransport, NotificationTransport notificationTransport,
CancellationToken cancellationToken); CancellationToken cancellationToken);
} }

View File

@ -23,6 +23,8 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Services.Notifications;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure
{ {
@ -145,8 +147,7 @@ namespace AsbCloudInfrastructure
services.AddTransient<INotificationService, NotificationService>(); services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<INotificationRepository, NotificationRepository>(); services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddSingleton<INotificationSendingQueueService, NotificationSendingQueueService>();
// admin crud services: // admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s => services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheRepositoryBase<TelemetryDto, Telemetry>( new CrudCacheRepositoryBase<TelemetryDto, Telemetry>(

View File

@ -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<INotificationSender> notificationSenders;
public NotificationSenderManager(IEnumerable<INotificationSender> notificationSenders)
{
this.notificationSenders = notificationSenders;
}
public INotificationSender? GetOrDefault(NotificationTransport notificationTransport)
{
return notificationSenders.FirstOrDefault(x => x.NotificationTransport == notificationTransport);
}
}

View File

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

View File

@ -4,7 +4,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Requests;
using AsbCloudApp.Services.Notifications;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -29,39 +30,39 @@ public class NotificationController : ControllerBase
} }
/// <summary> /// <summary>
/// Метод отправки уведомления /// Отправка уведомления
/// </summary> /// </summary>
/// <param name="idUser">Id пользователя</param> /// <param name="idUser">Id пользователя</param>
/// <param name="idNotificationTransport">Id способа отправки уведомления</param>
/// <param name="idNotificationCategory">Id категории уведомления</param> /// <param name="idNotificationCategory">Id категории уведомления</param>
/// <param name="title">Заголовок уведомления</param> /// <param name="title">Заголовок уведомления</param>
/// <param name="subject">Сообщение уведомления</param> /// <param name="message">Сообщение уведомления</param>
/// <param name="timeToLife">Время жизни уведомления</param> /// <param name="timeToLife">Время жизни уведомления</param>
/// <param name="notificationTransport">Способ отправки уведомления</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[Route("send")] [Route("send")]
public async Task<IActionResult> SendAsync([Required] int idUser, public async Task<IActionResult> SendAsync([Required] int idUser,
[Required] int idNotificationTransport,
[Required] int idNotificationCategory, [Required] int idNotificationCategory,
[Required] string title, [Required] string title,
[Required] string subject, [Required] string message,
[Required] TimeSpan timeToLife, [Required] TimeSpan timeToLife,
[Required] NotificationTransport notificationTransport,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
await notificationService.SendNotificationAsync(idUser, await notificationService.NotifyAsync(idUser,
idNotificationTransport,
idNotificationCategory, idNotificationCategory,
title, title,
subject, message,
timeToLife, timeToLife,
notificationTransport,
cancellationToken); cancellationToken);
return Ok(); return Ok();
} }
/// <summary> /// <summary>
/// Метод обновления уведомления /// Обновление уведомления
/// </summary> /// </summary>
/// <param name="idNotification">Id уведомления</param> /// <param name="idNotification">Id уведомления</param>
/// <param name="isRead">Прочитано ли уведомление</param> /// <param name="isRead">Прочитано ли уведомление</param>
@ -79,21 +80,17 @@ public class NotificationController : ControllerBase
return Ok(); return Ok();
} }
/// <summary> /// <summary>
/// Метод получения уведомлений по параметрам /// Получение списка уведомлений
/// </summary> /// </summary>
/// <param name="skip">Кол-во пропускаемых записей</param> /// <param name="request">Параметры запроса</param>
/// <param name="take">Кол-во выбираемых записей</param>
/// <param name="idNotificationTransport">Id способа доставки уведомления</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet] [HttpGet]
[Route("getList")] [Route("getList")]
[ProducesResponseType(typeof(PaginationContainer<NotificationDto>), (int)System.Net.HttpStatusCode.OK)] [ProducesResponseType(typeof(PaginationContainer<NotificationDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetListAsync(int? skip, public async Task<IActionResult> GetListAsync([FromQuery] NotificationRequest request,
int? take,
[Required] int idNotificationTransport,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
int? idUser = User.GetUserId(); int? idUser = User.GetUserId();
@ -101,17 +98,15 @@ public class NotificationController : ControllerBase
if (!idUser.HasValue) if (!idUser.HasValue)
return Forbid(); return Forbid();
var result = await notificationRepository.GetNotificationsAsync(skip, var result = await notificationRepository.GetNotificationsAsync(idUser.Value,
take, request,
idUser.Value,
idNotificationTransport,
cancellationToken); cancellationToken);
return Ok(result); return Ok(result);
} }
/// <summary> /// <summary>
/// Метод удаления уведомления /// Удаление уведомления
/// </summary> /// </summary>
/// <param name="idNotification">Id уведомления</param> /// <param name="idNotification">Id уведомления</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>

View File

@ -9,6 +9,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Services.Notifications;
using AsbCloudWebApi.SignalR.Services;
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
namespace AsbCloudWebApi namespace AsbCloudWebApi
@ -105,5 +108,11 @@ namespace AsbCloudWebApi
}; };
}); });
} }
public static void AddNotificationSenders(this IServiceCollection services)
{
services.AddScoped<INotificationSenderManager, NotificationSenderManager>();
services.AddTransient<INotificationSender, SignalRNotificationSender>();
}
} }
} }

View File

@ -4,22 +4,22 @@ namespace AsbCloudWebApi.SignalR.ConnectionManager;
public class ConnectionManager : IConnectionManager public class ConnectionManager : IConnectionManager
{ {
private readonly ConcurrentDictionary<string, string> _connections = new(); private readonly ConcurrentDictionary<int, string> _connections = new();
public void AddConnection(string userId, public void AddConnection(int userId,
string connectionId) string connectionId)
{ {
_connections.TryAdd(userId, connectionId); _connections.TryAdd(userId, connectionId);
} }
public void RemoveConnection(string userId) public void RemoveConnection(int userId)
{ {
_connections.TryRemove(userId, out _); _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; return connectionId;
} }
} }

View File

@ -2,10 +2,10 @@ namespace AsbCloudWebApi.SignalR.ConnectionManager;
public interface IConnectionManager public interface IConnectionManager
{ {
void AddConnection(string userId, void AddConnection(int userId,
string connectionId); string connectionId);
void RemoveConnection(string userId); void RemoveConnection(int userId);
string? GetConnectionIdByUserId(string userId); string? GetConnectionIdByUserId(int userId);
} }

View File

@ -1,10 +1,9 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Services; using AsbCloudApp.Data;
using AsbCloudWebApi.Options.Notifications; using AsbCloudApp.Services.Notifications;
using AsbCloudWebApi.SignalR.ConnectionManager; using AsbCloudWebApi.SignalR.ConnectionManager;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace AsbCloudWebApi.SignalR; namespace AsbCloudWebApi.SignalR;
@ -13,25 +12,22 @@ public class NotificationHub : BaseHub
{ {
private readonly IConnectionManager connectionManager; private readonly IConnectionManager connectionManager;
private readonly INotificationService notificationService; private readonly INotificationService notificationService;
private readonly NotificationsOptionsSignalR notificationsOptionsSignalR;
public NotificationHub(IConnectionManager connectionManager, public NotificationHub(IConnectionManager connectionManager,
INotificationService notificationService, INotificationService notificationService)
IOptions<NotificationsOptionsSignalR> notificationsOptionsSignalR)
{ {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.notificationService = notificationService; this.notificationService = notificationService;
this.notificationsOptionsSignalR = notificationsOptionsSignalR.Value;
} }
public async Task OnConnected(int idUser) public async Task OnConnected(int idUser)
{ {
string connectionId = Context.ConnectionId; string connectionId = Context.ConnectionId;
connectionManager.AddConnection(idUser.ToString(), connectionId); connectionManager.AddConnection(idUser, connectionId);
await notificationService.ResendNotificationAsync(idUser, await notificationService.ResendNotificationAsync(idUser,
notificationsOptionsSignalR.IdTransport, NotificationTransport.SignalR,
CancellationToken.None); CancellationToken.None);
await base.OnConnectedAsync(); await base.OnConnectedAsync();

View File

@ -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<NotificationHub> notificationHubContext;
private readonly INotificationRepository notificationRepository;
public SignalRNotificationSender(IConnectionManager connectionManager,
IHubContext<NotificationHub> 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<NotificationDto> notifications,
CancellationToken cancellationToken)
{
foreach (var notification in notifications)
{
await SendAsync(notification,
cancellationToken);
}
}
}

View File

@ -1,9 +1,7 @@
using AsbCloudInfrastructure; using AsbCloudInfrastructure;
using AsbCloudWebApi.Converters; using AsbCloudWebApi.Converters;
using AsbCloudWebApi.Middlewares; using AsbCloudWebApi.Middlewares;
using AsbCloudWebApi.Options.Notifications;
using AsbCloudWebApi.SignalR; using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.BackgroundServices;
using AsbCloudWebApi.SignalR.ConnectionManager; using AsbCloudWebApi.SignalR.ConnectionManager;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -44,13 +42,13 @@ namespace AsbCloudWebApi
services.AddSwagger(); services.AddSwagger();
services.AddInfrastructure(Configuration); services.AddInfrastructure(Configuration);
services.AddNotificationSenders();
services.AddJWTAuthentication(); services.AddJWTAuthentication();
services.AddSignalR() services.AddSignalR()
.Services.AddSingleton<IConnectionManager, ConnectionManager>() .Services.AddSingleton<IConnectionManager, ConnectionManager>();
.AddHostedService<SignalRNotificationSender>()
.Configure<NotificationsOptionsSignalR>(Configuration.GetSection("NotificationsOptionsSignalR"));
services.AddCors(options => services.AddCors(options =>
{ {