最近笔者浏览网页的时候发现站内消息提醒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