You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 10:54:03