Node.js多路由场景下Socket.io的初始化与使用方法
在MERN应用中多路由使用Socket.io实现实时通知的方案
核心问题分析
你当前的Socket.io初始化代码局限在mongoose.connect的回调函数中,导致io实例的作用域被限制,无法在其他路由模块中访问。要在多路由中使用Socket.io,关键是让io实例全局可访问,并建立用户与Socket连接的映射,以便精准推送通知。
解决方案步骤及示例代码
1. 重构Socket.io与服务器初始化
将服务器创建、Socket.io初始化与数据库连接解耦,把io实例挂载到Express的app对象上,同时维护用户-Socket ID的映射表:
const express = require('express'); const mongoose = require('mongoose'); const http = require('http'); const { Server } = require('socket.io'); const app = express(); // 先创建HTTP服务器,再挂载Socket.io const server = http.createServer(app); // 初始化Socket.io并配置CORS const io = new Server(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST"] }, }); // 存储用户ID到Socket ID的映射,用于定位用户连接 const userSocketMap = new Map(); // 挂载io和映射表到app对象,方便后续路由访问 app.set('io', io); app.set('userSocketMap', userSocketMap); // 处理Socket连接事件 io.on('connection', (socket) => { // 前端连接后需发送用户ID完成初始化(比如登录后) socket.on('setup', (userId) => { userSocketMap.set(userId, socket.id); socket.join(userId); // 创建用户专属房间,简化推送逻辑 }); // 用户断开连接时清理映射 socket.on('disconnect', () => { for (const [id, socketId] of userSocketMap.entries()) { if (socketId === socket.id) { userSocketMap.delete(id); break; } } }); }); // 数据库连接成功后启动服务器 mongoose.connect(process.env.MongoDb_Url) .then(() => { server.listen(5000, () => console.log("Server And Database Are Running")); }) .catch((error) => console.log(error.message));
2. 创建Socket中间件注入路由
编写中间件,将io实例和用户映射表注入到请求对象中,让路由可以直接调用:
// middleware/socketMiddleware.js module.exports = (req, res, next) => { req.io = req.app.get('io'); req.userSocketMap = req.app.get('userSocketMap'); next(); };
3. 在POST路由中发送实时通知
以通知路由为例,应用中间件后即可在接口中触发Socket推送:
// routes/notifications.js const express = require('express'); const router = express.Router(); const socketMiddleware = require('../middleware/socketMiddleware'); // 假设你有通知模型 const Notification = require('../models/Notification'); // 给该路由下所有请求注入Socket相关对象 router.use(socketMiddleware); // 示例:创建并发送通知的POST接口 router.post('/send', async (req, res) => { try { const { recipientId, title, content } = req.body; // 1. 先将通知保存到数据库(可选,根据业务需求) const newNotification = new Notification({ recipient: recipientId, title, content, read: false }); await newNotification.save(); // 2. 通过用户映射表找到接收者的Socket连接 const recipientSocketId = req.userSocketMap.get(recipientId); if (recipientSocketId) { // 方式1:通过Socket ID推送 req.io.to(recipientSocketId).emit('newNotification', newNotification); // 方式2:通过用户房间推送(更简洁) // req.io.to(recipientId).emit('newNotification', newNotification); } res.status(200).json({ success: true, message: '通知已创建并推送', notification: newNotification }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); module.exports = router;
4. 前端Socket连接与监听
在React前端中连接Socket.io,并完成用户初始化、监听通知:
// 前端示例(比如App.js) import { useEffect } from 'react'; import { io } from 'socket.io-client'; function App() { useEffect(() => { // 连接后端Socket服务 const socket = io('http://localhost:5000'); // 假设从登录状态中获取用户ID const userId = localStorage.getItem('userId'); if (userId) { // 发送用户ID完成Socket初始化 socket.emit('setup', userId); } // 监听实时通知 socket.on('newNotification', (notification) => { console.log('收到新通知:', notification); // 这里可以做通知弹窗、更新通知列表等操作 }); // 组件卸载时断开连接 return () => { socket.disconnect(); }; }, []); return ( <div className="App"> {/* 你的应用内容 */} </div> ); } export default App;
关键说明
- 作用域问题:将
io挂载到app对象上,避免了回调函数的作用域限制,让所有路由模块都能访问到实例。 - 用户定位:通过
userSocketMap维护用户与Socket的映射,是实现精准推送的核心,确保通知能发送到目标用户的在线连接。 - 房间功能:使用
socket.join(userId)创建用户专属房间,后续推送时直接通过房间ID发送,比通过Socket ID更简洁可靠。
内容的提问来源于stack exchange,提问作者Jalal Khan
相关产品推荐
相关产品推荐

