first commit

This commit is contained in:
2026-06-02 10:42:33 +08:00
commit dd4975fd2c
1084 changed files with 442416 additions and 0 deletions

366
utils/socket.js Normal file
View File

@@ -0,0 +1,366 @@
const WS_APP_URL = 'ws://cloud_test.yuxindazhineng.com'
// (工具类)连接、发送、接收、重连...
class WebSocketManager {
constructor() {
this.ws = null
this.url = `${WS_APP_URL}/cloud_api/phone/app_ws`
this.options = {
token: '',
conversationId: '',
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);
// uni.setStorageSync('currentSessionId', data.task_call_id)
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.stopHeartbeat()
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.stopHeartbeat()
if (!this.isManualClose && this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnect()
}
}
}
// #endif
}
/**
* 处理服务端推送的消息
* @param {Object} msg - 服务端返回的原始消息对象
*/
handleMessage(msg) {
this.options.onMessage?.(msg)
const type = msg.type
// const data = msg.data || {}
// 连接确认:服务端认证成功响应
if (type === 'auth_ok') {
// 发送消息携带的上一次返回的验证id
// const taskCallId = data.task_call_id
// uni.setStorageSync('taskCallId', taskCallId)
console.log('认证成功socket连接成功');
return
}
// 断开连接指令:服务端主动要求断开
if (type === 'auth_fail') {
if(msg?.reason === "pc offline")
console.log("pc端不在线不可聊天");
// console.log('服务端要求断开连接')
this.isManualClose = true
this.close()
return
}
}
/**
* 统一发送消息方法
* 兼容 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 socketManager = new WebSocketManager()
export default socketManager