393 lines
No EOL
10 KiB
JavaScript
393 lines
No EOL
10 KiB
JavaScript
import { defineStore } from 'pinia'
|
||
|
||
export const useWebSocketStore = defineStore('websocket', () => {
|
||
let socketTask = null;
|
||
let reconnectTimer = null;
|
||
let heartbeatTimer = null;
|
||
let reconnectAttempts = 0;
|
||
const MAX_RECONNECT_ATTEMPTS = 5;
|
||
const HEARTBEAT_INTERVAL = 30000; // 30秒心跳
|
||
|
||
// 连接状态
|
||
const connectionState = ref('disconnected'); // disconnected, connecting, connected, reconnecting
|
||
|
||
/**
|
||
* 建立WebSocket连接
|
||
* @param {Object} registerInfo - 注册信息
|
||
* @param {string} registerInfo.device_id - 设备ID
|
||
* @param {string} registerInfo.token - JWT Token
|
||
*/
|
||
const buildWebSocket = async (registerInfo) => {
|
||
try {
|
||
// 确保先关闭已有连接
|
||
if (socketTask) {
|
||
socketTask.close({ code: 1000, reason: '重新连接' });
|
||
socketTask = null;
|
||
}
|
||
|
||
// 清除重连定时器
|
||
if (reconnectTimer) {
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
}
|
||
|
||
connectionState.value = 'connecting';
|
||
|
||
// WebSocket地址
|
||
const wsUrl = `wss://api.faxianwo.me/api/v1/ws?device_id=${registerInfo.device_id}`;
|
||
|
||
console.log('开始连接WebSocket:', wsUrl);
|
||
|
||
// uniapp WebSocket连接配置
|
||
socketTask = uni.connectSocket({
|
||
url: wsUrl,
|
||
header: {
|
||
'Authorization': `Bearer ${registerInfo.token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
// 移除protocols参数
|
||
success: () => {
|
||
console.log('WebSocket连接请求发送成功');
|
||
},
|
||
fail: (error) => {
|
||
console.error('WebSocket连接请求失败:', error);
|
||
connectionState.value = 'disconnected';
|
||
handleConnectionError(error);
|
||
}
|
||
});
|
||
|
||
// 连接成功事件
|
||
socketTask.onOpen((response) => {
|
||
console.log('WebSocket连接成功:', response);
|
||
connectionState.value = 'connected';
|
||
reconnectAttempts = 0; // 重置重连次数
|
||
|
||
// 发送认证确认消息
|
||
sendAuthConfirmation();
|
||
|
||
// 开始心跳
|
||
startHeartbeat();
|
||
|
||
// 显示连接成功提示
|
||
uni.showToast({
|
||
title: '连接成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
});
|
||
|
||
// 接收消息事件
|
||
socketTask.onMessage((message) => {
|
||
try {
|
||
const data = JSON.parse(message.data);
|
||
console.log('收到WebSocket消息:', data);
|
||
handleIncomingMessage(data);
|
||
} catch (error) {
|
||
console.error('解析WebSocket消息失败:', error, message.data);
|
||
}
|
||
});
|
||
|
||
// 连接错误事件
|
||
socketTask.onError((error) => {
|
||
console.error('WebSocket连接错误:', error);
|
||
connectionState.value = 'disconnected';
|
||
stopHeartbeat();
|
||
handleConnectionError(error);
|
||
});
|
||
|
||
// 连接关闭事件
|
||
socketTask.onClose((closeEvent) => {
|
||
console.log('WebSocket连接关闭:', closeEvent);
|
||
connectionState.value = 'disconnected';
|
||
stopHeartbeat();
|
||
|
||
// 如果不是主动关闭,尝试重连
|
||
if (closeEvent.code !== 1000 && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||
attemptReconnect(registerInfo);
|
||
}
|
||
});
|
||
|
||
return socketTask;
|
||
|
||
} catch (error) {
|
||
console.error('建立WebSocket连接异常:', error);
|
||
connectionState.value = 'disconnected';
|
||
return null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 发送认证确认消息
|
||
*/
|
||
const sendAuthConfirmation = () => {
|
||
if (!socketTask || connectionState.value !== 'connected') {
|
||
console.warn('WebSocket未连接,无法发送认证确认');
|
||
return;
|
||
}
|
||
|
||
const authMessage = {
|
||
type: 'auth_confirm',
|
||
id: `auth_${Date.now()}`,
|
||
data: {
|
||
timestamp: Date.now()
|
||
}
|
||
};
|
||
|
||
socketTask.send({
|
||
data: JSON.stringify(authMessage),
|
||
success: () => console.log('认证确认发送成功'),
|
||
fail: (err) => console.error('认证确认发送失败:', err)
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 开始心跳检测
|
||
*/
|
||
const startHeartbeat = () => {
|
||
stopHeartbeat(); // 先清除已有的心跳
|
||
|
||
heartbeatTimer = setInterval(() => {
|
||
if (socketTask && connectionState.value === 'connected') {
|
||
const heartbeatMessage = {
|
||
type: 'heartbeat',
|
||
id: `hb_${Date.now()}`,
|
||
data: {
|
||
timestamp: Date.now()
|
||
}
|
||
};
|
||
|
||
socketTask.send({
|
||
data: JSON.stringify(heartbeatMessage),
|
||
success: () => console.log('心跳发送成功'),
|
||
fail: (err) => {
|
||
console.error('心跳发送失败:', err);
|
||
// 心跳失败可能表示连接有问题
|
||
connectionState.value = 'disconnected';
|
||
}
|
||
});
|
||
}
|
||
}, HEARTBEAT_INTERVAL);
|
||
};
|
||
|
||
/**
|
||
* 停止心跳检测
|
||
*/
|
||
const stopHeartbeat = () => {
|
||
if (heartbeatTimer) {
|
||
clearInterval(heartbeatTimer);
|
||
heartbeatTimer = null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 处理接收到的消息
|
||
*/
|
||
const handleIncomingMessage = (message) => {
|
||
switch (message.type) {
|
||
case 'new_message':
|
||
// 处理新消息
|
||
console.log('收到新消息:', message.data);
|
||
break;
|
||
|
||
case 'message_read':
|
||
// 处理已读回执
|
||
console.log('消息已读:', message.data);
|
||
break;
|
||
|
||
case 'message_recalled':
|
||
// 处理消息撤回
|
||
console.log('消息撤回:', message.data);
|
||
break;
|
||
|
||
case 'heartbeat_response':
|
||
// 心跳响应
|
||
console.log('收到心跳响应');
|
||
break;
|
||
|
||
default:
|
||
console.log('收到其他类型消息:', message);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 发送消息的通用方法
|
||
*/
|
||
const sendMessage = (messageType, messageData) => {
|
||
if (!socketTask || connectionState.value !== 'connected') {
|
||
console.warn('WebSocket未连接,无法发送消息');
|
||
return false;
|
||
}
|
||
|
||
const message = {
|
||
type: messageType,
|
||
id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||
data: messageData
|
||
};
|
||
|
||
socketTask.send({
|
||
data: JSON.stringify(message),
|
||
success: () => console.log(` ${messageType} 发送成功`),
|
||
fail: (err) => console.error(` ${messageType} 发送失败:`, err)
|
||
});
|
||
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* 发送聊天消息
|
||
*/
|
||
const sendChatMessage = (receiverId, content, msgType = 0, chatType = 0) => {
|
||
return sendMessage('send_message', {
|
||
receiverId: String(receiverId), // 确保是字符串格式
|
||
chatType,
|
||
msgType,
|
||
content,
|
||
timestamp: Date.now()
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 处理连接错误
|
||
*/
|
||
const handleConnectionError = (error) => {
|
||
let errorMessage = '连接错误';
|
||
|
||
// 完善的错误码处理
|
||
switch (error.errCode) {
|
||
case 1000:
|
||
errorMessage = '正常关闭';
|
||
break;
|
||
case 1001:
|
||
errorMessage = '连接断开';
|
||
break;
|
||
case 1002:
|
||
errorMessage = '协议错误';
|
||
break;
|
||
case 1003:
|
||
errorMessage = '数据格式错误';
|
||
break;
|
||
case 1006:
|
||
errorMessage = '异常断开';
|
||
break;
|
||
case 1011:
|
||
errorMessage = '服务器错误';
|
||
break;
|
||
case 1012:
|
||
errorMessage = '服务重启';
|
||
break;
|
||
default:
|
||
console.log(`未知错误代码: ${error.errCode}`);
|
||
errorMessage = `连接错误 (${error.errCode})`;
|
||
}
|
||
|
||
console.error(` ${errorMessage}:`, error);
|
||
|
||
// 只有在非正常关闭时才显示错误提示
|
||
if (error.errCode !== 1000) {
|
||
uni.showToast({
|
||
title: errorMessage,
|
||
icon: 'none',
|
||
duration: 3000
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 重连机制
|
||
*/
|
||
const attemptReconnect = (registerInfo) => {
|
||
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
||
console.error('已达到最大重连次数,停止重连');
|
||
uni.showToast({
|
||
title: '连接失败,请检查网络',
|
||
icon: 'none',
|
||
duration: 3000
|
||
});
|
||
return;
|
||
}
|
||
|
||
reconnectAttempts++;
|
||
connectionState.value = 'reconnecting';
|
||
|
||
// 指数退避策略
|
||
const delay = Math.min(2000 * Math.pow(1.5, reconnectAttempts), 30000);
|
||
|
||
console.log(` 将在 ${delay}ms 后尝试重连 (第${reconnectAttempts}次)`);
|
||
|
||
reconnectTimer = setTimeout(async () => {
|
||
console.log(`开始重连 (第${reconnectAttempts}次)`);
|
||
|
||
try {
|
||
const task = await buildWebSocket(registerInfo);
|
||
if (task) {
|
||
console.log('重连成功');
|
||
}
|
||
} catch (error) {
|
||
console.error(`重连失败 (第${reconnectAttempts}次):`, error);
|
||
// 继续尝试重连
|
||
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||
attemptReconnect(registerInfo);
|
||
}
|
||
}
|
||
}, delay);
|
||
};
|
||
|
||
/**
|
||
* 关闭WebSocket连接
|
||
*/
|
||
const closeWebSocket = () => {
|
||
console.log('主动关闭WebSocket连接');
|
||
|
||
// 停止心跳
|
||
stopHeartbeat();
|
||
|
||
// 清除重连定时器
|
||
if (reconnectTimer) {
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
}
|
||
|
||
// 重置重连计数器
|
||
reconnectAttempts = 0;
|
||
|
||
// 关闭连接
|
||
if (socketTask) {
|
||
socketTask.close({
|
||
code: 1000, // 正常关闭
|
||
reason: '用户主动关闭',
|
||
success: () => {
|
||
console.log('WebSocket已关闭');
|
||
connectionState.value = 'disconnected';
|
||
},
|
||
fail: (err) => console.error('关闭WebSocket失败:', err)
|
||
});
|
||
|
||
socketTask = null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 获取连接状态
|
||
*/
|
||
const getConnectionState = () => connectionState.value;
|
||
|
||
/**
|
||
* 检查是否已连接
|
||
*/
|
||
const isConnected = () => connectionState.value === 'connected';
|
||
|
||
return {
|
||
// 主要方法
|
||
buildWebSocket,
|
||
closeWebSocket,
|
||
sendMessage,
|
||
sendChatMessage,
|
||
|
||
// 状态查询
|
||
getConnectionState,
|
||
isConnected,
|
||
|
||
// 响应式状态(用于UI绑定)
|
||
connectionState: readonly(connectionState)
|
||
};
|
||
}); |