站内信是很多系统中的必备模块,结构设计也是老生常谈的问题。

设计如下,其中mail表示用户–>用户之间的站内消息,notice表示系统–>用户之间的系统通知:

站内信系统的设计思路-风君雪科技博客

两者结构基本一致,由于消息体本身可能包含text这种大容量的数据内容,因此将消息体独立存储在一个表中,再将消息体与收件方关联,是更加高效一些的做法。

在多数应用场景中,可能会包含群发行为,例如:用户发送给好友的祝福;系统向部分用户发送通知。此时,遍历目标用户,批量插入至消息接收表,是比较可靠的做法。

但是相对于mail站内信而言,notice有一个重要的需求是【广播】,即面向所有注册用户的群发消息。面对这种需求,当用户只有几十人时,这与前面的方法并无显著的差异。但是当用户体量过千,甚至过万的时候,这将是非常不智的做法。

更好一些的做法,是将notice消息提设置为广播属性,当用户发生操作时,才将消息体导入接收信息表中。

关于此类消息的查询,通常采用如下方式:

select IFNULL(nr.id, 0) as id, IFNULL(nr.recipient_user_id, 0) as recipient_user_id, IFNULL(is_read, 0) as is_read, IFNULL(is_delete, 0) as is_delete
      , n.id as notice_id, notice_content, is_broadcast, n.create_time as notice_create_time
from notice_receive nr
      right outer join notice n on nr.notice_id = n.id
where (nr.recipient_user_id = #{uid} or is_broadcast = 1) and nc.create_time > #{user_create_time}

查询当前用户的关联信息,以及所有广播信息,同时要注意,消息体时间大于用户注册时间,避免出现历史信息出现的状况。

分析上面的sql语句,很明显right outer join是很消耗系统性能的查询操作,这并不是一个更优的方案,还需要进一步优化。

再考虑一下用户日常操作消息的行为:未读消息设为已读、未读消息直接删除、已读消息删除(类似于Email系统的未读和已读之间的关系转换,并无同质考虑的意义)。

站内信系统的设计思路-风君雪科技博客

上图是很常见的消息列表,结合前面的table结构,实际我们在做上面批量操作时,应有的查询是:

where nr.id in (notice_receive id list)

对于已经阅读过的消息,已经存在于notice_receive表中,可以直接根据notice_receive_id来操作。但是对于尚未阅读操作过的信息,则需要混合notice_id来进行操作,这样一来可能需要混合处理语句来完成。

或许,在这种权衡选择当中,在用户登录、或者打开消息列表时,就将notice关联存入notice_receive表,或许会更好的解决这个问题。