Node.js WebSocket聊天应用结合MongoDB实现用户活跃状态管控咨询
实现方案
核心逻辑思路
- WS连接建立前的握手阶段完成身份鉴权,拿到对应用户ID,实现连接与用户身份的绑定
- 鉴权通过后先查询用户活跃状态,已处于
active状态直接拒绝连接 - 连接建立成功更新用户状态为
active,内存中维护用户ID与WS连接实例的映射 - 监听连接关闭/错误事件,触发时更新用户状态为非活跃
代码实现示例
1. 基础初始化与升级拦截
// 依赖引入,替换为你自己的实际依赖路径 const WebSocket = require('ws'); const express = require('express'); const http = require('http'); const { User } = require('./models'); // 你的用户数据库模型文件 const app = express(); const server = http.createServer(app); // 关闭WS默认自动升级逻辑,手动处理升级请求做鉴权 const wss = new WebSocket.Server({ noServer: true }); // 内存映射表:存储用户ID与对应WS连接实例,断开连接时快速定位对应用户 const userWsMap = new Map(); // 服务启动时全局重置所有用户活跃状态,避免服务异常重启后状态残留导致用户无法登录 async function initGlobalUserStatus() { await User.updateMany({}, { activeStatus: 'inactive' }); } initGlobalUserStatus();
2. 握手阶段鉴权与状态校验
server.on('upgrade', async (request, socket, head) => { // 替换为你现有登录体系的token校验逻辑,从Cookie取session、从query参数取token都可以 const reqUrl = new URL(request.url, `http://${request.headers.host}`); const token = reqUrl.searchParams.get('token'); if (!token) { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); return; } // 校验token合法性,返回对应用户ID,替换为你自己的token解析逻辑 const userId = verifyYourToken(token); if (!userId) { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); return; } // 查询用户当前活跃状态,已活跃直接拒绝连接 const targetUser = await User.findById(userId); if (targetUser.activeStatus === 'active') { socket.write('HTTP/1.1 403 Forbidden\r\n\r\n'); socket.destroy(); return; } // 所有校验通过,升级为WS连接 wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request, userId); }); });
3. 连接建立与断开的状态更新
wss.on('connection', async (ws, request, userId) => { // 更新用户状态为活跃 await User.findByIdAndUpdate(userId, { activeStatus: 'active' }); // 给当前WS实例挂载用户ID,后续断开事件直接读取 ws.userId = userId; userWsMap.set(userId, ws); // 监听连接关闭事件 ws.on('close', async () => { await User.findByIdAndUpdate(userId, { activeStatus: 'inactive' }); userWsMap.delete(userId); }); // 监听连接异常事件 ws.on('error', async () => { await User.findByIdAndUpdate(userId, { activeStatus: 'inactive' }); userWsMap.delete(userId); }); // 此处保留你原有WS消息处理逻辑 }); server.listen(3000, () => { console.log('服务启动在3000端口'); });
可选优化点
- 增加心跳检测:定时给客户端发送ping包,超时未收到pong回包主动断开连接,避免网络异常断开未触发close事件导致状态异常
- 集群部署适配:如果是多实例部署,内存Map替换为Redis存储用户连接映射,活跃状态也可以存在Redis中降低数据库查询压力
- 主动踢人逻辑扩展:如果需要支持新登录挤掉旧登录,校验到用户已活跃时可以主动断开旧连接,再允许新连接建立
内容的提问来源于stack exchange,提问作者flaneurx
相关产品推荐
相关产品推荐

