| | |
| | | } from "@/utils/common.js"; |
| | | |
| | | class WebSocketServices { |
| | | constructor() { |
| | | this.wsInstance = null; // WebSocket 实例 |
| | | this.isConnecting = false; // 避免并发重连 |
| | | this.isConnected = false; // 避免并发重连 |
| | | this.wsUrl = CommonUtils.httpFormatWs() |
| | | } |
| | | constructor() { |
| | | this.wsInstance = null; // WebSocket 实例 |
| | | this.isConnecting = false; // 连接中状态(避免并发重连) |
| | | this.isConnected = false; // 已连接状态 |
| | | this.isReconnectStopped = false; // 停止重连标记 |
| | | this.wsUrl = CommonUtils.httpFormatWs(); // WebSocket 基础地址 |
| | | |
| | | // 超时校验配置 |
| | | this.noMessageTimeout = 45000; // 45秒内没收到任何业务消息 → 判定连接失效(服务端30秒发一次Ping,留15秒容错) |
| | | this.noMessageTimer = null; // 无消息超时定时器 |
| | | |
| | | // 监听函数引用 |
| | | this.openListener = null; |
| | | this.messageListener = null; |
| | | this.closeListener = null; |
| | | this.errorListener = null; |
| | | |
| | | // 缓存用户信息 |
| | | this.currentUserId = ""; |
| | | this.currentUserName = ""; |
| | | } |
| | | |
| | | /** |
| | | * 建立 WebSocket 连接 |
| | | * @param {string/number} userId - 用户ID(必填) |
| | | * @param {string} userName - 用户名(必填) |
| | | * @param {number} count - 当前重连次数 |
| | | * @param {number} limit - 最大重连次数(默认3次) |
| | | */ |
| | | createConnect(userId, userName, count = 0, limit = 3) { |
| | | // 缓存用户信息(用于重连) |
| | | this.currentUserId = userId; |
| | | this.currentUserName = userName; |
| | | |
| | | // 前置校验:避免无效连接和并发重连 |
| | | if (this.isConnecting || this.isConnected) return; |
| | | if (!userId && userId !== 0) { // 兼容 userId 为 0 的合法场景 |
| | | CommonUtils.showTips({ message: "用户标识不能为空,无法建立WebSocket连接" }); |
| | | return; |
| | | } |
| | | if (count > limit) { |
| | | CommonUtils.showTips({ |
| | | message: `WebSocket 重连次数超出最大限制(${limit}次),已停止重连`, |
| | | }); |
| | | this.isReconnectStopped = true; |
| | | return; |
| | | } |
| | | |
| | | console.log(`[WebSocket] 开始建立连接(第 ${count || 0} 次)`, { |
| | | wsUrl: this.wsUrl, |
| | | userId, |
| | | userName, |
| | | }); |
| | | |
| | | // 清除历史残留:监听+定时器 |
| | | this.clearAllListeners(); |
| | | this.clearNoMessageTimer(); |
| | | |
| | | // 发起连接 |
| | | this.isConnecting = true; |
| | | this.wsInstance = uni.connectSocket({ |
| | | url: `${this.wsUrl}?userId=${encodeURIComponent(userId)}&userName=${encodeURIComponent(userName)}`, |
| | | fail: (error) => { |
| | | console.error("[WebSocket] 连接发起失败", error); |
| | | this.isConnecting = false; |
| | | this.triggerReconnect(count); |
| | | }, |
| | | }); |
| | | |
| | | // 监听连接成功:启动无消息超时校验 |
| | | this.openListener = uni.onSocketOpen((res) => { |
| | | console.log("[WebSocket] 连接建立成功", res); |
| | | this.isConnecting = false; |
| | | this.isConnected = true; |
| | | this.isReconnectStopped = false; |
| | | this.startNoMessageCheck(); |
| | | count = 0; |
| | | }); |
| | | |
| | | this.messageListener = uni.onSocketMessage((res) => { |
| | | try { |
| | | const message = JSON.parse(res.data); |
| | | console.log("[WebSocket] 收到业务消息", message); |
| | | |
| | | // 重置无消息定时器(有业务消息=连接正常) |
| | | this.resetNoMessageTimer(); |
| | | |
| | | // 处理业务消息 |
| | | if (message.Type === "Message") { |
| | | const content = JSON.parse(message.Content); |
| | | this.showTaskTip(`您有${content.length}条消息需要处理!`); |
| | | // this.emit("message", content); // 支持外部监听 |
| | | } |
| | | } catch (error) { |
| | | console.error("[WebSocket] 消息解析失败", error, res.data); |
| | | } |
| | | }); |
| | | |
| | | // 监听连接关闭:仅异常关闭触发重连 |
| | | this.closeListener = uni.onSocketClose((res) => { |
| | | console.log("[WebSocket] 连接关闭", res); |
| | | this.isConnecting = false; |
| | | this.isConnected = false; |
| | | this.clearNoMessageTimer(); |
| | | |
| | | // 正常关闭(code=1000)或主动停止重连时,不重连 |
| | | if (!this.isReconnectStopped && res.code !== 1000) { |
| | | this.triggerReconnect(count); |
| | | } |
| | | }); |
| | | |
| | | // 监听连接错误:触发重连 |
| | | this.errorListener = uni.onSocketError((error) => { |
| | | console.error("[WebSocket] 连接错误", error); |
| | | this.isConnecting = false; |
| | | this.clearNoMessageTimer(); |
| | | if (!this.isReconnectStopped) { |
| | | this.triggerReconnect(count); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 统一触发重连(延迟3秒) |
| | | * @param {number} count - 当前重连次数 |
| | | */ |
| | | triggerReconnect(count) { |
| | | console.log(`[WebSocket] 准备第 ${count + 1} 次重连`); |
| | | setTimeout(() => { |
| | | this.createConnect(this.currentUserId, this.currentUserName, count + 1); |
| | | }, 3000); |
| | | } |
| | | |
| | | /** |
| | | * 启动“无业务消息”超时校验 |
| | | * 逻辑:45秒内没收到任何业务消息 → 判定连接失效(服务端30秒发Ping,底层已处理,此处仅校验业务通道) |
| | | */ |
| | | startNoMessageCheck() { |
| | | this.clearNoMessageTimer(); |
| | | this.noMessageTimer = setTimeout(() => { |
| | | console.warn("[WebSocket] 45秒未收到业务消息,判定连接失效,主动重连"); |
| | | this.closeSocket(); |
| | | this.createConnect(this.currentUserId, this.currentUserName); |
| | | }, this.noMessageTimeout); |
| | | } |
| | | |
| | | /** |
| | | * 收到业务消息后,重置无消息定时器 |
| | | */ |
| | | resetNoMessageTimer() { |
| | | this.startNoMessageCheck(); |
| | | } |
| | | |
| | | /** |
| | | * 清除无消息定时器 |
| | | */ |
| | | clearNoMessageTimer() { |
| | | if (this.noMessageTimer) { |
| | | clearTimeout(this.noMessageTimer); |
| | | this.noMessageTimer = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清除所有 Socket 监听 |
| | | */ |
| | | clearAllListeners() { |
| | | if (this.openListener) { |
| | | uni.offSocketOpen(this.openListener); |
| | | this.openListener = null; |
| | | } |
| | | if (this.messageListener) { |
| | | uni.offSocketMessage(this.messageListener); |
| | | this.messageListener = null; |
| | | } |
| | | if (this.closeListener) { |
| | | uni.offSocketClose(this.closeListener); |
| | | this.closeListener = null; |
| | | } |
| | | if (this.errorListener) { |
| | | uni.offSocketError(this.errorListener); |
| | | this.errorListener = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 主动关闭 WebSocket 连接 (登出时关闭WebSocket连接) |
| | | */ |
| | | closeSocket() { |
| | | this.isReconnectStopped = true; |
| | | this.clearAllListeners(); |
| | | this.clearNoMessageTimer(); |
| | | |
| | | if (this.wsInstance) { |
| | | uni.closeSocket({ |
| | | success: () => console.log("[WebSocket] 主动关闭连接成功"), |
| | | fail: (error) => console.error("[WebSocket] 主动关闭连接失败", error), |
| | | }); |
| | | this.wsInstance = null; |
| | | } |
| | | |
| | | this.isConnected = false; |
| | | this.isConnecting = false; |
| | | } |
| | | |
| | | // 建立WebSocket连接 |
| | | createConnect(userId, userName) { // 使用用户标识 作为后端连接的凭据 |
| | | console.log('wsUrl: ', this.wsUrl); |
| | | console.log('userId: ', userId); |
| | | console.log('userName: ', userName); |
| | | if (this.isConnecting || !userId || this.isConnected) { |
| | | return |
| | | } |
| | | this.wsInstance = uni.connectSocket({ |
| | | url: this.wsUrl + `?userId=${encodeURIComponent(userId)}&userName=${encodeURIComponent(userName)}`, |
| | | success() { |
| | | this.isConnecting = true |
| | | } |
| | | }) |
| | | |
| | | // 监听套接字连接建立 |
| | | uni.onSocketOpen((res) => { |
| | | console.log('[webSocket]: 套接字连接建立成功'); |
| | | this.isConnecting = false |
| | | this.isConnected = true |
| | | console.log('res: ', res); |
| | | this.wsInstance = res.socketTask |
| | | }) |
| | | |
| | | uni.onSocketMessage((res) => { |
| | | let message = JSON.parse(res.data) |
| | | console.log('message: ', message); |
| | | if (message.Type == 'Message') { |
| | | // 消息信号 |
| | | let content = JSON.parse(message.Content) |
| | | console.log('content: ', content); |
| | | |
| | | this.showTaskTip(`您有${content.length}条消息需要处理!`) |
| | | } else if (message.Type == 'ping') { |
| | | // 心跳信号 |
| | | uni.sendSocketMessage({ |
| | | data: "pong" |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 重连 |
| | | reConnect(reCount = 1, limit = 3) { |
| | | if (reCount > limit) { |
| | | uni.showToast({ |
| | | icon: 'none', |
| | | title: `超出最大重连次数。已退出连接` |
| | | }) |
| | | this.isConnecting = false |
| | | return |
| | | } |
| | | uni.showToast({ |
| | | icon: 'none', |
| | | title: `正在尝试重连,重连次数 ${reCount}` |
| | | }) |
| | | |
| | | reConnect(reCount + 1, limit) |
| | | |
| | | uni.hideToast() |
| | | } |
| | | |
| | | // 连接注销 |
| | | disConnect() { |
| | | |
| | | } |
| | | |
| | | showTaskTip(Content) { |
| | | console.log('Content: ', Content); |
| | | // #ifdef APP-PLUS || APP |
| | | console.log('Content2: ', Content); |
| | | let content = Content; |
| | | let options = { |
| | | title: "重要通知", |