A:增加并调整websocket的ref状态管理
This commit is contained in:
1 parent
f2dc4441d4
commit
38d9378e0f
7 files changed
+279
-552
No files matched your search
@@ -1,432 +0,0 @@
|
||||
// composables/useWebSocket.ts
|
||||
import { ref, reactive, onUnmounted, nextTick } from 'vue';
|
||||
import type {
|
||||
WebSocketMessage,
|
||||
WebSocketResponse,
|
||||
WebSocketState,
|
||||
EventListener,
|
||||
SubscriptionCallback,
|
||||
RequestOptions,
|
||||
WebSocketConfig,
|
||||
SubscriptionMessage,
|
||||
NetworkStatus,
|
||||
DatabaseQueryRequest,
|
||||
ConfigUpdateRequest,
|
||||
CommandRequest
|
||||
} from '@/types/websocket';
|
||||
|
||||
class WebSocketManager {
|
||||
private ws: WebSocket | null = null;
|
||||
private url: string = '';
|
||||
private config: WebSocketConfig;
|
||||
private reconnectAttempts: number = 0;
|
||||
private reconnectTimer: number | null = null;
|
||||
private heartbeatTimer: number | null = null;
|
||||
|
||||
// 状态管理
|
||||
private state = ref<WebSocketState>(WebSocketState.DISCONNECTED);
|
||||
private isReconnecting = ref(false);
|
||||
|
||||
// 事件监听器
|
||||
private eventListeners = new Map<string, Set<EventListener>>();
|
||||
|
||||
// 订阅管理
|
||||
private subscriptions = new Map<string, Set<SubscriptionCallback>>();
|
||||
private activeSubscriptions = new Set<string>();
|
||||
|
||||
// 请求-响应管理
|
||||
private pendingRequests = new Map<string, {
|
||||
resolve: (value: WebSocketResponse) => void;
|
||||
reject: (error: Error) => void;
|
||||
timeout: number;
|
||||
}>();
|
||||
|
||||
// 消息队列(连接断开时暂存消息)
|
||||
private messageQueue: WebSocketMessage[] = [];
|
||||
|
||||
constructor(config: WebSocketConfig = {}) {
|
||||
this.config = {
|
||||
reconnectInterval: 3000,
|
||||
maxReconnectAttempts: 5,
|
||||
heartbeatInterval: 30000,
|
||||
requestTimeout: 10000,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
// 获取状态的响应式引用
|
||||
get connectionState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// 发送请求并等待响应
|
||||
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 {
|
||||
const message: WebSocketResponse = JSON.parse(data);
|
||||
|
||||
// 处理请求响应
|
||||
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) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 重连逻辑
|
||||
private attemptReconnect(): void {
|
||||
if (this.isReconnecting.value) return;
|
||||
|
||||
this.isReconnecting.value = true;
|
||||
this.reconnectAttempts++;
|
||||
|
||||
console.log(`Attempting to reconnect... (${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
|
||||
|
||||
this.reconnectTimer = setTimeout(async () => {
|
||||
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');
|
||||
}
|
||||
}
|
||||
}, this.config.reconnectInterval);
|
||||
}
|
||||
|
||||
// 处理消息队列
|
||||
private processMessageQueue(): void {
|
||||
while (this.messageQueue.length > 0) {
|
||||
const message = this.messageQueue.shift()!;
|
||||
this.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新订阅主题
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
private generateRequestId(): string {
|
||||
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
destroy(): void {
|
||||
this.disconnect();
|
||||
this.eventListeners.clear();
|
||||
this.subscriptions.clear();
|
||||
this.activeSubscriptions.clear();
|
||||
this.pendingRequests.clear();
|
||||
this.messageQueue.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 单例模式的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);
|
||||
},
|
||||
|
||||
// ============================ 真实业务 ===============================
|
||||
subscribeNetInfo(callback: SubscriptionCallback): () => void {
|
||||
return wsManager!.subscribe('network:NetInfo', 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;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { createNaiveUI } from './naive'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { useUuidStore } from '@/stores/uuidStore'
|
||||
import { logB } from '@/utils/logHelper/logUtils'
|
||||
import { httpConfig } from '@/config/httpConfig'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
@@ -26,10 +27,19 @@ useThemeStore()
|
||||
// 获取全局 UUID
|
||||
const useUuidStore1 = useUuidStore()
|
||||
logB.success('全局 ID :', useUuidStore1.uuid)
|
||||
// 设置所有请求头
|
||||
httpConfig.setConfig({
|
||||
headers: {
|
||||
uuid: useUuidStore1.uuid,
|
||||
},
|
||||
})
|
||||
|
||||
// http 注册
|
||||
import httpPlugin from './plugins/http';
|
||||
app.use(httpPlugin);
|
||||
import httpPlugin from './plugins/http'
|
||||
|
||||
// httpPlugin.install(app)
|
||||
|
||||
app.use(httpPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
|
||||
@@ -1,65 +1,86 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { WebSocketManager } from '@/utils/websocket'
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed,shallowRef } from 'vue'
|
||||
import { WebSocketState } from '@/types/websocket'
|
||||
|
||||
export const useWebSocketStore = defineStore('websocket', () => {
|
||||
const manager = ref<WebSocketManager | null>(null)
|
||||
const isInitialized = ref(false)
|
||||
const manager = shallowRef<WebSocketManager | null>(null)
|
||||
|
||||
// 计算属性,自动响应WebSocketManager内部状态变化
|
||||
const isInitialized = ref<boolean>(false)
|
||||
const isOpen = ref<boolean>(false)
|
||||
const isConnected = computed(() => manager.value?.isConnected.value ?? false)
|
||||
const isReconnecting = computed(() => manager.value?.isReconnecting.value ?? false)
|
||||
const connectionState = computed(() => manager.value?.state.value ?? WebSocketState.DISCONNECTED)
|
||||
|
||||
// 初始化WebSocket
|
||||
const initialize = (url: string) => {
|
||||
if (url.trim() === '') return
|
||||
if (url.trim() === '') {
|
||||
console.warn('WebSocket URL is empty')
|
||||
return
|
||||
}
|
||||
|
||||
if (!manager.value) {
|
||||
manager.value = new WebSocketManager()
|
||||
manager.value.initialize(url)
|
||||
// 同步状态
|
||||
isInitialized.value = manager.value.isInitialized
|
||||
console.log(manager.value.state,'this is store');
|
||||
}
|
||||
}
|
||||
|
||||
const getIsConnected = (): boolean => {
|
||||
return manager.value?.isReconnecting
|
||||
manager.value.initialize(url)
|
||||
|
||||
isInitialized.value = manager.value.isInit
|
||||
isOpen.value = manager.value.isOpen
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
const send = <T>(command: string, data?: T) => {
|
||||
if (!getIsConnected()) throw new Error('连接未建立')
|
||||
manager.value?.send(command, data)
|
||||
}
|
||||
|
||||
// 订阅数据
|
||||
const subscribe = (type: string, interval?: number) => {
|
||||
manager.value?.subscribe({ type, interval })
|
||||
}
|
||||
|
||||
// 取消订阅
|
||||
const unsubscribe = (type: string) => {
|
||||
manager.value?.unsubscribe(type)
|
||||
if (!manager.value) {
|
||||
throw new Error('WebSocket未初始化')
|
||||
}
|
||||
if (!isConnected.value) {
|
||||
console.warn('WebSocket连接未建立,消息将被加入队列')
|
||||
}
|
||||
manager.value.send(command, data)
|
||||
}
|
||||
|
||||
// 注册处理器
|
||||
const registerHandler = (command: string, handler: (data: unknown) => void) => {
|
||||
manager.value?.registerHandler(command, handler)
|
||||
if (!manager.value) {
|
||||
console.warn('WebSocket未初始化,处理器将在初始化后注册')
|
||||
return
|
||||
}
|
||||
manager.value.registerHandler(command, handler)
|
||||
}
|
||||
|
||||
// 注销处理器
|
||||
const unregisterHandler = (command: string, handler: (data: unknown) => void) => {
|
||||
manager.value?.unregisterHandler(command, handler)
|
||||
if (!manager.value) {
|
||||
return
|
||||
}
|
||||
manager.value.unregisterHandler(command, handler)
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
const disconnect = () => {
|
||||
manager.value?.disconnect()
|
||||
if (manager.value) {
|
||||
manager.value.disconnect()
|
||||
manager.value = null
|
||||
isInitialized.value = false
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 响应式状态
|
||||
manager,
|
||||
getIsConnected,
|
||||
isInitialized,
|
||||
isOpen,
|
||||
isConnected,
|
||||
isReconnecting,
|
||||
connectionState,
|
||||
|
||||
// 方法
|
||||
initialize,
|
||||
send,
|
||||
subscribe,
|
||||
unsubscribe,
|
||||
registerHandler,
|
||||
unregisterHandler,
|
||||
disconnect,
|
||||
|
||||
+11
-5
@@ -1,12 +1,18 @@
|
||||
/**
|
||||
* 响应模型
|
||||
*/
|
||||
interface ResponseModel {
|
||||
success: boolean,
|
||||
data: unknown,
|
||||
interface ResponseModel {
|
||||
success: boolean
|
||||
data: unknown
|
||||
message: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅信息
|
||||
*/
|
||||
interface SubscriptionInfo {
|
||||
subscriptionType: 'ApplicationInfo'
|
||||
interval: number
|
||||
}
|
||||
|
||||
|
||||
export { ResponseModel }
|
||||
export { ResponseModel, SubscriptionInfo }
|
||||
@@ -1,19 +1,19 @@
|
||||
// types/websocket.ts
|
||||
|
||||
// 基础消息格式
|
||||
export interface WebSocketMessage<T = unknown> {
|
||||
type: string;
|
||||
data: T;
|
||||
requestId?: string; // 用于请求-响应匹配
|
||||
timestamp?: number;
|
||||
export interface WebSocketMessage<T = any> {
|
||||
command: string
|
||||
data?: T
|
||||
timestamp?: string
|
||||
}
|
||||
|
||||
// 响应消息格式
|
||||
export interface WebSocketResponse<T = unknown> extends WebSocketMessage<T> {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
message?: string;
|
||||
messageId?: string;
|
||||
export interface WebSocketResponse<T = any> {
|
||||
type: string
|
||||
data?: T
|
||||
success?: boolean
|
||||
error?: string
|
||||
timestamp?: string
|
||||
}
|
||||
|
||||
// 订阅相关
|
||||
@@ -51,9 +51,9 @@ export interface CommandRequest {
|
||||
|
||||
// WebSocket连接状态
|
||||
export enum WebSocketState {
|
||||
DISCONNECTED = 'disconnected',
|
||||
CONNECTING = 'connecting',
|
||||
CONNECTED = 'connected',
|
||||
DISCONNECTED = 'disconnected',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
@@ -79,10 +79,20 @@ export interface WebSocketConfig {
|
||||
|
||||
// 订阅请求格式
|
||||
export interface SubscriptionRequest {
|
||||
type: string;
|
||||
interval?: number; // 更新间隔(ms)
|
||||
// 可添加其他订阅参数
|
||||
type: string
|
||||
interval?: number
|
||||
params?: Record<string, any>
|
||||
}
|
||||
// 命令处理器类型
|
||||
export type CommandHandler = (data: WebSocketResponse) => void
|
||||
|
||||
// WebSocket 配置选项
|
||||
export interface WebSocketOptions {
|
||||
url: string
|
||||
heartbeatInterval?: number
|
||||
maxReconnectDelay?: number
|
||||
baseReconnectDelay?: number
|
||||
autoReconnect?: boolean
|
||||
}
|
||||
// 命令处理函数类型
|
||||
export type CommandHandler = (data: unknown) => void | Promise<void>;
|
||||
@@ -1,7 +1,12 @@
|
||||
import { CommandHandler, SubscriptionRequest, WebSocketMessage, WebSocketResponse } from './types'
|
||||
import {
|
||||
CommandHandler,
|
||||
SubscriptionRequest,
|
||||
WebSocketMessage,
|
||||
WebSocketResponse,
|
||||
WebSocketState,
|
||||
} from '@/types/websocket'
|
||||
import CSharpBridgeV2 from '@/correspond/CSharpBridgeV2'
|
||||
import { ref } from 'vue'
|
||||
import { WebSocketState } from '@/types/websocket'
|
||||
import { ref, Ref } from 'vue'
|
||||
import { logB } from '@/utils/logHelper/logUtils'
|
||||
import { useUuidStore } from '@/stores/uuidStore'
|
||||
|
||||
@@ -10,54 +15,85 @@ export class WebSocketManager {
|
||||
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
|
||||
// 响应式状态 - 修复:使用 shallowRef 或强制创建正确的 ref
|
||||
public state: Ref<WebSocketState>
|
||||
public isReconnecting: Ref<boolean>
|
||||
public isConnected: Ref<boolean>
|
||||
public isInit: Ref<boolean>
|
||||
public isOpen: Ref<boolean>
|
||||
|
||||
constructor() {
|
||||
// 初始化响应式状态
|
||||
this.state = ref(WebSocketState.DISCONNECTED)
|
||||
this.isReconnecting = ref(false)
|
||||
this.isConnected = ref(false)
|
||||
this.isInit = ref(false)
|
||||
this.isOpen = ref(false)
|
||||
}
|
||||
|
||||
private heartbeatTimer: NodeJS.Timeout | null = null
|
||||
private reconnectTimer: NodeJS.Timeout | null = null
|
||||
private heartbeatInterval = 30000
|
||||
|
||||
// 可配置参数
|
||||
maxDelay = 3000 // 最大重连间隔 30s
|
||||
baseDelay = 500 // 初始间隔 1s
|
||||
maxDelay = 30000 // 最大重连间隔 30s
|
||||
baseDelay = 500 // 初始间隔 0.5s
|
||||
reconnectAttempts = 0
|
||||
isManuallyClosed = false
|
||||
// 定时检测
|
||||
timedDetection = 0
|
||||
|
||||
// 获取状态的响应式引用
|
||||
get connectionState() {
|
||||
return this.state
|
||||
}
|
||||
|
||||
get reconnecting() {
|
||||
return this.isReconnecting
|
||||
}
|
||||
private url: string = ''
|
||||
|
||||
// 初始化WebSocket连接
|
||||
public initialize(url: string) {
|
||||
logB.info('进入 web Socket 初始化, url:', url)
|
||||
logB.info('进入 WebSocket 初始化,url:', url)
|
||||
if (url.trim() === '') return
|
||||
if (this.socket) return
|
||||
this.state = WebSocketState.CONNECTING
|
||||
const useUuidStore1 = useUuidStore()
|
||||
logB.info('准备建立连接, uuid:', useUuidStore1.uuid)
|
||||
if (this.socket?.readyState === WebSocket.OPEN) {
|
||||
logB.warning('WebSocket 已经连接,跳过初始化')
|
||||
return
|
||||
}
|
||||
|
||||
const url1 = url + '?uuid=' + useUuidStore1.uuid
|
||||
logB.info('拼接链接:', url1)
|
||||
this.socket = new WebSocket(url1)
|
||||
this.url = url
|
||||
this.state.value = WebSocketState.CONNECTING
|
||||
this.isManuallyClosed = false
|
||||
|
||||
this.isInitialized = true
|
||||
const uuidStore = useUuidStore()
|
||||
logB.info('准备建立连接, uuid:', uuidStore.uuid)
|
||||
|
||||
const fullUrl = url + '?uuid=' + uuidStore.uuid
|
||||
logB.info('拼接链接:', fullUrl)
|
||||
|
||||
try {
|
||||
this.socket = new WebSocket(fullUrl)
|
||||
this.isInit.value = true
|
||||
this.setupSocketHandlers()
|
||||
} catch (error) {
|
||||
console.error('创建WebSocket失败:', error)
|
||||
this.state.value = WebSocketState.ERROR
|
||||
this.retryConnect()
|
||||
}
|
||||
}
|
||||
|
||||
private setupSocketHandlers() {
|
||||
if (!this.socket) return
|
||||
|
||||
// 注册欢迎消息处理器
|
||||
this.registerHandler('welcome', (data: WebSocketResponse) => {
|
||||
if (data.success) logB.success('WebSocket 连接建立成功 !! ')
|
||||
else console.error('收到WbeSocket 欢迎信息,但是解析失败!!1')
|
||||
else console.error('收到WebSocket 欢迎信息,但是解析失败!!')
|
||||
})
|
||||
this.registerHandler('pong', (data: WebSocketResponse) => {
|
||||
console.log("接收到服务器心跳");
|
||||
})
|
||||
|
||||
this.socket.onopen = () => {
|
||||
this.state = WebSocketState.CONNECTED
|
||||
this.isReconnecting = true
|
||||
this.state.value = WebSocketState.CONNECTED
|
||||
this.isConnected.value = true
|
||||
this.isReconnecting.value = false
|
||||
this.reconnectAttempts = 0
|
||||
logB.success('WebSocket connected')
|
||||
this.isOpen.value = true
|
||||
|
||||
// 发送队列中的消息
|
||||
this.flushMessageQueue()
|
||||
// 重新激活订阅
|
||||
@@ -68,46 +104,75 @@ export class WebSocketManager {
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(event.data)
|
||||
const message: WebSocketResponse = JSON.parse(event.data)
|
||||
console.log('收到消息:', message)
|
||||
this.handleMessage(message)
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error)
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.onclose = () => {
|
||||
this.socket.onclose = (event) => {
|
||||
this.isOpen.value = false
|
||||
this.socket = null
|
||||
this.state = WebSocketState.DISCONNECTED
|
||||
this.isReconnecting = false
|
||||
console.log('WebSocket disconnected')
|
||||
// 尝试重连
|
||||
this.retryConnect()
|
||||
this.state.value = WebSocketState.DISCONNECTED
|
||||
this.isConnected.value = false
|
||||
this.stopHeartbeat()
|
||||
|
||||
console.log('WebSocket disconnected', event.code, event.reason)
|
||||
|
||||
// 如果不是主动关闭,尝试重连
|
||||
if (!this.isManuallyClosed) {
|
||||
this.retryConnect()
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
this.state = WebSocketState.ERROR
|
||||
this.state.value = WebSocketState.ERROR
|
||||
}
|
||||
}
|
||||
|
||||
retryConnect() {
|
||||
private retryConnect() {
|
||||
// 如果已经在重连中,不重复执行
|
||||
if (this.isReconnecting.value) {
|
||||
logB.info('已经在重连中,跳过')
|
||||
return
|
||||
}
|
||||
|
||||
this.isReconnecting.value = true
|
||||
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(() => {
|
||||
console.log(
|
||||
`[WebSocket] Attempting reconnect in ${delay / 1000}s (attempt ${this.reconnectAttempts})`,
|
||||
)
|
||||
|
||||
// 清除之前的重连定时器
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer)
|
||||
}
|
||||
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
if (!this.isManuallyClosed) {
|
||||
const bridge = CSharpBridgeV2?.getBridge()
|
||||
if (bridge) {
|
||||
bridge.send('GetWebSocketPath', {}, (data) => {
|
||||
this.initialize(data)
|
||||
console.log(data, '======')
|
||||
this.retryConnect()
|
||||
bridge.send('GetWebSocketPath', {}, (data: string) => {
|
||||
if (data && data.trim()) {
|
||||
this.initialize(data)
|
||||
} else {
|
||||
console.error('获取WebSocket路径失败')
|
||||
this.isReconnecting.value = false
|
||||
// 继续重试
|
||||
this.retryConnect()
|
||||
}
|
||||
})
|
||||
} else if (this.url) {
|
||||
// 如果没有bridge,使用原来的URL重连
|
||||
this.initialize(this.url)
|
||||
} else {
|
||||
console.error('无法获取WebSocket连接地址')
|
||||
this.isReconnecting.value = false
|
||||
}
|
||||
}
|
||||
}, delay)
|
||||
@@ -115,13 +180,21 @@ export class WebSocketManager {
|
||||
|
||||
// 心跳机制
|
||||
private startHeartbeat(): void {
|
||||
this.stopHeartbeat() // 先清理之前的
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.send({ command: 'ping', data: null })
|
||||
if (this.socket?.readyState === WebSocket.OPEN) {
|
||||
this.send('ping', null)
|
||||
}
|
||||
}, this.heartbeatInterval)
|
||||
}
|
||||
|
||||
private stopHeartbeat(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer)
|
||||
this.heartbeatTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 注册命令处理器
|
||||
public registerHandler(command: string, handler: CommandHandler) {
|
||||
if (!this.handlers.has(command)) {
|
||||
@@ -149,9 +222,10 @@ export class WebSocketManager {
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// 如果未连接或连接中,将消息加入队列
|
||||
if (!this.isReconnecting || !this.socket) {
|
||||
this.messageQueue.push(message)
|
||||
// 如果未连接,将消息加入队列
|
||||
if (!this.isConnected.value || !this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
||||
this.messageQueue.push(message as WebSocketMessage)
|
||||
logB.warning('WebSocket未连接,消息已加入队列:', command)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,7 +233,7 @@ export class WebSocketManager {
|
||||
this.socket.send(JSON.stringify(message))
|
||||
} catch (error) {
|
||||
console.error('Error sending WebSocket message:', error)
|
||||
this.messageQueue.push(message)
|
||||
this.messageQueue.push(message as WebSocketMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,20 +250,26 @@ export class WebSocketManager {
|
||||
// 取消订阅
|
||||
public unsubscribe(type: string) {
|
||||
this.send('UNSUBSCRIBE', { type })
|
||||
|
||||
// 清除定时器
|
||||
const sub = this.subscriptions.get(type)
|
||||
if (sub?.timer) {
|
||||
clearInterval(sub.timer)
|
||||
}
|
||||
this.subscriptions.delete(type)
|
||||
}
|
||||
|
||||
// 重新激活所有订阅
|
||||
private reactivateSubscriptions() {
|
||||
this.subscriptions.forEach((_, type) => {
|
||||
this.subscribe({ type })
|
||||
this.subscriptions.forEach((sub, type) => {
|
||||
this.subscribe({ type, interval: sub.interval })
|
||||
})
|
||||
}
|
||||
|
||||
// 处理接收到的消息
|
||||
private handleMessage(message: WebSocketResponse) {
|
||||
const handlers = this.handlers.get(message.type)
|
||||
if (handlers) {
|
||||
if (handlers && handlers.length > 0) {
|
||||
handlers.forEach((handler) => {
|
||||
try {
|
||||
handler(message)
|
||||
@@ -197,6 +277,8 @@ export class WebSocketManager {
|
||||
console.error(`Error handling command '${message.type}':`, error)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
logB.warning(`No handler registered for message type: ${message.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,13 +294,30 @@ export class WebSocketManager {
|
||||
|
||||
// 关闭连接
|
||||
public disconnect() {
|
||||
this.isManuallyClosed = true
|
||||
|
||||
// 清理定时器
|
||||
this.stopHeartbeat()
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer)
|
||||
this.reconnectTimer = null
|
||||
}
|
||||
|
||||
// 关闭socket
|
||||
if (this.socket) {
|
||||
this.socket.close()
|
||||
this.socket = null
|
||||
}
|
||||
this.isReconnecting = false
|
||||
this.state = WebSocketState.DISCONNECTED
|
||||
|
||||
this.isInitialized.value = false
|
||||
// 重置状态
|
||||
this.isReconnecting.value = false
|
||||
this.isConnected.value = false
|
||||
this.state.value = WebSocketState.DISCONNECTED
|
||||
this.isInit.value = false
|
||||
this.isOpen.value = false
|
||||
this.reconnectAttempts = 0
|
||||
|
||||
// 清空消息队列
|
||||
this.messageQueue = []
|
||||
}
|
||||
}
|
||||
@@ -63,12 +63,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineEmits, defineProps } from 'vue'
|
||||
import { ref, defineEmits, defineProps, onMounted, watch } from 'vue'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { CheckmarkOutline, DesktopOutline } from '@vicons/ionicons5'
|
||||
import { httpClient } from '@/utils/http.ts'
|
||||
import { ResponseModel } from '@/types/response'
|
||||
|
||||
import { ResponseModel, SubscriptionInfo } from '@/types/response'
|
||||
import { useWebSocketStore,getIsConnected } from '@/stores/websocketStore'
|
||||
// 定义应用数据类型
|
||||
interface Application {
|
||||
id: string
|
||||
@@ -87,11 +88,23 @@ interface Application {
|
||||
const emit = defineEmits<{
|
||||
'app-selected': [app: Application | null]
|
||||
}>()
|
||||
|
||||
// 发送请求
|
||||
httpClient.post(`/subscribe`, '发送的测试例子').then((res:ResponseModel) => {
|
||||
console.log(res)
|
||||
})
|
||||
const webSocketStore = useWebSocketStore()
|
||||
const { isOpen } = storeToRefs(webSocketStore)
|
||||
console.log(isOpen);
|
||||
watch(isOpen,(oldValue, newValue) => {
|
||||
if (oldValue || newValue) {
|
||||
console.log("触发!!!!!!!!");
|
||||
// 发送请求【请求订阅软件列表】
|
||||
// const subAppInfo: SubscriptionInfo = {
|
||||
// subscriptionType: 'ApplicationInfo',
|
||||
// interval: 1000,
|
||||
// }
|
||||
//
|
||||
// httpClient.post(`/subscribe`, JSON.stringify(subAppInfo)).then((res: ResponseModel) => {
|
||||
// console.log(res)
|
||||
// })
|
||||
}
|
||||
},{immediate:true})
|
||||
|
||||
// 选中的应用
|
||||
const selectedApp = ref<Application | null>(null)
|
||||
|
||||
Reference in new issue
Block a user