这是使用uniapp原生方法连接的WebSocket,所以需要跟后端沟通信息接收与发送的信息格式
import { ref, onMounted, onUnmounted } from 'vue';
export default function useUniWebSocket(
url : string,
onMessage : (res : any) => void,
heartbeatInterval = 30000,
) {
const socketTask = ref<UniApp.SocketTask | null>(null);
const isDisconnect = ref(false);
const isConnected = ref(false);
const reconnectAttempts = ref(0);
const maxReconnectAttempts = 5;
const topic = ref('/topic/global'); // 默认订阅主题
const subscriptionActive = ref(false); // 跟踪订阅状态
const heartbeatCount = ref(0); // 添加心跳计数器
const heartbeatStatus = ref('inactive'); // 'active' | 'inactive'
let heartbeatTimer : number | null = null;
let reconnectTimer : number | null = null;
// 清理资源
const cleanup = () => {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
reconnectAttempts.value = 0;
subscriptionActive.value = false;
heartbeatStatus.value = 'inactive';
};
// 修复后的心跳函数
const startHeartbeat = () => {
// 关键修复:清除现有定时器
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
heartbeatTimer = setInterval(() => {
if (socketTask.value && isConnected.value) {
socketTask.value.send({
data: JSON.stringify({
type: 'heartbeat',
timestamp: Date.now(),
count: heartbeatCount.value++ // 添加计数跟踪
}),
success: () => console.log('♥️ 心跳发送成功'),
fail: (err) => console.error('心跳发送失败:', err)
});
sendMessage('我发送信息了')
}
}, heartbeatInterval) as unknown as number;
heartbeatStatus.value = 'active';
};
// 初始化WebSocket连接
const connect = () => {
if (isConnected.value || isDisconnect.value) return;
console.log('🔌 WebSocket连接中...', url);
isConnected.value = false;
subscriptionActive.value = false;
// 关闭现有连接(如果存在)
if (socketTask.value) {
try {
socketTask.value.close({});
} catch (e) {
console.warn('关闭旧连接时出错:', e);
}
socketTask.value = null;
}
try {
socketTask.value = uni.connectSocket({
url,
success: () => console.log('🟢 WebSocket创建成功'),
fail: (err) => {
console.error('🔴 WebSocket创建失败:', err);
handleReconnect();
}
});
// 事件监听
socketTask.value.onOpen((res:any) => {
console.log('✅ WebSocket连接已打开回调',res);
console.log('✅ WebSocket连接已打开');
isConnected.value = true;
reconnectAttempts.value = 0;
startHeartbeat();
subscribeTopic(); // 连接成功后订阅主题
});
socketTask.value.onClose((res) => {
console.log('⛔ WebSocket连接关闭', res);
isConnected.value = false;
subscriptionActive.value = false;
cleanup();
if (!isDisconnect.value && reconnectAttempts.value < maxReconnectAttempts) {
handleReconnect();
}
});
socketTask.value.onError((error) => {
console.error('❌ WebSocket错误:', error);
isConnected.value = false;
handleReconnect();
});
socketTask.value.onMessage((res : { data : string }) => {
try {
console.log('📥 收到原始消息:', res.data);
// 尝试解析JSON
let data;
try {
data = JSON.parse(res.data);
} catch (e) {
// 如果解析失败,保持原始数据
data = res.data;
}
// 检查是否是订阅确认消息
if (data && data.type === 'subscribed' && data.topic === topic.value) {
console.log(`✅ 主题订阅确认: ${topic.value}`);
subscriptionActive.value = true;
return;
}
// 检查是否是心跳响应
if (data && data.type === 'heartbeat_ack') {
console.log('❤️🔥 收到心跳响应');
return;
}
// 传递给外部处理程序
onMessage(data);
} catch (e) {
console.error('❗ 消息处理失败:', e, '原始数据:', res.data);
}
});
} catch (error) {
console.error('创建WebSocket时异常:', error);
handleReconnect();
}
};
// 处理重连逻辑
const handleReconnect = () => {
if (isDisconnect.value || reconnectAttempts.value >= maxReconnectAttempts) {
console.warn('停止重连,已达最大尝试次数');
return;
}
cleanup();
reconnectAttempts.value++;
const delay = Math.min(3000 * reconnectAttempts.value, 15000);
console.log(`⌛ ${delay}ms 后尝试重连 (#${reconnectAttempts.value})`);
reconnectTimer = setTimeout(() => {
connect();
}, delay) as unknown as number;
};
// 订阅主题
const subscribeTopic = () => {
if (!socketTask.value || !isConnected.value) {
console.warn('WebSocket 尚未连接,无法订阅主题');
return false;
}
if (subscriptionActive.value) {
console.log('主题已订阅,无需重复订阅');
return true;
}
const subscriptionMessage = JSON.stringify({
type: 'subscribe',
topic: topic.value,
timestamp: Date.now()
});
socketTask.value.send({
data: subscriptionMessage,
success: (res:any) => {
console.log(`📤 订阅成功回调: ${res}`);
console.log(`📤 订阅请求已发送: ${topic.value}`);
},
fail: (err) => {
console.error(`❌ 订阅请求发送失败: ${topic.value}`, err);
}
});
return true;
};
// 更新订阅主题
const updateTopic = (newTopicValue : string) => {
topic.value = newTopicValue;
subscriptionActive.value = false; // 重置订阅状态
if (isConnected.value) {
subscribeTopic();
}
};
// 断开连接
const disconnect = () => {
console.log('主动断开WebSocket连接');
isDisconnect.value = true;
isConnected.value = false;
subscriptionActive.value = false;
if (socketTask.value) {
try {
// 发送取消订阅消息
if (subscriptionActive.value) {
const unsubscribeMessage = JSON.stringify({
type: 'unsubscribe',
topic: topic.value
});
socketTask.value.send({
data: unsubscribeMessage,
success: () => console.log(`取消订阅请求已发送: ${topic.value}`),
fail: (err) => console.error(`取消订阅请求发送失败: ${err}`)
});
}
// 关闭连接
socketTask.value.close({ reason: '用户主动断开' });
} catch (e) {
console.error('❌ 关闭连接时出错:', e);
}
socketTask.value = null;
}
cleanup();
};
// 发送消息
const sendMessage = (message : any) => {
console.log(message);
return new Promise<void>((resolve, reject) => {
if (!socketTask.value || !isConnected.value) {
reject('WebSocket未连接');
return;
}
// const payload = typeof message === 'string'
// ? message
// : JSON.stringify(message);
const payload = JSON.stringify({
type: 'custom',
topic: '/app',
timestamp: Date.now(),
content:message
});
socketTask.value.send({
data: payload,
success: () => resolve(),
fail: (err) => reject(err)
});
});
};
// 生命周期钩子
onMounted(() => {
isDisconnect.value = false;
connect();
});
onUnmounted(() => {
disconnect();
});
return {
socketTask,
isConnected,
subscriptionActive,
connect,
disconnect,
sendMessage,
subscribe: subscribeTopic,
updateTopic,
reconnectAttempts
};
}