通知系统

介绍

  在系统中通知用来基于特定的事件告知用户。ABP提供了pub/sub基础实时通知基础设施。

发送模型

  有两种给用户发送通知的方式:

  • 用户订阅一个特定的通知类型。然后我们发布此类型的一个通知,此类型会被发送给所有订阅的用户。这就是pub/sub模型。
  • 我们可以直接给目标用户发送一个通知。

通知类型

  通知有两种类型:

  • 普通通知可以是任意类型。“如果一个用户发送给我一个友好的请求,通知我”就是这种通知类型的一个例子。
  • 实体通知关联到一个特定的实体。“如果一个用户评论了这张照片,通知我”就是一种实体基础类型,因为它和一个特定的photo实体关联。用户可能想得到一些照片的通知,而不是全部。

通知数据

  一个通知一般包含通知数据。例如:“如果一个用户发送给我一个友好的请求,通知我”通知会有两个数据属性:sender user name(哪个用户发送了这个友好请求)和request note(发送用户在请求中的笔记)。很显然通知数据的类型和通知类型紧密相连。不同的通知类型有不同的数据类型。

  通知数据是可选的。一些通知可能不请求数据。这有预定义的通知数据类型,这些可以满足大多数场景。MessageNotificationData可以用于简单的信息,LocalizableMessageNotificationData可以用于本地化和参数化的通知信息。在之后的部分我们将会看到示例用法。

通知严重性

  通知严重性有5种等级,定义在NotificationSeverity枚举中:InfoSuccessWarnErrorFatal。默认值是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,entityTypeNameentityId:实体信息,如果这是一个实体相关的通知。
    • severityNotificationSeverity枚举的值。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.notifyapi来显示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来获取通知定义。然后你可能想准备一个自动页来允许用户订阅这些通知。

返回主目录

ABP官方文档翻译 8.1 通知系统的更多相关文章

  1. ABP官方文档翻译 1.3 模块系统

    ABP模块系统 介绍 模块定义 生命周期方法 PreInitialize Initialize PostInitialize Shutdown 模块依赖 插件系统 ASP.NET Core ASP.N ...

  2. ABP官方文档翻译 8.2 SignalR集成

    SignalR集成 介绍 安装 服务器端 客户端 建立连接 內建特征 通知 在线客户端 PascalCase与CamelCase对比 你的SignalR代码 介绍 ABP中的Abp.Web.Signa ...

  3. ABP官方文档翻译 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  4. ABP官方文档翻译 6.3 本地化

    本地化 介绍 应用程序语言 本地化源 XML文件 注册XML本地化源 JSON文件 注册JSON本地化源 资源文件 自定义源 当前语言是如何决定的 ASP.NET Core ASP.NET MVC 5 ...

  5. ABP官方文档翻译 4.1 应用服务

    应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRU ...

  6. ABP官方文档翻译 7.1 后台Jobs和Workers

    后台Jobs和Workers 介绍 后台Jobs 关于Job持久化 创建后台Job 在队列中添加一个新Job 默认的后台Job管理器 后台Job存储 配置 禁用Job执行 异常处理 Hangfire集 ...

  7. ABP官方文档翻译 3.7 领域事件(事件总线)

    领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...

  8. ABP官方文档翻译 2.1 依赖注入

    依赖注入 什么是依赖注入 传统方式的问题 解决方案 构造函数注入模式 属性注入模式 依赖注入框架 ABP依赖注入基础设施 注册依赖注入 传统注册 帮助接口 自定义/直接注册 使用IocManager ...

  9. ABP官方文档翻译 1.2 N层架构

    N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构, ...

随机推荐

  1. 基于Windows下python环境变量配置

    方法和Java环境变量配置是一样的,不懂的请移步这里 虽然这样说,还是唠唠叨叨几句吧QAQ 默认情况下,在windows下安装python之后,系统并不会自动添加相应的环境变量.此时不能在命令行直接使 ...

  2. UESTC 1591 An easy problem A【线段树点更新裸题】

    An easy problem A Time Limit: 2000/1000MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others ...

  3. CodeForces723-A. The New Year: Meeting Friends

    A. The New Year: Meeting Friends time limit per test 1 second memory limit per test 256 megabytes in ...

  4. 动态计算rem的js代码

    以最小1024尺寸为例: function rem() { var htmlEle = document.documentElement; var winWidth = htmlEle.clientW ...

  5. 空数组在以下三种遍历中均不可更改:forEach、map和for...in

    首先,我们要知道对于forEach.map和for...in三种遍历,在不是空数组的情况下,要想实现更改原数组的方法,代码如下: var list = [1,2,3,4]; var list1 = [ ...

  6. Nginx实战之反向代理WebSocket的配置实例

    http://www.jb51.net/article/112183.htm 最近在工作中遇到一个需求,需要使用 nginx 反向代理websocket,经过查找一番资料,目前已经测试通过,所以这篇文 ...

  7. linux 下 tomcat 安装

    下载 根据已安装的jdk版本选择合适的版本,否则不兼容 https://tomcat.apache.org/whichversion.html 选择合适的压缩包 源码 二进制:已针对固定的操作系统和机 ...

  8. Java hashtable和hastmap的区别

    1. 继承和实现区别 Hashtable是基于陈旧的Dictionary类,完成了Map接口:HashMap是Java 1.2引进的Map接口的一个实现(HashMap继承于AbstractMap,A ...

  9. WEB前端大神之路之基础篇

    CSS篇: 1.CSS权重: 不重复造轮子啦,直接传送门(CSS选择器的权重与优先规则) JavaScript篇: 1.this关键字: 它是一种引用(referent).指向的是当前上下文(cont ...

  10. webpack 基本打包方法

    webpack的打包基本配置文件webpack.config.js 可以在webpack.config.js里面写好配置:比如前章节所总结的四大核心 |-- add.js // 定义一个普通加法函数 ...