Node.js 编程实战:即时聊天应用 —— 聊天记录与离线处理

92 阅读4分钟

在前面的文章中,我们已经使用 WebSocket 实现了即时聊天的实时通信功能,并实现了群聊与私聊功能,同时支持消息存储与推送。但一个完整的聊天系统,还需要处理 聊天记录的管理 以及 离线消息处理,确保用户能够随时查看历史消息,并在离线后仍能收到未读消息。

本文将深入探讨 Node.js 聊天系统中的聊天记录与离线处理设计与实现。


一、聊天记录的存储意义

聊天记录不仅是用户查看历史消息的基础,还在多个方面发挥作用:

  • 支持聊天回溯与消息搜索
  • 提供聊天统计与分析数据
  • 支持多端同步(Web、移动端)
  • 作为系统审计与安全依据

因此,聊天系统必须持久化存储消息。


二、聊天记录的数据模型设计

在数据库中,每条消息通常包含以下字段:

  • messageId:消息唯一标识
  • fromUserId:发送者 ID
  • toUserIdroomId:接收者 ID 或群 ID
  • type:消息类型(私聊/群聊)
  • content:消息内容
  • createdAt:发送时间
  • status:消息状态(未读/已读/撤回)

示例 MongoDB 文档结构:

{
  "messageId": "m10001",
  "fromUserId": "u1001",
  "toUserId": "u1002",
  "type": "private",
  "content": "你好啊!",
  "createdAt": "2026-01-19T12:00:00Z",
  "status": "unread"
}

这种结构可以满足大部分聊天场景,包括群聊和私聊。


三、聊天记录的写入流程

当用户发送一条消息时,服务器通常执行以下操作:

  1. 校验消息合法性
  2. 写入数据库,保证消息持久化
  3. 推送给在线用户
  4. 标记离线用户消息状态为未读

示例 Node.js 逻辑:

async function handleMessage(message) {
  // 1. 存储消息
  await Message.create(message);

  // 2. 判断接收方是否在线
  const target = users.get(message.toUserId);
  if (target && target.readyState === WebSocket.OPEN) {
    // 在线则实时推送
    target.send(JSON.stringify(message));
    // 更新状态为已读
    await Message.updateOne({ messageId: message.messageId }, { status: 'read' });
  } else {
    // 离线用户,保持未读状态
  }
}

通过这种方式,可以确保消息既不会丢失,也能及时推送给在线用户。


四、离线消息处理

离线消息是聊天系统的重要功能,主要包括两部分:

1. 离线消息存储

  • 用户不在线时,消息状态标记为 unread
  • 可以存储在数据库或 Redis 中
  • 便于用户下次上线时获取

2. 离线消息推送

当用户重新上线时,服务器执行以下操作:

async function sendOfflineMessages(userId, ws) {
  const messages = await Message.find({ toUserId: userId, status: 'unread' }).sort({ createdAt: 1 });
  messages.forEach(async (msg) => {
    ws.send(JSON.stringify(msg));
    // 标记为已读
    await Message.updateOne({ messageId: msg.messageId }, { status: 'read' });
  });
}

这种机制确保用户不会错过任何消息,无论是群聊还是私聊。


五、聊天记录分页与查询

为了支持历史消息回溯,通常需要提供分页查询接口:

  • 私聊:根据 fromUserIdtoUserId 查询
  • 群聊:根据 roomId 查询
  • 分页:避免一次性返回大量数据

示例 MongoDB 查询:

const pageSize = 20;
const messages = await Message.find({
  $or: [
    { fromUserId: userA, toUserId: userB },
    { fromUserId: userB, toUserId: userA }
  ]
}).sort({ createdAt: -1 }).limit(pageSize);

这种方式可以保证前端按需加载历史消息,提高性能。


六、多端同步与状态更新

在现代应用中,一个用户可能同时登录多个设备:

  • 手机、平板、Web端
  • 在线状态需要同步
  • 未读消息需要在各端保持一致

实现方法:

  • 每个设备建立 WebSocket 连接
  • 发送消息时,将消息推送到所有在线设备
  • 使用数据库或缓存统一管理已读/未读状态

七、性能与优化考虑

随着用户数量增长,聊天记录和离线消息量可能非常大。优化方向包括:

  1. 消息存储优化

    • 按时间或用户分表
    • 最近消息存 Redis,高频访问
  2. 消息查询优化

    • 索引 fromUserIdtoUserIdroomId
    • 分页查询
  3. 推送优化

    • WebSocket 集群化
    • 使用消息队列(如 RabbitMQ / Kafka)进行跨节点广播

这些措施可以保证聊天系统在高并发场景下依然稳定。


八、总结

聊天记录和离线消息处理是即时聊天系统不可或缺的核心能力。通过合理的消息存储结构、离线消息标记和多端同步机制,Node.js 聊天系统能够保证消息可靠性与用户体验。

在《Node.js 编程实战》中,即时聊天项目通过 WebSocket、数据库存储和离线处理逻辑的结合,全面展示了 Node.js 在高并发、实时通信场景中的能力。