From 98cb8ee0aa7c4b6f83a4798ac1f99f45e920892e Mon Sep 17 00:00:00 2001 From: v Date: Thu, 28 Aug 2025 22:27:53 +0800 Subject: [PATCH] up. ws update. --- z_ele/src/components/WebsocketState/index.vue | 12 +- z_ele/src/main.ts | 7 +- .../plugins/websocket/core/event-manager.ts | 57 ++++++ z_ele/src/plugins/websocket/core/instance.ts | 146 +++++++++++++++ z_ele/src/plugins/websocket/index.ts | 29 --- z_ele/src/plugins/websocket/instance.ts | 171 ------------------ z_ele/src/plugins/websocket/plugin.ts | 21 +++ z_ele/src/plugins/websocket/subscriber.ts | 81 --------- z_ele/src/plugins/websocket/type.ts | 51 ------ z_ele/src/plugins/websocket/types.ts | 65 +++++++ z_ele/src/store/modules/user.ts | 34 ++-- 11 files changed, 315 insertions(+), 359 deletions(-) create mode 100644 z_ele/src/plugins/websocket/core/event-manager.ts create mode 100644 z_ele/src/plugins/websocket/core/instance.ts delete mode 100644 z_ele/src/plugins/websocket/index.ts delete mode 100644 z_ele/src/plugins/websocket/instance.ts create mode 100644 z_ele/src/plugins/websocket/plugin.ts delete mode 100644 z_ele/src/plugins/websocket/subscriber.ts delete mode 100644 z_ele/src/plugins/websocket/type.ts create mode 100644 z_ele/src/plugins/websocket/types.ts diff --git a/z_ele/src/components/WebsocketState/index.vue b/z_ele/src/components/WebsocketState/index.vue index 379d151..69393e5 100644 --- a/z_ele/src/components/WebsocketState/index.vue +++ b/z_ele/src/components/WebsocketState/index.vue @@ -16,18 +16,16 @@ diff --git a/z_ele/src/main.ts b/z_ele/src/main.ts index e24a7ef..4a2c642 100644 --- a/z_ele/src/main.ts +++ b/z_ele/src/main.ts @@ -7,8 +7,7 @@ import DictData from '@/components/DictData/index.vue'; import i18n from './i18n'; import installer from './as-needed'; import { iconsInstaller } from '@/components/IconSelect/util'; -import WebSocketPlugin from '@/plugins/websocket'; -import type { WebSocketConfig } from '@/plugins/websocket/type'; +import { WsConfig, WsPlugin } from '@/plugins/websocket'; import 'element-plus/theme-chalk/display.css'; import 'ele-admin-plus/es/style/nprogress.scss'; import './styles/themes/rounded.scss'; @@ -19,14 +18,14 @@ import './styles/index.scss'; const app = createApp(App); // WebSocket配置 -const websocketConfig: Partial = { +const websocketConfig: Partial = { url: 'ws://139.155.146.146:19980', // 你的WebSocket服务器地址 reconnectAttempts: 10, reconnectDelay: 5000, autoConnect: true // 应用启动时自动连接 }; // 安装WebSocket插件 -app.use(WebSocketPlugin, { +app.use(WsPlugin, { globalConfig: websocketConfig }); diff --git a/z_ele/src/plugins/websocket/core/event-manager.ts b/z_ele/src/plugins/websocket/core/event-manager.ts new file mode 100644 index 0000000..fc0bbbb --- /dev/null +++ b/z_ele/src/plugins/websocket/core/event-manager.ts @@ -0,0 +1,57 @@ +import { WsEvent, WsEventContext, WsEventListener } from '../types'; +import { generateGUID } from '@/utils/common'; +import { wsInstance } from './instance'; + +export interface WsEventManager { + listeners: Record; + subscribe(event: WsEvent, handler: Function): WsEventContext; + unsubscribe(event: WsEvent, listenerId: string): void; + emit(event: WsEvent, data?: any): void; +} + +export class WsEventManagerImpl implements WsEventManager { + listeners: Record = {}; + + subscribe(event: WsEvent, handler: Function): WsEventContext { + if (!this.listeners[event]) this.listeners[event] = []; + + const listenerId = generateGUID(); + const listener: WsEventListener = { handler, id: listenerId }; + this.listeners[event].push(listener); + + // 立即触发已连接事件 + if (event === WsEvent.CONNECTED && wsInstance?.isConnected.value) { + handler({ + id: listenerId, + remove: () => this.unsubscribe(event, listenerId) + }); + } + + return { + id: listenerId, + remove: () => this.unsubscribe(event, listenerId) + }; + } + + unsubscribe(event: WsEvent, listenerId: string): void { + if (!this.listeners[event]) return; + this.listeners[event] = this.listeners[event].filter(l => l.id !== listenerId); + } + + emit(event: WsEvent, data?: any): void { + console.debug(`[WS Event] Emitting: ${event}`); + const listeners = this.listeners[event] || []; + + // 安全遍历 + listeners.forEach((listener) => { + const context: WsEventContext = { + id: listener.id, + remove: () => this.unsubscribe(event, listener.id) + }; + listener.handler(context, wsInstance, data); + }); + } +} + +// 单例事件管理器 +export const wsEventManager = new WsEventManagerImpl(); diff --git a/z_ele/src/plugins/websocket/core/instance.ts b/z_ele/src/plugins/websocket/core/instance.ts new file mode 100644 index 0000000..709dc2f --- /dev/null +++ b/z_ele/src/plugins/websocket/core/instance.ts @@ -0,0 +1,146 @@ +import { + WsConfig, + WsInstance, + WsConnectionStatus, + WsMessageType, + WsMessage, + WsEvent, + WsSendEventType +} from '../types'; +import { computed, ref } from 'vue'; +import { wsEventManager } from './event-manager'; + +// 创建WebSocket实例 +export function createWsInstance(config: WsConfig): WsInstance { + // 响应式状态 + const socket = ref(null); + const isConnected = ref(false); + const isConnecting = ref(false); + const reconnectCount = ref(0); + const connectionStatus = ref( + WsConnectionStatus.DISCONNECTED + ); + const messages = ref([]); + + // 计算状态类 + const statusClass = computed( + () => `status-${connectionStatus.value.toLowerCase()}` + ); + + // 内部方法 + const addMessage = (content: string, type: WsMessageType) => { + messages.value.push({ content, type, timestamp: new Date() }); + }; + + const addSystemMessage = (content: string) => + addMessage(content, WsMessageType.SYSTEM); + + // 事件处理器 + const handleOpen = (e: Event) => { + isConnected.value = true; + isConnecting.value = false; + connectionStatus.value = WsConnectionStatus.CONNECTED; + reconnectCount.value = 0; + addSystemMessage('Connection established'); + wsEventManager.emit(WsEvent.CONNECTED, e); + }; + + const handleMessage = (e: MessageEvent) => + addMessage(e.data, WsMessageType.INCOMING); + + const handleError = (e: Event) => { + isConnecting.value = false; + connectionStatus.value = WsConnectionStatus.ERROR; + addSystemMessage(`Error: ${e.type}`); + !isConnected.value && attemptReconnect(); + console.log(e); + }; + + const handleClose = (e: CloseEvent) => { + isConnected.value = false; + isConnecting.value = false; + connectionStatus.value = WsConnectionStatus.DISCONNECTED; + addSystemMessage(`Connection closed: ${e.code} ${e.reason || ''}`); + e.code !== 1000 && attemptReconnect(); + }; + + // 重连逻辑 + const attemptReconnect = () => { + if (reconnectCount.value >= config.reconnectAttempts) { + return addSystemMessage('Max reconnect attempts reached'); + } + + reconnectCount.value++; + addSystemMessage( + `Reconnecting (${reconnectCount.value}/${config.reconnectAttempts})...` + ); + setTimeout(connectSocket, config.reconnectDelay); + }; + + // 公开方法 + const connectSocket = () => { + if (isConnected.value || isConnecting.value) return; + + isConnecting.value = true; + connectionStatus.value = WsConnectionStatus.CONNECTING; + addSystemMessage('Connecting...'); + + try { + socket.value = new WebSocket(config.url); + + socket.value.onopen = handleOpen; + socket.value.onmessage = handleMessage; + socket.value.onerror = handleError; + socket.value.onclose = handleClose; + } catch (error) { + handleError(error as Event); + } + }; + + const disconnectSocket = () => { + socket.value?.close(); + socket.value = null; + isConnected.value = false; + isConnecting.value = false; + connectionStatus.value = WsConnectionStatus.DISCONNECTED; + reconnectCount.value = 0; + addSystemMessage('Connection closed'); + }; + + const sendMessage = (event: WsSendEventType, data: any) => { + if (!socket.value || !isConnected.value || !data) return; + + const payload = JSON.stringify({ event, data }); + socket.value.send(payload); + addMessage(payload, WsMessageType.OUTGOING); + }; + + const clearMessages = () => (messages.value = []); + + // 自动连接 + console.log('autoConnect->'); + config.autoConnect && connectSocket(); + + return { + isConnected, + isConnecting, + connectionStatus, + messages, + statusClass, + events: wsEventManager, + + connectSocket, + disconnectSocket, + sendMessage, + clearMessages + }; +} + +// 全局实例 +export let wsInstance: WsInstance | null = null; + +// 初始化方法 +export function initWs(config: WsConfig): WsInstance { + return (wsInstance = wsInstance || createWsInstance(config)); +} + diff --git a/z_ele/src/plugins/websocket/index.ts b/z_ele/src/plugins/websocket/index.ts deleted file mode 100644 index f459260..0000000 --- a/z_ele/src/plugins/websocket/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// plugins/websocket -import { App, Plugin } from 'vue'; -import { - WebSocketConfig, - WebSocketPluginOptions -} from '@/plugins/websocket/type'; -import { initWebSocket } from '@/plugins/websocket/instance'; - -// 插件安装函数 -const WebSocketPlugin: Plugin = { - install(app: App, options: WebSocketPluginOptions = {}) { - const { globalConfig = {} } = options; - - // 合并默认配置和全局配置 - const defaultConfig: WebSocketConfig = { - url: 'wss://echo.websocket.org', - protocols: '', - reconnectAttempts: 5, - reconnectDelay: 3000, - autoConnect: false, - ...globalConfig - }; - // 创建WebSocket实例 - initWebSocket(defaultConfig); - console.debug('initWebSocket.', defaultConfig); - } -}; - -export default WebSocketPlugin; diff --git a/z_ele/src/plugins/websocket/instance.ts b/z_ele/src/plugins/websocket/instance.ts deleted file mode 100644 index 84a0147..0000000 --- a/z_ele/src/plugins/websocket/instance.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { - ConnectionStatus, MessageType, - PaWebSocket, - SendEventType, - WebSocketConfig, - WebSocketMessage, - WsEvent -} from '@/plugins/websocket/type'; -import { subscriptionManager } from '@/plugins/websocket/subscriber'; -import { computed, ref } from 'vue'; - -// 创建WebSocket实例的函数 -function createWebSocket(config: WebSocketConfig) { - // 响应式数据 - const socket = ref(null); - const isConnected = ref(false); - const isConnecting = ref(false); - const reconnectCount = ref(0); - const connectionStatus = ref(ConnectionStatus.DISCONNECTED); - const messages = ref([]); - - // 计算属性 - const statusClass = computed(() => { - switch (connectionStatus.value) { - case ConnectionStatus.CONNECTED: - return 'status-connected'; - case ConnectionStatus.CONNECTING: - return 'status-connecting'; - default: - return 'status-disconnected'; - } - }); - - // 方法 - const connect = () => { - if (isConnected.value || isConnecting.value) return; - - isConnecting.value = true; - connectionStatus.value = ConnectionStatus.CONNECTING; - addSystemMessage('正在连接...'); - - try { - socket.value = new WebSocket( - config.url, - config.protocols ? config.protocols.split(',') : undefined - ); - - socket.value.onopen = onOpen; - socket.value.onmessage = onMessage; - socket.value.onerror = onError; - socket.value.onclose = onClose; - } catch (error) { - onError(error as Event); - } - }; - - const disconnect = () => { - if (socket.value) { - socket.value.close(); - socket.value = null; - } - isConnected.value = false; - isConnecting.value = false; - connectionStatus.value = ConnectionStatus.DISCONNECTED; - reconnectCount.value = 0; - addSystemMessage('连接已关闭'); - }; - - const reconnect = () => { - if (reconnectCount.value < config.reconnectAttempts) { - reconnectCount.value++; - addSystemMessage( - `尝试重新连接 (${reconnectCount.value}/${config.reconnectAttempts})...` - ); - setTimeout(() => connect(), config.reconnectDelay); - } else { - addSystemMessage('已达到最大重连次数,连接终止'); - } - }; - - const sendMessage = (event: SendEventType, data: any) => { - if (socket.value && isConnected.value && data) { - const message: string = JSON.stringify({ event, data }); - socket.value.send(message); - /* - * 扔进最近的消息记录 - */ - addMessage(message, MessageType.OUTGOING); - } - }; - - const clearMessages = () => { - messages.value = []; - }; - - const addMessage = (content: string, type: MessageType) => { - messages.value.push({ - content, - type, - timestamp: new Date() - }); - }; - - const addSystemMessage = (content: string) => { - addMessage(content, MessageType.SYSTEM); - }; - - // WebSocket事件处理 - const onOpen = (e: Event) => { - isConnected.value = true; - isConnecting.value = false; - connectionStatus.value = ConnectionStatus.CONNECTED; - reconnectCount.value = 0; - addSystemMessage('连接已建立'); - // 触发连接成功事件 - subscriptionManager.trigger(WsEvent.CONNECTED, e); - }; - - const onMessage = (event: MessageEvent) => { - addMessage(event.data, MessageType.INCOMING); - }; - - const onError = (error: Event) => { - isConnecting.value = false; - connectionStatus.value = ConnectionStatus.ERROR; - addSystemMessage(`错误: ${error.type}`); - - if (!isConnected.value) { - reconnect(); - } - }; - - const onClose = (event: CloseEvent) => { - isConnected.value = false; - isConnecting.value = false; - connectionStatus.value = ConnectionStatus.DISCONNECTED; - addSystemMessage(`连接关闭: ${event.code} ${event.reason || ''}`); - - if (event.code !== 1000) { - reconnect(); - } - }; - - // 自动连接(如果配置了) - if (config.autoConnect) { - connect(); - } - return { - // 状态 - isConnected, - isConnecting, - connectionStatus, - messages, - statusClass, - // 方法 - connect, - disconnect, - sendMessage, - clearMessages, - subscription: subscriptionManager - }; -} - -export let websocketInstance: PaWebSocket | null = null; - -export function initWebSocket(config: WebSocketConfig): PaWebSocket { - if (!websocketInstance) { - websocketInstance = createWebSocket(config); - } - return websocketInstance; -} diff --git a/z_ele/src/plugins/websocket/plugin.ts b/z_ele/src/plugins/websocket/plugin.ts new file mode 100644 index 0000000..d6a71a6 --- /dev/null +++ b/z_ele/src/plugins/websocket/plugin.ts @@ -0,0 +1,21 @@ +import { App, Plugin } from 'vue'; +import { WsPluginOptions, WsConfig } from './types'; +import { initWs } from './core/instance'; + +const WsPlugin: Plugin = { + install(app: App, options: WsPluginOptions = {}) { + const mergedConfig: WsConfig = { + url: 'wss://echo.websocket.org', + protocols: 'chat', + reconnectAttempts: 5, + reconnectDelay: 3000, + autoConnect: false, + ...options.globalConfig + }; + // 初始化并挂载到app + console.log('初始化ws', mergedConfig); + app.config.globalProperties.$ws = initWs(mergedConfig); + } +}; + +export default WsPlugin; diff --git a/z_ele/src/plugins/websocket/subscriber.ts b/z_ele/src/plugins/websocket/subscriber.ts deleted file mode 100644 index 0f10b5e..0000000 --- a/z_ele/src/plugins/websocket/subscriber.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 事件订阅管理器 - * 用于统一管理WebSocket事件订阅与触发 - */ - -import { WsEvent } from '@/plugins/websocket/type'; -import { generateGUID } from '@/utils/common'; -import { websocketInstance } from '@/plugins/websocket/instance'; - -// 重命名接口,使用更具描述性的名称 -export interface EventListener { - handler: Function; - id: string; -} - -export interface SubscriptionContext { - id: string; - destroy(): void; -} - -export interface SubscriptionManager { - events: Record; - addListener(event: WsEvent, listener: Function): void; - removeListener(event: WsEvent, listenerId: string): void; - trigger(event: WsEvent, data: any): void; -} - -class SubscriptionManagerImpl implements SubscriptionManager { - events: Record = { - // 全局事件名称定义 - [WsEvent.CONNECTED]: [] - }; - addListener(event: WsEvent, listener: Function): void { - // 确保事件数组已初始化 - if (!this.events[event]) { - this.events[event] = []; - } - const listenerId = generateGUID(); - this.events[event].push({ - handler: listener, - id: listenerId - }); - // 部分一次性的事件(如果处于此事件,则直接触发) - if (event == WsEvent.CONNECTED) { - if (websocketInstance?.isConnected) { - this.trigger(event, null); - } - } - } - - removeListener(event: WsEvent, listenerId: string): void { - if (!this.events[event]) return; - - this.events[event] = this.events[event].filter( - (listener) => listener.id !== listenerId - ); - } - - trigger(event: WsEvent, data: any): void { - console.log('触发事件', event); - - if (!this.events[event] || this.events[event].length === 0) { - return; - } - - // 创建副本以避免在迭代过程中修改原数组 - const listeners = [...this.events[event]]; - - listeners.forEach((listener) => { - const context: SubscriptionContext = { - id: listener.id, - destroy: () => this.removeListener(event, listener.id) - }; - - listener.handler(context, websocketInstance, data); - }); - } -} - -// 创建单例实例 -export const subscriptionManager = new SubscriptionManagerImpl(); diff --git a/z_ele/src/plugins/websocket/type.ts b/z_ele/src/plugins/websocket/type.ts deleted file mode 100644 index 0b68cb6..0000000 --- a/z_ele/src/plugins/websocket/type.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Ref } from 'vue'; -import { SubscriptionManager } from '@/plugins/websocket/subscriber'; - -export enum WsEvent { - CONNECTED = 'CONNECTED' // websocket连接成功 -} - -export interface PaWebSocket { - isConnected: Ref; - connectionStatus: Ref; - sendMessage: Function; - subscription: SubscriptionManager; -} - -export enum SendEventType { - BIND_CONNECT_ID = 'bind_connect_id' -} - - -export enum ConnectionStatus { - CONNECTING = 'connecting', - CONNECTED = 'connected', - DISCONNECTED = 'disconnected', - ERROR = 'error' -} - -export enum MessageType { - INCOMING = 'incoming', - OUTGOING = 'outgoing', - SYSTEM = 'system' -} - -export interface WebSocketMessage { - content: string; - type: MessageType; - timestamp: Date; -} - -export interface WebSocketConfig { - url: string; - protocols?: string; - reconnectAttempts: number; - reconnectDelay: number; - autoConnect?: boolean; -} - -// 插件选项接口 -export interface WebSocketPluginOptions { - // 全局配置 - globalConfig?: Partial; -} diff --git a/z_ele/src/plugins/websocket/types.ts b/z_ele/src/plugins/websocket/types.ts new file mode 100644 index 0000000..65ce5d4 --- /dev/null +++ b/z_ele/src/plugins/websocket/types.ts @@ -0,0 +1,65 @@ +import type { Ref } from 'vue'; +import { WsEventManager } from '@/plugins/websocket/core/event-manager'; + +export enum WsEvent { + CONNECTED = 'CONNECTED' +} + +export enum WsSendEventType { + BIND_CONNECT_ID = 'bind_connect_id' +} + +export enum WsConnectionStatus { + CONNECTING = 'connecting', + CONNECTED = 'connected', + DISCONNECTED = 'disconnected', + ERROR = 'error' +} + +export enum WsMessageType { + INCOMING = 'incoming', + OUTGOING = 'outgoing', + SYSTEM = 'system' +} + +export interface WsMessage { + content: string; + type: WsMessageType; + timestamp: Date; +} + +export interface WsConfig { + url: string; + protocols?: string; + reconnectAttempts: number; + reconnectDelay: number; + autoConnect?: boolean; +} + +export interface WsPluginOptions { + globalConfig?: Partial; +} + +export interface WsEventListener { + handler: Function; + id: string; +} + +export interface WsEventContext { + id: string; + remove(): void; +} + +export interface WsInstance { + isConnected: Ref; + isConnecting: Ref; + connectionStatus: Ref; + messages: Ref; + statusClass: Ref; + + connectSocket: () => void; + disconnectSocket: () => void; + sendMessage: (event: WsSendEventType, data: any) => void; + clearMessages: () => void; + events: WsEventManager; // 重命名为更清晰的events +} diff --git a/z_ele/src/store/modules/user.ts b/z_ele/src/store/modules/user.ts index a331d26..10cee90 100644 --- a/z_ele/src/store/modules/user.ts +++ b/z_ele/src/store/modules/user.ts @@ -9,10 +9,16 @@ import type { User } from '@/api/system/user/model'; import type { Menu } from '@/api/system/menu/model'; import type { DictionaryData } from '@/api/system/dictionary-data/model'; import { getUserInfo } from '@/api/layout'; -import { inject } from 'vue'; -import { PaWebSocket, SendEventType, WsEvent } from '@/plugins/websocket/type'; import { getToken } from '@/utils/token-util'; -import {getSubscriptionManager, SubscriptionContext, subscriptionManager} from "@/plugins/websocket/subscriber"; +import { + WsEventContext, + wsEventManager, + WsEvent, + WsInstance, + WsSendEventType, + wsInstance +} from '@/plugins/websocket'; + /** 直接指定菜单数据 */ const USER_MENUS: Menu[] | null = null; @@ -69,19 +75,15 @@ export const useUserStore = defineStore('user', { ); this.setMenus(menus); - console.log('管理器塞事件进去', inject('websocket')); - subscriptionManager.addListener( - WsEvent.CONNECTED, - (sub: SubscriptionContext, ws: PaWebSocket) => { - console.log('事件执行'); - // 绑定用户到websocket的connect_id中 - ws.sendMessage(SendEventType.BIND_CONNECT_ID, { - token: getToken(), - userId: result.userId - }); - // sub.destroy(); - } - ); + console.log('管理器塞事件进去'); + wsEventManager.subscribe(WsEvent.CONNECTED, (sub: WsEventContext) => { + console.log('事件执行, 绑定连接和用户关系'); + // 绑定用户到websocket的connect_id中 + wsInstance.sendMessage(WsSendEventType.BIND_CONNECT_ID, { + token: getToken(), + userId: result.userId + }); + }); return { menus, homePath }; }, setClientState(value: any) {