当前位置: 首页 > news >正文

好友聊天已读状态总结

在即时通讯系统中,用户需要知道发送的消息是否被对方阅读。传统方案是为每条消息单独存储 is_read 字段,但这种方式在高并发场景下会导致数据库压力过大——每收到一条消息就要更新一条记录,消息量增长后性能急剧下降。
为解决这个问题,我借鉴了一些大佬的想法,基于IM系统的设计,采用会话维度水位记录而不是逐条标记;

核心思想: - 每条消息在会话内分配单调递增序号 seq - 每个用户在每个会话只存一条"阅读水位"记录 - 判断规则:消息.seq <= 用户.last_read_seq → 已读

方案对比:

数据库设计:

  1. 消息表新增序号列
ALTER TABLE chat_msg ADD COLUMN seq BIGINT NOT NULL DEFAULT 0 COMMENT '会话内单调递增序号';
  1. 会话序号分配表
CREATE TABLE conversation_seq ( conversation_key VARCHAR(50) PRIMARY KEY COMMENT '会话标识:小ID_大ID', seq BIGINT NOT NULL DEFAULT 0 COMMENT '下一条消息的序号' );
  1. 会话阅读水位表
CREATE TABLE conversation_read ( id BIGINT PRIMARY KEY AUTO_INCREMENT, conversation_key VARCHAR(50) NOT NULL COMMENT '会话标识', user_id BIGINT NOT NULL COMMENT '阅读方用户ID', last_read_seq BIGINT NOT NULL DEFAULT 0 COMMENT '读到的最大消息序号', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_conv_user (conversation_key, user_id) );

后端实现:

1.会话序号分配(原子递增)

@Insert("INSERT INTO conversation_seq (conversation_key, seq) VALUES (#{key}, 1) " + "ON DUPLICATE KEY UPDATE seq = seq + 1") void incrementSeq(String key);

每次发送消息时调用,INSERT … ON DUPLICATE KEY UPDATE 保证原子性。
2.阅读水位更新(取最大值)

@Insert("INSERT INTO conversation_read (conversation_key, user_id, last_read_seq) " + "VALUES (#{key}, #{userId}, #{seq}) " + "ON DUPLICATE KEY UPDATE last_read_seq = GREATEST(last_read_seq, #{seq})") void upsertLastReadSeq(String key, Long userId, Long seq);

3. 发送消息时分配序号

