基于MERN Stack+Socket.io实现好友请求实时通知的最优方案探讨
实现MongoDB事件触发的实时好友请求通知(无轮询)
核心思路是事件驱动推送:在好友请求被写入数据库的瞬间,直接通过Socket.io向前端推送通知,彻底替代轮询方案,完全避免不必要的数据库查询。
步骤1:后端维护用户Socket连接映射
在Node.js后端初始化Socket.io,并维护用户ID与Socket连接ID的映射(支持多设备登录):
const http = require('http'); const express = require('express'); const { Server } = require('socket.io'); const mongoose = require('mongoose'); const app = express(); const server = http.createServer(app); // 配置Socket.io跨域,替换为你的React前端地址 const io = new Server(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST"] } }); // 存储用户ID对应的Socket ID列表 const userSocketMap = new Map(); // 处理Socket连接 io.on('connection', (socket) => { // 前端登录后发送用户ID,建立映射 socket.on('user_connected', async (userId) => { if (!userSocketMap.has(userId)) { userSocketMap.set(userId, []); } userSocketMap.get(userId).push(socket.id); // 推送用户离线时产生的未读通知 const unreadRequests = await mongoose.model('UnreadFriendRequest').find({ targetUserId: userId }); unreadRequests.forEach(req => { socket.emit('new_friend_request', req); }); // 清空已推送的未读记录 await mongoose.model('UnreadFriendRequest').deleteMany({ targetUserId: userId }); }); // 用户断开连接时清理映射 socket.on('disconnect', () => { for (const [userId, socketIds] of userSocketMap.entries()) { const updatedIds = socketIds.filter(id => id !== socket.id); updatedIds.length ? userSocketMap.set(userId, updatedIds) : userSocketMap.delete(userId); } }); }); server.listen(5000, () => console.log('Server running on port 5000'));
步骤2:在好友请求创建接口触发推送
当后端处理好友请求创建的API时,写入数据库成功后立即推送通知:
// 假设已定义FriendRequest和UnreadFriendRequest模型 app.post('/api/send-friend-request', express.json(), async (req, res) => { const { fromUserId, toUserId } = req.body; try { // 写入MongoDB好友请求记录 const newRequest = await mongoose.model('FriendRequest').create({ from: fromUserId, to: toUserId, status: 'pending' }); // 获取目标用户的Socket连接ID const targetSocketIds = userSocketMap.get(toUserId); if (targetSocketIds?.length) { // 向目标用户的所有在线设备推送通知 targetSocketIds.forEach(socketId => { io.to(socketId).emit('new_friend_request', { id: newRequest._id, from: fromUserId, timestamp: newRequest.createdAt }); }); } else { // 如果用户离线,将请求存入未读表 await mongoose.model('UnreadFriendRequest').create({ targetUserId: toUserId, requestData: newRequest.toObject() }); } res.status(201).json({ success: true, request: newRequest }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } });
步骤3:React前端监听通知
前端连接Socket.io,登录后发送用户ID,并监听新通知事件:
import { useEffect, useState } from 'react'; import io from 'socket.io-client'; // 连接后端Socket服务 const socket = io('http://localhost:5000'); function FriendNotificationPanel() { const [friendRequests, setFriendRequests] = useState([]); const userId = localStorage.getItem('authUserId'); // 假设从本地存储获取登录用户ID useEffect(() => { if (!userId) return; // 告知后端当前用户已上线 socket.emit('user_connected', userId); // 监听新好友请求事件 socket.on('new_friend_request', (request) => { setFriendRequests(prev => [request, ...prev]); // 可添加弹窗、音效等提醒 window.alert('收到新的好友请求!'); }); // 组件卸载时断开Socket连接 return () => socket.disconnect(); }, [userId]); return ( <div className="notification-panel"> <h3>好友请求</h3> {friendRequests.length === 0 ? ( <p>暂无新请求</p> ) : ( friendRequests.map(req => ( <div key={req.id} className="request-item"> 用户 {req.from} 申请添加你为好友 </div> )) )} </div> ); } export default FriendNotificationPanel;
关键优势
- 零不必要数据库查询:仅在好友请求创建时触发一次写入,无轮询开销
- 实时性拉满:用户在线时立即收到通知,离线时自动存入未读表,上线后补发
- 支持多设备:一个用户的多个在线设备都会收到通知
内容的提问来源于stack exchange,提问作者Samyak Jain
相关产品推荐
相关产品推荐

