在前面的文章中,我们已经使用 WebSocket 实现了即时聊天的实时通信功能,并实现了群聊与私聊功能,同时支持消息存储与推送。但一个完整的聊天系统,还需要处理 聊天记录的管理 以及 离线消息处理,确保用户能够随时查看历史消息,并在离线后仍能收到未读消息。
本文将深入探讨 Node.js 聊天系统中的聊天记录与离线处理设计与实现。
一、聊天记录的存储意义
聊天记录不仅是用户查看历史消息的基础,还在多个方面发挥作用:
- 支持聊天回溯与消息搜索
- 提供聊天统计与分析数据
- 支持多端同步(Web、移动端)
- 作为系统审计与安全依据
因此,聊天系统必须持久化存储消息。
二、聊天记录的数据模型设计
在数据库中,每条消息通常包含以下字段:
messageId:消息唯一标识fromUserId:发送者 IDtoUserId或roomId:接收者 ID 或群 IDtype:消息类型(私聊/群聊)content:消息内容createdAt:发送时间status:消息状态(未读/已读/撤回)
示例 MongoDB 文档结构:
{
"messageId": "m10001",
"fromUserId": "u1001",
"toUserId": "u1002",
"type": "private",
"content": "你好啊!",
"createdAt": "2026-01-19T12:00:00Z",
"status": "unread"
}
这种结构可以满足大部分聊天场景,包括群聊和私聊。
三、聊天记录的写入流程
当用户发送一条消息时,服务器通常执行以下操作:
- 校验消息合法性
- 写入数据库,保证消息持久化
- 推送给在线用户
- 标记离线用户消息状态为未读
示例 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' });
});
}
这种机制确保用户不会错过任何消息,无论是群聊还是私聊。
五、聊天记录分页与查询
为了支持历史消息回溯,通常需要提供分页查询接口:
- 私聊:根据
fromUserId与toUserId查询 - 群聊:根据
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 连接
- 发送消息时,将消息推送到所有在线设备
- 使用数据库或缓存统一管理已读/未读状态
七、性能与优化考虑
随着用户数量增长,聊天记录和离线消息量可能非常大。优化方向包括:
-
消息存储优化
- 按时间或用户分表
- 最近消息存 Redis,高频访问
-
消息查询优化
- 索引
fromUserId、toUserId、roomId - 分页查询
- 索引
-
推送优化
- WebSocket 集群化
- 使用消息队列(如 RabbitMQ / Kafka)进行跨节点广播
这些措施可以保证聊天系统在高并发场景下依然稳定。
八、总结
聊天记录和离线消息处理是即时聊天系统不可或缺的核心能力。通过合理的消息存储结构、离线消息标记和多端同步机制,Node.js 聊天系统能够保证消息可靠性与用户体验。
在《Node.js 编程实战》中,即时聊天项目通过 WebSocket、数据库存储和离线处理逻辑的结合,全面展示了 Node.js 在高并发、实时通信场景中的能力。