354 lines
7.8 KiB
JavaScript
354 lines
7.8 KiB
JavaScript
|
|
/**
|
|||
|
|
* WebSocket 管理器
|
|||
|
|
*
|
|||
|
|
* 功能:
|
|||
|
|
* 1. 自动重连
|
|||
|
|
* 2. 心跳检测
|
|||
|
|
* 3. 消息队列
|
|||
|
|
* 4. 事件监听
|
|||
|
|
*
|
|||
|
|
* 使用示例:
|
|||
|
|
* import wsManager from '@/utils/websocket-manager.js'
|
|||
|
|
*
|
|||
|
|
* // 连接
|
|||
|
|
* wsManager.connect('wss://example.com/chat')
|
|||
|
|
*
|
|||
|
|
* // 监听消息
|
|||
|
|
* wsManager.on('message', (data) => {
|
|||
|
|
* console.log('收到消息:', data)
|
|||
|
|
* })
|
|||
|
|
*
|
|||
|
|
* // 发送消息
|
|||
|
|
* wsManager.send({ type: 'text', content: 'Hello' })
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
class WebSocketManager {
|
|||
|
|
constructor() {
|
|||
|
|
// WebSocket 实例
|
|||
|
|
this.socketTask = null
|
|||
|
|
|
|||
|
|
// 连接状态
|
|||
|
|
this.isConnected = false
|
|||
|
|
this.isConnecting = false
|
|||
|
|
|
|||
|
|
// 配置
|
|||
|
|
this.url = ''
|
|||
|
|
this.reconnectAttempts = 0
|
|||
|
|
this.maxReconnectAttempts = 5
|
|||
|
|
this.reconnectInterval = 5000
|
|||
|
|
this.heartbeatInterval = 30000
|
|||
|
|
|
|||
|
|
// 定时器
|
|||
|
|
this.reconnectTimer = null
|
|||
|
|
this.heartbeatTimer = null
|
|||
|
|
|
|||
|
|
// 消息队列(连接未建立时暂存)
|
|||
|
|
this.messageQueue = []
|
|||
|
|
|
|||
|
|
// 事件监听器
|
|||
|
|
this.listeners = {
|
|||
|
|
message: [], // 收到消息
|
|||
|
|
open: [], // 连接成功
|
|||
|
|
close: [], // 连接关闭
|
|||
|
|
error: [], // 连接错误
|
|||
|
|
reconnect: [] // 重连中
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 连接 WebSocket
|
|||
|
|
* @param {String} url WebSocket 服务器地址
|
|||
|
|
* @param {Object} options 配置选项
|
|||
|
|
*/
|
|||
|
|
connect(url, options = {}) {
|
|||
|
|
// 如果已经在连接中,直接返回
|
|||
|
|
if (this.isConnecting || this.isConnected) {
|
|||
|
|
console.log('[WebSocket] 已在连接中或已连接')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.url = url
|
|||
|
|
this.isConnecting = true
|
|||
|
|
|
|||
|
|
// 合并配置
|
|||
|
|
Object.assign(this, options)
|
|||
|
|
|
|||
|
|
console.log(`[WebSocket] 开始连接: ${url}`)
|
|||
|
|
|
|||
|
|
// 创建连接
|
|||
|
|
this.socketTask = uni.connectSocket({
|
|||
|
|
url: this.url,
|
|||
|
|
success: () => {
|
|||
|
|
console.log('[WebSocket] 连接请求已发送')
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[WebSocket] 连接失败:', err)
|
|||
|
|
this.isConnecting = false
|
|||
|
|
this.handleError(err)
|
|||
|
|
this.handleReconnect()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听连接打开
|
|||
|
|
this.socketTask.onOpen(() => {
|
|||
|
|
console.log('[WebSocket] 连接成功')
|
|||
|
|
this.isConnected = true
|
|||
|
|
this.isConnecting = false
|
|||
|
|
this.reconnectAttempts = 0
|
|||
|
|
|
|||
|
|
// 触发 open 事件
|
|||
|
|
this.emit('open')
|
|||
|
|
|
|||
|
|
// 启动心跳
|
|||
|
|
this.startHeartbeat()
|
|||
|
|
|
|||
|
|
// 发送队列中的消息
|
|||
|
|
this.flushMessageQueue()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听消息
|
|||
|
|
this.socketTask.onMessage((res) => {
|
|||
|
|
console.log('[WebSocket] 收到消息:', res.data)
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const data = JSON.parse(res.data)
|
|||
|
|
|
|||
|
|
// 处理 pong(心跳响应)
|
|||
|
|
if (data.type === 'pong') {
|
|||
|
|
console.log('[WebSocket] 收到心跳响应')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 触发 message 事件
|
|||
|
|
this.emit('message', data)
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[WebSocket] 消息解析失败:', e)
|
|||
|
|
// 如果不是 JSON,直接传递原始数据
|
|||
|
|
this.emit('message', res.data)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听连接关闭
|
|||
|
|
this.socketTask.onClose((res) => {
|
|||
|
|
console.log('[WebSocket] 连接已关闭:', res)
|
|||
|
|
this.isConnected = false
|
|||
|
|
this.isConnecting = false
|
|||
|
|
|
|||
|
|
// 停止心跳
|
|||
|
|
this.stopHeartbeat()
|
|||
|
|
|
|||
|
|
// 触发 close 事件
|
|||
|
|
this.emit('close', res)
|
|||
|
|
|
|||
|
|
// 尝试重连
|
|||
|
|
this.handleReconnect()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听错误
|
|||
|
|
this.socketTask.onError((err) => {
|
|||
|
|
console.error('[WebSocket] 连接错误:', err)
|
|||
|
|
this.isConnected = false
|
|||
|
|
this.isConnecting = false
|
|||
|
|
|
|||
|
|
// 触发 error 事件
|
|||
|
|
this.handleError(err)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送消息
|
|||
|
|
* @param {Object|String} data 要发送的数据
|
|||
|
|
*/
|
|||
|
|
send(data) {
|
|||
|
|
// 如果未连接,加入队列
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
console.warn('[WebSocket] 未连接,消息加入队列')
|
|||
|
|
this.messageQueue.push(data)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const message = typeof data === 'string' ? data : JSON.stringify(data)
|
|||
|
|
|
|||
|
|
this.socketTask.send({
|
|||
|
|
data: message,
|
|||
|
|
success: () => {
|
|||
|
|
console.log('[WebSocket] 消息发送成功:', data)
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[WebSocket] 消息发送失败:', err)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 关闭连接
|
|||
|
|
* @param {Boolean} manual 是否手动关闭(手动关闭不重连)
|
|||
|
|
*/
|
|||
|
|
close(manual = true) {
|
|||
|
|
console.log(`[WebSocket] 关闭连接 (手动: ${manual})`)
|
|||
|
|
|
|||
|
|
// 如果是手动关闭,清除重连定时器
|
|||
|
|
if (manual) {
|
|||
|
|
this.clearReconnectTimer()
|
|||
|
|
this.reconnectAttempts = this.maxReconnectAttempts // 阻止重连
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.stopHeartbeat()
|
|||
|
|
|
|||
|
|
if (this.socketTask) {
|
|||
|
|
this.socketTask.close({
|
|||
|
|
code: 1000,
|
|||
|
|
reason: manual ? '手动关闭' : '自动关闭'
|
|||
|
|
})
|
|||
|
|
this.socketTask = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isConnected = false
|
|||
|
|
this.isConnecting = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 启动心跳
|
|||
|
|
*/
|
|||
|
|
startHeartbeat() {
|
|||
|
|
console.log('[WebSocket] 启动心跳')
|
|||
|
|
this.stopHeartbeat()
|
|||
|
|
|
|||
|
|
this.heartbeatTimer = setInterval(() => {
|
|||
|
|
if (this.isConnected) {
|
|||
|
|
console.log('[WebSocket] 发送心跳')
|
|||
|
|
this.send({ type: 'ping', timestamp: Date.now() })
|
|||
|
|
}
|
|||
|
|
}, this.heartbeatInterval)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 停止心跳
|
|||
|
|
*/
|
|||
|
|
stopHeartbeat() {
|
|||
|
|
if (this.heartbeatTimer) {
|
|||
|
|
console.log('[WebSocket] 停止心跳')
|
|||
|
|
clearInterval(this.heartbeatTimer)
|
|||
|
|
this.heartbeatTimer = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理重连
|
|||
|
|
*/
|
|||
|
|
handleReconnect() {
|
|||
|
|
// 如果达到最大重连次数,停止重连
|
|||
|
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|||
|
|
console.error('[WebSocket] 达到最大重连次数,停止重连')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果已经在重连中,直接返回
|
|||
|
|
if (this.reconnectTimer) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.reconnectAttempts++
|
|||
|
|
console.log(`[WebSocket] 准备重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
|
|||
|
|
|
|||
|
|
// 触发 reconnect 事件
|
|||
|
|
this.emit('reconnect', this.reconnectAttempts)
|
|||
|
|
|
|||
|
|
this.reconnectTimer = setTimeout(() => {
|
|||
|
|
this.reconnectTimer = null
|
|||
|
|
this.connect(this.url)
|
|||
|
|
}, this.reconnectInterval)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除重连定时器
|
|||
|
|
*/
|
|||
|
|
clearReconnectTimer() {
|
|||
|
|
if (this.reconnectTimer) {
|
|||
|
|
clearTimeout(this.reconnectTimer)
|
|||
|
|
this.reconnectTimer = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送队列中的消息
|
|||
|
|
*/
|
|||
|
|
flushMessageQueue() {
|
|||
|
|
if (this.messageQueue.length === 0) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`[WebSocket] 发送队列中的 ${this.messageQueue.length} 条消息`)
|
|||
|
|
|
|||
|
|
while (this.messageQueue.length > 0) {
|
|||
|
|
const message = this.messageQueue.shift()
|
|||
|
|
this.send(message)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理错误
|
|||
|
|
*/
|
|||
|
|
handleError(err) {
|
|||
|
|
this.emit('error', err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 监听事件
|
|||
|
|
* @param {String} event 事件名称
|
|||
|
|
* @param {Function} callback 回调函数
|
|||
|
|
*/
|
|||
|
|
on(event, callback) {
|
|||
|
|
if (this.listeners[event]) {
|
|||
|
|
this.listeners[event].push(callback)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 移除事件监听
|
|||
|
|
* @param {String} event 事件名称
|
|||
|
|
* @param {Function} callback 回调函数
|
|||
|
|
*/
|
|||
|
|
off(event, callback) {
|
|||
|
|
if (this.listeners[event]) {
|
|||
|
|
const index = this.listeners[event].indexOf(callback)
|
|||
|
|
if (index > -1) {
|
|||
|
|
this.listeners[event].splice(index, 1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 触发事件
|
|||
|
|
* @param {String} event 事件名称
|
|||
|
|
* @param {*} data 数据
|
|||
|
|
*/
|
|||
|
|
emit(event, data) {
|
|||
|
|
if (this.listeners[event]) {
|
|||
|
|
this.listeners[event].forEach(callback => {
|
|||
|
|
try {
|
|||
|
|
callback(data)
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(`[WebSocket] 事件 ${event} 回调执行失败:`, e)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取连接状态
|
|||
|
|
*/
|
|||
|
|
getState() {
|
|||
|
|
return {
|
|||
|
|
isConnected: this.isConnected,
|
|||
|
|
isConnecting: this.isConnecting,
|
|||
|
|
reconnectAttempts: this.reconnectAttempts,
|
|||
|
|
queueLength: this.messageQueue.length
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出单例
|
|||
|
|
export default new WebSocketManager()
|
|||
|
|
|