ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 8.1 ABP实时服务 - 通知系统 ### 8.1.1 简介 在系统中,通知是用来告知用户特定事件的。ABP提供了一个基于实时通知的基础设施 **pub/sub**. ### 8.1.2 发送模式 有两种方法来发送通知给用户: * 用户 **订阅** 一个特定的通知类型。当我们发布这个类型的通知时,该通知会被投递给所有的订阅用户。这就是 **pub/sub** 模式。 * 我们能直接的发送通知给目标用户。 ### 8.1.3 通知类型 通知类型也有两种: * **常规通知** 是任意类型的通知。例如:"如果某个用户发送给我一个交友请求,那么通知我。" * **实体通知** 是被关联到一个指定的实体。"如果某个用户评论了这个照片,那么通知我。",这是一个基于实体的通知,因为它被关联到一个特定的照片实体。用户可以得到对某些照片评论的通知,而不是所有照片的。 ### 8.1.4 通知数据 通常来说通知中包含了通知数据。例如:"如果某个用户发送给我一个友好的请求,那么通知我。",该通知可以有两个数据属性:发送者的用户名(那个用户发送了这个交友请求)和请求信息(用户发送请求的具体信息)。很显然,这个通知的数据类型是和通知类型紧密耦合的。不同的通知类型有不同的数据类型。 通知数据是可选的。有些通知不需要数据。ABP预定义了一些通知数据类型,这些类型适合于大多数情况下。**MessageNotificationData** 能够用于一些简单的消息,而 **LocalizableMessageNotificationData** 可以用于本地化和参数化的通知消息。在后面的部分我们会看到一些有用的例子。 ### 8.1.5 通知的程度 ABP有5种程度的通知等级,它被定义在枚举类型 **NotificationSeverity** 中: **Info, Success, Warn, Error 以及 Fatal**。 默认是 **Info**。很显然,这个通知的数据类型是和通知类型紧密耦合的。不同的通知类型有不同的数据类型。 >#####关于通知持久化 >了解更多请看 **Notification Store** ### 8.1.6 订阅通知 **INotificationSubscriptionManager** 接口中提供了订阅通知的API,例如: ```csharp public class MyService : ITransientDependency { private readonly INotificationSubscriptionManager _notificationSubscriptionManager; public MyService(INotificationSubscriptionManager notificationSubscriptionManager) { _notificationSubscriptionManager = notificationSubscriptionManager; } //订阅常规通知 public async Task Subscribe_SentFrendshipRequest(int? tenantId, long userId) { await _notificationSubscriptionManager.SubscribeAsync(tenantId, userId, "SentFrendshipRequest"); } //订阅实体通知 public async Task Subscribe_CommentPhoto(int? tenantId, long userId, Guid photoId) { await _notificationSubscriptionManager.SubscribeAsync(tenantId, userId, "CommentPhoto", new EntityIdentifier(typeof(Photo), photoId)); } } ``` 首先,我们 [注入](225827) **INotificationSubscriptionManager** 接口。第一个方法是定义 **常规通知**,用户想得到某些用户发来的交友请求的通知。第二个方法是订阅与 **特定实体(Photo)** 有关的通知。用户想得到某些用户对特定的照片写了评论的通知。 每个通知的类型应该有唯一的名字(就像例子中的 SentFrendshipRequest 和 CommentPhoto 一样)。 **INotificationSubscriptionManager** 还有其它的方法来管理订阅,如: UnsubscribeAsync, IsSubscribedAsync, GetSubscriptionsAsync等等。 ### 8.1.7 发布通知 **INotificationPublisher** 被用来发布通知,例如: ```csharp public class MyService : ITransientDependency { private readonly INotificationPublisher _notiticationPublisher; public MyService(INotificationPublisher notiticationPublisher) { _notiticationPublisher = notiticationPublisher; } //发送常规通知给特定用户 public async Task Publish_SentFrendshipRequest(string senderUserName, string friendshipMessage, long targetUserId) { await _notiticationPublisher.PublishAsync("SentFrendshipRequest", new SentFrendshipRequestNotificationData(senderUserName, friendshipMessage), userIds: new[] { targetUserId }); } //发送实体通知给特定用户 public async Task Publish_CommentPhoto(string commenterUserName, string comment, Guid photoId, long photoOwnerUserId) { await _notiticationPublisher.PublishAsync("CommentPhoto", new CommentPhotoNotificationData(commenterUserName, comment), new EntityIdentifier(typeof(Photo), photoId), userIds: new[] { photoOwnerUserId }); } //发送常规通知给所有的订阅用户并制定通知的严重等级 public async Task Publish_LowDisk(int remainingDiskInMb) { //例如:"LowDiskWarningMessage" 英文内容是 -> "Attention! Only {remainingDiskInMb} MBs left on the disk!" //注意,磁盘空间仅剩下{remainingDiskInMb} MBs var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName")); data["remainingDiskInMb"] = remainingDiskInMb; await _notiticationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn); } } ``` 在第一个例子中,我们对一个用户发布了一条通知。SentFrendshipRequestNotificationData 应该派生自 **NotificationData** 如下所示: ```csharp [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)。但是还是建议你将特性:**Serializable** 加在数据类上,因为你可能需要在应用之间移动通知,也可能在将来使用二进制序列化。此外,正如之前声明的那样,通知数据是可选的,而且对于所有的通知可能不是必须的。 >注意:如果我们发布通知给特定的用户,那么他们不需要订阅那些通知。 在第三个例子中,我们没有定义一个专门的通知数据类。相反,直接使用了内置的基于字典类型的 **LocalizableMessageNotificationData** 类,并以 **Warn** 等级来发布通知。 **LocalizableMessageNotificationData** 能存储基于字典的任意数据(这是由于自定义通知数据类型是继承 **NotificationData** 类)。我们在[本地化](225852)时使用 **remainingDiskInMb** 作为参数。本地化消息可以包含这些参数(就像例子中的"Attention! Only {remainingDiskInMb} MBs left on the disk!")。我们将会在客户端看到如何本地化。 ### 8.1.8 用户通知管理 **IUserNotificationManager** 被用来管理用户通知。有这些方法来为用户 **get, update 或 delete** 通知。你可以使用它们为你的应用程序来准备一个通知列表页面。 ### 8.1.9 实时通知 当你使用 IUserNotificationManager 来查询通知时,通常我们是想实时推送通知到客户端。 通知系统使用 **IRealTimeNotifier** 接口发送实时通知给用户。任何类型的实时通信系统都能实现它。它已经被实现在一个独立的 **SignalR** 包中。在启动模板中已经安装了SignalR。详情请参考[集成SignalR](225869)。 >注意:在[后台作业](225866)中,通信系统异步调用 **IRealTimeNotifier**。所以,通知可能会延迟一小会才会被发送。 ### 8.1.10 客户端 当实时通知被接受的时候,ABP在客户端触发了一个 **全局事件** 。你可以像下面一样注册它来获取通知: ```javascript abp.event.on('abp.notifications.received', function (userNotification) { console.log(userNotification); }); ``` 每次接受到实时通知 **abp.notifications.received** 事件会被触发。你能够想上面展示的那样注册这个事件来获取通知。详情请参考[事件总线](6.6ABP表现层-Javascript函数库.md)。下面是一个传入的JSON格式的"System.LowDisk"示例: ```javascript { "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**:通知详细信息: 1. **notificationName**: 通知的唯一名字(发布通知时使用相同的值)。 2. **data**:通知数据。在上面例子中,我们使用了 **LocalizableMessageNotificationData** (在之前的例子中我们使用它来发布的)。 * **message**: 本地化信息。在UI端,我们可以使用 **sourceName 和 name** 来本地化信息。 * **type**:通知数据类型。类型的全名称,包含名称空间。当处理这个通知数据的时候,我们可以检查这个类型。 * **properties**:自定义属的基于字典类型的属性。 3. **entityType,entityTypeName 和 entityId**:实体信息,如果这是一个与实体相关的通知。 4. **severity**:枚举类型 **NotificationSeverity** 的值。0: **Info**, 1: **Success**, 2: **Warn**, 3: **Error**, 4: **Fatal**。 5. **creationTime**:表示通知被创建的时间。 6. **id**:通知的id。 * **id**:用户通知id。 当然你不会去记录这个通知。你可以使用通知数据来显示通知信息给用户。例如: ```javascript 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通知**。 ```javascript abp.event.on('abp.notifications.received', function (userNotification) { abp.notifications.showUiNotifyForUserNotification(userNotification); }); ``` 下面展示的是一个 **UI 通知** (上面System.LowDisk示例)。 ![](images/notification-warn.png) 它对于内置的通知数据类型(LocalizableMessageNotificationData 和 MessageNotificationData)可以很好的工作。如果你有自定义的通知数据类型,那么你应该像下面一样使用格式化器来注册这个数据: ```javascript abp.notifications.messageFormatters['MyProject.MyNotificationDataType'] = function(userNotification) { return ...; //在这是实现格式化数据的逻辑并返回消息 }; ``` 因此,**showUiNotifyForUserNotification** 能对你的数据类型来创建并显示该消息。如果你仅需要格式化消息,你可以直接的使用 **abp.notifications.getFormattedMessageFromUserNotification(userNotification)**,这是由showUiNotifyForUserNotification内部使用的。 当你接受到推送通知的时候,启动模板已经包含了显示UI通知的代码。 ### 8.1.11 存储通知 通知系统使用 **INotificationStore** 来持久化通知。为了使通知系统正确的工作,我们应该实现这个接口。你可以自己实现它,或者使用已经实现了该接口的 **module-zero**。 ### 8.1.12 通知定义 使用之前,你不需要定义一个通知。你可以使用任何的没有被定义过的通知名字。但是,定义通知名字可以给你带来额外的好处。例如:你可能需要在你的应用程序中调研所有的通知。在这种情况下,我们需要为我们模块 **通知提供器(notification provider)** ,如下所示: ```csharp 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** 方法中注册它。如下所示: ```csharp public class AbpZeroTemplateCoreModule : AbpModule { public override void PreInitialize() { Configuration.Notifications.Providers.Add<MyAppNotificationProvider>(); } //... } ``` 最后,你可以在你的应用程序中注入并使用 **INotificationDefinitionManager** 接口来获取通知定义。