393 lines
10 KiB
JavaScript
393 lines
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)
|
|||
|
|
};
|
|||
|
|
});
|