Rails应用聊天模块消息已读标记功能的实现机制咨询
Rails双向聊天已读功能设计方案
首先明确现有结构的核心局限:messages表的单个read字段无法适配单条消息对应两个用户的不同已读状态(发送方默认已读、接收方默认未读),因此无法直接复用该字段实现需求,以下提供两种适配不同场景的落地方案:
方案1:会话级轻量实现(适合中小流量、无需单条消息已读提示的场景)
基于现有inboxes表扩展,改动最小、查询效率最高:
- 给
inboxes表新增两个datetime类型字段:user1_last_read_at、user2_last_read_at,默认值为null - 核心逻辑
- 消息发送时无需额外操作,用户进入对应会话页、或会话页处于活跃状态时收到新消息,就将该用户对应的
last_read_at字段更新为当前时间 - 判定某用户的消息已读状态:只要消息的
created_at<= 该用户对应inbox下的last_read_at,即判定为已读 - 未读数统计:直接统计当前inbox下
created_at> 对应用户last_read_at的消息总数即可
- 消息发送时无需额外操作,用户进入对应会话页、或会话页处于活跃状态时收到新消息,就将该用户对应的
- 优势:无新增表、存储成本极低、查询逻辑简单,适合绝大多数C端小体量聊天场景
- 局限:无法实现单条消息的精准已读标记(例如“对方已读”的单条消息提示),只能感知到用户已读某个时间点之前的全部消息
方案2:单条消息精准已读实现(适合大流量、需要单条已读提示的场景)
新增中间表存储每条消息的已读回执,扩展性最强:
- 新增
message_read_receipts表,迁移文件示例:create_table :message_read_receipts do |t| t.bigint :user_id, null: false t.bigint :message_id, null: false t.bigint :inbox_id, null: false t.datetime :read_at, null: false t.index [:user_id, :message_id], unique: true t.index [:inbox_id, :user_id] end - 核心逻辑
- 用户发送消息时,自动生成该用户对这条消息的已读回执,
read_at设为当前时间 - 接收方进入对应会话页、或会话页处于活跃状态时,批量插入当前inbox下所有未生成回执的消息的已读记录
- 判定某用户是否已读单条消息:直接查询是否存在对应
user_id、message_id的回执记录即可 - 未读数统计:统计当前inbox下不存在对应用户id回执的消息总数即可
- 用户发送消息时,自动生成该用户对这条消息的已读回执,
- 优势:支持单条消息的已读状态标记,可实现“对方已读”这类精细化交互,扩展性强
- 局限:数据规模随消息量翻倍增长,需定期归档历史冷数据降低存储压力
ActionCable实时联动逻辑
两种方案都需要配合WebSocket实现已读状态实时同步:
- 用户触发已读更新操作后,除了更新存储的已读状态,还需要通过对应inbox的专属频道推送已读事件给会话另一方
- 对方前端收到已读事件后,实时更新界面上的已读标识、未读数
原有messages表中的闲置
read字段可直接删除,两种方案均无需使用该字段。
内容的提问来源于stack exchange,提问作者Yeahprettymuch
相关产品推荐
相关产品推荐

