/** * SignalR 客户端封装 * 用于小程序实时通信 */ import { getToken } from './storage' import config from '../config/index' // SignalR 协议分隔符 const RECORD_SEPARATOR = '\x1e' // 消息类型 const MessageTypes = { Invocation: 1, StreamItem: 2, Completion: 3, StreamInvocation: 4, CancelInvocation: 5, Ping: 6, Close: 7 } class SignalRClient { constructor() { this.socketTask = null this.isConnected = false this.isConnecting = false this.reconnectAttempts = 0 this.maxReconnectAttempts = 5 this.reconnectDelay = 1000 this.heartbeatInterval = null this.messageHandlers = new Map() this.invocationId = 0 this.pendingCalls = new Map() } /** * 连接到 SignalR Hub */ async connect() { if (this.isConnected || this.isConnecting) { console.log('[SignalR] 已连接或正在连接中') return Promise.resolve() } const token = getToken() if (!token) { console.error('[SignalR] 未登录,无法连接') return Promise.reject(new Error('未登录')) } this.isConnecting = true return new Promise((resolve, reject) => { try { // 使用配置中的 SignalR URL const wsUrl = config.SIGNALR_URL console.log('[SignalR] 正在连接:', wsUrl) this.socketTask = uni.connectSocket({ url: `${wsUrl}?access_token=${encodeURIComponent(token)}`, header: { 'Authorization': `Bearer ${token}` }, success: () => { console.log('[SignalR] WebSocket 创建成功') }, fail: (err) => { console.error('[SignalR] WebSocket 创建失败:', err) this.isConnecting = false reject(err) } }) // 监听连接打开 this.socketTask.onOpen(() => { console.log('[SignalR] WebSocket 连接已打开') this.sendHandshake() }) // 监听消息 this.socketTask.onMessage((res) => { this.handleMessage(res.data, resolve) }) // 监听连接关闭 this.socketTask.onClose((res) => { console.log('[SignalR] WebSocket 连接已关闭:', res) this.handleClose() }) // 监听错误 this.socketTask.onError((err) => { console.error('[SignalR] WebSocket 错误:', err) this.isConnecting = false reject(err) }) } catch (error) { console.error('[SignalR] 连接异常:', error) this.isConnecting = false reject(error) } }) } /** * 发送握手消息 */ sendHandshake() { const handshake = { protocol: 'json', version: 1 } this.sendRaw(JSON.stringify(handshake) + RECORD_SEPARATOR) } /** * 处理接收到的消息 */ handleMessage(data, connectResolve) { try { // SignalR 消息以 \x1e 分隔 const messages = data.split(RECORD_SEPARATOR).filter(m => m.trim()) messages.forEach(msg => { try { const message = JSON.parse(msg) // 握手响应(空对象表示成功) if (Object.keys(message).length === 0 || message.type === undefined) { console.log('[SignalR] 握手成功') this.isConnected = true this.isConnecting = false this.reconnectAttempts = 0 this.startHeartbeat() if (connectResolve) { connectResolve() } return } switch (message.type) { case MessageTypes.Invocation: this.handleInvocation(message) break case MessageTypes.Completion: this.handleCompletion(message) break case MessageTypes.Ping: // 收到 Ping,发送 Pong this.sendPing() break case MessageTypes.Close: console.log('[SignalR] 服务端关闭连接:', message.error) this.disconnect() break default: console.log('[SignalR] 未知消息类型:', message.type) } } catch (parseError) { console.error('[SignalR] 解析消息失败:', parseError, msg) } }) } catch (error) { console.error('[SignalR] 处理消息失败:', error) } } /** * 处理服务端调用 */ handleInvocation(message) { const { target, arguments: args } = message console.log('[SignalR] 收到服务端调用:', target, args) // 触发对应的事件处理器 const handlers = this.messageHandlers.get(target) if (handlers && handlers.length > 0) { handlers.forEach(handler => { try { handler(...(args || [])) } catch (err) { console.error('[SignalR] 处理器执行错误:', err) } }) } } /** * 处理方法调用完成 */ handleCompletion(message) { const { invocationId, result, error } = message const pending = this.pendingCalls.get(invocationId) if (pending) { this.pendingCalls.delete(invocationId) if (error) { pending.reject(new Error(error)) } else { pending.resolve(result) } } } /** * 处理连接关闭 */ handleClose() { this.isConnected = false this.isConnecting = false this.stopHeartbeat() // 尝试重连 if (this.reconnectAttempts < this.maxReconnectAttempts) { this.scheduleReconnect() } else { console.error('[SignalR] 重连次数已达上限') // 触发断开连接事件 const handlers = this.messageHandlers.get('Disconnected') if (handlers) { handlers.forEach(h => h()) } } } /** * 安排重连 */ scheduleReconnect() { this.reconnectAttempts++ const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), 30000) console.log(`[SignalR] ${delay}ms 后尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`) setTimeout(() => { if (!this.isConnected && !this.isConnecting) { this.connect().catch(err => { console.error('[SignalR] 重连失败:', err) }) } }, delay) } /** * 开始心跳 */ startHeartbeat() { this.stopHeartbeat() this.heartbeatInterval = setInterval(() => { if (this.isConnected) { this.sendPing() } }, 15000) // 每15秒发送一次心跳 } /** * 停止心跳 */ stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) this.heartbeatInterval = null } } /** * 发送 Ping */ sendPing() { const ping = { type: MessageTypes.Ping } this.sendRaw(JSON.stringify(ping) + RECORD_SEPARATOR) } /** * 发送原始数据 */ sendRaw(data) { if (!this.socketTask) { console.error('[SignalR] WebSocket 未连接') return false } this.socketTask.send({ data, success: () => { // console.log('[SignalR] 发送成功') }, fail: (err) => { console.error('[SignalR] 发送失败:', err) } }) return true } /** * 调用服务端方法 * @param {string} method 方法名 * @param {...any} args 参数 * @returns {Promise} */ invoke(method, ...args) { return new Promise((resolve, reject) => { if (!this.isConnected) { reject(new Error('SignalR 未连接')) return } const invocationId = String(++this.invocationId) const message = { type: MessageTypes.Invocation, invocationId, target: method, arguments: args } this.pendingCalls.set(invocationId, { resolve, reject }) // 设置超时 setTimeout(() => { if (this.pendingCalls.has(invocationId)) { this.pendingCalls.delete(invocationId) reject(new Error('调用超时')) } }, 30000) this.sendRaw(JSON.stringify(message) + RECORD_SEPARATOR) }) } /** * 发送消息(不等待响应) * @param {string} method 方法名 * @param {...any} args 参数 */ send(method, ...args) { if (!this.isConnected) { console.error('[SignalR] 未连接,无法发送') return false } const message = { type: MessageTypes.Invocation, target: method, arguments: args } return this.sendRaw(JSON.stringify(message) + RECORD_SEPARATOR) } /** * 监听服务端事件 * @param {string} event 事件名 * @param {Function} callback 回调函数 */ on(event, callback) { if (!this.messageHandlers.has(event)) { this.messageHandlers.set(event, []) } this.messageHandlers.get(event).push(callback) console.log('[SignalR] 注册事件监听:', event) } /** * 移除事件监听 * @param {string} event 事件名 * @param {Function} callback 回调函数(可选,不传则移除所有) */ off(event, callback) { if (!callback) { this.messageHandlers.delete(event) } else { const handlers = this.messageHandlers.get(event) if (handlers) { const index = handlers.indexOf(callback) if (index > -1) { handlers.splice(index, 1) } if (handlers.length === 0) { this.messageHandlers.delete(event) } } } } /** * 断开连接 */ disconnect() { console.log('[SignalR] 主动断开连接') this.stopHeartbeat() this.isConnected = false this.isConnecting = false this.reconnectAttempts = this.maxReconnectAttempts // 阻止自动重连 if (this.socketTask) { this.socketTask.close({ success: () => { console.log('[SignalR] 连接已关闭') } }) this.socketTask = null } // 清理待处理的调用 this.pendingCalls.forEach((pending) => { pending.reject(new Error('连接已断开')) }) this.pendingCalls.clear() } /** * 重置并重新连接 */ async reconnect() { this.disconnect() this.reconnectAttempts = 0 await new Promise(resolve => setTimeout(resolve, 100)) return this.connect() } /** * 加入会话组 * @param {number} sessionId 会话ID */ async joinSession(sessionId) { if (!this.isConnected) { console.warn('[SignalR] 未连接,无法加入会话') return } try { await this.invoke('JoinSession', sessionId) console.log('[SignalR] 已加入会话:', sessionId) } catch (err) { console.error('[SignalR] 加入会话失败:', err) } } /** * 离开会话组 * @param {number} sessionId 会话ID */ async leaveSession(sessionId) { if (!this.isConnected) { return } try { await this.invoke('LeaveSession', sessionId) console.log('[SignalR] 已离开会话:', sessionId) } catch (err) { console.error('[SignalR] 离开会话失败:', err) } } /** * 获取连接状态 */ get connected() { return this.isConnected } } // 创建单例实例 const signalR = new SignalRClient() export { signalR, SignalRClient } export default signalR