在個人上一篇文章 網站消息通知設計 中提到了網站消息通知系統的組成,其中有個很重要的部分就是通知渠道,包括站內信、短信、郵件等其餘方式。而在衆多渠道中最重要和必不可少的就是站內信了,畢竟短信、郵件這些觸達方式要錢不說,還會分分鐘鐘被用戶吐槽和拉黑。 須要注意的是:不管在在 PC 端網站仍是 APP 端的推送,本篇文章都統一稱爲站內信,它們在底層都是同一套,只是展示方式不一樣而已。java
站內信的通知來源通常包括如下三種:web
1.用戶事件觸發:當某個用戶對某個對象執行了評論、@、點贊、留言等動做,都須要對對象擁有者進行通知。這是最多見的須要通知的場景。
2.知足系統的規則後自動觸發:好比被系統封號、等級提高、得到勳章時,理論上都應該對用戶進行通知。
3.管理員觸發:管理員主動向全網或者某個用戶發送通知,好比發送公告等。
複製代碼
相信讀者在使用掘金、知乎等網站或者 APP 收到最多的站內信類型應該就是 1 和 3 了。json
站內信的具體內容咱們沒法枚舉,可是內容的結構卻有着固定的模型。根據不一樣的消息來源參考知乎和掘金咱們枚舉一些站內信內容,從而更容易的總結出該模型。服務器
1. 用戶事件觸發微信
【xxx】【點贊】了你的【文章】【文章的標題】
【xxx】【評論】了你的【文章】【文章的標題】
【xxx】【點贊】了你在【文章】【文章的標題】下的評論
【xxx】【回覆】了你的【評論】【被回覆的評論的內容】
【xxx】【點贊】了你在【文章】【文章標題】下的回覆
【xxx】在【文章】【文章的標題】中【@】了你
【xxx】在【文章】下的【評論】中【@】了你
【xxx】在【文章】下的【回覆】中【@】了你
【xxx】回答了你關注的【問題】【問題標題】
【xxx】更新了你關注的【文章】【文章標題】
【xxx】邀請你回答【問答】【問答標題】
【xxx】關注了你
複製代碼
2. 系統自動觸發app
恭喜你,你的【會員】成功升級到了【13級】
因爲你已經屢次違反網站規定,現已經被封號【3個月】
複製代碼
3. 管理員發送框架
您的【沸點】被選爲編輯精選
【管理員】發佈了系統公告【文章】【文章標題,請點擊查看。
【xxx】專欄新增了【N篇】文章
複製代碼
能夠看到站內信內容不勝枚舉,但稍微總結能夠看出它們都須要如下的變量信息:異步
1. 站內信的主語:某某某用戶、系統、管理員,固然也能夠不展現,不展現的通常就是系統。爲了後文討論方便,咱們稱之爲主語。該變量是爲了告知接受者是誰觸發了通知。post
2. 站內信觸發時所在關聯的實體對象:問答、文章、專欄、問答下的回答、回答下的評論、評論下的回覆、沸點等。網站
這裏想強調的是:實體對象在通知的時候只取兩層,什麼意思呢。好比問答下的回答的評論,通知的時候只取【回答下的評論】不用帶上問答這個實體信息,不然顯得累贅不說還得保存不少實體信息。
複製代碼
一樣爲了方便,咱們稱之爲關聯對象。是爲了將相關聯的實體對象信息展現在通知中。
3. 站內信接收人:這個比較簡單,確定是都是當前登陸人。
4. 觸發站內信的事件:點贊、評論、回覆、@、回答、邀請、被關注等等。咱們稱之爲事件,主要是爲了具化站內信內容。
5. 站內信觸發時所在的主實體的類型:問答、文章、專欄、回答、評論、回覆、沸點等,注意與關聯實體對象自己的區別。咱們稱之爲主實體類型,主要是爲了具化站內信內容,同時減小 event 枚舉的個數。
站內信的內容模型能夠用僞代碼展現以下:
public NoticeMode<FatherEntity, SonEntity> {
private User subject; //主語
private FatherEntity fatherEntity; //發生對象父實體(能夠爲空)
private SonEntity sonEntity; //發生對象子實體(即主實體)。
private User receiver;//接收人
private NoticeEvent event; //通知事件,枚舉中的一個元素
private String sonEntityType; //事件發生時主實體的類型
}
複製代碼
event 和 sonEntityType 組合決定本次站內信的 key。
複製代碼
不要將站內信內容的組織放在應用代碼中,這樣若是要改變通知的內容就得修改代碼而後從新發布。在 網站消息通知設計 也講到了通知的設計能夠參考 MVC 模式。模型(M)有了,程序在 C 中(通常是 velocity 中)經過 event 字段組合出想要的站內信內容(V)。同時 C 的代碼(假如用 velocity 模板代碼實現)能夠放到配置中心,這樣須要修改站內信內容格式直接像修改配置同樣可實時生效。固然這裏也能夠用規則引擎來實現,但感受有點大材小用了。
有時候用戶對某種事件的站內信並不感興趣,甚至討厭。那站內信就要提供設置讓用戶有選擇的接受站內信,固然了,系統的站內信確定得必須接收。如下是知乎站內信設置的一部分:
本文的站內信內容模型中只須要以 event 和 sonEntityType 爲維度設置是否接收消息、接收哪些人的消息,就能比較容易的實現消息通知的設置。
說了那麼多,我想把本身的一些設計思路特別是站內信的生成、保存和獲取功能展現在這裏。給須要者一個參考:
不少時候站內信都是在某個用戶進行某個動做的時候生成,好比評論。也就是說在評論的時候除了保存評論相關的信息,後臺還須要生成站內信。相信不少人都知道保存評論和生成站內信兩個操做應該採用異步的方式,防止後者阻塞了前者的返回,影響體驗。 應用內的異步使用消息隊列有點大材小用了,推薦開源的異步事件框架 Google guava Eventbus.
站內信表:tbl_website_message,包含的字段大體以下:
字段 | 類型 | 備註 |
---|---|---|
id | Long | 主鍵 id |
event | String | 事件類型 |
sonEntityType | String | 主實體類型 |
notice_type | String | 通知類型(系統消息、用戶消息) |
fatherEntity | json | 關聯實體的父對象 |
sonEntity | json | 關聯實體的子對象 |
content | String | 站內信的內容 |
站內信與用戶關係表:tbl_user_message,包含的字段大體以下:
字段 | 類型 | 備註 |
---|---|---|
id | Long | 主鍵 id |
website_message_id | Long | 站內信表id |
notice_type | String | 通知類型(系統消息、用戶消息) |
max_message_id | Long | 已經遍歷過的最大站內信表id |
is_read | Integer | 是否已讀 |
其中 notice_type 都是通知類型,好比:掘金的用戶消息和系統消息。還可分的更細:點贊、評論、@、其餘通知等。具體根據業務須要劃分,方便用戶區分。
用戶在登錄網站或者打開 app 的時候觸發查詢,從用戶站內信表(tbl_user_message關聯tbl_website_message) 讀取站內信 list,比較簡單。但有一點須要注意:若是是一對多的場景,即一個事件觸發多人接收通知。並不須要將全部人須要接受站內通知放入 tbl_user_message 表,須要被通知的人在登錄獲取站內信的時候按需去 tbl_website_message 中查詢並出入到 tbl_user_message。具體流程以下:
1. 獲取 tbl_user_message 中跟本身有關的最大 max_message_id。
2. 去 tbl_website_message 中獲取 > max_message_id 的全部記錄。
3. 循環遍歷判斷記錄是否與本身有關(根據業務邏輯,若是有關將關聯關係插入到 tbl_user_message 便可(記得 id 也要保存到 max_message_id 字段中。
4. 更新最晚插入 tbl_user_message 那條記錄中的 max_message_id 爲新遍歷的最大的 website_message 表 id。防止下次重複遍歷。
複製代碼
前面設計的站內信內容模型保存的是關聯實體的對象,在生成站內信內容的時候從模型中將關聯的實體信息(例如標題)填充進去。若是在這以後關聯實體的信息(如標題)發生改變,在消息中並不能體現出來。若是要動態獲取關聯內容也很簡單,在模型中保存關聯實體的 id 就能夠了,在用戶獲取站內信的時候再去根據 id 獲取關聯實體內容組裝成內容返回便可。動態獲取關聯實體內容能夠保證明時性,但我以爲,非動態獲取關聯實體內容會更好,理由有四:
1.動態獲取關聯實體的內容致使每次獲取站內信都發生表關聯查詢,對服務器形成必定的壓力。
2.站內信消息反應的是觸發的那一刻的一個狀態,並不須要動態性。
3.動態改變內容可能會給接收人帶來必定的困擾。特別是一些關鍵性信息,如觸發人用戶名、標題等。
4.若是還有其餘通知渠道,好比釘釘、微信、郵件,這些渠道沒法作到動態獲取。因此還不如統一。
複製代碼
有時候相同類型的站內信過多會致使消息列表很長,好比點贊,這時候能夠作一個連續的、相同的 event 和 sonEntityType、相同的操做對象的消息的聚合。雖然本人還沒實踐過,但個人理解比較簡單:在用戶登錄網站或者打開 app 獲取站內信的時候,後臺在原有的站內信消息獲取接口上再作一層封裝便可。不知道實踐過的人的採用的方法是什麼樣的,歡迎留言討論。