当你的项目只有一个 WebSocket,写法可以随便。
当你的项目需要管理多个 WebSocket,你会发现页面切换、状态同步、回调解绑,全都变麻烦。
这篇文章带来一套通用、可扩展、多实例复用的 WebSocket 管理架构,适用于:
在线问诊系统多订单实时同步实时客服聊天IoT 设备状态监听
使用方式非常优雅,只需三行代码即可建立稳定连接👇:
let socketManager = null
const socketStore = useSocketStore()
socketManager = socketStore.get(websocketId.value, handleSocketMsg)
socketManager.updateMessageHandler(handleSocketMsg)
socketManager.connect()
目录
🔥 背景与问题🧩 架构设计📍 useSocketStore 代码解析📍 useSocketManager 代码解析🔁 心跳与重连机制时序图🚀 使用示例⚠️ 踩坑记录🧾 总结
🔥 背景与问题
| 问题 | 表现 |
|---|---|
| 重复创建连接 | 多次进入页面就生成多个 socket |
| 回调 handler 堆积 | 退出页面再进,消息触发多次 |
| 假在线状态 | 安卓后台休眠后 WebSocket 实际断开,但看起来正常 |
| 多个业务模块使用 WebSocket 难管理 | IM、订单消息、状态推送 → 多实例需要隔离管理 |
要解决这些问题,需要:
连接复用心跳机制断线重连动态回调绑定自动清理机制
🧩 架构设计
┌──────────────────────┐
│ Pinia store │
│ 缓存所有 socket 实例 │
│ 复用 / 移除 / 查询 │
└───────────▲──────────┘
│
▼
┌──────────────────────┐
│ useSocketManager │
│ 单实例逻辑: │
│ 心跳/发送/监听/重连 │
└───────────▲──────────┘
│
▼
应用业务页面
目标是让业务层保持干净,像 axios 调用一样。
📍 useSocketStore 代码解析
用于维护多个 socket 实例,并防止重复创建。
import { defineStore } from 'pinia'
import { useSocketManager } from '@/utils/useSocketManager.js'
export const useSocketStore = defineStore('socketStore', {
state: () => ({
sockets: new Map(),
websocketOrderMap: new Map()
}),
actions: {
get(orderId, onMessage) {
if (!orderId) return null
if (this.sockets.has(orderId)) {
const socket = this.sockets.get(orderId)
const status = socket?.getConnectionStatus?.()
if (status?.isConnected) return socket
}
const socket = useSocketManager(orderId, onMessage, this)
this.sockets.set(orderId, socket)
return socket
},
remove(orderId) {
if (!this.sockets.has(orderId)) return
const socket = this.sockets.get(orderId)
socket?.close(false)
this.sockets.delete(orderId)
},
clearAll() {
this.sockets.forEach(socket => socket?.close(false))
this.sockets.clear()
this.websocketOrderMap.clear()
}
}
})
📍 useSocketManager 代码解析
核心能力:
心跳重连机制(指数退避)防重复连接动态 message handler 替换(避免回调叠加)
import { ref } from 'vue'
const connectionInstances = new Map()
export function useSocketManager(orderId, onMessage, socketStore) {
const socketTask = ref(null)
const isConnected = ref(false)
const heartbeatTimer = ref(null)
const reconnectTimer = ref(null)
const reconnectCount = ref(0)
const MAX_RECONNECT = 5
let onMessageHandler = onMessage
const baseUrl = import.meta.env.VITE_APP_WS_HLW_API
const wsUrl = `${baseUrl}/${orderId}_flfmyl_prescription`
🔗 connect()
const connect = () => {
if (connectionInstances.has(orderId)) {
const exist = connectionInstances.get(orderId)
if (exist.isConnected) return
}
close(false)
socketTask.value = uni.connectSocket({
url: wsUrl
})
socketTask.value.onOpen(() => {
isConnected.value = true
reconnectCount.value = 0
startHeartbeat()
})
socketTask.value.onMessage((res) => {
let data = {}
try { data = JSON.parse(res.data) } catch {}
onMessageHandler(data)
})
socketTask.value.onError(() => tryReconnect())
socketTask.value.onClose(() => tryReconnect())
connectionInstances.set(orderId, socketTask.value)
}
🔁 重连(指数退避)
const tryReconnect = () => {
if (reconnectCount.value >= MAX_RECONNECT) return
reconnectCount.value++
const delay = Math.min(1000 * Math.pow(2, reconnectCount.value), 30000)
reconnectTimer.value = setTimeout(() => connect(), delay)
}
❤️ 心跳机制
const startHeartbeat = () => {
stopHeartbeat()
heartbeatTimer.value = setInterval(() => {
if (isConnected.value) send({ type: 'ping' })
}, 15000)
}
🧹 关闭连接
const close = (removeFromStore = true) => {
stopHeartbeat()
clearTimeout(reconnectTimer.value)
connectionInstances.delete(orderId)
if (socketTask.value) {
socketTask.value.close({})
socketTask.value = null
}
if (removeFromStore && socketStore) {
socketStore.remove(orderId)
}
isConnected.value = false
}
🎯 动态替换回调
const updateMessageHandler = (fn) => {
onMessageHandler = fn
}
🔁 心跳与重连机制时序图
connect
│
├──> onOpen → startHeartbeat → 可通信
│
├──> onError / onClose → tryReconnect (指数递增)
│
└──> 达到上限 → 停止重连
🚀 使用示例
const socketStore = useSocketStore()
const handleMsg = (msg) => {
console.log("收到:", msg)
}
const initSocket = () => {
const socket = socketStore.get(orderId, handleMsg)
socket.updateMessageHandler(handleMsg)
socket.connect()
}
发送消息:
socketManager.send({ text: '你好医生' })
关闭:
socketManager.close()
⚠️ 踩坑记录
| 坑点 | 解决方式 |
|---|---|
| 进入页面多次绑定 onMessage → 回调叠加 | 使用 |
| 后台断开但状态假在线 | 心跳机制检测 |
| 多 socket 混乱 | 使用 Pinia Map 分发实例 |
🧾 总结
这套方案具备:
多 WebSocket 实例管理自动复用机制健壮的断线重连策略心跳检测-动态消息 handler 替换非常适用于需要实时消息、安全稳定通信的场景。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...


