后端与接口
用 Socket.io 实现实时功能(聊天、通知…
当需要为应用添加实时更新、WebSocket 通信、Socket.io 聊天或推送通知时,按此流程搭建带认证、房间、重连处理的实时系统。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
用 Socket.io 实现实时功能(聊通用
解决思路
- 选择库:在 Socket.io 与原生 WebSocket 之间做取舍
- 安装依赖:服务端执行 npm install socket.io,客户端执行 npm install socket.io-client
- 搭建服务端:用 express 创建 app,用 http 的 createServer(app) 创建 httpServer,再用 new Server(httpServer, {...}) 创建 io,配置 cors(origin 取 process.env.CLIENT_URL、credentials: true)以及 pingInterval: 25000、pingTimeout: 60000,最后 httpServer.listen(3001) 并导出 io
- 添加认证中间件:用 io.use(async (socket, next) => {...}) 从 socket.handshake.auth.token 取 token,无 token 时 next(new Error('Authentication required')),用 verifyToken(token) 校验成功后把用户挂到 socket.data.user 再 next(),失败则 next(new Error('Invalid token'))
- 连接处理:在 io.on('connection', (socket) => {...}) 中读取 socket.data.user,让每个用户加入个人房间 socket.join(`user:${user.id}`),并监听 disconnect 做日志
- 使用命名空间与房间:用 io.of('/chat') 创建命名空间,在连接回调中处理 join-room(先用 canAccessRoom(user.id, roomId) 校验权限,无权限则 socket.emit('error', { message: 'Access denied' }),有权限则 socket.join(`room:${roomId}`) 并向 socket.to(`room:${roomId}`) 广播 user-joined)
- 处理离开房间:监听 leave-room,执行 socket.leave(`room:${roomId}`) 并向房间广播 user-left
- 处理发送消息:监听 send-message,从 data 取 roomId 与 content,先写库 db.message.create({ data: { roomId, authorId: user.id, content }, include: { author: true } }),再用 chatNamespace.to(`room:${roomId}`).emit('new-message', {...}) 广播消息(含 id、content、author、createdAt)
- 实现输入中指示器:监听 typing-start 与 typing-stop,用 socket.to(`room:${roomId}`).emit('user-typing' / 'user-stopped-typing', {...}) 通知房间内其他人
- 封装事件发射服务:在 NotificationService 中提供 sendToUser(io.to(`user:${userId}`).emit)、sendToUsers(遍历 userIds 逐个 emit)、broadcast(io.emit)、sendToRoom(io.to(`room:${roomId}`).emit),以及 notifyNewOrder 这类业务方法(同时通知客户与 io.to('role:admin') 管理员)
- 编写客户端 useSocket Hook:用 useAuth 取 token,用 io(`${process.env.NEXT_PUBLIC_WS_URL}${namespace}`, { auth: { token }, reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, reconnectionDelayMax: 5000 }) 建立连接,监听 connect/disconnect/connect_error 维护 isConnected 与 error 状态,暴露 emit、on(返回取消订阅函数)、off,并在 cleanup 中 socket.disconnect()
- 编写 useChat Hook:基于 useSocket({ namespace: '/chat' }),连接后 emit('join-room', roomId) 并在 cleanup 中 emit('leave-room', roomId);用 on 监听 new-message 追加消息、user-typing 去重加入 typingUsers、user-stopped-typing 移除;暴露 sendMessage、startTyping、stopTyping
- 编写聊天组件:用 useChat 渲染消息列表,用 messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) 自动滚动到底部;输入变化时调用 startTyping 并用 setTimeout(stopTyping, 2000) 做防抖;提交时 sendMessage(input) 后清空输入并 stopTyping;未连接时显示 Connecting...
- 实现实时通知 Hook:用 on('notification', ...) 把新通知插入列表头部、unreadCount 加一,并用 toast(notification.title, { description: notification.message }) 弹出提示;提供 markAsRead(标记单条已读并递减未读数)与 markAllAsRead(全部已读并清零)
- 实现在线状态系统:用 Map 维护 onlineUsers,连接时写入 { userId, name, status: 'online', lastSeen: new Date() },io.emit('presence:update', {...}) 广播,socket.emit('presence:list', Array.from(onlineUsers.values())) 给新连接发送当前在线列表;监听 presence:status 更新状态并广播;disconnect 时从 Map 删除并广播 offline
- 处理错误与重连:客户端配置 reconnectionAttempts: 10、reconnectionDelay: 1000、reconnectionDelayMax: 10000、timeout: 20000;connect_error 时若 message 为 'Authentication required' 则 router.push('/login'),否则打印错误;reconnect 时 emit('sync-state') 重新同步状态;reconnect_failed 时用 toast.error 提示用户刷新页面
- 遵循最佳实践:连接时校验 token、用房间做逻辑分组、重连后重新同步状态、对事件做防抖(如输入指示器)、组件卸载时清理监听器、对关键事件做确认、用命名空间分离关注点(chat、notifications)、生产环境用 Redis adapter 支持多服务器扩展
- 按输出清单自检:服务端认证中间件、带重连的客户端 Hook、房间/命名空间组织、事件类型定义、错误处理与恢复、聊天输入指示器、在线状态系统、干净的断开处理、事件限流、生产环境 Redis adapter
适用边界 · 注意事项
- 需要极低延迟或自定义二进制协议、且不需要 Socket.io 的房间/重连/降级能力时,可考虑原生 WebSocket
- 单机部署且无横向扩展需求时,不必引入 Redis adapter
- 不需要实时双向通信、仅需服务端单向推送时,SSE 或轮询可能更简单