一、背景
當(dāng)前使用運(yùn)維平臺(tái)的用戶進(jìn)行溝通時(shí),更多的是依賴微信和郵件通知,而運(yùn)維平臺(tái)作為一個(gè)整體的產(chǎn)品,也需要能夠進(jìn)行內(nèi)部溝通的一種服務(wù) - 站內(nèi)信。
站內(nèi)信的設(shè)計(jì)基調(diào)
站內(nèi)信的設(shè)計(jì)基調(diào)取決于用戶如何使用站內(nèi)信:
- 用戶不會(huì)守著運(yùn)維平臺(tái)這個(gè)頁(yè)面,等待消息通知,查看消息內(nèi)容,然后跳轉(zhuǎn)到要操作的頁(yè)面。
- 也就是說(shuō)站內(nèi)信不是第一入口,站內(nèi)信的實(shí)時(shí)性意義也不大。
- 同很多社交網(wǎng)站不同(Facebook,知乎,微博等),用戶會(huì)守在社交網(wǎng)站的主頁(yè)面,不斷刷新新內(nèi)容,同時(shí)檢查新消息(主要是個(gè)人私信、別人的回復(fù)等,也絕不是為了檢查系統(tǒng)通知消息)
- 用戶會(huì)根據(jù)郵件通知,決定是否要進(jìn)入運(yùn)維平臺(tái)進(jìn)行操作
- 如果郵件特別多,例如同時(shí)有多個(gè)工單需要用戶處理,用戶也會(huì)在工單平臺(tái)提供的“我的待辦”頁(yè)面進(jìn)行所有工作。
- 如果郵件被誤刪了,沒(méi)有郵件鏈接直接進(jìn)入要操作的模塊
- 那么或者通過(guò)索要鏈接/單號(hào)的方式,前往指定頁(yè)面
- 或者直接在相關(guān)模塊進(jìn)行搜索
上面的描述都意味著用戶基本不會(huì)使用站內(nèi)信,那么在什么樣的場(chǎng)合會(huì)使用站內(nèi)信呢?
- 不發(fā)郵件,只發(fā)站內(nèi)信的消息通知,例如全站通知、編輯操作、Comment操作等
- 當(dāng)具體模塊沒(méi)有詳細(xì)的操作記錄時(shí),可以通過(guò)查看站內(nèi)信的發(fā)生時(shí)間
當(dāng)前只有產(chǎn)品消息通知,消息展示也沒(méi)有進(jìn)行歸類聚合,以后增加全站通知、mention、like、comment等類型的站內(nèi)信時(shí),就需要考慮按類型進(jìn)行消息聚合了。
二、需求描述
- 站內(nèi)信通常需要解決兩個(gè)需求:
- 用戶對(duì)用戶的站內(nèi)信,管理員對(duì)用戶的站內(nèi)信:即一對(duì)一發(fā)送
- 管理員對(duì)多用戶、用戶組、全站的站內(nèi)信:即一對(duì)多發(fā)送
(還有一種是用戶對(duì)產(chǎn)品的站內(nèi)信,例如對(duì)某個(gè)模塊的反饋、疑問(wèn)之類的)
我們目前的需求是:
1管理員對(duì)多用戶發(fā)送站內(nèi)信
對(duì)用戶真實(shí)性不做校驗(yàn)
對(duì)標(biāo)題長(zhǎng)度、內(nèi)容長(zhǎng)度進(jìn)行限制(分別是45個(gè)字節(jié)、150個(gè)字節(jié),對(duì)應(yīng)中文字符15個(gè)、50個(gè))
對(duì)收件人的拼音長(zhǎng)度進(jìn)行限制(最長(zhǎng)50個(gè)字節(jié))
2 用戶可以查看自己的站內(nèi)信
按“全部、已讀、未讀”過(guò)濾
按消息來(lái)源分類:工單平臺(tái)、資源管理、自動(dòng)裝機(jī)、漏洞平臺(tái)、故障平臺(tái)。。。
3 用戶可以刪除、批量刪除站內(nèi)信
4 用戶可以已閱、批量已閱、全部標(biāo)記為已讀 站內(nèi)信
5 運(yùn)維平臺(tái)頁(yè)面頂部的消息圖標(biāo)
- 展示未讀消息數(shù),超過(guò)99顯示 99+
- 鼠標(biāo)放上去,會(huì)有下拉框,展示最近10條未讀消息(展示“時(shí)間”,“消息來(lái)源”,“標(biāo)題”)
- 下拉框的底部有兩個(gè)按鈕:“更多”,加載更多未讀消息;“查看全部”,跳轉(zhuǎn)到站內(nèi)信列表頁(yè)面(最好另開(kāi)一個(gè)窗口)
- 點(diǎn)擊下拉框里的未讀消息,通過(guò)彈出框展示詳情;然后在未讀列表里刪除該記錄,在數(shù)據(jù)庫(kù)里標(biāo)記為已讀,消息圖標(biāo)的未讀消息數(shù)量減一
6 管理員頁(yè)面:
更新用戶
刪除消息
統(tǒng)計(jì)數(shù)據(jù)
增加module
增加站內(nèi)信類型
發(fā)送全站消息
三、系統(tǒng)設(shè)計(jì)
功能設(shè)計(jì)

四、系統(tǒng)流程
發(fā)送站內(nèi)信
- 讀取POST請(qǐng)求的request body
- 校驗(yàn)長(zhǎng)度
- 插入數(shù)據(jù)庫(kù)
- 返回
獲取站內(nèi)信列表
- 調(diào)用子模塊,插入發(fā)送給全站或我所屬用戶組的站內(nèi)信
- 根據(jù)查詢條件,返回?cái)?shù)據(jù)庫(kù)數(shù)據(jù)
獲取未讀站內(nèi)信數(shù)量
- 調(diào)用子模塊,插入發(fā)送給全站或我所屬用戶組的站內(nèi)信
- 返回?cái)?shù)量
批量已閱
- 檢查messageId是不是屬于當(dāng)前用戶
- inbox_message表里把 read 置為1,修改update_time
全部已閱
update inbox_message set “read”=1, “update_time”=now where “receiver_name”=currentUser() and “read” = 0
批量刪除
- 檢查messageId是不是屬于當(dāng)前用戶
- inbox_message表里把 deleted 置為1,修改update_time
全部刪除
update inbox_message set “deleted”=1, “update_time”=now where “receiver_name”=currentUser() and “deleted” = 0
五、數(shù)據(jù)庫(kù)設(shè)計(jì)
站內(nèi)信內(nèi)容表
CREATE TABLE `inbox_message_text` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(128) NOT NULL DEFAULT '', `content` longtext NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, `send_type` tinyint(4) NOT NULL DEFAULT '0', `creator_name` varchar(255) NOT NULL DEFAULT '', `deleted` tinyint(4) NOT NULL DEFAULT '0', `module_id` bigint(20) NOT NULL, `link` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

站內(nèi)信本身除了消息來(lái)源(module_name),還有一個(gè)緯度的描述,叫消息類型(message_type),例如安全消息、活動(dòng)消息、服務(wù)消息等,每一大類里,又可以劃分子類,例如活動(dòng)消息-優(yōu)惠活動(dòng)
消息來(lái)源和消息類型可以是正交關(guān)系,即工單平臺(tái)也可以有活動(dòng)消息;消息來(lái)源也可以是消息類型的一種,稱為“產(chǎn)品消息”
站內(nèi)信發(fā)送表
CREATE TABLE `inbox_message` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `message_text_id` bigint(20) NOT NULL, `receiver_name` varchar(255) NOT NULL DEFAULT '', `read` tinyint(4) NOT NULL DEFAULT '0', `deleted` tinyint(4) NOT NULL DEFAULT '0', `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `inbox_message_receiver_name_deleted_read_id` (`receiver_name`,`deleted`,`read`,`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

消息來(lái)源表
CREATE TABLE `inbox_module` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `code` varchar(128) NOT NULL DEFAULT '', `name` varchar(128) NOT NULL DEFAULT '', `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `code` (`code`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

六、API設(shè)計(jì)
發(fā)送站內(nèi)信:POST /v1/message
request body Content-Type: Application/json
{ "title": "工單審批", "content": "XXX提交了變更申請(qǐng),請(qǐng)審批", "to": "sunzhongyuan,shenli,wangya", "module_name": "工單平臺(tái)", "link": "xxx" }
response
{ "code": 200, "data": 32, "msg": "OK" }
獲取站內(nèi)信列表:GET /v1/message User-Id: xxx
http://127.0.0.1:10085/v1/message?query=message_text_id.module_id.name:xxx&limit=1
{ "code": 200, "data": { "data": [ { "id": 1, "message_text": { "id": 1, "title": "title 2", "content": "content 2", "create_time": "2018-01-12 11:13:48", "update_time": "2018-01-12 11:13:48", "send_type": 1, "creator_name": "sysadmin", "deleted": 0, "link": "xxx", "Messages": null, "module": { "id": 4, "code": "secure", "name": "xxx", "create_time": "2018-01-11 15:38:01", "update_time": "2018-01-11 15:38:01", "MessageTexts": null } }, "receiver_name": "xxx", "read": 0, "deleted": 0, "create_time": "2018-01-12 11:13:48", "update_time": "2018-01-12 11:13:48" } ], "total": 2 }, "msg": "OK" }
注:
返回?cái)?shù)據(jù)的個(gè)數(shù)是由 limit 限制,而 total 是符合query條件的總數(shù)(用于分頁(yè))
目前沒(méi)有發(fā)送用戶組、全站的行為,如果有的話,在獲取列表接口里,增加一步“插入所有發(fā)送給我所在用戶組,或發(fā)給全站的,且我自己的站內(nèi)信列表里沒(méi)有記錄到的站內(nèi)信”
獲取未讀站內(nèi)信數(shù)量:GET /v1/message/unread_count
response
{ "code": 200, "data": 29, "msg": "OK" }
獲取單個(gè)站內(nèi)信內(nèi)容:GET /v1/message/:id
{ "code": 200, "data": { "id": 2, "message_text": { "id": 2, "title": "title 2", "content": "content 3", "create_time": "2018-01-12 11:37:54", "update_time": "2018-01-12 11:37:54", "send_type": 1, "creator_name": "sysadmin", "deleted": 0, "link": "", "Messages": null, "module": { "id": 4, "code": "secure", "name": "xxx", "create_time": "2018-01-11 15:38:01", "update_time": "2018-01-11 15:38:01", "MessageTexts": null } }, "receiver_name": "xxx", "read": 1, "deleted": 0, "create_time": "2018-01-12 11:37:54", "update_time": "2018-01-22 17:33:20" }, "msg": "OK" }
已閱、批量已閱站內(nèi)信:PUT /v1/read_messages/:messageIds
response
{ "code": 200, "data": "OK", "msg": "OK" }
全部已閱 PUT:/v1/read_all_messages
response 同上
刪除、批量刪除站內(nèi)信:PUT /v1/delete_messages/:messageIds
response 同上
全部刪除站內(nèi)信:PUT /v1/delete_all_messages
response 同上
獲取消息來(lái)源列表:GET /v1/module
response
{ "code": 200, "data": [ { "id": 1, "code": "worksheet", "name": "工單平臺(tái)", "create_time": "2018-01-11 15:21:38", "update_time": "2018-01-11 15:21:38", "MessageTexts": null }, { "id": 2, "code": "cmdb", "name": "資源管理", "create_time": "2018-01-11 15:22:28", "update_time": "2018-01-11 15:22:28", "MessageTexts": null }, ... ], "msg": "OK" }
七、測(cè)試注意點(diǎn)
1 發(fā)送站內(nèi)信
- 純接口
- 收件用戶以逗號(hào)分割,真實(shí)性不做校驗(yàn)
- 收件用戶有長(zhǎng)度校驗(yàn),50個(gè)字節(jié)
- title content 有長(zhǎng)度校驗(yàn),分別是45,150個(gè)字節(jié)
- module_name 是一個(gè)列表,必須從這里選一個(gè)
2 其他接口都可以通過(guò)前端頁(yè)面測(cè)試
八、優(yōu)化
- 未讀列表可以加上粗體顯示,已讀則是普通字體
- 對(duì)站內(nèi)信進(jìn)行分類,打上不同緯度的標(biāo)簽,方便過(guò)濾、搜索、屏蔽
- 用戶可以設(shè)置允許接收的站內(nèi)信的消息來(lái)源
- 管理員可以對(duì)全站消息、全站人員、全站的消息屬性進(jìn)行增刪改查,比如撤銷某個(gè)站內(nèi)信,讓所有人都看不見(jiàn)
- 管理員可以統(tǒng)計(jì)站內(nèi)信的發(fā)送數(shù)量、各產(chǎn)品的使用情況、消息被讀的比例、消息被讀的時(shí)間、消息被讀的方式(點(diǎn)開(kāi)還是批量操作),等
九、關(guān)鍵功能點(diǎn)設(shè)計(jì)
右上角的圖標(biāo)行為
1 點(diǎn)擊圖標(biāo),展示最近的N條未讀消息
- 展示下拉框
- 實(shí)時(shí)獲取最近N條未讀消息
- N可以為5~10,具體數(shù)值取決于下拉框的高度限制
- 當(dāng)未讀數(shù)不足N時(shí),下拉框能自適應(yīng)高度
- 如果沒(méi)有未讀消息,展示”暫無(wú)新消息”
- 停止每10秒的獲取未讀消息數(shù)接口
2 下拉框里,展示消息來(lái)源、時(shí)間(相對(duì)現(xiàn)在的時(shí)間:10分鐘前)、title
- 向下滑動(dòng)下拉框,展示更多未讀消息(只獲取id小于已展示消息列表里的最小id,即不獲取點(diǎn)擊圖標(biāo)后新來(lái)的消息)
3 點(diǎn)擊下拉框里的某一個(gè)消息
- 下拉框不消失
- 依然停止每10秒的獲取未讀消息數(shù)接口
- 未讀消息數(shù)減1
- 未讀消息列表刪除當(dāng)前消息(slice)
- 展示彈出框
4 彈出框展示消息的來(lái)源、時(shí)間(絕對(duì)時(shí)間)、title、content
5 關(guān)閉彈出框或者點(diǎn)擊外圍:
- 彈出框消失
- 下拉框不消失
- 可以繼續(xù)點(diǎn)擊某一個(gè)未讀消息
6 再次點(diǎn)擊下拉框和圖標(biāo)的外圍
- 下拉框消失
- 清空已有的未讀消息列表
- 恢復(fù)每10秒的獲取未讀消息數(shù)接口
7 再次點(diǎn)擊圖標(biāo),重新回到#1狀態(tài)
阿里云的圖標(biāo)行為是:
- 刷新頁(yè)面的時(shí)候才會(huì)請(qǐng)求一次未讀消息數(shù),之后不再定時(shí)刷新(當(dāng)然也可能是刷新時(shí)間間隔比較長(zhǎng),沒(méi)發(fā)現(xiàn);又或者采用了 socket 的方式,建立了一個(gè)長(zhǎng)鏈接)
- hover圖標(biāo),即顯示未讀消息的下拉框
- 點(diǎn)擊圖標(biāo),進(jìn)入站內(nèi)信管理頁(yè)面,默認(rèn)是“未讀消息”

4 點(diǎn)擊未讀消息,新開(kāi)一個(gè)Tab,展示該消息的詳情(detail頁(yè)面),原Tab內(nèi)容不變,即沒(méi)有未讀數(shù)減一,也沒(méi)從下拉框里刪除剛點(diǎn)擊的消息
5 最多展示5條消息,只要不刷新頁(yè)面,就一直是這5條
6 沒(méi)有滾動(dòng)更多的功能,只有查看更多,點(diǎn)擊進(jìn)入站內(nèi)信管理頁(yè)面,默認(rèn)是“未讀消息”
- 和點(diǎn)擊圖標(biāo)的區(qū)別是:點(diǎn)擊圖標(biāo)直接當(dāng)前頁(yè)面跳轉(zhuǎn)到站內(nèi)信管理頁(yè)面,點(diǎn)擊“查看更多”會(huì)新建一個(gè)Tab
7 多了一個(gè)“消息接受管理”的按鈕,當(dāng)前頁(yè)面跳轉(zhuǎn)到站內(nèi)信管理頁(yè)面,但是默認(rèn)即“基本接收管理”

隱藏瀏覽器進(jìn)度條
每10秒的獲取未讀消息數(shù)接口,會(huì)觸發(fā)瀏覽器展示進(jìn)度條,導(dǎo)致分散用戶注意力,要把這個(gè)進(jìn)度條隱藏掉。
其他刷新頁(yè)面的行為不受影響。
參考文檔
《站內(nèi)信需求背景及需求分析的全過(guò)程》
《站內(nèi)信功能設(shè)計(jì)》
《站內(nèi)信的實(shí)現(xiàn):數(shù)據(jù)庫(kù)的設(shè)計(jì)》
《站內(nèi)信的實(shí)現(xiàn)思路表的設(shè)計(jì)》
《Web網(wǎng)站通知系統(tǒng)設(shè)計(jì)》