ABP理論學習之通知系統


返回總目錄


本篇目錄

介紹

通知(Notification)用於告知用戶系統中的特定事件。ABP提供了基於實時通知基礎設施的發布訂閱模型(pub/sub)。

發送模型

給用戶發送通知有兩種方式:

  • 首先用戶訂閱特定的通知類型,然后我們發布這種類型的通知,這種類型的通知會傳遞給所有已經訂閱的用戶。這就是發布訂閱(pub/sub)模型。
  • 直接給目標用戶發送通知。

通知類型

通知類型也有兩種:

  • 一般通知:是任意類型的通知。“如果一個用戶給我發送了添加好友的請求就通知我”是這種通知類型的一個例子。
  • 實體通知:和特定的實體相關聯。“如果一個用戶在 這張圖片上評論那么通知我”是基於實體的通知,因為它和特定的實體相關聯。用戶可能想獲得某些圖片的通知而不是所有的圖片通知。

通知數據

一個通知一般都會包括一個通知數據。比如,“如果一個用戶給我發送了添加好友的請求就通知我”,這個通知可能有兩個數據屬性:發送者用戶名(那哪一個用戶發送的這個添加好友的請求)和請求內容(該用戶在這個請求中寫的東西)。很明顯,該通知數據類型緊耦合於通知類型。不同的通知類型有不同的數據類型。

  • 通知數據是可選的。某些通知可能不需要數據。一些預定義的通知數據類型可能對於大多數情況夠用了。 MessageNotificationData可以用於簡單的信息, LocalizableMessageNotificationData可以用於本地化的,帶參數的通知信息。

通知安全

通知的安全性有5個級別,它們定義在NotificationSeverity枚舉類中,分別是 Info,Success,Warn,Error和Fatal。默認值是Info。

訂閱通知

INotificationSubscriptionManager 提供了訂閱通知的API。例如:

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));   
    }
}

首先,我們注入了INotificationSubscriptionManager。第一個方法訂閱了一個一般通知。當某人發送了一個添加好友的請求時,用戶想得到通知。第二個方法訂閱了一個和特定實體(Photo)相關的通知。如果任何人對這個特定的圖片進行了評論,那么用戶就會收到通知。

每一個通知應該有唯一的名字(比如例子中的SentFrendshipRequest和 CommentPhoto)。

INotificationSubscriptionManager還有很多方法來管理通知,比如UnsubscribeAsync, IsSubscribedAsync, GetSubscriptionsAsync等方法。

發布通知

INotificationPublisher用於發布通知,例如:

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!"
        var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName"));
        data["remainingDiskInMb"] = remainingDiskInMb;

        await _notiticationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn);    
    }
}

在第一個例子中,我們向單個用戶發布了一個通知。SentFrendshipRequestNotificationData應該從 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;
    }
}

在第二個例子中,我們向特定的用戶發送了一個特定實體的通知。通知數據類CommentPhotoNotificationData一般不需要serializble(因為默認使用了JSON序列化)。但是建議把它標記為serializable,因為你可能需要在應用之間移動通知,也可能在未來使用二進制的序列。此外,正如之前聲明的那樣,通知數據是可選的,而且對於所有的通知可能不是必須的。

注意:如果我們對特定的用戶發布了一個通知,那么他們不需要訂閱那些通知。

在第三個例子中,我們沒有定義一個專用的通知數據類。相反,我們直接使用了內置的具有基於字典數據的LocalizableMessageNotificationData,並且以 Warn發布了通知。LocalizableMessageNotificationData可以存儲基於字典的任意數據(這對於自定義的通知數據類也是成立的,因為它們也從 NotificationData類繼承)。在本地化時我們使用了“ remaingDiskInMb”作為參數。本地化信息可以包含這些參數(比如例子中的"Attention! Only {remainingDiskInMb} MBs left on the disk!")

用戶通知管理者

IUserNotificationManager用於管理用戶的通知,它有 get,update或delete用戶通知的方法。你可以為你的應用程序准備一個通知列表頁面。

實時通知

當使用IUserNotificationManager來查詢通知時,我們一般想實時地將通知推送到客戶端。

通知系統使用了IRealTimeNotifier向用戶發送實時通知。這可以使用任何類型的實時通訊系統來實現。ABP在一個單獨的包中使用了 SignalR來實現。ABP官網的起始模板已經安裝了SignalR。

注意:通知系統在一個后台工作中異步調用IRealTimeNotifier。因此,通知發送時可能伴有輕微的延遲。

客戶端

當接收到一個實時通知時,ABP會在客戶端觸發一個全局的事件(global event)。你可以像下面那樣注冊來獲取通知:

abp.event.on('abp.notifications.received', function (userNotification) {
    console.log(userNotification);
});

abp.notifications.received事件對於每個接收到的實時通知都會觸發。你可以像上面那樣注冊該事件來獲取通知。查看javascript事件總線文檔獲取更多信息。對於上面的“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。一般來說不需要這個,因為你知道當前的用戶。
  • stateUserNotificationState枚舉的值。0:未讀,1:已讀。
  • notification:通知細節。
    • notificationName:通知的唯一名稱。(當發布該通知時使用相同的值)
    • data:通知數據。在本例中,我們使用了**LocalizableMessageNotificationData **(正如之前的例子中發布的)。
    • message:本地的信息通知。我們可以使用 sourceNamename來本地化UI上的信息。
    • type:通知數據類型。全類型名稱,包括命名空間。當處理該通知數據時我們可以檢查該類型。
    • properties:基於字典的自定義屬性。
    • entityType, entityTypeName和entityId:和通知相關的實體的信息。
    • 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通知。

如果你要實現這么上面的邏輯,那么有一種更簡單且可伸縮的方式。當接收到一個push通知時,你可以只使用一句代碼來顯示UI通知:

abp.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。

當接收到一個push通知時,啟動模板包含了展示UI通知的代碼。

通知存儲

通知系統使用了INotificationStore來持久化通知。為了使通知系統合適地工作,應該實現這個接口。你可以自己實現或者使用module-zero(它已經實現了該接口)。

通知定義

在使用之前你不必定義一個通知。你無需定義任何類型的通知名就可以使用它們。但是,定義通知名可能會給你帶來額外的好處。比如,你以后要研究應用程序中所有的通知,在這種情況下,我們可以給我們的模塊定義一個通知提供器(notification provider ),如下所示:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM