一、背景
當前使用運維平臺的用戶進行溝通時,更多的是依賴微信和郵件通知,而運維平臺作為一個整體的產品,也需要能夠進行內部溝通的一種服務 - 站內信。
站內信的設計基調
站內信的設計基調取決于用戶如何使用站內信:
- 用戶不會守著運維平臺這個頁面,等待消息通知,查看消息內容,然后跳轉到要操作的頁面。
- 也就是說站內信不是第一入口,站內信的實時性意義也不大。
- 同很多社交網站不同(Facebook,知乎,微博等),用戶會守在社交網站的主頁面,不斷刷新新內容,同時檢查新消息(主要是個人私信、別人的回復等,也絕不是為了檢查系統通知消息)
- 用戶會根據郵件通知,決定是否要進入運維平臺進行操作
- 如果郵件特別多,例如同時有多個工單需要用戶處理,用戶也會在工單平臺提供的“我的待辦”頁面進行所有工作。
- 如果郵件被誤刪了,沒有郵件鏈接直接進入要操作的模塊
- 那么或者通過索要鏈接/單號的方式,前往指定頁面
- 或者直接在相關模塊進行搜索
上面的描述都意味著用戶基本不會使用站內信,那么在什么樣的場合會使用站內信呢?
- 不發郵件,只發站內信的消息通知,例如全站通知、編輯操作、Comment操作等
- 當具體模塊沒有詳細的操作記錄時,可以通過查看站內信的發生時間
當前只有產品消息通知,消息展示也沒有進行歸類聚合,以后增加全站通知、mention、like、comment等類型的站內信時,就需要考慮按類型進行消息聚合了。
二、需求描述
- 站內信通常需要解決兩個需求:
- 用戶對用戶的站內信,管理員對用戶的站內信:即一對一發送
- 管理員對多用戶、用戶組、全站的站內信:即一對多發送
(還有一種是用戶對產品的站內信,例如對某個模塊的反饋、疑問之類的)
我們目前的需求是:
1管理員對多用戶發送站內信
對用戶真實性不做校驗
對標題長度、內容長度進行限制(分別是45個字節、150個字節,對應中文字符15個、50個)
對收件人的拼音長度進行限制(最長50個字節)
2 用戶可以查看自己的站內信
按“全部、已讀、未讀”過濾
按消息來源分類:工單平臺、資源管理、自動裝機、漏洞平臺、故障平臺。。。
3 用戶可以刪除、批量刪除站內信
4 用戶可以已閱、批量已閱、全部標記為已讀 站內信
5 運維平臺頁面頂部的消息圖標
- 展示未讀消息數,超過99顯示 99+
- 鼠標放上去,會有下拉框,展示最近10條未讀消息(展示“時間”,“消息來源”,“標題”)
- 下拉框的底部有兩個按鈕:“更多”,加載更多未讀消息;“查看全部”,跳轉到站內信列表頁面(最好另開一個窗口)
- 點擊下拉框里的未讀消息,通過彈出框展示詳情;然后在未讀列表里刪除該記錄,在數據庫里標記為已讀,消息圖標的未讀消息數量減一
6 管理員頁面:
更新用戶
刪除消息
統計數據
增加module
增加站內信類型
發送全站消息
三、系統設計
功能設計
四、系統流程
發送站內信
- 讀取POST請求的request body
- 校驗長度
- 插入數據庫
- 返回
獲取站內信列表
- 調用子模塊,插入發送給全站或我所屬用戶組的站內信
- 根據查詢條件,返回數據庫數據
獲取未讀站內信數量
- 調用子模塊,插入發送給全站或我所屬用戶組的站內信
- 返回數量
批量已閱
- 檢查messageId是不是屬于當前用戶
- inbox_message表里把 read 置為1,修改update_time
全部已閱
update inbox_message set “read”=1, “update_time”=now where “receiver_name”=currentUser() and “read” = 0
批量刪除
- 檢查messageId是不是屬于當前用戶
- inbox_message表里把 deleted 置為1,修改update_time
全部刪除
update inbox_message set “deleted”=1, “update_time”=now where “receiver_name”=currentUser() and “deleted” = 0
五、數據庫設計
站內信內容表
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;
站內信本身除了消息來源(module_name),還有一個緯度的描述,叫消息類型(message_type),例如安全消息、活動消息、服務消息等,每一大類里,又可以劃分子類,例如活動消息-優惠活動
消息來源和消息類型可以是正交關系,即工單平臺也可以有活動消息;消息來源也可以是消息類型的一種,稱為“產品消息”
站內信發送表
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;
消息來源表
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設計
發送站內信:POST /v1/message
request body Content-Type: Application/json
{ "title": "工單審批", "content": "XXX提交了變更申請,請審批", "to": "sunzhongyuan,shenli,wangya", "module_name": "工單平臺", "link": "xxx" }
response
{ "code": 200, "data": 32, "msg": "OK" }
獲取站內信列表: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" }
注:
返回數據的個數是由 limit 限制,而 total 是符合query條件的總數(用于分頁)
目前沒有發送用戶組、全站的行為,如果有的話,在獲取列表接口里,增加一步“插入所有發送給我所在用戶組,或發給全站的,且我自己的站內信列表里沒有記錄到的站內信”
獲取未讀站內信數量:GET /v1/message/unread_count
response
{ "code": 200, "data": 29, "msg": "OK" }
獲取單個站內信內容: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" }
已閱、批量已閱站內信:PUT /v1/read_messages/:messageIds
response
{ "code": 200, "data": "OK", "msg": "OK" }
全部已閱 PUT:/v1/read_all_messages
response 同上
刪除、批量刪除站內信:PUT /v1/delete_messages/:messageIds
response 同上
全部刪除站內信:PUT /v1/delete_all_messages
response 同上
獲取消息來源列表:GET /v1/module
response
{ "code": 200, "data": [ { "id": 1, "code": "worksheet", "name": "工單平臺", "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" }
七、測試注意點
1 發送站內信
- 純接口
- 收件用戶以逗號分割,真實性不做校驗
- 收件用戶有長度校驗,50個字節
- title content 有長度校驗,分別是45,150個字節
- module_name 是一個列表,必須從這里選一個
2 其他接口都可以通過前端頁面測試
八、優化
- 未讀列表可以加上粗體顯示,已讀則是普通字體
- 對站內信進行分類,打上不同緯度的標簽,方便過濾、搜索、屏蔽
- 用戶可以設置允許接收的站內信的消息來源
- 管理員可以對全站消息、全站人員、全站的消息屬性進行增刪改查,比如撤銷某個站內信,讓所有人都看不見
- 管理員可以統計站內信的發送數量、各產品的使用情況、消息被讀的比例、消息被讀的時間、消息被讀的方式(點開還是批量操作),等
九、關鍵功能點設計
右上角的圖標行為
1 點擊圖標,展示最近的N條未讀消息
- 展示下拉框
- 實時獲取最近N條未讀消息
- N可以為5~10,具體數值取決于下拉框的高度限制
- 當未讀數不足N時,下拉框能自適應高度
- 如果沒有未讀消息,展示”暫無新消息”
- 停止每10秒的獲取未讀消息數接口
2 下拉框里,展示消息來源、時間(相對現在的時間:10分鐘前)、title
- 向下滑動下拉框,展示更多未讀消息(只獲取id小于已展示消息列表里的最小id,即不獲取點擊圖標后新來的消息)
3 點擊下拉框里的某一個消息
- 下拉框不消失
- 依然停止每10秒的獲取未讀消息數接口
- 未讀消息數減1
- 未讀消息列表刪除當前消息(slice)
- 展示彈出框
4 彈出框展示消息的來源、時間(絕對時間)、title、content
5 關閉彈出框或者點擊外圍:
- 彈出框消失
- 下拉框不消失
- 可以繼續點擊某一個未讀消息
6 再次點擊下拉框和圖標的外圍
- 下拉框消失
- 清空已有的未讀消息列表
- 恢復每10秒的獲取未讀消息數接口
7 再次點擊圖標,重新回到#1狀態
阿里云的圖標行為是:
- 刷新頁面的時候才會請求一次未讀消息數,之后不再定時刷新(當然也可能是刷新時間間隔比較長,沒發現;又或者采用了 socket 的方式,建立了一個長鏈接)
- hover圖標,即顯示未讀消息的下拉框
- 點擊圖標,進入站內信管理頁面,默認是“未讀消息”
4 點擊未讀消息,新開一個Tab,展示該消息的詳情(detail頁面),原Tab內容不變,即沒有未讀數減一,也沒從下拉框里刪除剛點擊的消息
5 最多展示5條消息,只要不刷新頁面,就一直是這5條
6 沒有滾動更多的功能,只有查看更多,點擊進入站內信管理頁面,默認是“未讀消息”
- 和點擊圖標的區別是:點擊圖標直接當前頁面跳轉到站內信管理頁面,點擊“查看更多”會新建一個Tab
7 多了一個“消息接受管理”的按鈕,當前頁面跳轉到站內信管理頁面,但是默認即“基本接收管理”
隱藏瀏覽器進度條
每10秒的獲取未讀消息數接口,會觸發瀏覽器展示進度條,導致分散用戶注意力,要把這個進度條隱藏掉。
其他刷新頁面的行為不受影響。
參考文檔
《站內信需求背景及需求分析的全過程》
《站內信功能設計》
《站內信的實現:數據庫的設計》
《站內信的實現思路表的設計》
《Web網站通知系統設計》