本篇文章的目的:1.了解ABP实时通讯的类型及用法。2.根据实时通讯相关的类、数据库,去使用
1.ABP中关于Notification的数据结构如下:
在ABP中对应的Dto
NotificationSubscription:用于封装封装notification 和subscriptor(User) 的关系的DTO,不是Entity。记录订阅某种通知的人。
AbpTenantNotifications存储通知信息的实体。对应字段信息为
1.notificationName: 通知的唯一名字(发布通知时使用相同的值).
2.DataTypeName:通知数据类型。完整类型名称,包括名称空间。我们可以在处理通知数据时检查此类型。
3.EntityTypeName:实体信息,如果这是有关通知的实体。
4.Severity:严重性:NotificationSeverity枚举的值。0: 信息; 1:成功; 2:警告; 3:错误; 4: 致命。
实体在程序中相似的Dto是UserNotification:用于封装User和Notification关系的信息。对应字段信息为:
1.UserId:接受的用户Id
2.TenantNotificationId:关联的消息Id
3.State:阅读状态。已读/未读。
其中TenantNotification与UserNotification存在的是一对多的关系。TenantNotification存储发布的通知的信息(内容,依赖类名等),UserNotification存储消息的发送与接受对象等信息(接收人,TenantNotificationID,是否已读等信息)。
二、消息的使用(以下内容来源于官网):
ASP.NET样板提供的pub / sub基于(发布/订阅)的实时通知系统。可知系统有两种向用户发送通知的方式。
1.订阅。用户订阅特定的通知类型。然后,我们发布这种类型的通知,该通知传递给所有订阅用户。这是发布/订阅模型
2.直接发布
通知类型:
①一般通知是任意类型的通知。如果用户发送了一个友好请求,请通知我“这是此类通知的一个示例。
②实体通知。实体通知与特定实体相关联。“如果有用户评论此照片,请通知我”是基于实体的通知,因为它与特定的照片实体相关联。用户可能希望获取一些照片的通知,但不是所有照片的通知。
通知又分级别,在NotificationSeverity枚举中定义了5种通知严重性级别 :Info,Success,Warn, Error和Fatal。默认值为Info。对应到AbpTenantNotifications实体的Severity字段。
3.订阅样例。
public class MyService : ITransientDependency
{
//ABP封装好的订阅管理类
private readonly INotificationSubscriptionManager _notificationSubscriptionManager;
public MyService(INotificationSubscriptionManager notificationSubscriptionManager)
{
_notificationSubscriptionManager = notificationSubscriptionManager;
}
//订阅一般通知,如用户希望在某人发送友谊请求时得到通知
public async Task Subscribe_SentFriendshipRequest(int? tenantId, long userId)
{
await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "SentFriendshipRequest");
}
//订阅关联实体的通知,如用户希望在有人向指定照片写评论时得到通知
public async Task Subscribe_CommentPhoto(int? tenantId, long userId, Guid photoId)
{
await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "CommentPhoto", new EntityIdentifier(typeof(Photo), photoId));
}
}
该INotificationSubscriptionManager也有UnsubscribeAsync,IsSubscribedAsync,GetSubscriptionsAsync ...方法来管理订阅。
发布通知
public class MyService : ITransientDependency
{
private readonly INotificationPublisher _notificationPublisher;
public MyService(INotificationPublisher notificationPublisher)
{
_notificationPublisher = notificationPublisher;
}
//发送一个普通通知
public async Task Publish_SentFrendshipRequest(string senderUserName, string friendshipMessage, UserIdentifier targetUserId)
{
await _notificationPublisher.PublishAsync("SentFrendshipRequest", new SentFrendshipRequestNotificationData(senderUserName, friendshipMessage), userIds: new[] { targetUserId });
}
//发送某个实体的通知到某个用户
public async Task Publish_CommentPhoto(string commenterUserName, string comment, Guid photoId, UserIdentifier photoOwnerUserId)
{
await _notificationPublisher.PublishAsync("CommentPhoto", new CommentPhotoNotificationData(commenterUserName, comment), new EntityIdentifier(typeof(Photo), photoId), userIds: new[] { photoOwnerUserId });
}
//发送通知到当前租户会话中的所有用户
public async Task Publish_LowDisk(int remainingDiskInMb)
{
var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName"));
data["remainingDiskInMb"] = remainingDiskInMb;
await _notificationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn);
}
}
以上代码中,第一、二个样例使用了通知数据类SentFrendshipRequestNotificationData、CommentPhotoNotificationData,第三个样例使用的是本地化和可参数化的通知信息LocalizableMessageNotificationData与,但其实在数据库存储,所有通知数据类都是序列化存储的。
通知数据类的样例如下:
[Serializable]
public class SentFrendshipRequestNotificationData : NotificationData
{
public string SenderUserName { get; set; }
public string FriendshipMessage { get; set; }
public SentFrendshipRequestNotificationData(string senderUserName, string friendshipMessage)
{
SenderUserName = senderUserName;
FriendshipMessage = friendshipMessage;
}
}
用户通知管理器
该IUserNotificationManager用于管理用户的通知。它具有获取,更新或删除用户通知的方法。您可以使用实现获取用户通知列表页面、消息置为已读等功能。
//消息置为已读
public async Task SetAllNotificationsAsRead()
{
await _userNotificationManager.UpdateAllUserNotificationStatesAsync(AbpSession.ToUserIdentifier(), UserNotificationState.Read);
}
public async Task SetNotificationAsRead(EntityDto<Guid> input)
{
var userNotification = await _userNotificationManager.GetUserNotificationAsync(AbpSession.TenantId, input.Id);
if (userNotification.UserId != AbpSession.GetUserId())
{
throw new ApplicationException(string.Format("Given user notification id ({0}) is not belong to the current user ({1})", input.Id, AbpSession.GetUserId()));
}
await _userNotificationManager.UpdateUserNotificationStateAsync(AbpSession.TenantId, input.Id, UserNotificationState.Read);
}
//获取用户消息列表
[DisableAuditing]
public async Task<GetNotificationsOutput> GetUserNotifications(GetUserNotificationsInput input)
{
var totalCount = await _userNotificationManager.GetUserNotificationCountAsync(
AbpSession.ToUserIdentifier(), input.State
);
var unreadCount = await _userNotificationManager.GetUserNotificationCountAsync(
AbpSession.ToUserIdentifier(), UserNotificationState.Unread
);
var notifications = await _userNotificationManager.GetUserNotificationsAsync(
AbpSession.ToUserIdentifier(), input.State, input.SkipCount, input.MaxResultCount
);
return new GetNotificationsOutput(totalCount, unreadCount, notifications);
}
实时通知
当使用IUserNotificationManager来查询通知时,我们一般想实时地将通知推送到客户端。
通知系统使用了IRealTimeNotifier向用户发送实时通知。这可以使用任何类型的实时通讯系统来实现。ABP在一个单独的包中使用了 SignalR来实现。ABP官网的起始模板已经安装了SignalR。
注意:通知系统在一个后台工作中异步调用IRealTimeNotifier。因此,通知发送时可能伴有轻微的延迟。
public class EmailRealTimeNotifier : IRealTimeNotifier, ITransientDependency
{
private readonly IEmailSender _emailSender;
private readonly UserManager _userManager;
public EmailRealTimeNotifier(
IEmailSender emailSender,
UserManager userManager)
{
_emailSender = emailSender;
_userManager = userManager;
}
public async Task SendNotificationsAsync(UserNotification[] userNotifications)
{
foreach (var userNotification in userNotifications)
{
if (userNotification.Notification.Data is MessageNotificationData data)
{
var user = await _userManager.GetUserByIdAsync(userNotification.UserId);
_emailSender.Send(
to: user.EmailAddress,
subject: "You have a new notification!",
body: data.Message,
isBodyHtml: true
);
}
}
}
}
将其添加到模块的PreInitialize方法中:
Configuration.Notifications.Notifiers.Add<EmailRealTimeNotifier>();
客户端
当接收到一个实时通知时,ABP会在客户端触发一个全局的事件(global event)。若前端使用的是vue,则在main.js函数中,在created方法里注册接受通知的事件,如下:
abp.event.on('abp.notifications.received', function (userNotification) {
//这里写入收到消息后的操作。
//例如我的操作是,在界面显示消息内容,并更新用户通知列表
});
通知定义
您无需定义通知即可使用它。您可以使用任何通知名称而无需定义它。但是,定义它可能会给您带来一些额外的好处。例如,您可以随后 调查应用程序中的所有通知。在这种情况下,我们可以为我们的模块定义一个通知提供程序, 如下所示:
public class MyAppNotificationProvider : NotificationProvider
{
public override void SetNotifications(INotificationDefinitionContext context)
{
context.Manager.Add(
new NotificationDefinition(
"App.NewUserRegistered",
displayName: new LocalizableString("NewUserRegisteredNotificationDefinition", "MyLocalizationSourceName"),
permissionDependency: new SimplePermissionDependency("App.Pages.UserManagement")
)
);
}
}
“ App.NewUserRegistered ”是通知的唯一名称。我们定义了一个可本地化的displayName,以便我们在订阅UI上的通知时可以显示它。最后,我们声明仅当用户具有“ App.Pages.UserManagement ” 权限时,此通知才对用户可用。
您还可以在代码中研究其他一些参数。注意:通知名称是通知定义所必需的。
定义了这样的通知提供程序之后,我们必须在模块的PreInitialize方法中注册它 ,如下所示:
public class AbpZeroTemplateCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Notifications.Providers.Add<MyAppNotificationProvider>();
}
//...
}
最后,您可以在应用程序中注入并使用INotificationDefinitionManager来获取通知定义。然后,您可能需要准备一个自动页面,以允许用户订阅这些通知。
参考资料: