考拉消息中心消息盒子處理重構(策略模式)

本文由作者周敏敏授權網易雲社區發佈。


一.消息中心簡單介紹

考拉app的消息中心是負責發送和接受app站內信的服務,比如營銷系統發送的活動消息,優惠券到期消息等。考拉app中的消息中心入口在首頁的右上角。點擊進去能夠看到消息盒子列表,點擊消息盒子能夠看到該盒子中的消息列表(有些盒子點擊是跳轉到特定URL)。

1d9d6063-feed-4bf8-b084-9eb05a87b3c8eceb12c5-ffb4-4585-b932-887003a7867d

消息中心的功能簡單來說只有提供接口發送消息、用戶接收查看消息兩類

發送消息可分爲發送私信、組播、廣播


接受查看消息可分爲:

1.發消息時,根據消息類型設置消息盒子的未讀計數(+1操作),最新文案,最新時間等

2.查詢返回消息盒子列表(正確返回每個盒子的未讀計數,最新文案,最新時間)

3.點擊盒子,消除未讀消息數(當前實現是隻要點擊了盒子,就清空該盒子所有未讀消息數),返回盒子的消息列表(有些消息盒子是跳轉到特定url)


二.消息盒子越加越多,處理方式各不相同


剛接觸消息中心時,只有活動精選等6個消息盒子(處理邏輯相同,統稱爲普通盒子),每個盒子的處理邏輯都一樣:

1.發消息時,增加盒子未讀計數、消息ID塞入盒子的消息ID列表中、設置盒子的最新文案和時間

2.點擊盒子,清空盒子的未讀計數

3.進入盒子,根據盒子的消息ID列表返回消息列表


如果一直是這樣,那將是很美好的,可惜……。在增加種草社區盒子的需求中,種草社區盒子的處理邏輯不同於普通盒子,大概要求

1.種草社區盒子只作爲查看種草消息的一個入口,消息體不存在消息中心,盒子上的未讀計數和文案由種草社區給出。

2.點擊種草社區盒子不清空未讀計數。

3.點擊種草社區盒子跳轉到用戶的種草社區消息頁。

所以對於種草社區盒子的未讀計數、最新文案、最新時間以及清空未讀計數、點擊種草社區盒子跳轉等操作,都要求不同於普通盒子的一套邏輯。


如果僅僅是種草社區盒子需要特殊處理,也就罷了,可惜還有售後進度消息盒子。不僅僅如此,後面陸續增加了黑卡先生消息盒子和品牌動態消息盒子,這兩個盒子的處理邏輯也都不相同。如黑卡先生消息盒子需求要求所有關注了黑卡先生賬號或者是正式會員的用戶才能看到黑卡先生盒子,每當黑卡先生賬號有新文章發佈或更新就通過未讀計數提示用戶……。


如此一來就有4個消息盒子需要特殊處理了,可以預見未來可能需要接入更多的,需特殊處理的消息盒子。

三.簡單的實現方式

1.種草社區消息盒子簡單的實現方式是通過mq消息設置最新文案,未讀計數等,即完全由種草社區發mq消息告訴消息中心盒子的數據,消息中心只消費mq消息。


2.售後進度消息盒子則是開出兩個單獨的dubbo接口用戶設置和讀取售後進度盒子,這兩個dubbo接口只能用於售後消息盒子

// 設置售後進度消息盒子
    public boolean setAfterSaleMessageBoxContent(String accountId, Integer msgNum, String lastContent, Long lastTime, boolean oldReadStatus);    // 讀取售後進度消息盒子
    public boolean readAfterSaleMessageBox(String accountId);

這種實現方式的優點是簡單直接,再有特殊處理盒子只需要再開dubbo接口實現特殊邏輯即可。缺點是接口不通用,每次都需要開dubbo接口,可擴展性差,部分操作NCR代碼重複編寫。

四.利用策略模式重構消息盒子處理
在接黑卡先生和品牌動態消息盒子需求時,考慮到再開dubbo接口會讓代碼變得難以維護,因此考慮採用策略模式將每個盒子的處理抽象成統一的行爲。
先介紹策略模式,參考資料:https://blog.csdn.net/zuoxiaolong8810/article/details/9104281定義:策略模式定義了一系列的算法,並將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立於使用它的客戶而獨立變化。
分析下定義,策略模式定義和封裝了一系列的算法,它們是可以相互替換的,也就是說它們具有共性,而它們的共性就體現在策略接口的行爲上,另外爲了達到最後一句話的目的,也就是說讓算法獨立於使用它的客戶而獨立變化,我們需要讓客戶端依賴於策略接口。

