最近筆者瀏覽網頁的時候發現站內消息提醒99+,一時不知所措。點完消息后就在想消息功能是怎么實現的?
1. 站內信
站內信簡單點就是網站內的消息通知,在網站內部實現,不用郵件,短信等服務。很多時候我們都在使用,比如系統推送的公告,用戶的私信,訂閱的更新等等很多
根據站內信的發送范圍可將其分為:
一對一:屬於私信,用戶與用戶之間互相發送私信,或者是系統對某一特定用戶推送的內容
一對多:屬於群發,一用戶對多個用戶發送消息(垃圾廣告),或者系統對某特定的用戶群體推送內容
一對全體:屬於公告,是對全體用戶生效的,每個用戶都能收到這個公告消息
根據站內信的內容可將其大致分為(參考Bilibili模型):
回復我的
@ 我的
收到的贊
系統通知
我的消息
其他關注點:
消息的設置:是否開啟消息提醒、免擾時間、消息提醒的范圍
消息提醒的時限:消息也需要設置時限,不然幾年前發的公告,現在剛創建的用戶也會收到
用戶群體:對某些特定的群體發送消息,比如對常瀏覽科技區的用戶發送科技短訊
消息訂閱:對那些訂閱頻道的用戶推送更新提醒
說了那么多,開始說重點了。筆者只實現最基本的一對一私信與一對全體的公告功能,以最簡潔的方式表達站內信的設計(主要是筆者沒有實現整體功能的實力)
2. 數據庫設計
將消息內容與閱讀記錄分開,這樣做的目的是避免公告中每個用戶都需要一份消息內容而形成冗余。兩個表分別為t_message_content
內容表,t_message_record
記錄表
t_message_content
CREATE TABLE `t_message_content` (
`c_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '消息的id',
`send_id` int(11) DEFAULT NULL COMMENT '消息發送者的id',
`content` varchar(255) DEFAULT NULL COMMENT '消息的內容',
`type` int(11) DEFAULT NULL COMMENT '消息的類型',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息發送的時間',
PRIMARY KEY (`c_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
其中type消息類型分為私信和公告,0是私信、1為公告。發送時間默認為當前時間
t_message_record
CREATE TABLE `t_message_record` (
`r_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '閱讀記錄的id',
`rec_id` int(11) DEFAULT NULL COMMENT '消息接收者的id',
`c_id` int(11) DEFAULT NULL COMMENT '對應消息的id',
`status` int(11) DEFAULT '0' COMMENT '閱讀記錄的狀態',
PRIMARY KEY (`r_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
status表示閱讀記錄的狀態,0表示未讀,1已讀,2刪除。為什么需要刪除? 以公告為例:個人刪除公告的消息可將閱讀記錄標記為刪除,這樣個人就不會顯示該公告了。但公告本身內容不能被個人刪除,刪除的話其余的人就無法收到這條公告了。閱讀記錄默認為未讀。
3. 私信的操作步驟
3.1 用戶7 發送私信給 用戶10
一、在內容表里插入私信內容,並返回該內容的自增主鍵c_id = 5
INSERT INTO t_message_content (`send_id`,`content`,`type`) VALUES (7,"這是7發送私信給10",0)
二、往記錄表里插入私信接收方未讀的記錄
INSERT INTO t_message_record (`rec_id`,`c_id`) VALUES (10,5)
3.2 私信接收方接收消息
一、用戶10 登錄時獲取全部私信消息
SELECT c.*,r.status
FROM t_message_content c
LEFT JOIN t_message_record r
ON c.c_id = r.c_id
WHERE r.rec_id = 10
AND c.type = 0
AND r.`status` != 2
c_id | send_id | content | type | create_time | status |
---|---|---|---|---|---|
5 | 7 | 7發送私信給10 | 0 | 2020-03-09 13:23:15 | 0 |
內容表左聯記錄表,外聯+where都是操作在臨時表上的,篩選出用戶10 未刪除的私信
二、用戶10 點擊閱讀時
UPDATE t_message_record SET status = 1 WHERE c_id = 5 AND rec_id = 10
將閱讀記錄的狀態修改為已讀
三、當用戶10 點擊刪除私信時
UPDATE t_message_record SET status = 2 WHERE c_id = 5 AND rec_id = 10
4. 公告的操作步驟
4.1 后台用戶1 發布公告
INSERT INTO `t_message_content` (`send_id`,`content`,`type`) VALUES (1,"這是公告1的內容",1)
INSERT INTO `t_message_content` (`send_id`,`content`,`type`) VALUES (1,"這是公告2的內容",1)
沒錯就一步,不需要往記錄表插入記錄,因為公告是面對全體的。若插入記錄以用戶基數10萬人算,那數據庫不瞬間鎖表卡死,具體接收操作請看下面
4.2 用戶10 接收公告
一、用戶10 登陸時獲取全部公告消息
SELECT c.*,IFNULL(r.status,0) AS status
FROM t_message_content c
LEFT JOIN t_message_record r
ON (c.c_id = r.c_id AND r.rec_id = 10)
WHERE c.type = 1
這里需要思考了:先查出全部公告,然后左聯記錄表,得出臨時表(記錄了全部公告和能匹配的閱讀記錄),沒有匹配則是null,然后用IFNULL設為0表示未讀,此時閱讀表里是沒有這條記錄的
二、公告1設為已讀
INSERT INTO t_message_record (`rec_id`,`c_id`,`status`) VALUES (10,1,1)
三、刪除公告1
UPDATE t_message_record SET status = 2 WHERE rec_id = 10 AND c_id = 1
至此筆者理解的站內信就講完了
5. 看了其他優秀博主的消息通知,私信方面有更好的設計
直接搬運,地址在文末給出
5.1 私信表(notify_inbox)
id: {type: 'integer', primaryKey: true, autoIncrement:true} //編號;
dialogueID: {type: 'string', required: true} //對話編號;
senderID: {type: 'string', required: true} //發送者編號;
recipientID: {type: 'string', required: true} //接收者編號;
messageID: {type: 'integer', required: true} //私信內容ID;
createdAt:{type: 'timestamp', required: true} //發送時間;
state: {type: 'integer', required: true} //狀態,已讀|未讀;
readAt:{type: 'timestamp', required: true} //閱讀時間;
5.2 私信接收方
一、私信的通知
select * from notify_inbox where recipientID="uid" order by createdAt desc
二、私信對話框
select * from notify_inbox where dialogueID=“XXXX” and (recipientID=“uid” or senderID="uid") order by createdAt asc
私信回復時,回復的是dialogueID
5.3 私信發送方
一、私信的發送
select * from notify_inbox where senderID="uid" order by createdAt desc
二、私信對話框
select * from notify_inbox where dialogueID=“XXXX” and (senderID=“uid” or recipientID="uid") order by createdAt asc
5.4 私信內容表
id: {type: 'integer', primaryKey: true, autoIncrement:true} //編號;
senderID: {type: 'string', required: true} //發送者編號;
content: {type: 'string', required: true} //私信內容;
createdAt:{type: 'timestamp', required: true}
參考:
https://www.cnblogs.com/grenet/archive/2010/03/08/1680655.html
https://blog.csdn.net/lifaming15/article/details/51083178
https://segmentfault.com/a/1190000018238628