ABP中的实时通讯Notification

本篇文章的目的: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种通知严重性级别 :InfoSuccessWarn, ErrorFatal。默认值为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来获取通知定义。然后,您可能需要准备一个自动页面,以允许用户订阅这些通知。

参考资料:

abp源码分析

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章