| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- /**
- * WebSocket聊天管理工具类
- * 实现了心跳机制、断线重连、消息队列等功能
- */
- const DEBUG_WS = false;
- class WebSocketManager {
- constructor() {
- // 心跳相关
- this.heartbeatTimer = null;
- this.heartbeatInterval = 30000; // 30秒
-
- // 重连相关
- this.reconnectTimer = null;
- this.reconnectCount = 0;
- this.maxReconnectCount = 10;
- this.reconnectDelay = 1000; // 初始1秒
-
- // 消息队列(断线时暂存)
- this.messageQueue = [];
- this.maxQueueSize = 100;
-
- // 回调函数
- this.onMessageCallback = null;
- this.onConnectCallback = null;
- this.onDisconnectCallback = null;
- this.onErrorCallback = null;
- }
- /**
- * 连接WebSocket
- * @param {Number} userId 用户ID
- * @param {String} baseUrl WebSocket服务地址
- */
- connect(userId, baseUrl = 'ws://localhost:8083') {
- if (this.isConnected) {
- if (DEBUG_WS) console.log('WebSocket已连接');
- return;
- }
- this.userId = userId;
- this.url = `${baseUrl}/ws/chat?userId=${userId}`;
-
- if (DEBUG_WS) console.log('正在连接WebSocket...', this.url);
- this.socket = uni.connectSocket({
- url: this.url,
- success: () => {
- if (DEBUG_WS) console.log('WebSocket连接请求已发送');
- },
- fail: (err) => {
- console.error('WebSocket连接失败', err);
- this.onError(err);
- }
- });
- // 监听连接打开
- uni.onSocketOpen((res) => {
- if (DEBUG_WS) console.log('WebSocket连接成功');
- this.isConnected = true;
- this.reconnectCount = 0;
-
- // 启动心跳
- this.startHeartbeat();
-
- // 发送队列中的消息
- this.flushMessageQueue();
-
- // 触发连接回调
- if (this.onConnectCallback) {
- this.onConnectCallback();
- }
- });
- // 监听消息接收
- uni.onSocketMessage((res) => {
- try {
- if (DEBUG_WS) console.log('🔵 收到原始WebSocket消息:', res.data);
- const message = JSON.parse(res.data);
- if (DEBUG_WS) console.log('🔵 解析后的消息对象:', JSON.stringify(message, null, 2));
- this.handleMessage(message);
- } catch (e) {
- console.error('❌ 解析消息失败', e);
- console.error(' 原始数据:', res.data);
- }
- });
- // 监听连接关闭
- uni.onSocketClose((res) => {
- if (DEBUG_WS) console.log('WebSocket连接关闭', res);
- this.isConnected = false;
- this.stopHeartbeat();
-
- // 触发断开回调
- if (this.onDisconnectCallback) {
- this.onDisconnectCallback();
- }
-
- // 尝试重连
- this.reconnect();
- });
- // 监听错误
- uni.onSocketError((err) => {
- console.error('WebSocket错误', err);
- this.isConnected = false;
- this.onError(err);
- });
- }
- /**
- * 处理接收到的消息
- */
- handleMessage(message) {
- if (DEBUG_WS) console.log('收到消息', message);
- switch (message.type) {
- case 'pong':
- // 心跳响应
- console.log('收到pong');
- break;
-
- case 'chat':
- // 聊天消息
- if (this.onMessageCallback) {
- this.onMessageCallback(message);
- }
- // 自动发送ACK确认
- this.sendAck(message.messageId);
- break;
-
- case 'ack':
- // 消息确认
- if (DEBUG_WS) {
- console.log('✅ 收到ACK消息!');
- console.log(' messageId:', message.messageId);
- console.log(' 完整ACK消息:', JSON.stringify(message));
- }
- if (this.onMessageCallback) {
- console.log(' 正在调用onMessageCallback...');
- this.onMessageCallback(message);
- } else {
- console.warn(' ⚠️ onMessageCallback未设置!');
- }
- break;
-
- case 'recall':
- // 消息撤回
- if (this.onMessageCallback) {
- this.onMessageCallback(message);
- }
- break;
-
- case 'typing':
- // 正在输入
- if (this.onMessageCallback) {
- this.onMessageCallback(message);
- }
- break;
-
- case 'read':
- // 已读回执
- if (this.onMessageCallback) {
- this.onMessageCallback(message);
- }
- break;
-
- case 'online':
- // 上线通知
- if (DEBUG_WS) console.log('用户上线', message.fromUserId);
- break;
-
- case 'error':
- // 错误消息
- console.error('服务器错误', message.errorMessage);
- uni.showToast({
- title: message.errorMessage,
- icon: 'none'
- });
- break;
-
- default:
- if (DEBUG_WS) console.log('未知消息类型', message.type);
- }
- }
- /**
- * 发送聊天消息
- */
- sendMessage(toUserId, content, messageType = 1, extraData = {}) {
- const message = {
- type: 'chat',
- messageId: this.generateMessageId(),
- fromUserId: this.userId,
- toUserId: toUserId,
- messageType: messageType,
- content: content,
- timestamp: Date.now(),
- extraData: extraData
- };
- if (messageType === 2) {
- // 图片消息
- message.mediaUrl = extraData.mediaUrl;
- message.thumbnailUrl = extraData.thumbnailUrl;
- } else if (messageType === 3 || messageType === 4) {
- // 语音或视频消息
- message.mediaUrl = extraData.mediaUrl;
- message.duration = extraData.duration;
- message.mediaSize = extraData.mediaSize;
- }
- this.send(message);
- return message.messageId;
- }
- /**
- * 发送消息ACK确认
- */
- sendAck(messageId) {
- this.send({
- type: 'ack',
- messageId: messageId,
- timestamp: Date.now()
- });
- }
- /**
- * 撤回消息
- */
- recallMessage(messageId) {
- this.send({
- type: 'recall',
- messageId: messageId,
- timestamp: Date.now()
- });
- }
- /**
- * 发送正在输入状态
- */
- sendTyping(toUserId) {
- this.send({
- type: 'typing',
- toUserId: toUserId,
- timestamp: Date.now()
- });
- }
- /**
- * 发送已读回执
- */
- sendReadReceipt(toUserId) {
- this.send({
- type: 'read',
- fromUserId: this.userId,
- toUserId: toUserId,
- timestamp: Date.now()
- });
- }
- /**
- * 发送数据到WebSocket
- */
- send(data) {
- console.log('🔷 准备发送WebSocket消息');
- console.log(' 消息类型:', data.type);
- console.log(' isConnected:', this.isConnected);
- console.log(' 完整数据:', JSON.stringify(data, null, 2));
-
- if (!this.isConnected) {
- console.log('❌ WebSocket未连接,消息加入队列');
- // 加入消息队列
- if (this.messageQueue.length < this.maxQueueSize) {
- this.messageQueue.push(data);
- }
- return false;
- }
- try {
- const jsonStr = JSON.stringify(data);
- console.log('🔷 JSON字符串长度:', jsonStr.length);
-
- uni.sendSocketMessage({
- data: jsonStr,
- success: () => {
- console.log('✅ WebSocket消息已发送到服务器');
- console.log(' 消息类型:', data.type);
- console.log(' messageId:', data.messageId || 'N/A');
- },
- fail: (err) => {
- console.error('❌ WebSocket消息发送失败', err);
- console.error(' 错误详情:', JSON.stringify(err));
- // 发送失败,加入队列
- if (this.messageQueue.length < this.maxQueueSize) {
- this.messageQueue.push(data);
- }
- }
- });
- return true;
- } catch (e) {
- console.error('❌ 发送消息异常', e);
- console.error(' 异常详情:', e.message);
- return false;
- }
- }
- /**
- * 发送队列中的消息
- */
- flushMessageQueue() {
- if (this.messageQueue.length === 0) {
- return;
- }
- console.log(`发送队列中的 ${this.messageQueue.length} 条消息`);
-
- while (this.messageQueue.length > 0) {
- const message = this.messageQueue.shift();
- this.send(message);
- }
- }
- /**
- * 启动心跳
- */
- startHeartbeat() {
- this.stopHeartbeat();
-
- this.heartbeatTimer = setInterval(() => {
- if (this.isConnected) {
- this.send({
- type: 'ping',
- timestamp: Date.now()
- });
- }
- }, this.heartbeatInterval);
- }
- /**
- * 停止心跳
- */
- stopHeartbeat() {
- if (this.heartbeatTimer) {
- clearInterval(this.heartbeatTimer);
- this.heartbeatTimer = null;
- }
- }
- /**
- * 断线重连
- */
- reconnect() {
- if (this.reconnectCount >= this.maxReconnectCount) {
- console.log('达到最大重连次数,停止重连');
- uni.showToast({
- title: '连接失败,请检查网络',
- icon: 'none'
- });
- return;
- }
- // 指数退避算法
- const delay = Math.min(
- this.reconnectDelay * Math.pow(2, this.reconnectCount),
- 30000 // 最大30秒
- );
- console.log(`${delay}ms 后进行第 ${this.reconnectCount + 1} 次重连...`);
- this.reconnectTimer = setTimeout(() => {
- this.reconnectCount++;
- this.connect(this.userId, this.url.split('/ws/chat')[0]);
- }, delay);
- }
- /**
- * 取消重连
- */
- cancelReconnect() {
- if (this.reconnectTimer) {
- clearTimeout(this.reconnectTimer);
- this.reconnectTimer = null;
- }
- }
- /**
- * 关闭连接
- */
- close() {
- this.cancelReconnect();
- this.stopHeartbeat();
-
- if (this.socket) {
- uni.closeSocket();
- this.socket = null;
- }
-
- this.isConnected = false;
- }
- /**
- * 错误处理
- */
- onError(error) {
- if (this.onErrorCallback) {
- this.onErrorCallback(error);
- }
- }
- /**
- * 生成消息ID
- */
- generateMessageId() {
- return `${this.userId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
- }
- /**
- * 设置消息回调
- */
- onMessage(callback) {
- this.onMessageCallback = callback;
- }
- /**
- * 设置连接回调
- */
- onConnect(callback) {
- this.onConnectCallback = callback;
- }
- /**
- * 设置断开回调
- */
- onDisconnect(callback) {
- this.onDisconnectCallback = callback;
- }
- /**
- * 设置错误回调
- */
- onErrorHandler(callback) {
- this.onErrorCallback = callback;
- }
- }
- // 导出单例
- export default new WebSocketManager();
|