const DEFAULT_LOGIN_WEBSOCKET = import.meta.env.VITE_LOGIN_WEBSOCKET || 'ws://localhost:8080/ws'
|
|
class WebSocketClient {
|
constructor(options) {
|
this.ws = null
|
this.url = options.url || DEFAULT_LOGIN_WEBSOCKET
|
this.messageHandler = options.messageHandler || (() => {})
|
this.reconnectInterval = options.reconnectInterval ?? 20 * 1e3
|
this.heartbeatInterval = options.heartbeatInterval ?? 5 * 1e3
|
this.pingInterval = options.pingInterval ?? 10 * 1e3
|
this.reconnectTimeout = options.reconnectTimeout ?? 30 * 1e3
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10
|
this.connectionTimeout = options.connectionTimeout ?? 10 * 1e3
|
this.reconnectAttempts = 0
|
this.messageQueue = []
|
this.detectionTimer = null
|
this.timeoutTimer = null
|
this.reconnectTimer = null
|
this.pingTimer = null
|
this.connectionTimer = null
|
this.isConnected = false
|
this.isConnecting = false
|
this.stopReconnect = false
|
this.isReconnecting = false
|
}
|
// 单例模式获取实例
|
static getInstance(options) {
|
if (!WebSocketClient.instance) {
|
WebSocketClient.instance = new WebSocketClient(options)
|
} else {
|
WebSocketClient.instance.messageHandler = options.messageHandler || (() => {})
|
if (options.url && WebSocketClient.instance.url !== options.url) {
|
WebSocketClient.instance.url = options.url
|
WebSocketClient.instance.reconnectAttempts = 0
|
WebSocketClient.instance.init()
|
}
|
}
|
return WebSocketClient.instance
|
}
|
// 初始化连接
|
init() {
|
this.connect(true)
|
}
|
connect(resetReconnectAttempts = false) {
|
if (this.isConnecting) {
|
console.log('正在建立WebSocket连接中...')
|
return
|
}
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
console.warn('WebSocket连接已存在')
|
this.flushMessageQueue()
|
return
|
}
|
try {
|
this.isConnecting = true
|
this.stopReconnect = false
|
if (resetReconnectAttempts) {
|
this.reconnectAttempts = 0
|
this.isReconnecting = false
|
this.clearTimer('reconnectTimer')
|
}
|
this.ws = new WebSocket(this.url)
|
this.clearTimer('connectionTimer')
|
this.connectionTimer = setTimeout(() => {
|
console.error(`WebSocket连接超时 (${this.connectionTimeout}ms):${this.url}`)
|
this.handleConnectionTimeout()
|
}, this.connectionTimeout)
|
this.ws.onopen = (event) => this.handleOpen(event)
|
this.ws.onmessage = (event) => this.handleMessage(event)
|
this.ws.onclose = (event) => this.handleClose(event)
|
this.ws.onerror = (event) => this.handleError(event)
|
} catch (error) {
|
console.error('WebSocket初始化失败:', error)
|
this.isConnecting = false
|
this.reconnect()
|
}
|
}
|
// 处理连接超时
|
handleConnectionTimeout() {
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
console.error('WebSocket连接超时,强制关闭连接')
|
this.ws?.close(1e3, 'Connection timeout')
|
this.isConnecting = false
|
this.reconnect()
|
}
|
}
|
// 关闭连接
|
close(force) {
|
this.clearAllTimers()
|
this.stopReconnect = true
|
this.isReconnecting = false
|
this.isConnecting = false
|
if (this.ws) {
|
this.ws.close(force ? 1001 : 1e3, force ? 'Force closed' : 'Normal close')
|
this.ws = null
|
}
|
this.isConnected = false
|
}
|
// 发送消息 - 增加消息队列
|
send(data, immediate = false) {
|
if (immediate && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) {
|
console.error('WebSocket未连接,无法立即发送消息')
|
return
|
}
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
console.log('WebSocket未连接,消息已加入队列等待发送')
|
this.messageQueue.push(data)
|
if (!this.isConnecting && !this.stopReconnect) {
|
this.init()
|
}
|
return
|
}
|
try {
|
this.ws.send(data)
|
} catch (error) {
|
console.error('WebSocket发送消息失败:', error)
|
this.messageQueue.push(data)
|
this.reconnect()
|
}
|
}
|
// 发送队列中的消息
|
flushMessageQueue() {
|
if (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
|
console.log(`发送队列中的${this.messageQueue.length}条消息`)
|
while (this.messageQueue.length > 0) {
|
const data = this.messageQueue.shift()
|
if (data) {
|
try {
|
this.ws?.send(data)
|
} catch (error) {
|
console.error('发送队列消息失败:', error)
|
if (data) this.messageQueue.unshift(data)
|
break
|
}
|
}
|
}
|
}
|
}
|
// 处理连接打开
|
handleOpen(event) {
|
console.log('WebSocket连接成功', event)
|
this.clearTimer('connectionTimer')
|
this.isConnected = true
|
this.isConnecting = false
|
this.isReconnecting = false
|
this.stopReconnect = false
|
this.reconnectAttempts = 0
|
this.startHeartbeat()
|
this.startPing()
|
this.flushMessageQueue()
|
}
|
// 处理收到的消息
|
handleMessage(event) {
|
console.log('收到WebSocket消息:', event)
|
this.resetHeartbeat()
|
this.messageHandler(event)
|
}
|
// 处理连接关闭
|
handleClose(event) {
|
console.log(
|
`WebSocket断开: 代码=${event.code}, 原因=${event.reason}, 干净关闭=${event.wasClean}`
|
)
|
const isNormalClose = event.code === 1e3
|
this.isConnected = false
|
this.isConnecting = false
|
this.clearConnectionTimers()
|
this.ws = null
|
if (!this.stopReconnect && !isNormalClose) {
|
this.reconnect()
|
}
|
}
|
// 处理错误 - 增加详细错误信息
|
handleError(event) {
|
console.error('WebSocket连接错误:')
|
console.error('错误事件:', event)
|
console.error(
|
'当前连接状态:',
|
this.ws?.readyState ? this.getReadyStateText(this.ws.readyState) : '未初始化'
|
)
|
this.isConnected = false
|
this.isConnecting = false
|
if (!this.stopReconnect) {
|
this.reconnect()
|
}
|
}
|
closeCurrentSocketForReconnect() {
|
this.clearConnectionTimers()
|
this.isConnected = false
|
this.isConnecting = false
|
if (this.ws) {
|
this.ws.onopen = null
|
this.ws.onmessage = null
|
this.ws.onclose = null
|
this.ws.onerror = null
|
if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
|
this.ws.close(1001, 'Reconnect')
|
}
|
this.ws = null
|
}
|
}
|
// 转换连接状态为文本描述
|
getReadyStateText(state) {
|
switch (state) {
|
case WebSocket.CONNECTING:
|
return 'CONNECTING (0) - 正在连接'
|
case WebSocket.OPEN:
|
return 'OPEN (1) - 已连接'
|
case WebSocket.CLOSING:
|
return 'CLOSING (2) - 正在关闭'
|
case WebSocket.CLOSED:
|
return 'CLOSED (3) - 已关闭'
|
default:
|
return `未知状态 (${state})`
|
}
|
}
|
// 开始心跳检测
|
startHeartbeat() {
|
this.clearTimer('detectionTimer')
|
this.clearTimer('timeoutTimer')
|
this.detectionTimer = setTimeout(() => {
|
this.isConnected = this.ws?.readyState === WebSocket.OPEN
|
if (!this.isConnected) {
|
console.warn('WebSocket心跳检测失败,尝试重连')
|
this.reconnect()
|
this.timeoutTimer = setTimeout(() => {
|
console.warn('WebSocket重连超时')
|
this.close()
|
}, this.reconnectTimeout)
|
}
|
}, this.heartbeatInterval)
|
}
|
// 重置心跳检测
|
resetHeartbeat() {
|
this.clearTimer('detectionTimer')
|
this.clearTimer('timeoutTimer')
|
this.startHeartbeat()
|
}
|
// 开始发送ping消息
|
startPing() {
|
this.clearTimer('pingTimer')
|
this.pingTimer = setInterval(() => {
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
console.warn('WebSocket未连接,停止发送ping')
|
this.clearTimer('pingTimer')
|
this.reconnect()
|
return
|
}
|
try {
|
this.ws.send('ping')
|
console.log('发送ping消息')
|
} catch (error) {
|
console.error('发送ping消息失败:', error)
|
this.clearTimer('pingTimer')
|
this.reconnect()
|
}
|
}, this.pingInterval)
|
}
|
// 重连 - 增加重连次数限制
|
reconnect() {
|
if (this.stopReconnect || this.isConnecting || this.reconnectInterval <= 0) {
|
return
|
}
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
console.error(`已达到最大重连次数(${this.maxReconnectAttempts}),停止重连`)
|
this.close(true)
|
return
|
}
|
this.reconnectAttempts++
|
this.isReconnecting = true
|
this.closeCurrentSocketForReconnect()
|
const delay = this.calculateReconnectDelay()
|
console.log(
|
`将在${delay / 1e3}秒后尝试重新连接(第${this.reconnectAttempts}/${this.maxReconnectAttempts}次)`
|
)
|
this.clearTimer('reconnectTimer')
|
this.reconnectTimer = setTimeout(() => {
|
console.log(`尝试重新连接WebSocket(第${this.reconnectAttempts}次)`)
|
this.connect(false)
|
}, delay)
|
}
|
// 计算重连延迟 - 指数退避策略
|
calculateReconnectDelay() {
|
const jitter = Math.random() * 1e3
|
const baseDelay = Math.min(
|
this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1),
|
this.reconnectInterval * 5
|
)
|
return baseDelay + jitter
|
}
|
// 清除指定定时器
|
clearTimer(timerName) {
|
if (this[timerName]) {
|
clearTimeout(this[timerName])
|
this[timerName] = null
|
}
|
}
|
// 清除所有定时器
|
clearAllTimers() {
|
this.clearConnectionTimers()
|
this.clearTimer('reconnectTimer')
|
}
|
clearConnectionTimers() {
|
this.clearTimer('detectionTimer')
|
this.clearTimer('timeoutTimer')
|
this.clearTimer('pingTimer')
|
this.clearTimer('connectionTimer')
|
}
|
// 获取当前连接状态
|
get isWebSocketConnected() {
|
return this.isConnected
|
}
|
// 获取当前连接状态文本
|
get connectionStatusText() {
|
if (this.isConnecting) return '正在连接'
|
if (this.isConnected) return '已连接'
|
if (this.isReconnecting && this.reconnectAttempts > 0)
|
return `重连中(${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
return '已断开'
|
}
|
// 销毁实例
|
static destroyInstance() {
|
if (WebSocketClient.instance) {
|
WebSocketClient.instance.close()
|
WebSocketClient.instance = null
|
}
|
}
|
}
|
|
WebSocketClient.instance = null
|
|
export default WebSocketClient
|