通知系统
在系统中通知用来基于特定的事件告知用户。ABP提供了pub/sub基础实时通知基础设施。
有两种给用户发送通知的方式:
- 用户订阅一个特定的通知类型。然后我们发布此类型的一个通知,此类型会被发送给所有订阅的用户。这就是pub/sub模型。
- 我们可以直接给目标用户发送一个通知。
通知有两种类型:
- 普通通知可以是任意类型。“如果一个用户发送给我一个友好的请求,通知我”就是这种通知类型的一个例子。
- 实体通知关联到一个特定的实体。“如果一个用户评论了这张照片,通知我”就是一种实体基础类型,因为它和一个特定的photo实体关联。用户可能想得到一些照片的通知,而不是全部。
一个通知一般包含通知数据。例如:“如果一个用户发送给我一个友好的请求,通知我”通知会有两个数据属性:sender user name(哪个用户发送了这个友好请求)和request note(发送用户在请求中的笔记)。很显然通知数据的类型和通知类型紧密相连。不同的通知类型有不同的数据类型。
通知数据是可选的。一些通知可能不请求数据。这有预定义的通知数据类型,这些可以满足大多数场景。MessageNotificationData可以用于简单的信息,LocalizableMessageNotificationData可以用于本地化和参数化的通知信息。在之后的部分我们将会看到示例用法。
通知严重性有5种等级,定义在NotificationSeverity枚举中:Info,Success,Warn,Error和Fatal。默认值是Info。
参见通知存储部分了解更多关于通知持久化的信息。
INotificationSubscriptionManaer提供了API类来订阅通知。示例:
public class MyService : ITransientDependency { private readonly INotificationSubscriptionManager _notificationSubscriptionManager; public MyService(INotificationSubscriptionManager notificationSubscriptionManager) { _notificationSubscriptionManager = notificationSubscriptionManager; } //Subscribe to a general notification public async Task Subscribe_SentFrendshipRequest(int? tenantId, long userId) { await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "SentFrendshipRequest"); } //Subscribe to an entity notification 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。第一个方法订阅了一个一般通知。当某人发送了一个友好请求后,用户希望得到通知。第二个订阅了一个与特定实体(Photo)相关的通知。当任何人给特定的照片写评论时得到通知。
每一个通知类型需要有唯一的名称(如示例中的SentFrendshipRequest和CommentPhoto)。
INotificationSubscriptionManager还有UnsubscribeAsync,IsSubscribedAsync,GetSubscriptionsAsync...方法来管理订阅。
INotificationPublisher用来发布通知。示例:
public class MyService : ITransientDependency { private readonly INotificationPublisher _notiticationPublisher; public MyService(INotificationPublisher notiticationPublisher) { _notiticationPublisher = notiticationPublisher; } //Send a general notification to a specific user public async Task Publish_SentFrendshipRequest(string senderUserName, string friendshipMessage, UserIdentifier targetUserId) { await _notiticationPublisher.PublishAsync("SentFrendshipRequest", new SentFrendshipRequestNotificationData(senderUserName, friendshipMessage), userIds: new[] { targetUserId }); } //Send an entity notification to a specific user public async Task Publish_CommentPhoto(string commenterUserName, string comment, Guid photoId, UserIdentifier photoOwnerUserId) { await _notiticationPublisher.PublishAsync("CommentPhoto", new CommentPhotoNotificationData(commenterUserName, comment), new EntityIdentifier(typeof(Photo), photoId), userIds: new[] { photoOwnerUserId }); } //Send a general notification to all subscribed users in current tenant (tenant in the session) public async Task Publish_LowDisk(int remainingDiskInMb) { //Example "LowDiskWarningMessage" content for English -> "Attention! Only {remainingDiskInMb} MBs left on the disk!" var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName")); data["remainingDiskInMb"] = remainingDiskInMb; await _notiticationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn); } }
在第一个例子中,我们发布了一个通知给一个单独的用户。SetFrendshipRequestNotificationData需要继承NotificationData:
[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; } }
在第二个示例中,我们发送一个特定实体的通知给一个特定的用户。通知数据类不需要序列化(因为默认会使用JSON序列化)。但是建议将它标记为序列化,因为在将来可能会在应用之间移动通知并且可能会使用二进制序列化。如之前所述,通知数据是可选的,并不是所有的通知都需要。
注意:如果我们给特定的用户发布了一个通知,他们不需要订阅这些通知。
在第三个示例中,我们没有定义一个专用的通知数据类。而是直接使用了內建的LocalizableMessageNotificationData并发布通知为'Warn'级别。LocalizableMessageNotificationData可以存储任意基于字典类型的数据(这也同样适用于自定义通知数据类,因为他们也继承自NotificationData类)。我们使用“remainingDiskInMb”作为本地化参数。本地化信息包含三个参数(如“Attention!Only{remainingDiskInMb}MBs left on the disk!”)。我们将在客户端部分看到如何本地化它。
IUserNotificationManager用来管理用户通知。它有方法可以用来get,update,delete用户通知。你可以用它来准备一个通知列表页。
当你使用IUserNotificationManager来查询通知时,我们通常希望推送实时通知到客户端。
通知系统使用IRealTimeNotifier来发送实时通知给用户。这可以被任何实时通信系统实现。在一个单独的包里,使用SignalR对它进行了实现。启动模板已经安装了SignalR。参见SignalR集成文档了解更多信息。
注意:通知系统在后台异步调用IRealTimeNotifier。所以,通知可能会有一些小延迟。
当一个事实通知被接收时,ABP在客户端触发一个全局事件。你可以按如下方式注册来获取通知:
abp.event.on('abp.notifications.received', function (userNotification) { console.log(userNotification); });
abp.notifications.received事件在每次接收到实时通知时触发。你可以按如上所示注册这个事件来获取通知。参见javascript event bus文档来获取关于事件的更多信息。一个通知"System.LowDisk"的JSON字符串示例:
{ "userId": 2, "state": 0, "notification": { "notificationName": "System.LowDisk", "data": { "message": { "sourceName": "MyLocalizationSourceName", "name": "LowDiskWarningMessage" }, "type": "Abp.Notifications.LocalizableMessageNotificationData", "properties": { "remainingDiskInMb": "42" } }, "entityType": null, "entityTypeName": null, "entityId": null, "severity": 0, "creationTime": "2016-02-09T17:03:32.13", "id": "0263d581-3d8a-476b-8e16-4f6a6f10a632" }, "id": "4a546baf-bf17-4924-b993-32e420a8d468" }
在这个对象中:
- userid:当前用户id。一般不需要这个因为你知道当前用户。
- state:UserNotificationState枚举的值。0:Unread,1:Read。
- notification:通知详情。
- notificationName:通知的唯一名称(当发布通知时也使用这个值)。
- data:通知数据。在这个示例中,我们使用LocalizableMessageNotificationData(如在之前示例中发布的)。
- message:本地化消息信息。我们可以使用sourceName和name在UI中本地化信息。
- type:通知数据类型。全类型名称,包含命名空间。当处理通知数据时,我们可以检查这个类型。
- properties:基于字典的自定义属性。
- entityType,entityTypeName和entityId:实体信息,如果这是一个实体相关的通知。
- severity:NotificationSeverity枚举的值。0:Info,1:Success,2:Warn,3:Error,4:Fatal。
- creationTime:通知创建的时间。
- id:通知id。
- id:用户通知id。
当然,你不需要记录这个通知。你可以使用通知数据显示通知消息给用户。示例:
abp.event.on('abp.notifications.received', function (userNotification) { if (userNotification.notification.data.type === 'Abp.Notifications.LocalizableMessageNotificationData') { var localizedText = abp.localization.localize( userNotification.notification.data.message.name, userNotification.notification.data.message.sourceName ); $.each(userNotification.notification.data.properties, function (key, value) { localizedText = localizedText.replace('{' + key + '}', value); }); alert('New localized notification: ' + localizedText); } else if (userNotification.notification.data.type === 'Abp.Notifications.MessageNotificationData') { alert('New simple notification: ' + userNotification.notification.data.message); } });
为了能够处理通知数据,我们应该检查数据类型。这个示例简单从通知数据中获取消息。对于本地化消息(LocalizableMessageNotificationData),我们本地化消息并取代参数。对于简单的消息(MessageNotificationData),我们直接获取消息。当然,在实际工程中,我们将不使用alert函数,我们可以使用abp.notify api来显示UI通知。
如果你需要实现这样的一个逻辑,这有一个简单且可伸缩的方式。当收到一个推送通知时,你可以仅使用一行代码就可以显示UI通知:
p.event.on('abp.notifications.received', function (userNotification) { abp.notifications.showUiNotifyForUserNotification(userNotification); });
这会显示一个UI通知(如收到上面发布的System.LowDisk通知):
这适用于內建通知数据类型(LocalizableMessageNotificationData和MessageNotificationData)。如果你有自定义数据类型,那么你应该如下所示注册数据格式化器:
abp.notifications.messageFormatters['MyProject.MyNotificationDataType'] = function(userNotification) { return ...; //format and return message here };
因此,showUiNotifyForUserNotification可以针对于你的数据类型创建显示消息 。如果你仅仅需要格式化信息,你可以直接使用abp.notifications.getFormattedMessageFromUserNotification(userNotification),它被showUiNotifyForUserNotification内部所使用。
启动模板包含当收到一个推送通知时,显示UI通知的代码。
通知系统使用INotificationStore来持久化通知。为了使通知系统正常工作,需要实现这个接口。你可以自己实现它或使用已经实现了它的module-zero.
在使用通知之前,你可以不用定义它。不定义的话,你仅仅能使用任何通知名称。但是,定义通知会带来一些额外的好处。例如,你可以调查你应用中的所有通知。在这种情况下,我们可以为我们的模块定义一个通知提供者,如下:
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来获取通知定义。然后你可能想准备一个自动页来允许用户订阅这些通知。