Node.js使用socket.io给指定用户发通知时controller获取用户列表为空怎么办
问题原因
- 核心问题是
users数组的引用被重置了:你写的removeUser方法里直接执行users = users.filter(...),这里是给users变量赋值了一个全新的数组,而你导出的时候const usersInObject = users是把初始的空数组引用存下来的,后续users变量指向新数组后,导出的usersInObject还是指向原来的空数组,自然永远是空的。 - 次要问题:你只导出了io实例和用户数组,没有导出
getUser这类工具方法,后续业务调用也不方便。
解决方案
1. 改造index.js的用户存储逻辑,保证引用唯一
不要直接给users重新赋值,而是用数组的可变方法操作同一个引用的数组,直接把用户列表、操作方法都封装在导出对象里,避免引用丢失。
// 把用户数组、操作方法、io实例都放到统一的存储对象里,保证引用唯一 const ioStore = { users: [], addUser(userId, socketId) { if (!this.users.some(user => user.userId === userId)) { this.users.push({ userId, socketId }); } }, removeUser(socketId) { // 用splice修改原数组,不要重新赋值替换数组引用 const index = this.users.findIndex(item => item.socketId === socketId); if (index > -1) { this.users.splice(index, 1); } }, getUser(userId) { console.log("inside function", this.users); return this.users.find(item => item.userId === userId); }, io: null } // 初始化io后赋值到存储对象 ioStore.io = io; io.on("connection", (socket) => { socket.on("addUser", (userId) => { ioStore.addUser(userId, socket.id); io.emit("getUsers", ioStore.users); console.log(ioStore.users) }); socket.on("disconnect", () => { ioStore.removeUser(socket.id); io.emit("getUsers", ioStore.users); }); }); module.exports = ioStore;
2. 改造controller文件直接调用存储对象
// 导入刚才定义的统一存储对象,路径换成你项目里index.js的实际路径 const ioStore = require('./index'); exports.createNotifications = async (req, res) => { try { const { userId, title, type = "default", isPublic } = req.body; if (!title) { return res.status(401).send("Data is required"); } const notification = await notificationsModel.create({ userId, title, type, isPublic: userId ? false : true, }); // 现在可以正常拿到实时的在线用户列表 console.log("socket", ioStore.users); // 给指定用户发通知的逻辑 if(userId) { const targetUser = ioStore.getUser(userId); if(targetUser) { ioStore.io.to(targetUser.socketId).emit('newNotification', notification); } } else { // 公共通知全量推送 ioStore.io.emit('newNotification', notification); } return res.status(200).send("sent"); } catch (err) { return res.status(400).send(err.message); } };
注意事项
- 要确保你的socket.io初始化的index.js在服务启动时就被加载执行,不要等请求进来才第一次加载,否则用户连接的监听逻辑不会生效。
- 如果是多实例部署的场景,内存存储的用户列表在多个实例之间不共享,需要改用Redis等公共存储来存用户ID和socketId的映射关系。
内容的提问来源于stack exchange,提问作者Omar Atef
相关产品推荐
相关产品推荐