3e70be3a-68bf-45be-b9de-cfaefc9ed412

對比策略模式的定義,我們可以將所有消息盒子的處理抽象成一個策略接口,每個具體盒子的處理對應一個具體的策略實現類。同時實現一個簡單工廠方法,根據消息盒子類型選擇特定的策略類去處理這個盒子。

朝着這個方向思考,將盒子的處理抽象成如下一個MessageBoxHandler接口,該接口中有四個方法:

1.設置消息盒子的內容(最新文案,未讀計數,最新時間等)

2.獲取消息盒子的內容

3.讀取清理消息盒子內容

4.獲取消息盒子裏的消息列表

/**
 * 通用消息盒子處理接口。使用策略模式
 */public interface MessageBoxHandler {    /**
     * 設置對應消息盒子的內容,包括最新文案,最新時間,強弱消息數,跳轉url
     */
    public void setMessageBoxContent(KaolaAppMessage kaolaAppMessage, MessageBoxSetInfo messageBoxSetInfo, List accountIdList, int boxType, Boolean needUpdateNewMessageTime);    /**
     * 根據用戶賬號和盒子類型(或者是傳入的盒子緩存)得到盒子內容(最新文案,最新時間,強弱消息數)
     */
    public KaolaAppMessageBox getMessageBoxContent(String accountId, int boxType, Map hintMap, boolean needMessage, boolean hasCalculate, Set filterMsgTypeSet);    /**
     * 清空消息盒子上的內容
     */
    public void clearMessageBoxContent(List accountIdList, int boxType);    /**
     * 根據賬號和盒子類型返回該盒子中消息列表
     */
    public List getMessageList(String accountId, int boxType, Long indexMessageId, int limit, List filterMsgTypes);
}
有具體盒子處理策略實現類:

UML圖如下:

675f2896-daff-4162-9b80-9d21a34b1027?imageView&thumbnail=980x0

對照策略模式的UML圖,可以看出MessageBoxHandler對應策略接口,抽象了消息盒子可以有的所有操作。MessageBoxHandlerWrapper是該接口的默認空實現,所有具體的盒子處理類只需要直接繼承該類,然後覆蓋需要的方法即可。對於消息盒子不需要的方法,則直接使用MessageBoxHandlerWrapper中的空實現(如種草社區盒子的getMessageList就是空實現)。

MessageBoxHandlerFactory中簡單工廠方法getMessageBoxHandler根據盒子類型獲取具體的盒子處理類:


    public void init() {
        messageBoxhandlerArr = new MessageBoxHandler[11];
        messageBoxhandlerArr[0] = defaultMessageBoxHandler;
        messageBoxhandlerArr[1] = defaultMessageBoxHandler;
        messageBoxhandlerArr[2] = defaultMessageBoxHandler;
        messageBoxhandlerArr[3] = defaultMessageBoxHandler;
        messageBoxhandlerArr[4] = defaultMessageBoxHandler;
        messageBoxhandlerArr[5] = defaultMessageBoxHandler;
        messageBoxhandlerArr[6] = defaultMessageBoxHandler;
        messageBoxhandlerArr[7] = communityMessageBoxHandler;
        messageBoxhandlerArr[8] = afterSaleMessageBoxHandler;
        messageBoxhandlerArr[9] = blackCardMessageBoxHandler;
        messageBoxhandlerArr[10] = brandDynamicMessageBoxHandler;
    }    public static MessageBoxHandler getMessageBoxHandler(int boxType) {        if (boxType < 0 || boxType >= messageBoxhandlerArr.length) {            return messageBoxhandlerArr[0];
        }        return messageBoxhandlerArr[boxType];
    }

如此重構之後在代碼中調用盒子處理類只需要如下調用

MessageBoxHandlerFactory.getMessageBoxHandler(boxType).getMessageList(accountId, boxType, indexMessageId, limit, filterMsgTypes);

重構之後的優點:
1.所有消息盒子的處理有一個統一的抽象,對外dubbo接口更加通用。
2.dubbo接口實現變得簡單,只需要根據盒子類型調用相關Handler的方法即可。
3.可擴展性強,此後再接入特殊處理的消息盒子,只需要增加對應的Handler類,幾乎不需要修改新增dubbo接口。


更多網易技術、產品、運營經驗分享請訪問網易雲社區

相關文章:
【推薦】 Spring Boot 學習系列(02)—使用熱部署,提升開發效