public ChatMsg saveMsg(Long sendId, Long receiveId, String content) { String convKey = buildKey(sendId, receiveId); // "小ID_大ID" long seq = conversationService.nextSeq(convKey); ChatMsg msg = new ChatMsg(); msg.setSeq(seq); // ... 其他字段 save(msg); return msg; }

4. 获取历史消息时计算已读状态

public ChatHistoryVO getHistoryMsg(Long userId, Long friendId) { String convKey = buildKey(userId, friendId); // 打开聊天时,先更新我的阅读水位 long myLastReadSeq = updateMyReadSeq(convKey, userId, friendId); // 查询对方的阅读水位(判断我发的消息是否被对方已读) long friendLastReadSeq = getLastReadSeq(convKey, friendId); // 查询消息列表 List<ChatMsg> list = list(...); // 计算每条消息的已读状态 for (ChatMsg m : list) { if (m.getReceiveUserId().equals(userId)) { // 对方发给我的:用我的水位判断 m.setReadStatus(m.getSeq() <= myLastReadSeq ? 1 : 0); } else if (m.getSendUserId().equals(userId)) { // 我发给对方的:用对方的水位判断 m.setReadStatus(m.getSeq() <= friendLastReadSeq ? 1 : 0); } } return new ChatHistoryVO(list, myLastReadSeq); }

5.WebSocket 推送已读回执

@PostMapping("/read") public Result<Void> readMsg(@RequestParam String username) { Long userId = UserHolder.getUserId(); User friend = userService.selectByUserName(username); long maxSeq = chatMsgService.readMsg(friend.getId(), userId); // 推送已读回执给发送方 if (maxSeq > 0) { ChatWebSocket.pushToUser(friend.getId(), "READ|" + maxSeq + "|" + userId); } return Result.success(null); }

前端实现

收到新消息时立即标记已读:

onPush: (msg) => { if (String(msg.sendUserId) === String(activeId.value)) { // 我在聊天界面内 → 立即标记已读 messages.value.push({ ...msg, readStatus: 1 // 本地立即标记 }); // 异步调用 readMsg,更新后端水位并推送回执 readMsg(activeUsername.value).catch(() => {}); } }

收到已读回执时批量更新状态:

onRead: (maxSeq, readerId) => { // readerId 已读到 maxSeq // 我发给 readerId 的消息中 seq <= maxSeq 的 → 已读 messages.value.forEach((m) => { if (String(m.sendUserId) === String(myId.value) && String(m.receiveUserId) === String(readerId) && m.seq <= maxSeq) { m.readStatus = 1; } }); }

关键结束点:

  • 会话标识固定格式:min(userId1, userId2) + “_” + max(userId1, userId2),保证双方使用同一 key
  • 原子递增序号:INSERT … ON DUPLICATE KEY UPDATE seq = seq + 1
  • 水位更新防回退:GREATEST(last_read_seq, #{seq}) 取最大值
  • 前端实时体验:收到消息立即本地标记已读,异步调用后端更新水位
  • WebSocket 推送格式:READ|maxSeq|readerId,一次推送同步所有历史状态
http://www.jsqmd.com/news/1041312/

相关文章:

  • ZIP/RAR密码恢复实战:从John the Ripper到Hashcat GPU加速破解
  • 2026沈阳黄金回收哪家靠谱?2427笔成交数据实测靠谱门店 - 奢品小当家
  • 2026年6月19日海安大灯升级到店前怎么聊?先把原车灯状态和升级顺序问细 - Ayu8888
  • 从文案策划到视频渲染:多模型混合链路的最佳实践指南
  • 滁州来安县大型罐体吸污抽粪处理工地混杂污水,重载车辆抽泥浆清运基坑沉淀淤泥沙土 - 天堂海洋
  • 2026潍坊黄金回收实测攻略:六大商圈门店评测与防坑指南 - 余生黄金回收
  • 2026达州黄金回收白银回收铂金回收门店+工商公安双备案+中检认证商家推荐 - 诚金汇钻回收公司
  • 2026石嘴山黄金回收行情与六家实体门店实测 - 余生黄金回收
  • 87456
  • 昆明黄金回收全维度测评:门店排行 + 报价拆解,告别虚高引流 - 奢品小当家
  • RK3288_Android7.1:从驱动适配到事件上报,打通ES8388音频全链路
  • 上电考试-言语之路
  • 2026年湘阴车主的安心之选:四家轮胎养护中心实力解析 - 国麟测评
  • PMD Java代码检查工具:从零到一,实战集成与自定义规则详解
  • 天津黄金回收门店实力排行榜|禹竞名奢汇稳居榜首行情透明价更高 - 名奢变现站
  • 贵阳黄金回收指南:六家靠谱店铺推荐,覆盖全市区县安心变现 - 清奢黄金上门回收
  • 实测无套路出价,2026哈尔滨黄金回收口碑门店深度甄选 - 名奢变现站
  • 2026潮州黄金回收白银回收铂金回收门店+工商公安双备案+中检认证商家推荐 - 诚金汇钻回收公司
  • Claude 长文梳理实战:高效提炼技术文档与论文核心要点
  • 2026邯郸黄金回收白银回收铂金回收门店实测|本地正规实体老店无套路门店推荐 - 中安检金银铂钻回收
  • 广州海珠区大小管道疏通清理工程|马桶疏通通厕所下水道疏通地漏疏通|化粪池清理抽粪隔油池清洗管道改管 - 天堂海洋
  • LLM应用开发、RAG、Agent、MCP、A2A、多模态与AI Infra系统工程师进阶学习路线图
  • GCP Vertex AI Provisioned Throughput 完全指南 — 从 429 限流到 PT 预留吞吐量
  • 2026红河黄金回收白银回收铂金回收门店实测|本地正规实体老店无套路门店推荐 - 中安检金银铂钻回收
  • GPT-4.0自述式提示工程:构建可验证的能力契约
  • 2026广州黄金回收白银回收铂金回收门店实测|本地正规实体老店无套路门店推荐 - 中安检金银铂钻回收
  • 2025-2026年北京慧考教育电话查询:选择学历提升服务前需核实资质与流程 - 品牌推荐
  • 泰州市室内行车专业可靠 - 天堂海洋
  • 同校大数据和计算机,历年录取分数线谁更高
  • 十六层PCB打样,怎么选厂家才不踩坑?