增加WebSocketClient

This commit is contained in:
JoyD
2025-10-22 22:09:49 +08:00
parent 5e54d94853
commit 21ce8a5706

View File

@@ -0,0 +1,252 @@
<script setup>import { ref, onUnmounted, watch } from 'vue';
// 定义组件的props
const props = defineProps({
url: {
type: String,
default: 'ws://localhost:8805'
},
protocols: {
type: Array,
default: () => ['device-web']
},
autoConnect: {
type: Boolean,
default: true
},
maxReconnectAttempts: {
type: Number,
default: 0
},
initialReconnectDelay: {
type: Number,
default: 1000
},
maxReconnectDelay: {
type: Number,
default: 30000
}
});
// 定义组件的emits
const emit = defineEmits([
'connect', // 连接成功时触发
'disconnect', // 断开连接时触发
'message', // 收到消息时触发
'error', // 发生错误时触发
'status-change' // 连接状态改变时触发
]);
// 内部状态管理
const ws = ref(null);
const isConnected = ref(false);
const reconnectAttempts = ref(0);
const reconnectDelay = ref(props.initialReconnectDelay);
const reconnectTimer = ref(null);
const manualDisconnect = ref(false);
const currentUrl = ref(props.url);
// 调试日志函数
const debugLog = (...args) => {
if (window.DEBUG) {
console.log('[WebSocketClient]', ...args);
}
};
// 更新连接状态并发出状态改变事件
const updateConnectionStatus = (connected) => {
if (isConnected.value !== connected) {
isConnected.value = connected;
emit('status-change', connected);
if (connected) {
// 连接成功时发出连接事件
emit('connect', currentUrl.value);
// 重置重连状态
reconnectAttempts.value = 0;
reconnectDelay.value = props.initialReconnectDelay;
} else {
ws.value = null;
}
}
};
// 取消当前的重连尝试
const cancelReconnect = () => {
if (reconnectTimer.value) {
clearTimeout(reconnectTimer.value);
reconnectTimer.value = null;
}
};
// 智能重连策略
const reconnect = () => {
// 如果是手动断开,不进行重连
if (manualDisconnect.value) {
return;
}
// 达到最大重连次数且最大重连次数不为0时停止重连
// 当maxReconnectAttempts为0时表示无限重试
if (props.maxReconnectAttempts > 0 && reconnectAttempts.value >= props.maxReconnectAttempts) {
debugLog('已达到最大重连次数');
emit('error', '已达到最大重连次数,请手动尝试重新连接');
return;
}
// 指数退避重连延迟
const delay = Math.min(reconnectDelay.value * Math.pow(2, reconnectAttempts.value), props.maxReconnectDelay);
// 调整日志显示当maxReconnectAttempts为0时显示"无限重试"模式
if (props.maxReconnectAttempts === 0) {
debugLog(`无限重试模式:将在${Math.round(delay/1000)}秒后尝试第${reconnectAttempts.value + 1}次重连`);
} else {
debugLog(`将在${Math.round(delay/1000)}秒后尝试第${reconnectAttempts.value + 1}/${props.maxReconnectAttempts}次重连`);
}
reconnectTimer.value = setTimeout(() => {
reconnectAttempts.value++;
connect();
}, delay);
};
// 连接WebSocket服务器
const connect = (url = null) => {
// 如果提供了新的URL则更新当前URL
if (url) {
currentUrl.value = url;
}
manualDisconnect.value = false;
cancelReconnect();
try {
debugLog(`尝试连接到: ${currentUrl.value}`);
ws.value = new WebSocket(currentUrl.value, props.protocols);
// 连接成功
ws.value.onopen = () => {
debugLog('连接成功');
updateConnectionStatus(true);
};
// 接收消息
ws.value.onmessage = (event) => {
debugLog('收到消息:', event.data);
emit('message', event.data);
};
// 连接关闭
ws.value.onclose = (event) => {
debugLog(`连接关闭: 代码=${event.code}, 原因=${event.reason}`);
updateConnectionStatus(false);
emit('disconnect', event.code, event.reason);
// 异常断开时自动重连
if (event.code === 1006) {
reconnect();
} else if (!manualDisconnect.value) {
// 非手动断开且非1006错误时也尝试重连
reconnect();
}
};
// 连接错误
ws.value.onerror = (error) => {
debugLog('WebSocket错误:', error);
emit('error', '连接错误,请检查服务器是否运行');
updateConnectionStatus(false);
// 错误时也尝试重连
if (!manualDisconnect.value) {
reconnect();
}
};
} catch (error) {
debugLog('连接失败:', error);
emit('error', '连接失败,请重试');
// 连接失败也尝试重连
if (!manualDisconnect.value) {
reconnect();
}
}
};
// 断开WebSocket连接
const disconnect = () => {
if (ws.value) {
manualDisconnect.value = true;
cancelReconnect();
ws.value.close();
updateConnectionStatus(false);
debugLog('已断开连接');
}
};
// 发送消息
const send = (message) => {
if (!message || !ws.value || !isConnected.value) {
debugLog('发送消息失败: 连接未建立或消息为空');
return false;
}
try {
ws.value.send(message);
debugLog('消息发送成功:', message);
return true;
} catch (error) {
debugLog('消息发送失败:', error);
emit('error', '消息发送失败');
return false;
}
};
// 切换连接状态
const toggleConnection = () => {
if (isConnected.value) {
disconnect();
} else {
connect();
}
};
// 监听props.url的变化
watch(() => props.url, (newUrl) => {
if (newUrl && newUrl !== currentUrl.value) {
// 如果当前已连接,则先断开连接
if (isConnected.value) {
disconnect();
// 延迟重新连接,确保旧连接完全关闭
setTimeout(() => {
connect(newUrl);
}, 500);
} else {
// 如果当前未连接直接更新URL
currentUrl.value = newUrl;
}
}
});
// 组件挂载时如果设置了autoConnect为true则自动连接
if (props.autoConnect) {
connect();
}
// 组件卸载时清理资源
onUnmounted(() => {
disconnect();
});
// 暴露方法给父组件使用
defineExpose({
connect,
disconnect,
send,
toggleConnection,
isConnected,
currentUrl
});
</script>
<template>
<!-- 这个组件没有UI界面元素 -->
</template>