344 lines
7.8 KiB
JavaScript
344 lines
7.8 KiB
JavaScript
const WS_Friend_URL = 'wss://wss.yuxindazhineng.com'
|
||
|
||
// (工具类)连接、发送、接收、重连...
|
||
class WebSocketManager {
|
||
constructor() {
|
||
this.ws = null
|
||
this.url = `${WS_Friend_URL}/ws`
|
||
this.options = {
|
||
token: '',
|
||
UserId: '',
|
||
onMessage: null,
|
||
onError: null,
|
||
onReconnect: null,
|
||
onOpen: null,
|
||
onClose: null
|
||
}
|
||
this.reconnectAttempts = 0
|
||
this.maxReconnectAttempts = 5
|
||
this.reconnectDelay = 3000
|
||
this.reconnectTimer = null
|
||
this.heartbeatTimer = null
|
||
this.isManualClose = false
|
||
}
|
||
/**
|
||
* 初始化连接
|
||
* @param {string} url WebSocket URL
|
||
* @param {Object} options 配置选项
|
||
*/
|
||
connect(options = {}) {
|
||
this.options = {
|
||
...this.options,
|
||
...options
|
||
}
|
||
this.isManualClose = false
|
||
// 关闭现有连接
|
||
if (this.ws) {
|
||
this.close()
|
||
}
|
||
try {
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
this.ws = uni.connectSocket({
|
||
url: this.url,
|
||
success: () => {
|
||
console.log('WebSocket 连接请求已发送');
|
||
},
|
||
fail: (err) => {
|
||
console.error('WebSocket 连接失败:', err)
|
||
this.handleError(err)
|
||
}
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
if (!this.ws) {
|
||
this.ws = new WebSocket(this.url)
|
||
}
|
||
// #endif
|
||
|
||
this.initEventHandlers()
|
||
} catch (err) {
|
||
this.handleError(err)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化事件处理
|
||
*/
|
||
initEventHandlers() {
|
||
if (!this.ws) return
|
||
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
// 打开连接
|
||
this.ws.onOpen((event) => {
|
||
console.log('WebSocket 已连接')
|
||
// 修复:先判断重连状态,再重置计数
|
||
if (this.reconnectAttempts > 0) {
|
||
this.options.onReconnect?.()
|
||
}
|
||
this.options.onOpen?.(event)
|
||
this.reconnectAttempts = 0
|
||
// this.startHeartbeat()
|
||
})
|
||
this.ws.onMessage((event) => {
|
||
|
||
try {
|
||
const data = JSON.parse(event.data)
|
||
console.log('收到了返回消息:', data);
|
||
this.handleMessage(data)
|
||
} catch (err) {
|
||
console.error('消息解析失败:', err)
|
||
uni.setStorageSync('currentSessionId', event.data.task_call_id)
|
||
}
|
||
})
|
||
this.ws.onError((err) => {
|
||
console.error('WebSocket 错误:', err)
|
||
this.handleError(err)
|
||
})
|
||
this.ws.onClose((event) => {
|
||
console.log('WebSocket 已关闭:', event.code, event.reason)
|
||
this.options.onClose?.(event)
|
||
// this.close()
|
||
if (!this.isManualClose && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||
this.reconnect()
|
||
}
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
// H5 环境使用原生 WebSocket API
|
||
if (typeof WebSocket !== 'undefined') {
|
||
this.ws.onopen = (event) => {
|
||
console.log('WebSocket 已连接 (H5)')
|
||
if (this.reconnectAttempts > 0) {
|
||
this.options.onReconnect?.()
|
||
}
|
||
this.options.onOpen?.(event)
|
||
this.reconnectAttempts = 0
|
||
}
|
||
|
||
this.ws.onmessage = (event) => {
|
||
try {
|
||
const data = JSON.parse(event.data)
|
||
console.log('收到了返回消息 (H5):', data);
|
||
this.handleMessage(data)
|
||
} catch (err) {
|
||
console.error('消息解析失败 (H5):', err)
|
||
}
|
||
}
|
||
|
||
this.ws.onerror = (err) => {
|
||
console.error('WebSocket 错误 (H5):', err)
|
||
this.handleError(err)
|
||
}
|
||
|
||
this.ws.onclose = (event) => {
|
||
console.log('WebSocket 已关闭 (H5):', event.code, event.reason)
|
||
this.options.onClose?.(event)
|
||
// this.close()
|
||
if (!this.isManualClose && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||
this.reconnect()
|
||
}
|
||
}
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
|
||
/**
|
||
* 处理服务端推送的消息
|
||
* @param {Object} msg - 服务端返回的原始消息对象
|
||
*/
|
||
handleMessage(msg) {
|
||
this.options.onMessage?.(msg)
|
||
}
|
||
|
||
/**
|
||
* 统一发送消息方法
|
||
* 兼容 APP / 微信小程序 环境
|
||
* @param {Object} data - 要发送的消息对象
|
||
* @returns {Promise} 发送结果
|
||
*/
|
||
send(data) {
|
||
return new Promise((resolve, reject) => {
|
||
// 未连接直接拒绝
|
||
if (!this.isConnected()) {
|
||
reject(new Error('WebSocket 未连接'))
|
||
return
|
||
}
|
||
const msg = JSON.stringify(data)
|
||
console.log("发送的消息是:", msg);
|
||
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
// APP / 小程序环境:uni-app 封装 API
|
||
this.ws.send({
|
||
data: msg,
|
||
success: () => resolve(),
|
||
fail: (err) => {
|
||
console.error('发送消息失败:', err)
|
||
reject(err)
|
||
}
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
// H5 环境:原生 WebSocket API
|
||
if (this.ws.readyState === WebSocket.OPEN) {
|
||
this.ws.send(msg)
|
||
resolve()
|
||
} else {
|
||
reject(new Error('WebSocket 未打开'))
|
||
}
|
||
// #endif
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 自动重连机制
|
||
* 指数退避重连,避免频繁重连
|
||
*/
|
||
reconnect() {
|
||
// 已有重连定时器则不重复触发
|
||
if (this.reconnectTimer) return
|
||
this.reconnectAttempts++
|
||
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`)
|
||
// 延迟执行重连,延迟时间随次数递增
|
||
this.reconnectTimer = setTimeout(() => {
|
||
this.reconnectTimer = null
|
||
// 非手动关闭才重连
|
||
if (!this.isManualClose) {
|
||
this.connect(this.options)
|
||
}
|
||
}, this.reconnectDelay * this.reconnectAttempts)
|
||
}
|
||
|
||
/**
|
||
* 启动心跳定时器
|
||
* 每30秒发送一次 ping 保持连接
|
||
*/
|
||
startHeartbeat() {
|
||
this.stopHeartbeat()
|
||
this.heartbeatTimer = setInterval(() => {
|
||
if (this.isConnected()) {
|
||
this.send({
|
||
ws_event: 'ping'
|
||
}).catch(() => {
|
||
console.log('心跳发送失败,准备重连')
|
||
// 避免在已经重连过程中重复触发
|
||
if (!this.reconnectTimer && !this.isManualClose) {
|
||
this.reconnect()
|
||
}
|
||
})
|
||
} else if (!this.isManualClose && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||
// 连接已断开但未触发 close 事件时主动重连
|
||
this.reconnect()
|
||
}
|
||
|
||
}, 30000)
|
||
}
|
||
/**
|
||
* 停止心跳定时器
|
||
*/
|
||
stopHeartbeat() {
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer)
|
||
this.heartbeatTimer = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 统一错误处理
|
||
* @param {Error} error - 错误对象
|
||
*/
|
||
handleError(error) {
|
||
console.error('WebSocket 错误处理:', JSON.stringify(error))
|
||
this.options.onError?.(error)
|
||
}
|
||
|
||
/**
|
||
* 判断 WebSocket 是否处于已连接状态
|
||
* @returns {boolean}
|
||
*/
|
||
isConnected() {
|
||
if (!this.ws) return false
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
return this.ws.readyState === 1
|
||
// #endif
|
||
// #ifdef H5
|
||
return this.ws.readyState === WebSocket.OPEN
|
||
// #endif
|
||
}
|
||
|
||
/**
|
||
* 方便外部获取连接状态码
|
||
* @returns {number} 0:连接中,1:已连接,2:关闭中,3:已关闭
|
||
*/
|
||
getReadyState() {
|
||
if (!this.ws) return 3
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
return this.ws.readyState
|
||
// #endif
|
||
// #ifdef H5
|
||
return this.ws.readyState
|
||
// #endif
|
||
}
|
||
/**
|
||
* 手动关闭 WebSocket 连接
|
||
* 清理所有定时器、标记手动关闭、停止重连
|
||
*/
|
||
close() {
|
||
console.log('手动关闭 WebSocket 连接')
|
||
this.isManualClose = true
|
||
this.stopHeartbeat()
|
||
|
||
// 清理重连定时器
|
||
if (this.reconnectTimer) {
|
||
clearTimeout(this.reconnectTimer)
|
||
this.reconnectTimer = null
|
||
}
|
||
|
||
if (this.ws) {
|
||
// 移除所有事件监听,避免内存泄漏
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
if (this.ws.onOpen) this.ws.onOpen(null)
|
||
if (this.ws.onMessage) this.ws.onMessage(null)
|
||
if (this.ws.onError) this.ws.onError(null)
|
||
if (this.ws.onClose) this.ws.onClose(null)
|
||
// #endif
|
||
|
||
try {
|
||
// #ifdef APP-PLUS || MP-WEIXIN
|
||
this.ws.close({
|
||
success: () => console.log('连接已关闭'),
|
||
fail: (err) => console.error('关闭失败:', err)
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
this.ws.close()
|
||
// #endif
|
||
} catch (error) {
|
||
console.error('关闭连接出错:', error)
|
||
}
|
||
this.ws = null
|
||
}
|
||
|
||
// 重置重连计数
|
||
this.reconnectAttempts = 0
|
||
}
|
||
/**
|
||
* 更新配置项
|
||
* 合并新配置,不覆盖原有配置
|
||
* @param {Object} options - 新配置
|
||
*/
|
||
updateOptions(options) {
|
||
this.options = {
|
||
...this.options,
|
||
...options
|
||
}
|
||
}
|
||
|
||
}
|
||
// 单例实例化,全局唯一
|
||
export const friendSocketManager = new WebSocketManager()
|
||
export default friendSocketManager |