A:前端代码提交
This commit is contained in:
1 parent
e2f0d78d97
commit
a87d58d76c
11 files changed
+826
-1128
No files matched your search
@@ -1,17 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, ref } from 'vue'
|
import { onMounted, type Ref, ref } from 'vue'
|
||||||
import type { CommandType } from '@/types/command'
|
import type { CommandType } from '@/types/command'
|
||||||
import { logN } from '@/utils/logHelper/logUtils.ts'
|
import { logN } from '@/utils/logHelper/logUtils.ts'
|
||||||
import CSharpBridgeV2 from '@/correspond/CSharpBridgeV2'
|
import CSharpBridgeV2 from '@/correspond/CSharpBridgeV2'
|
||||||
|
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||||
|
|
||||||
// 传递参数
|
// 传递参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pro: Array<CommandType>,
|
pro: Array<CommandType>,
|
||||||
|
isUseWebSocket: Boolean,
|
||||||
})
|
})
|
||||||
// 使用 ref 使数据变成响应式数据
|
// 使用 ref 使数据变成响应式数据
|
||||||
const modules: Ref<CommandType[]> = ref([...(props.pro || [])])
|
const modules: Ref<CommandType[]> = ref([...(props.pro || [])])
|
||||||
const bridge = CSharpBridgeV2.getBridge()
|
const bridge = CSharpBridgeV2.getBridge()
|
||||||
|
|
||||||
|
const useWebSocket = useWebSocketStore()
|
||||||
|
|
||||||
|
const newVar = (data) => {
|
||||||
|
logN.warning('后端返回的参数', module.name, data)
|
||||||
|
module.result = JSON.stringify(data, null, 2)
|
||||||
|
|
||||||
|
useWebSocket.unsubscribe(module.name)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 指令触发
|
* 指令触发
|
||||||
* @param module
|
* @param module
|
||||||
@@ -19,15 +29,20 @@ const bridge = CSharpBridgeV2.getBridge()
|
|||||||
const invokeCommand = async (module: CommandType) => {
|
const invokeCommand = async (module: CommandType) => {
|
||||||
const params = Object.fromEntries(module.params.map((param) => [param.name, param.value]))
|
const params = Object.fromEntries(module.params.map((param) => [param.name, param.value]))
|
||||||
logN.success('前端发送的参数', module.name, params)
|
logN.success('前端发送的参数', module.name, params)
|
||||||
|
if (props.isUseWebSocket) {
|
||||||
|
useWebSocket.registerHandler(module.name, newVar)
|
||||||
|
|
||||||
try {
|
useWebSocket.send('hello')
|
||||||
bridge.send(module.name, params, (data) => {
|
} else {
|
||||||
logN.warning('后端返回的参数', module.name, data)
|
try {
|
||||||
module.result = JSON.stringify(data, null, 2)
|
bridge.send(module.name, params, (data) => {
|
||||||
})
|
logN.warning('后端返回的参数', module.name, data)
|
||||||
} catch (error) {
|
module.result = JSON.stringify(data, null, 2)
|
||||||
console.error(err)
|
})
|
||||||
module.result = `Error: ${error}`
|
} catch (error) {
|
||||||
|
console.error(err)
|
||||||
|
module.result = `Error: ${error}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -35,7 +50,11 @@ const invokeCommand = async (module: CommandType) => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-6 font-sans">
|
<div class="p-6 font-sans">
|
||||||
<div class="flex flex-wrap gap-6 w-full justify-center">
|
<div class="flex flex-wrap gap-6 w-full justify-center">
|
||||||
<div v-for="(module, index) in modules" :key="index" class="w-full border shadow-lg p-4 shadow-md rounded-md">
|
<div
|
||||||
|
v-for="(module, index) in modules"
|
||||||
|
:key="index"
|
||||||
|
class="w-full border shadow-lg p-4 shadow-md rounded-md"
|
||||||
|
>
|
||||||
<h2 class="text-lg font-semibold mb-2 select-text pointer-events-auto">
|
<h2 class="text-lg font-semibold mb-2 select-text pointer-events-auto">
|
||||||
{{ module.name }}
|
{{ module.name }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
/**
|
|
||||||
* Time:2025/5/25 19:16 09
|
|
||||||
* Name:useFirewallRules.ts
|
|
||||||
* Path:src/composables
|
|
||||||
* ProjectName:ClientApp
|
|
||||||
* Author:charlatans
|
|
||||||
*
|
|
||||||
* Il n'ya qu'un héroïsme au monde :
|
|
||||||
* c'est de voir le monde tel qu'il est et de l'aimer.
|
|
||||||
*/
|
|
||||||
// composables/useFirewallRules.ts - 防火墙规则管理
|
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
|
||||||
import { useWebSocket } from './useWebSocket'
|
|
||||||
import type { FirewallRuleMessage } from '@/types/websocket'
|
|
||||||
|
|
||||||
interface FirewallRule {
|
|
||||||
name: string
|
|
||||||
action: string
|
|
||||||
protocol: string
|
|
||||||
port: string
|
|
||||||
enabled: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFirewallRules() {
|
|
||||||
const { sendCommand, onMessage, isConnected } = useWebSocket()
|
|
||||||
|
|
||||||
const firewallRules = ref<FirewallRule[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const cleanupFunctions: (() => void)[] = []
|
|
||||||
|
|
||||||
// 获取防火墙规则
|
|
||||||
const fetchFirewallRules = async () => {
|
|
||||||
if (!isConnected.value) return
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
await sendCommand('getFirewallRules')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取防火墙规则失败:', error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加防火墙规则
|
|
||||||
const addFirewallRule = async (rule: Omit<FirewallRule, 'enabled'>) => {
|
|
||||||
if (!isConnected.value) return false
|
|
||||||
|
|
||||||
const ruleData: FirewallRuleMessage = {
|
|
||||||
Type: 'firewallRule',
|
|
||||||
RuleName: rule.name,
|
|
||||||
Action: rule.action,
|
|
||||||
Protocol: rule.protocol,
|
|
||||||
Port: rule.port,
|
|
||||||
Enabled: true,
|
|
||||||
Timestamp: new Date().toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
return await sendCommand('addFirewallRule', ruleData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理防火墙规则响应
|
|
||||||
const handleFirewallRulesResponse = (message: any) => {
|
|
||||||
if (message.Success && message.Data && Array.isArray(message.Data)) {
|
|
||||||
firewallRules.value = message.Data.map((item: FirewallRuleMessage) => ({
|
|
||||||
name: item.RuleName,
|
|
||||||
action: item.Action,
|
|
||||||
protocol: item.Protocol,
|
|
||||||
port: item.Port,
|
|
||||||
enabled: item.Enabled
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 监听防火墙规则响应
|
|
||||||
cleanupFunctions.push(
|
|
||||||
onMessage('firewallRules', handleFirewallRulesResponse)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 监听防火墙规则更新
|
|
||||||
cleanupFunctions.push(
|
|
||||||
onMessage('firewallRulesUpdate', handleFirewallRulesResponse)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 监听添加规则响应
|
|
||||||
cleanupFunctions.push(
|
|
||||||
onMessage('addFirewallRuleResponse', (message) => {
|
|
||||||
if (message.Success) {
|
|
||||||
console.log('防火墙规则添加成功')
|
|
||||||
// 规则会通过广播更新自动刷新
|
|
||||||
} else {
|
|
||||||
console.error('防火墙规则添加失败:', message.Message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// 连接成功后获取规则
|
|
||||||
cleanupFunctions.push(
|
|
||||||
onMessage('welcome', () => {
|
|
||||||
fetchFirewallRules()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
cleanupFunctions.forEach(cleanup => cleanup())
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
firewallRules,
|
|
||||||
loading,
|
|
||||||
fetchFirewallRules,
|
|
||||||
addFirewallRule
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +1,427 @@
|
|||||||
|
|
||||||
// composables/useWebSocket.ts
|
// composables/useWebSocket.ts
|
||||||
import { onMounted, onUnmounted, watch } from 'vue'
|
import { ref, reactive, onUnmounted, nextTick } from 'vue';
|
||||||
import { useWebSocketStore } from '@/stores/websocket'
|
import type {
|
||||||
import type { ConnectionState } from '@/types/websocket'
|
WebSocketMessage,
|
||||||
|
WebSocketResponse,
|
||||||
|
WebSocketState,
|
||||||
|
EventListener,
|
||||||
|
SubscriptionCallback,
|
||||||
|
RequestOptions,
|
||||||
|
WebSocketConfig,
|
||||||
|
SubscriptionMessage,
|
||||||
|
NetworkStatus,
|
||||||
|
DatabaseQueryRequest,
|
||||||
|
ConfigUpdateRequest,
|
||||||
|
CommandRequest
|
||||||
|
} from '@/types/websocket';
|
||||||
|
|
||||||
export interface UseWebSocketOptions {
|
class WebSocketManager {
|
||||||
url: string
|
private ws: WebSocket | null = null;
|
||||||
autoConnect?: boolean
|
private url: string = '';
|
||||||
autoRefresh?: boolean
|
private config: WebSocketConfig;
|
||||||
refreshInterval?: number
|
private reconnectAttempts: number = 0;
|
||||||
onConnected?: () => void
|
private reconnectTimer: number | null = null;
|
||||||
onDisconnected?: () => void
|
private heartbeatTimer: number | null = null;
|
||||||
onError?: (error: string) => void
|
|
||||||
onStateChange?: (state: ConnectionState) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useWebSocket = (options: UseWebSocketOptions) => {
|
// 状态管理
|
||||||
const store = useWebSocketStore()
|
private state = ref<WebSocketState>(WebSocketState.DISCONNECTED);
|
||||||
let refreshTimer: NodeJS.Timeout | null = null
|
private isReconnecting = ref(false);
|
||||||
|
|
||||||
const {
|
// 事件监听器
|
||||||
url,
|
private eventListeners = new Map<string, Set<EventListener>>();
|
||||||
autoConnect = true,
|
|
||||||
autoRefresh = true,
|
|
||||||
refreshInterval = 5000,
|
|
||||||
onConnected,
|
|
||||||
onDisconnected,
|
|
||||||
onError,
|
|
||||||
onStateChange
|
|
||||||
} = options
|
|
||||||
|
|
||||||
// 启动自动刷新
|
// 订阅管理
|
||||||
const startAutoRefresh = (): void => {
|
private subscriptions = new Map<string, Set<SubscriptionCallback>>();
|
||||||
if (!autoRefresh || refreshTimer) return
|
private activeSubscriptions = new Set<string>();
|
||||||
|
|
||||||
refreshTimer = setInterval(async () => {
|
// 请求-响应管理
|
||||||
if (store.isConnected) {
|
private pendingRequests = new Map<string, {
|
||||||
try {
|
resolve: (value: WebSocketResponse) => void;
|
||||||
await store.fetchSystemInfo()
|
reject: (error: Error) => void;
|
||||||
} catch (error) {
|
timeout: number;
|
||||||
console.error('Auto refresh failed:', error)
|
}>();
|
||||||
}
|
|
||||||
}
|
// 消息队列(连接断开时暂存消息)
|
||||||
}, refreshInterval)
|
private messageQueue: WebSocketMessage[] = [];
|
||||||
|
|
||||||
|
constructor(config: WebSocketConfig = {}) {
|
||||||
|
this.config = {
|
||||||
|
reconnectInterval: 3000,
|
||||||
|
maxReconnectAttempts: 5,
|
||||||
|
heartbeatInterval: 30000,
|
||||||
|
requestTimeout: 10000,
|
||||||
|
...config
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止自动刷新
|
// 获取状态的响应式引用
|
||||||
const stopAutoRefresh = (): void => {
|
get connectionState() {
|
||||||
if (refreshTimer) {
|
return this.state;
|
||||||
clearInterval(refreshTimer)
|
}
|
||||||
refreshTimer = null
|
|
||||||
|
get reconnecting() {
|
||||||
|
return this.isReconnecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接WebSocket
|
||||||
|
async connect(url: string): Promise<void> {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
console.warn('WebSocket already connected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
this.state.value = WebSocketState.CONNECTING;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
this.ws = new WebSocket(url);
|
||||||
|
|
||||||
|
this.ws.onopen = () => {
|
||||||
|
console.log('WebSocket connected');
|
||||||
|
this.state.value = WebSocketState.CONNECTED;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
this.isReconnecting.value = false;
|
||||||
|
|
||||||
|
// 处理队列中的消息
|
||||||
|
this.processMessageQueue();
|
||||||
|
|
||||||
|
// 重新订阅之前的主题
|
||||||
|
this.resubscribeTopics();
|
||||||
|
|
||||||
|
// 启动心跳
|
||||||
|
this.startHeartbeat();
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
|
this.handleMessage(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = (event) => {
|
||||||
|
console.log('WebSocket closed', event.code, event.reason);
|
||||||
|
this.state.value = WebSocketState.DISCONNECTED;
|
||||||
|
this.stopHeartbeat();
|
||||||
|
|
||||||
|
if (!event.wasClean && this.reconnectAttempts < this.config.maxReconnectAttempts!) {
|
||||||
|
this.attemptReconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onerror = (error) => {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
this.state.value = WebSocketState.ERROR;
|
||||||
|
reject(new Error('WebSocket connection failed'));
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.state.value = WebSocketState.ERROR;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开连接
|
||||||
|
disconnect(): void {
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
clearTimeout(this.reconnectTimer);
|
||||||
|
this.reconnectTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stopHeartbeat();
|
||||||
|
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close(1000, 'Manual disconnect');
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.value = WebSocketState.DISCONNECTED;
|
||||||
|
this.isReconnecting.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
private send(message: WebSocketMessage): void {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
this.ws.send(JSON.stringify(message));
|
||||||
|
} else {
|
||||||
|
// 连接断开时将消息加入队列
|
||||||
|
this.messageQueue.push(message);
|
||||||
|
console.warn('WebSocket not connected, message queued');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手动连接
|
// 发送请求并等待响应
|
||||||
const connect = async (): Promise<void> => {
|
async request<TRequest = unknown, TResponse = unknown>(
|
||||||
|
command: string,
|
||||||
|
data: TRequest,
|
||||||
|
options: RequestOptions = {}
|
||||||
|
): Promise<TResponse> {
|
||||||
|
const requestId = this.generateRequestId();
|
||||||
|
const timeout = options.timeout || this.config.requestTimeout!;
|
||||||
|
|
||||||
|
const message: WebSocketMessage<TRequest> = {
|
||||||
|
command,
|
||||||
|
data,
|
||||||
|
requestId,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 设置超时
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
this.pendingRequests.delete(requestId);
|
||||||
|
reject(new Error(`Request timeout: ${command}`));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
// 存储请求回调
|
||||||
|
this.pendingRequests.set(requestId, {
|
||||||
|
resolve: (response: WebSocketResponse<TResponse>) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (response.success) {
|
||||||
|
resolve(response.data);
|
||||||
|
} else {
|
||||||
|
reject(new Error(response.error || 'Request failed'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reject: (error: Error) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
timeout: timeoutId
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
this.send(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅主题
|
||||||
|
subscribe<T = unknown>(topic: string, callback: SubscriptionCallback<T>): () => void {
|
||||||
|
// 添加回调到订阅列表
|
||||||
|
if (!this.subscriptions.has(topic)) {
|
||||||
|
this.subscriptions.set(topic, new Set());
|
||||||
|
}
|
||||||
|
this.subscriptions.get(topic)!.add(callback as SubscriptionCallback);
|
||||||
|
|
||||||
|
// 如果是新订阅,发送订阅消息到服务器
|
||||||
|
if (!this.activeSubscriptions.has(topic)) {
|
||||||
|
this.activeSubscriptions.add(topic);
|
||||||
|
const subscribeMessage: WebSocketMessage<SubscriptionMessage> = {
|
||||||
|
command: 'subscription',
|
||||||
|
data: { type: 'subscribe', topic }
|
||||||
|
};
|
||||||
|
this.send(subscribeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回取消订阅函数
|
||||||
|
return () => {
|
||||||
|
const callbacks = this.subscriptions.get(topic);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.delete(callback as SubscriptionCallback);
|
||||||
|
if (callbacks.size === 0) {
|
||||||
|
this.subscriptions.delete(topic);
|
||||||
|
this.activeSubscriptions.delete(topic);
|
||||||
|
|
||||||
|
// 发送取消订阅消息
|
||||||
|
const unsubscribeMessage: WebSocketMessage<SubscriptionMessage> = {
|
||||||
|
command: 'subscription',
|
||||||
|
data: { type: 'unsubscribe', topic }
|
||||||
|
};
|
||||||
|
this.send(unsubscribeMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加事件监听器
|
||||||
|
on<T = unknown>(event: string, listener: EventListener<T>): () => void {
|
||||||
|
if (!this.eventListeners.has(event)) {
|
||||||
|
this.eventListeners.set(event, new Set());
|
||||||
|
}
|
||||||
|
this.eventListeners.get(event)!.add(listener as EventListener);
|
||||||
|
|
||||||
|
// 返回移除监听器函数
|
||||||
|
return () => {
|
||||||
|
const listeners = this.eventListeners.get(event);
|
||||||
|
if (listeners) {
|
||||||
|
listeners.delete(listener as EventListener);
|
||||||
|
if (listeners.size === 0) {
|
||||||
|
this.eventListeners.delete(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理接收到的消息
|
||||||
|
private handleMessage(data: string): void {
|
||||||
try {
|
try {
|
||||||
await store.initialize(url)
|
const message: WebSocketResponse = JSON.parse(data);
|
||||||
if (autoRefresh) {
|
|
||||||
startAutoRefresh()
|
// 处理请求响应
|
||||||
|
if (message.requestId && this.pendingRequests.has(message.requestId)) {
|
||||||
|
const request = this.pendingRequests.get(message.requestId)!;
|
||||||
|
this.pendingRequests.delete(message.requestId);
|
||||||
|
request.resolve(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理订阅消息
|
||||||
|
if (message.command.startsWith('subscription:')) {
|
||||||
|
const topic = message.command.replace('subscription:', '');
|
||||||
|
const callbacks = this.subscriptions.get(topic);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.forEach(callback => callback(message.data, topic));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理普通事件
|
||||||
|
const listeners = this.eventListeners.get(message.command);
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach(listener => listener(message.data));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Connection failed:', error)
|
console.error('Failed to parse WebSocket message:', error);
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手动断开连接
|
// 重连逻辑
|
||||||
const disconnect = (): void => {
|
private attemptReconnect(): void {
|
||||||
stopAutoRefresh()
|
if (this.isReconnecting.value) return;
|
||||||
store.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听连接状态变化
|
this.isReconnecting.value = true;
|
||||||
watch(
|
this.reconnectAttempts++;
|
||||||
() => store.connectionState,
|
|
||||||
(newState, oldState) => {
|
|
||||||
onStateChange?.(newState)
|
|
||||||
|
|
||||||
if (newState === 'connected' && oldState !== 'connected') {
|
console.log(`Attempting to reconnect... (${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
|
||||||
onConnected?.()
|
|
||||||
if (autoRefresh) {
|
this.reconnectTimer = setTimeout(async () => {
|
||||||
startAutoRefresh()
|
try {
|
||||||
|
await this.connect(this.url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Reconnection failed:', error);
|
||||||
|
if (this.reconnectAttempts < this.config.maxReconnectAttempts!) {
|
||||||
|
this.attemptReconnect();
|
||||||
|
} else {
|
||||||
|
this.isReconnecting.value = false;
|
||||||
|
console.error('Max reconnection attempts reached');
|
||||||
}
|
}
|
||||||
} else if (newState === 'disconnected' && oldState === 'connected') {
|
|
||||||
stopAutoRefresh()
|
|
||||||
onDisconnected?.()
|
|
||||||
}
|
}
|
||||||
},
|
}, this.config.reconnectInterval);
|
||||||
{ immediate: true }
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// 监听错误
|
// 处理消息队列
|
||||||
watch(
|
private processMessageQueue(): void {
|
||||||
() => store.lastError,
|
while (this.messageQueue.length > 0) {
|
||||||
(error) => {
|
const message = this.messageQueue.shift()!;
|
||||||
if (error) {
|
this.send(message);
|
||||||
onError?.(error)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新订阅主题
|
||||||
|
private resubscribeTopics(): void {
|
||||||
|
this.activeSubscriptions.forEach(topic => {
|
||||||
|
const subscribeMessage: WebSocketMessage<SubscriptionMessage> = {
|
||||||
|
command: 'subscription',
|
||||||
|
data: { type: 'subscribe', topic }
|
||||||
|
};
|
||||||
|
this.send(subscribeMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 心跳机制
|
||||||
|
private startHeartbeat(): void {
|
||||||
|
this.heartbeatTimer = setInterval(() => {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
this.send({ command: 'ping', data: null });
|
||||||
}
|
}
|
||||||
|
}, this.config.heartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopHeartbeat(): void {
|
||||||
|
if (this.heartbeatTimer) {
|
||||||
|
clearInterval(this.heartbeatTimer);
|
||||||
|
this.heartbeatTimer = null;
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
// 生成请求ID
|
||||||
if (autoConnect) {
|
private generateRequestId(): string {
|
||||||
connect().catch(console.error)
|
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
// 清理资源
|
||||||
disconnect()
|
destroy(): void {
|
||||||
})
|
this.disconnect();
|
||||||
|
this.eventListeners.clear();
|
||||||
return {
|
this.subscriptions.clear();
|
||||||
// 状态
|
this.activeSubscriptions.clear();
|
||||||
connectionState: store.connectionState,
|
this.pendingRequests.clear();
|
||||||
isConnected: store.isConnected,
|
this.messageQueue.length = 0;
|
||||||
systemInfo: store.systemInfo,
|
|
||||||
networkStatus: store.networkStatus,
|
|
||||||
firewallRules: store.firewallRules,
|
|
||||||
lastError: store.lastError,
|
|
||||||
isLoading: store.isLoading,
|
|
||||||
connectionCount: store.connectionCount,
|
|
||||||
activeNetworkInterfaces: store.activeNetworkInterfaces,
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
fetchSystemInfo: store.fetchSystemInfo,
|
|
||||||
fetchNetworkStatus: store.fetchNetworkStatus,
|
|
||||||
fetchFirewallRules: store.fetchFirewallRules,
|
|
||||||
addFirewallRule: store.addFirewallRule,
|
|
||||||
updateFirewallRule: store.updateFirewallRule,
|
|
||||||
deleteFirewallRule: store.deleteFirewallRule,
|
|
||||||
toggleFirewallRule: store.toggleFirewallRule,
|
|
||||||
clearError: store.clearError,
|
|
||||||
startAutoRefresh,
|
|
||||||
stopAutoRefresh
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单例模式的WebSocket管理器
|
||||||
|
let wsManager: WebSocketManager | null = null;
|
||||||
|
|
||||||
|
export function useWebSocket(config?: WebSocketConfig) {
|
||||||
|
if (!wsManager) {
|
||||||
|
wsManager = new WebSocketManager(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务方法封装
|
||||||
|
const api = {
|
||||||
|
// 数据库查询
|
||||||
|
async queryDatabase<T = unknown>(request: DatabaseQueryRequest): Promise<T> {
|
||||||
|
return wsManager!.request<DatabaseQueryRequest, T>('database:query', request);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 配置更新
|
||||||
|
async updateConfig(request: ConfigUpdateRequest): Promise<void> {
|
||||||
|
return wsManager!.request<ConfigUpdateRequest, void>('config:update', request);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
async executeCommand<T = unknown>(request: CommandRequest): Promise<T> {
|
||||||
|
return wsManager!.request<CommandRequest, T>('command:execute', request);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 订阅网络状态
|
||||||
|
subscribeNetworkStatus(callback: SubscriptionCallback<NetworkStatus>): () => void {
|
||||||
|
return wsManager!.subscribe<NetworkStatus>('network:status', callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 订阅连接信息
|
||||||
|
subscribeConnectionInfo(callback: SubscriptionCallback): () => void {
|
||||||
|
return wsManager!.subscribe('network:connections', callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 管理器实例
|
||||||
|
manager: wsManager,
|
||||||
|
// 状态
|
||||||
|
state: wsManager.connectionState,
|
||||||
|
reconnecting: wsManager.reconnecting,
|
||||||
|
// 核心方法
|
||||||
|
connect: wsManager.connect.bind(wsManager),
|
||||||
|
disconnect: wsManager.disconnect.bind(wsManager),
|
||||||
|
request: wsManager.request.bind(wsManager),
|
||||||
|
subscribe: wsManager.subscribe.bind(wsManager),
|
||||||
|
on: wsManager.on.bind(wsManager),
|
||||||
|
// 业务API
|
||||||
|
...api
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在组件中使用的Composable
|
||||||
|
export function useWebSocketInComponent(config?: WebSocketConfig) {
|
||||||
|
const ws = useWebSocket(config);
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 注意:这里不要调用destroy,因为是单例模式
|
||||||
|
// 其他组件可能还在使用
|
||||||
|
});
|
||||||
|
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
/**
|
|
||||||
* Time:2025/5/25 20:42 29
|
|
||||||
* Name:useWebSocketEnhanced.ts
|
|
||||||
* Path:src/composables
|
|
||||||
* ProjectName:ClientApp
|
|
||||||
* Author:charlatans
|
|
||||||
*
|
|
||||||
* Il n'ya qu'un héroïsme au monde :
|
|
||||||
* c'est de voir le monde tel qu'il est et de l'aimer.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// composables/useWebSocketEnhanced.ts - 增强的WebSocket管理
|
|
||||||
import { ref, onUnmounted, watch } from 'vue';
|
|
||||||
import { useWebSocketStore } from '@/stores/websocketStore';
|
|
||||||
import { WebSocketRetryManager } from '@/utils/websocketHelpers';
|
|
||||||
import type { NetworkStatus, ConnectionInfo, FirewallRule, WebSocketMessage, WebSocketCommand } from '@/types/websocket';
|
|
||||||
|
|
||||||
export class EnhancedWebSocketManager {
|
|
||||||
private ws: WebSocket | null = null;
|
|
||||||
private retryManager = new WebSocketRetryManager();
|
|
||||||
private pingTimer: number | null = null;
|
|
||||||
private dataRefreshTimer: number | null = null;
|
|
||||||
private messageHandlers = new Map<string, Function[]>();
|
|
||||||
private pendingCommands = new Map<string, { resolve: Function; reject: Function; timeout: number }>();
|
|
||||||
private store = useWebSocketStore();
|
|
||||||
|
|
||||||
constructor(private url: string = 'ws://localhost:8080') {
|
|
||||||
this.setupEventHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(): Promise<void> {
|
|
||||||
if (this.ws?.readyState === WebSocket.OPEN) return;
|
|
||||||
|
|
||||||
this.store.updateConnectionState(false, true);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
this.ws = new WebSocket(this.url);
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
console.log('WebSocket connected successfully');
|
|
||||||
this.store.updateConnectionState(true, false, 'excellent');
|
|
||||||
this.retryManager.reset();
|
|
||||||
this.startPing();
|
|
||||||
this.startDataRefresh();
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
|
||||||
this.handleMessage(event.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onclose = (event) => {
|
|
||||||
console.log('WebSocket closed:', event.code, event.reason);
|
|
||||||
this.store.updateConnectionState(false, false);
|
|
||||||
this.stopPing();
|
|
||||||
this.stopDataRefresh();
|
|
||||||
|
|
||||||
if (event.code !== 1000 && this.retryManager.shouldRetry()) {
|
|
||||||
this.scheduleReconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onerror = (error) => {
|
|
||||||
console.error('WebSocket error:', error);
|
|
||||||
this.store.setError('WebSocket连接错误');
|
|
||||||
this.store.updateConnectionState(false, false);
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.store.updateConnectionState(false, false);
|
|
||||||
this.store.setError('无法创建WebSocket连接');
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
this.stopPing();
|
|
||||||
this.stopDataRefresh();
|
|
||||||
this.retryManager.reset();
|
|
||||||
|
|
||||||
// 清理待处理的命令
|
|
||||||
this.pendingCommands.forEach(({ reject, timeout }) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject(new Error('Connection closed'));
|
|
||||||
});
|
|
||||||
this.pendingCommands.clear();
|
|
||||||
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close(1000, 'User initiated disconnect');
|
|
||||||
this.ws = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.store.updateConnectionState(false, false, 'disconnected');
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendCommand<T = unknown>(action: WebSocketCommand['action'], data?: unknown): Promise<T> {
|
|
||||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
||||||
throw new Error('WebSocket not connected');
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandId = this.generateId();
|
|
||||||
const command: WebSocketCommand = { action, data, id: commandId };
|
|
||||||
const message: WebSocketMessage = {
|
|
||||||
type: 'command',
|
|
||||||
data: command,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
id: commandId
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// 设置超时
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.pendingCommands.delete(commandId);
|
|
||||||
reject(new Error(`Command timeout: ${action}`));
|
|
||||||
}, 15000);
|
|
||||||
|
|
||||||
// 存储待处理命令
|
|
||||||
this.pendingCommands.set(commandId, { resolve, reject, timeout });
|
|
||||||
|
|
||||||
// 发送命令
|
|
||||||
try {
|
|
||||||
this.ws!.send(JSON.stringify(message));
|
|
||||||
} catch (error) {
|
|
||||||
this.pendingCommands.delete(commandId);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleMessage(data: string) {
|
|
||||||
try {
|
|
||||||
const message: WebSocketMessage = JSON.parse(data);
|
|
||||||
|
|
||||||
switch (message.type) {
|
|
||||||
case 'network_status':
|
|
||||||
this.store.updateNetworkStatus(message.data);
|
|
||||||
this.emit('network_status', message.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'connection_info':
|
|
||||||
this.store.updateConnections(message.data);
|
|
||||||
this.emit('connection_info', message.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'firewall_rules':
|
|
||||||
this.store.updateFirewallRules(message.data);
|
|
||||||
this.emit('firewall_rules', message.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'response':
|
|
||||||
this.handleCommandResponse(message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'error':
|
|
||||||
this.store.setError(message.data.message || '服务器错误');
|
|
||||||
this.emit('error', message.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
this.emit('message', message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse WebSocket message:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleCommandResponse(message: WebSocketMessage) {
|
|
||||||
if (!message.id) return;
|
|
||||||
|
|
||||||
const pending = this.pendingCommands.get(message.id);
|
|
||||||
if (pending) {
|
|
||||||
clearTimeout(pending.timeout);
|
|
||||||
this.pendingCommands.delete(message.id);
|
|
||||||
|
|
||||||
if (message.data.success) {
|
|
||||||
pending.resolve(message.data.data);
|
|
||||||
} else {
|
|
||||||
pending.reject(new Error(message.data.error || 'Command failed'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleReconnect() {
|
|
||||||
const delay = this.retryManager.getNextDelay();
|
|
||||||
console.log(`Reconnecting in ${delay}ms (attempt ${this.retryManager.getCurrentAttempt()})`);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.connect().catch(console.error);
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private startPing() {
|
|
||||||
this.pingTimer = setInterval(() => {
|
|
||||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
||||||
this.sendPing().catch(() => {
|
|
||||||
this.store.updateConnectionState(true, false, 'poor');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopPing() {
|
|
||||||
if (this.pingTimer) {
|
|
||||||
clearInterval(this.pingTimer);
|
|
||||||
this.pingTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private startDataRefresh() {
|
|
||||||
// 定期刷新数据
|
|
||||||
this.dataRefreshTimer = setInterval(() => {
|
|
||||||
this.refreshAllData().catch(console.error);
|
|
||||||
}, 5000); // 每5秒刷新一次
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopDataRefresh() {
|
|
||||||
if (this.dataRefreshTimer) {
|
|
||||||
clearInterval(this.dataRefreshTimer);
|
|
||||||
this.dataRefreshTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendPing() {
|
|
||||||
const message: WebSocketMessage = {
|
|
||||||
type: 'command',
|
|
||||||
data: { action: 'ping' },
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
||||||
this.ws.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async refreshAllData() {
|
|
||||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Promise.all([
|
|
||||||
this.sendCommand('get_network_status'),
|
|
||||||
this.sendCommand('get_connections'),
|
|
||||||
this.sendCommand('get_firewall_rules')
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to refresh data:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupEventHandlers() {
|
|
||||||
// 监听store状态变化
|
|
||||||
watch(() => this.store.isConnected, (connected) => {
|
|
||||||
if (connected) {
|
|
||||||
this.refreshAllData();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 事件系统
|
|
||||||
on(event: string, handler: Function) {
|
|
||||||
if (!this.messageHandlers.has(event)) {
|
|
||||||
this.messageHandlers.set(event, []);
|
|
||||||
}
|
|
||||||
this.messageHandlers.get(event)!.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
off(event: string, handler: Function) {
|
|
||||||
const handlers = this.messageHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
const index = handlers.indexOf(handler);
|
|
||||||
if (index > -1) {
|
|
||||||
handlers.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private emit(event: string, data?: unknown) {
|
|
||||||
const handlers = this.messageHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
handlers.forEach(handler => handler(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateId(): string {
|
|
||||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// API方法
|
|
||||||
async getNetworkStatus(): Promise<NetworkStatus[]> {
|
|
||||||
return this.sendCommand('get_network_status');
|
|
||||||
}
|
|
||||||
|
|
||||||
async getConnections(): Promise<ConnectionInfo[]> {
|
|
||||||
return this.sendCommand('get_connections');
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFirewallRules(): Promise<FirewallRule[]> {
|
|
||||||
return this.sendCommand('get_firewall_rules');
|
|
||||||
}
|
|
||||||
|
|
||||||
async addFirewallRule(rule: Omit<FirewallRule, 'id'>): Promise<void> {
|
|
||||||
return this.sendCommand('add_firewall_rule', rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeFirewallRule(ruleId: string): Promise<void> {
|
|
||||||
return this.sendCommand('remove_firewall_rule', { id: ruleId });
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleFirewallRule(ruleId: string): Promise<void> {
|
|
||||||
return this.sendCommand('toggle_firewall_rule', { id: ruleId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局WebSocket管理器实例
|
|
||||||
let wsManager: EnhancedWebSocketManager | null = null;
|
|
||||||
|
|
||||||
export function useWebSocketEnhanced(url?: string) {
|
|
||||||
if (!wsManager) {
|
|
||||||
wsManager = new EnhancedWebSocketManager(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = useWebSocketStore();
|
|
||||||
|
|
||||||
// 自动连接
|
|
||||||
if (!store.isConnected && !store.isConnecting) {
|
|
||||||
wsManager.connect().catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
// 组件卸载时不断开连接,因为是单例
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
// 从store获取状态
|
|
||||||
...store,
|
|
||||||
|
|
||||||
// WebSocketManager方法
|
|
||||||
connect: () => wsManager!.connect(),
|
|
||||||
disconnect: () => wsManager!.disconnect(),
|
|
||||||
sendCommand: (action: WebSocketCommand['action'], data?: unknown) => wsManager!.sendCommand(action, data),
|
|
||||||
|
|
||||||
// API方法
|
|
||||||
getNetworkStatus: () => wsManager!.getNetworkStatus(),
|
|
||||||
getConnections: () => wsManager!.getConnections(),
|
|
||||||
getFirewallRules: () => wsManager!.getFirewallRules(),
|
|
||||||
addFirewallRule: (rule: Omit<FirewallRule, 'id'>) => wsManager!.addFirewallRule(rule),
|
|
||||||
removeFirewallRule: (ruleId: string) => wsManager!.removeFirewallRule(ruleId),
|
|
||||||
toggleFirewallRule: (ruleId: string) => wsManager!.toggleFirewallRule(ruleId),
|
|
||||||
|
|
||||||
// 事件监听
|
|
||||||
on: (event: string, handler: Function) => wsManager!.on(event, handler),
|
|
||||||
off: (event: string, handler: Function) => wsManager!.off(event, handler),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// plugins/websocket.ts - Vue插件
|
|
||||||
import type { App } from 'vue';
|
|
||||||
import { EnhancedWebSocketManager } from '@/composables/useWebSocketEnhanced';
|
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
|
||||||
interface ComponentCustomProperties {
|
|
||||||
$ws: EnhancedWebSocketManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install(app: App, options: { url?: string } = {}) {
|
|
||||||
const wsManager = new EnhancedWebSocketManager(options.url);
|
|
||||||
|
|
||||||
app.config.globalProperties.$ws = wsManager;
|
|
||||||
app.provide('websocket', wsManager);
|
|
||||||
|
|
||||||
// 应用关闭时断开连接
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
wsManager.disconnect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+23
-1
@@ -8,6 +8,9 @@ import router from './router'
|
|||||||
|
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
|
import CSharpBridgeV2 from '@/correspond/CSharpBridgeV2'
|
||||||
|
import websocketPlugin from '@/plugins/websocketPlugin'
|
||||||
|
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
@@ -16,6 +19,25 @@ app.use(router)
|
|||||||
app.use(ElementPlus)
|
app.use(ElementPlus)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
app.use(websocketPlugin)
|
||||||
|
|
||||||
// 注册 C# 函数
|
// 注册 C# 函数
|
||||||
window.externalFunctions = {};
|
window.externalFunctions = {}
|
||||||
|
|
||||||
|
// 监听获取 WebSocket 链接
|
||||||
|
const webSocketStore = useWebSocketStore()
|
||||||
|
|
||||||
|
// 监听WebSocket地址
|
||||||
|
const bridge = CSharpBridgeV2?.getBridge()
|
||||||
|
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
if (webSocketStore.isInitialized) {
|
||||||
|
clearInterval(intervalId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.send('GetWebSocketPath', {}, (data) => {
|
||||||
|
webSocketStore.initialize(data)
|
||||||
|
console.log(data, '======')
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { App } from 'vue';
|
||||||
|
import { useWebSocketStore } from '@/stores/websocketStore';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install: (app: App) => {
|
||||||
|
const store = useWebSocketStore();
|
||||||
|
|
||||||
|
// 提供全局访问
|
||||||
|
app.config.globalProperties.$websocket = {
|
||||||
|
send: store.send,
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
unsubscribe: store.unsubscribe,
|
||||||
|
registerHandler: store.registerHandler,
|
||||||
|
unregisterHandler: store.unregisterHandler,
|
||||||
|
isConnected: () => store.isConnected
|
||||||
|
};
|
||||||
|
|
||||||
|
// 注入到组合式API
|
||||||
|
app.provide('websocket', {
|
||||||
|
send: store.send,
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
unsubscribe: store.unsubscribe,
|
||||||
|
registerHandler: store.registerHandler,
|
||||||
|
unregisterHandler: store.unregisterHandler,
|
||||||
|
isConnected: store.isConnected
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
|
|
||||||
// services/WebSocketManager.ts
|
|
||||||
import { EventEmitter } from 'events'
|
|
||||||
import type {
|
|
||||||
WebSocketMessage,
|
|
||||||
WebSocketConfig,
|
|
||||||
ConnectionState,
|
|
||||||
MessageType
|
|
||||||
} from '@/types/websocket'
|
|
||||||
|
|
||||||
export class WebSocketManager extends EventEmitter {
|
|
||||||
private ws: WebSocket | null = null
|
|
||||||
private config: WebSocketConfig
|
|
||||||
private reconnectTimer: NodeJS.Timeout | null = null
|
|
||||||
private heartbeatTimer: NodeJS.Timeout | null = null
|
|
||||||
/**
|
|
||||||
* 重新连接尝试
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private reconnectAttempts = 0
|
|
||||||
private state: ConnectionState = ConnectionState.DISCONNECTED
|
|
||||||
/**
|
|
||||||
* 待处理请求
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private pendingRequests = new Map<string, {
|
|
||||||
resolve: (value: unknown) => void
|
|
||||||
reject: (reason: unknown) => void
|
|
||||||
timeout: NodeJS.Timeout
|
|
||||||
}>()
|
|
||||||
|
|
||||||
constructor(config: WebSocketConfig) {
|
|
||||||
super()
|
|
||||||
this.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
public connect(): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (this.state === ConnectionState.CONNECTED) {
|
|
||||||
resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(ConnectionState.CONNECTING)
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.ws = new WebSocket(this.config.url)
|
|
||||||
this.setupEventListeners(resolve, reject)
|
|
||||||
} catch (error) {
|
|
||||||
this.setState(ConnectionState.ERROR)
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnect(): void {
|
|
||||||
this.clearTimers()
|
|
||||||
this.setState(ConnectionState.DISCONNECTED)
|
|
||||||
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close()
|
|
||||||
this.ws = null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rejectPendingRequests('Connection closed')
|
|
||||||
}
|
|
||||||
|
|
||||||
public send<T>(type: MessageType, data?: T): void {
|
|
||||||
if (!this.isConnected()) {
|
|
||||||
throw new Error('WebSocket is not connected')
|
|
||||||
}
|
|
||||||
|
|
||||||
const message: WebSocketMessage<T> = {
|
|
||||||
type,
|
|
||||||
data,
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws!.send(JSON.stringify(message))
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendRequest<T, R>(type: MessageType, data?: T): Promise<R> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!this.isConnected()) {
|
|
||||||
reject(new Error('WebSocket is not connected'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestId = this.generateRequestId()
|
|
||||||
const message: WebSocketMessage<T> = {
|
|
||||||
type,
|
|
||||||
data,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
requestId
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.pendingRequests.delete(requestId)
|
|
||||||
reject(new Error('Request timeout'))
|
|
||||||
}, this.config.timeout)
|
|
||||||
|
|
||||||
this.pendingRequests.set(requestId, { resolve, reject, timeout })
|
|
||||||
this.ws!.send(JSON.stringify(message))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public getState(): ConnectionState {
|
|
||||||
return this.state
|
|
||||||
}
|
|
||||||
|
|
||||||
public isConnected(): boolean {
|
|
||||||
return this.state === ConnectionState.CONNECTED && this.ws?.readyState === WebSocket.OPEN
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupEventListeners(
|
|
||||||
connectResolve: () => void,
|
|
||||||
connectReject: (error: unknown) => void
|
|
||||||
): void {
|
|
||||||
if (!this.ws) return
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
this.setState(ConnectionState.CONNECTED)
|
|
||||||
this.reconnectAttempts = 0
|
|
||||||
this.startHeartbeat()
|
|
||||||
connectResolve()
|
|
||||||
this.emit('connected')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.onclose = (event) => {
|
|
||||||
this.setState(ConnectionState.DISCONNECTED)
|
|
||||||
this.stopHeartbeat()
|
|
||||||
this.emit('disconnected', event)
|
|
||||||
|
|
||||||
if (!event.wasClean && this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
||||||
this.scheduleReconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.onerror = (error) => {
|
|
||||||
this.setState(ConnectionState.ERROR)
|
|
||||||
this.emit('error', error)
|
|
||||||
connectReject(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
|
||||||
try {
|
|
||||||
const message: WebSocketMessage = JSON.parse(event.data)
|
|
||||||
this.handleMessage(message)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse WebSocket message:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleMessage(message: WebSocketMessage): void {
|
|
||||||
// 处理响应消息
|
|
||||||
if (message.requestId && this.pendingRequests.has(message.requestId)) {
|
|
||||||
const request = this.pendingRequests.get(message.requestId)!
|
|
||||||
clearTimeout(request.timeout)
|
|
||||||
this.pendingRequests.delete(message.requestId)
|
|
||||||
|
|
||||||
if (message.type === MessageType.ERROR) {
|
|
||||||
request.reject(new Error(message.data))
|
|
||||||
} else {
|
|
||||||
request.resolve(message.data)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 触发消息事件
|
|
||||||
this.emit('message', message)
|
|
||||||
this.emit(message.type, message.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleReconnect(): void {
|
|
||||||
if (this.reconnectTimer) return
|
|
||||||
|
|
||||||
this.setState(ConnectionState.RECONNECTING)
|
|
||||||
this.reconnectAttempts++
|
|
||||||
|
|
||||||
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 30000)
|
|
||||||
|
|
||||||
this.reconnectTimer = setTimeout(() => {
|
|
||||||
this.reconnectTimer = null
|
|
||||||
this.connect().catch(() => {
|
|
||||||
// 重连失败,会自动触发下一次重连
|
|
||||||
})
|
|
||||||
}, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private startHeartbeat(): void {
|
|
||||||
this.heartbeatTimer = setInterval(() => {
|
|
||||||
if (this.isConnected()) {
|
|
||||||
this.send(MessageType.GET_SYSTEM_INFO)
|
|
||||||
}
|
|
||||||
}, this.config.heartbeatInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopHeartbeat(): void {
|
|
||||||
if (this.heartbeatTimer) {
|
|
||||||
clearInterval(this.heartbeatTimer)
|
|
||||||
this.heartbeatTimer = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearTimers(): void {
|
|
||||||
if (this.reconnectTimer) {
|
|
||||||
clearTimeout(this.reconnectTimer)
|
|
||||||
this.reconnectTimer = null
|
|
||||||
}
|
|
||||||
this.stopHeartbeat()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setState(state: ConnectionState): void {
|
|
||||||
if (this.state !== state) {
|
|
||||||
this.state = state
|
|
||||||
this.emit('stateChange', state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateRequestId(): string {
|
|
||||||
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
private rejectPendingRequests(reason: string): void {
|
|
||||||
this.pendingRequests.forEach(({ reject, timeout }) => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
reject(new Error(reason))
|
|
||||||
})
|
|
||||||
this.pendingRequests.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +1,67 @@
|
|||||||
|
|
||||||
// stores/websocket.ts
|
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { WebSocketManager } from '@/utils/websocket'
|
||||||
import { WebSocketManager } from '@/services/WebSocketManager'
|
import { ref } from 'vue'
|
||||||
import type {
|
|
||||||
SystemInfo,
|
|
||||||
NetworkStatus,
|
|
||||||
FirewallRule,
|
|
||||||
ConnectionState,
|
|
||||||
MessageType
|
|
||||||
} from '@/types/websocket'
|
|
||||||
|
|
||||||
export const useWebSocketStore = defineStore('websocket', () => {
|
export const useWebSocketStore = defineStore('websocket', () => {
|
||||||
// 状态
|
const manager = ref<WebSocketManager | null>(null)
|
||||||
const wsManager = ref<WebSocketManager | null>(null)
|
const isInitialized = ref(false)
|
||||||
const connectionState = ref<ConnectionState>('disconnected' as ConnectionState)
|
|
||||||
const systemInfo = ref<SystemInfo | null>(null)
|
|
||||||
const networkStatus = ref<NetworkStatus[]>([])
|
|
||||||
const firewallRules = ref<FirewallRule[]>([])
|
|
||||||
const lastError = ref<string | null>(null)
|
|
||||||
const isLoading = ref(false)
|
|
||||||
|
|
||||||
// 计算属性
|
// 初始化WebSocket
|
||||||
const isConnected = computed(() => connectionState.value === 'connected')
|
const initialize = (url: string) => {
|
||||||
const connectionCount = computed(() => systemInfo.value?.connectionCount ?? 0)
|
if (url.trim() === '') return
|
||||||
const activeNetworkInterfaces = computed(() =>
|
if (!manager.value) {
|
||||||
networkStatus.value.filter(status => status.isConnected)
|
manager.value = new WebSocketManager()
|
||||||
)
|
manager.value.initialize(url)
|
||||||
|
// 同步状态
|
||||||
// 初始化WebSocket连接
|
isInitialized.value = manager.value.isInitialized
|
||||||
const initialize = async (url: string): Promise<void> => {
|
|
||||||
if (wsManager.value) {
|
|
||||||
wsManager.value.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
wsManager.value = new WebSocketManager({
|
|
||||||
url,
|
|
||||||
reconnectInterval: 5000,
|
|
||||||
maxReconnectAttempts: 10,
|
|
||||||
heartbeatInterval: 30000,
|
|
||||||
timeout: 10000
|
|
||||||
})
|
|
||||||
|
|
||||||
setupEventListeners()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await wsManager.value.connect()
|
|
||||||
} catch (error) {
|
|
||||||
lastError.value = error instanceof Error ? error.message : 'Connection failed'
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置事件监听器
|
const getIsConnected = (): boolean => {
|
||||||
const setupEventListeners = (): void => {
|
return manager.value?.isReconnecting
|
||||||
if (!wsManager.value) return
|
|
||||||
|
|
||||||
wsManager.value.on('stateChange', (state: ConnectionState) => {
|
|
||||||
connectionState.value = state
|
|
||||||
})
|
|
||||||
|
|
||||||
wsManager.value.on('error', (error: Error) => {
|
|
||||||
lastError.value = error.message
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听数据更新事件
|
|
||||||
wsManager.value.on('network_status_response', (data: NetworkStatus[]) => {
|
|
||||||
networkStatus.value = data
|
|
||||||
})
|
|
||||||
|
|
||||||
wsManager.value.on('firewall_rules_response', (data: FirewallRule[]) => {
|
|
||||||
firewallRules.value = data
|
|
||||||
})
|
|
||||||
|
|
||||||
wsManager.value.on('system_info_response', (data: SystemInfo) => {
|
|
||||||
systemInfo.value = data
|
|
||||||
networkStatus.value = data.networkInterfaces
|
|
||||||
firewallRules.value = data.firewallRules
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听实时更新事件
|
|
||||||
wsManager.value.on('network_status_changed', (data: NetworkStatus[]) => {
|
|
||||||
networkStatus.value = data
|
|
||||||
})
|
|
||||||
|
|
||||||
wsManager.value.on('firewall_rule_changed', (data: FirewallRule[]) => {
|
|
||||||
firewallRules.value = data
|
|
||||||
})
|
|
||||||
|
|
||||||
wsManager.value.on('system_alert', (data: { message: string; level: string }) => {
|
|
||||||
console.warn('System Alert:', data)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据获取方法
|
// 发送消息
|
||||||
const fetchSystemInfo = async (): Promise<SystemInfo> => {
|
const send = <T>(command: string, data?: T) => {
|
||||||
if (!wsManager.value || !isConnected.value) {
|
if (!getIsConnected()) throw new Error('连接未建立')
|
||||||
throw new Error('WebSocket not connected')
|
manager.value?.send(command, data)
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const data = await wsManager.value.sendRequest<void, SystemInfo>(
|
|
||||||
'get_system_info' as MessageType
|
|
||||||
)
|
|
||||||
systemInfo.value = data
|
|
||||||
return data
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchNetworkStatus = async (): Promise<NetworkStatus[]> => {
|
// 订阅数据
|
||||||
if (!wsManager.value || !isConnected.value) {
|
const subscribe = (type: string, interval?: number) => {
|
||||||
throw new Error('WebSocket not connected')
|
manager.value?.subscribe({ type, interval })
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const data = await wsManager.value.sendRequest<void, NetworkStatus[]>(
|
|
||||||
'get_network_status' as MessageType
|
|
||||||
)
|
|
||||||
networkStatus.value = data
|
|
||||||
return data
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFirewallRules = async (): Promise<FirewallRule[]> => {
|
// 取消订阅
|
||||||
if (!wsManager.value || !isConnected.value) {
|
const unsubscribe = (type: string) => {
|
||||||
throw new Error('WebSocket not connected')
|
manager.value?.unsubscribe(type)
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const data = await wsManager.value.sendRequest<void, FirewallRule[]>(
|
|
||||||
'get_firewall_rules' as MessageType
|
|
||||||
)
|
|
||||||
firewallRules.value = data
|
|
||||||
return data
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 防火墙操作方法
|
// 注册处理器
|
||||||
const addFirewallRule = async (rule: Omit<FirewallRule, 'id'>): Promise<void> => {
|
const registerHandler = (command: string, handler: (data: unknown) => void) => {
|
||||||
if (!wsManager.value || !isConnected.value) {
|
manager.value?.registerHandler(command, handler)
|
||||||
throw new Error('WebSocket not connected')
|
|
||||||
}
|
|
||||||
|
|
||||||
await wsManager.value.sendRequest<Omit<FirewallRule, 'id'>, void>(
|
|
||||||
'add_firewall_rule' as MessageType,
|
|
||||||
rule
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFirewallRule = async (rule: FirewallRule): Promise<void> => {
|
// 注销处理器
|
||||||
if (!wsManager.value || !isConnected.value) {
|
const unregisterHandler = (command: string, handler: (data: unknown) => void) => {
|
||||||
throw new Error('WebSocket not connected')
|
manager.value?.unregisterHandler(command, handler)
|
||||||
}
|
|
||||||
|
|
||||||
await wsManager.value.sendRequest<FirewallRule, void>(
|
|
||||||
'update_firewall_rule' as MessageType,
|
|
||||||
rule
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteFirewallRule = async (ruleId: string): Promise<void> => {
|
|
||||||
if (!wsManager.value || !isConnected.value) {
|
|
||||||
throw new Error('WebSocket not connected')
|
|
||||||
}
|
|
||||||
|
|
||||||
await wsManager.value.sendRequest<{ id: string }, void>(
|
|
||||||
'delete_firewall_rule' as MessageType,
|
|
||||||
{ id: ruleId }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleFirewallRule = async (ruleId: string): Promise<void> => {
|
|
||||||
if (!wsManager.value || !isConnected.value) {
|
|
||||||
throw new Error('WebSocket not connected')
|
|
||||||
}
|
|
||||||
|
|
||||||
await wsManager.value.sendRequest<{ id: string }, void>(
|
|
||||||
'toggle_firewall_rule' as MessageType,
|
|
||||||
{ id: ruleId }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 断开连接
|
// 断开连接
|
||||||
const disconnect = (): void => {
|
const disconnect = () => {
|
||||||
if (wsManager.value) {
|
manager.value?.disconnect()
|
||||||
wsManager.value.disconnect()
|
|
||||||
wsManager.value = null
|
|
||||||
}
|
|
||||||
connectionState.value = 'disconnected' as ConnectionState
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除错误
|
|
||||||
const clearError = (): void => {
|
|
||||||
lastError.value = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
manager,
|
||||||
connectionState,
|
getIsConnected,
|
||||||
systemInfo,
|
isInitialized,
|
||||||
networkStatus,
|
|
||||||
firewallRules,
|
|
||||||
lastError,
|
|
||||||
isLoading,
|
|
||||||
|
|
||||||
// 计算属性
|
|
||||||
isConnected,
|
|
||||||
connectionCount,
|
|
||||||
activeNetworkInterfaces,
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
initialize,
|
initialize,
|
||||||
fetchSystemInfo,
|
send,
|
||||||
fetchNetworkStatus,
|
subscribe,
|
||||||
fetchFirewallRules,
|
unsubscribe,
|
||||||
addFirewallRule,
|
registerHandler,
|
||||||
updateFirewallRule,
|
unregisterHandler,
|
||||||
deleteFirewallRule,
|
|
||||||
toggleFirewallRule,
|
|
||||||
disconnect,
|
disconnect,
|
||||||
clearError
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,88 +1,88 @@
|
|||||||
// types/websocket.ts
|
// types/websocket.ts
|
||||||
|
|
||||||
|
// 基础消息格式
|
||||||
export interface WebSocketMessage<T = unknown> {
|
export interface WebSocketMessage<T = unknown> {
|
||||||
type: string
|
type: string;
|
||||||
data: T
|
data: T;
|
||||||
timestamp: number
|
requestId?: string; // 用于请求-响应匹配
|
||||||
requestId?: string
|
timestamp?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 响应消息格式
|
||||||
|
export interface WebSocketResponse<T = unknown> extends WebSocketMessage<T> {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
message?: string;
|
||||||
|
messageId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅相关
|
||||||
|
export interface SubscriptionMessage {
|
||||||
|
type: 'subscribe' | 'unsubscribe';
|
||||||
|
topic: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络状态数据结构示例
|
||||||
export interface NetworkStatus {
|
export interface NetworkStatus {
|
||||||
interfaceName: string
|
connectionCount: number;
|
||||||
isConnected: boolean
|
bandwidth: number;
|
||||||
ipAddress: string
|
latency: number;
|
||||||
macAddress: string
|
timestamp: number;
|
||||||
speed: number
|
|
||||||
bytesReceived: number
|
|
||||||
bytesSent: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FirewallRule {
|
// 数据库查询请求
|
||||||
id: string
|
export interface DatabaseQueryRequest {
|
||||||
name: string
|
table: string;
|
||||||
direction: 'inbound' | 'outbound'
|
query: Record<string, unknown>;
|
||||||
action: 'allow' | 'block'
|
limit?: number;
|
||||||
protocol: 'tcp' | 'udp' | 'icmp' | 'any'
|
|
||||||
localPort?: string
|
|
||||||
remotePort?: string
|
|
||||||
localAddress?: string
|
|
||||||
remoteAddress?: string
|
|
||||||
enabled: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SystemInfo {
|
// 配置更新请求
|
||||||
cpuUsage: number
|
export interface ConfigUpdateRequest {
|
||||||
memoryUsage: number
|
section: string;
|
||||||
networkInterfaces: NetworkStatus[]
|
config: Record<string, unknown>;
|
||||||
firewallRules: FirewallRule[]
|
|
||||||
connectionCount: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MessageType {
|
// 命令执行请求
|
||||||
// 数据获取相关
|
export interface CommandRequest {
|
||||||
GET_NETWORK_STATUS = 'get_network_status',
|
functionName: string;
|
||||||
GET_FIREWALL_RULES = 'get_firewall_rules',
|
parameters: Record<string, unknown>;
|
||||||
GET_SYSTEM_INFO = 'get_system_info',
|
|
||||||
|
|
||||||
// 数据响应相关
|
|
||||||
NETWORK_STATUS_RESPONSE = 'network_status_response',
|
|
||||||
FIREWALL_RULES_RESPONSE = 'firewall_rules_response',
|
|
||||||
SYSTEM_INFO_RESPONSE = 'system_info_response',
|
|
||||||
|
|
||||||
// 操作相关
|
|
||||||
ADD_FIREWALL_RULE = 'add_firewall_rule',
|
|
||||||
UPDATE_FIREWALL_RULE = 'update_firewall_rule',
|
|
||||||
DELETE_FIREWALL_RULE = 'delete_firewall_rule',
|
|
||||||
TOGGLE_FIREWALL_RULE = 'toggle_firewall_rule',
|
|
||||||
|
|
||||||
// 通知相关
|
|
||||||
NETWORK_STATUS_CHANGED = 'network_status_changed',
|
|
||||||
FIREWALL_RULE_CHANGED = 'firewall_rule_changed',
|
|
||||||
SYSTEM_ALERT = 'system_alert',
|
|
||||||
ERROR = 'error',
|
|
||||||
SUCCESS = 'success'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebSocketConfig {
|
// WebSocket连接状态
|
||||||
url: string
|
export enum WebSocketState {
|
||||||
/**
|
|
||||||
* 重新连接间隔
|
|
||||||
*/
|
|
||||||
reconnectInterval: number
|
|
||||||
/**
|
|
||||||
* 最大重新连接尝试次数
|
|
||||||
*/
|
|
||||||
maxReconnectAttempts: number
|
|
||||||
/**
|
|
||||||
* 心跳间隔
|
|
||||||
*/
|
|
||||||
heartbeatInterval: number
|
|
||||||
timeout: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ConnectionState {
|
|
||||||
CONNECTING = 'connecting',
|
CONNECTING = 'connecting',
|
||||||
CONNECTED = 'connected',
|
CONNECTED = 'connected',
|
||||||
DISCONNECTED = 'disconnected',
|
DISCONNECTED = 'disconnected',
|
||||||
RECONNECTING = 'reconnecting',
|
|
||||||
ERROR = 'error'
|
ERROR = 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 事件监听器类型
|
||||||
|
export type EventListener<T = unknown> = (data: T) => void;
|
||||||
|
|
||||||
|
// 订阅回调类型
|
||||||
|
export type SubscriptionCallback<T = unknown> = (data: T, topic: string) => void;
|
||||||
|
|
||||||
|
// 请求选项
|
||||||
|
export interface RequestOptions {
|
||||||
|
timeout?: number;
|
||||||
|
retries?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocket管理器配置
|
||||||
|
export interface WebSocketConfig {
|
||||||
|
reconnectInterval?: number;
|
||||||
|
maxReconnectAttempts?: number;
|
||||||
|
heartbeatInterval?: number;
|
||||||
|
requestTimeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅请求格式
|
||||||
|
export interface SubscriptionRequest {
|
||||||
|
type: string;
|
||||||
|
interval?: number; // 更新间隔(ms)
|
||||||
|
// 可添加其他订阅参数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 命令处理函数类型
|
||||||
|
export type CommandHandler = (data: unknown) => void | Promise<void>;
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import { CommandHandler, SubscriptionRequest, WebSocketMessage, WebSocketResponse } from './types'
|
||||||
|
import CSharpBridgeV2 from '@/correspond/CSharpBridgeV2'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { WebSocketState } from '@/types/websocket'
|
||||||
|
|
||||||
|
export class WebSocketManager {
|
||||||
|
private socket: WebSocket | null = null
|
||||||
|
private handlers: Map<string, CommandHandler[]> = new Map()
|
||||||
|
private subscriptions: Map<string, { interval: number; timer?: NodeJS.Timeout }> = new Map()
|
||||||
|
private messageQueue: WebSocketMessage[] = []
|
||||||
|
private state = WebSocketState.DISCONNECTED
|
||||||
|
private isReconnecting = false;
|
||||||
|
private heartbeatTimer: number | null = null;
|
||||||
|
|
||||||
|
public isInitialized: boolean = false
|
||||||
|
private heartbeatInterval = 30000
|
||||||
|
// 可配置参数
|
||||||
|
maxDelay = 3000 // 最大重连间隔 30s
|
||||||
|
baseDelay = 500 // 初始间隔 1s
|
||||||
|
reconnectAttempts = 0
|
||||||
|
isManuallyClosed = false
|
||||||
|
// 定时检测
|
||||||
|
timedDetection = 0
|
||||||
|
|
||||||
|
// 获取状态的响应式引用
|
||||||
|
get connectionState() {
|
||||||
|
return this.state
|
||||||
|
}
|
||||||
|
get reconnecting() {
|
||||||
|
return this.isReconnecting;
|
||||||
|
}
|
||||||
|
// 初始化WebSocket连接
|
||||||
|
public initialize(url: string) {
|
||||||
|
console.log('initialize', url)
|
||||||
|
if (url.trim() === '') return
|
||||||
|
if (this.socket) return
|
||||||
|
this.state = WebSocketState.CONNECTING
|
||||||
|
this.socket = new WebSocket(url)
|
||||||
|
this.isInitialized = true
|
||||||
|
|
||||||
|
this.registerHandler('welcome', (data: WebSocketResponse) => {
|
||||||
|
if (data.success) console.log('WebSocket 连接建立成功 !! ')
|
||||||
|
else console.error('收到WbeSocket 欢迎信息,但是解析失败!!1')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.socket.onopen = () => {
|
||||||
|
this.state = WebSocketState.CONNECTED
|
||||||
|
this.isReconnecting = true
|
||||||
|
this.reconnectAttempts = 0
|
||||||
|
console.log('WebSocket connected')
|
||||||
|
// 发送队列中的消息
|
||||||
|
this.flushMessageQueue()
|
||||||
|
// 重新激活订阅
|
||||||
|
this.reactivateSubscriptions()
|
||||||
|
// 启动心跳
|
||||||
|
this.startHeartbeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const message: WebSocketMessage = JSON.parse(event.data)
|
||||||
|
this.handleMessage(message)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing WebSocket message:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
this.socket = null
|
||||||
|
this.state = WebSocketState.DISCONNECTED
|
||||||
|
this.isReconnecting = false
|
||||||
|
console.log('WebSocket disconnected')
|
||||||
|
// 尝试重连
|
||||||
|
this.retryConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.onerror = (error) => {
|
||||||
|
console.error('WebSocket error:', error)
|
||||||
|
this.state = WebSocketState.ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retryConnect() {
|
||||||
|
this.reconnectAttempts++
|
||||||
|
const delay = Math.min(this.baseDelay * 2 ** (this.reconnectAttempts - 1), this.maxDelay)
|
||||||
|
|
||||||
|
console.log(`[WebSocket] Attempting reconnect in ${delay / 1000}s`)
|
||||||
|
const bridge = CSharpBridgeV2?.getBridge()
|
||||||
|
// 监听获取 WebSocket 链接
|
||||||
|
const isConnected1 = this.isReconnecting
|
||||||
|
console.log(isConnected1)
|
||||||
|
if (isConnected1) return
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.isManuallyClosed) {
|
||||||
|
bridge.send('GetWebSocketPath', {}, (data) => {
|
||||||
|
this.initialize(data)
|
||||||
|
console.log(data, '======')
|
||||||
|
this.retryConnect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 心跳机制
|
||||||
|
private startHeartbeat(): void {
|
||||||
|
this.heartbeatTimer = setInterval(() => {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
this.send({ command: 'ping', data: null });
|
||||||
|
}
|
||||||
|
}, this.heartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册命令处理器
|
||||||
|
public registerHandler(command: string, handler: CommandHandler) {
|
||||||
|
if (!this.handlers.has(command)) {
|
||||||
|
this.handlers.set(command, [])
|
||||||
|
}
|
||||||
|
this.handlers.get(command)?.push(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注销命令处理器
|
||||||
|
public unregisterHandler(command: string, handler: CommandHandler) {
|
||||||
|
const handlers = this.handlers.get(command)
|
||||||
|
if (handlers) {
|
||||||
|
const index = handlers.indexOf(handler)
|
||||||
|
if (index !== -1) {
|
||||||
|
handlers.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
public send<T>(command: string, data?: T) {
|
||||||
|
const message: WebSocketMessage<T> = {
|
||||||
|
command,
|
||||||
|
data,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果未连接或连接中,将消息加入队列
|
||||||
|
if (!this.isReconnecting || !this.socket) {
|
||||||
|
this.messageQueue.push(message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.socket.send(JSON.stringify(message))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending WebSocket message:', error)
|
||||||
|
this.messageQueue.push(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅数据
|
||||||
|
public subscribe(request: SubscriptionRequest) {
|
||||||
|
this.send('SUBSCRIBE', request)
|
||||||
|
|
||||||
|
// 存储订阅信息
|
||||||
|
this.subscriptions.set(request.type, {
|
||||||
|
interval: request.interval || 1000, // 默认1秒
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消订阅
|
||||||
|
public unsubscribe(type: string) {
|
||||||
|
this.send('UNSUBSCRIBE', { type })
|
||||||
|
this.subscriptions.delete(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新激活所有订阅
|
||||||
|
private reactivateSubscriptions() {
|
||||||
|
this.subscriptions.forEach((_, type) => {
|
||||||
|
this.subscribe({ type })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理接收到的消息
|
||||||
|
private handleMessage(message: WebSocketResponse) {
|
||||||
|
const handlers = this.handlers.get(message.type)
|
||||||
|
if (handlers) {
|
||||||
|
handlers.forEach((handler) => {
|
||||||
|
try {
|
||||||
|
handler(message)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error handling command '${message.type}':`, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送队列中的消息
|
||||||
|
private flushMessageQueue() {
|
||||||
|
while (this.messageQueue.length > 0 && this.socket?.readyState === WebSocket.OPEN) {
|
||||||
|
const message = this.messageQueue.shift()
|
||||||
|
if (message) {
|
||||||
|
this.send(message.command, message.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭连接
|
||||||
|
public disconnect() {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.close()
|
||||||
|
this.socket = null
|
||||||
|
}
|
||||||
|
this.isReconnecting = false
|
||||||
|
this.state = WebSocketState.DISCONNECTED
|
||||||
|
|
||||||
|
this.isInitialized.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommandManager from '@/components/CommandManager.vue'
|
||||||
|
import type { CommandType } from '@/types/command'
|
||||||
|
|
||||||
|
const commands: CommandType[] = [
|
||||||
|
{
|
||||||
|
name: 'CloseWebSocket',
|
||||||
|
description: '关闭 WebSocket',
|
||||||
|
params: [],
|
||||||
|
result: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OpenWebSocket',
|
||||||
|
description: '打开 WebSocket',
|
||||||
|
params: [],
|
||||||
|
result: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CommandManager :isUseWebSocket="true" :pro="commands"></CommandManager>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
Reference in new issue
Block a user