up. ws update.
This commit is contained in:
parent
5f69431080
commit
9afa5e5583
@ -9,6 +9,7 @@
|
||||
<ele-app>
|
||||
<router-view />
|
||||
</ele-app>
|
||||
<websocket-state />
|
||||
</ele-config-provider>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
@ -18,8 +19,7 @@
|
||||
import { useGlobalConfig } from '@/config/use-global-config';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useLocale } from '@/i18n/use-locale';
|
||||
import { inject } from 'vue';
|
||||
import {webSocket_handler} from "@/plugins/webSocket_handler";
|
||||
import WebsocketState from '@/components/WebsocketState/index.vue';
|
||||
|
||||
/** 组件全局配置 */
|
||||
const { tableConfig } = useGlobalConfig();
|
||||
@ -31,6 +31,4 @@
|
||||
/** 国际化配置 */
|
||||
const { elLocale, eleLocale } = useLocale();
|
||||
|
||||
// 通过inject获取WebSocket实例
|
||||
webSocket_handler(inject('websocket'))
|
||||
</script>
|
||||
|
||||
29
z_ele/src/components/LockScreenState/index.vue
Normal file
29
z_ele/src/components/LockScreenState/index.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
title="安全锁定"
|
||||
:width="480"
|
||||
v-model="visible"
|
||||
:modal-penetrable="false"
|
||||
:append-to-body="true"
|
||||
:lock-scroll="true"
|
||||
center
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, inject} from 'vue';
|
||||
import {ConnectionStatus} from "@/plugins/webSocket_plugin";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
const { connectionStatus } = <any>inject('websocket');
|
||||
const userStore = useUserStore();
|
||||
|
||||
const visible = computed<boolean>(()=> {
|
||||
return (connectionStatus.value != ConnectionStatus.CONNECTED && userStore.info)
|
||||
});
|
||||
|
||||
</script>
|
||||
33
z_ele/src/components/WebsocketState/index.vue
Normal file
33
z_ele/src/components/WebsocketState/index.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
title="AdminWorker错误"
|
||||
:width="480"
|
||||
v-model="visible"
|
||||
:modal-penetrable="false"
|
||||
:append-to-body="true"
|
||||
:lock-scroll="true"
|
||||
center
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<h1 style="color: red">错误!admin:worker未正确连接!</h1>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, inject} from 'vue';
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import { ConnectionStatus} from "@/plugins/websocket/type";
|
||||
import { websocketInstance } from "@/plugins/websocket/instance";
|
||||
const userStore = useUserStore();
|
||||
|
||||
// const visible = ref(false);
|
||||
const visible = computed<boolean>(()=>{
|
||||
return (
|
||||
websocketInstance.connectionStatus.value != ConnectionStatus.CONNECTED &&
|
||||
userStore.info
|
||||
);
|
||||
});
|
||||
|
||||
</script>
|
||||
@ -7,8 +7,8 @@ import DictData from '@/components/DictData/index.vue';
|
||||
import i18n from './i18n';
|
||||
import installer from './as-needed';
|
||||
import { iconsInstaller } from '@/components/IconSelect/util';
|
||||
import WebSocketPlugin, { WebSocketConfig } from './plugins/webSocket_plugin';
|
||||
|
||||
import WebSocketPlugin from '@/plugins/websocket';
|
||||
import type { WebSocketConfig } from '@/plugins/websocket/type';
|
||||
import 'element-plus/theme-chalk/display.css';
|
||||
import 'ele-admin-plus/es/style/nprogress.scss';
|
||||
import './styles/themes/rounded.scss';
|
||||
@ -27,12 +27,9 @@ const websocketConfig: Partial<WebSocketConfig> = {
|
||||
};
|
||||
// 安装WebSocket插件
|
||||
app.use(WebSocketPlugin, {
|
||||
globalConfig: websocketConfig,
|
||||
injectGlobal: true,
|
||||
globalPropertyName: '$websocket'
|
||||
globalConfig: websocketConfig
|
||||
});
|
||||
|
||||
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
app.use(permission);
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
|
||||
export const webSocket_handler = (websocket: any) => {
|
||||
const {
|
||||
isConnected,
|
||||
isConnecting,
|
||||
connectionStatus,
|
||||
messages,
|
||||
statusClass,
|
||||
connect,
|
||||
disconnect,
|
||||
sendMessage
|
||||
} = websocket;
|
||||
console.log(messages.value)
|
||||
}
|
||||
|
||||
|
||||
29
z_ele/src/plugins/websocket/index.ts
Normal file
29
z_ele/src/plugins/websocket/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// 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;
|
||||
@ -1,45 +1,16 @@
|
||||
// plugins/WebSocketPlugin.ts
|
||||
import { App, Plugin, ref, computed } from 'vue';
|
||||
|
||||
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<WebSocketConfig>;
|
||||
// 是否注入全局实例
|
||||
injectGlobal?: boolean;
|
||||
// 全局属性名
|
||||
globalPropertyName?: string;
|
||||
}
|
||||
import {
|
||||
ConnectionStatus, MessageType,
|
||||
PaWebSocket,
|
||||
SendEventType,
|
||||
WebSocketConfig,
|
||||
WebSocketMessage,
|
||||
WsEvent
|
||||
} from '@/plugins/websocket/type';
|
||||
import { subscriptionManager } from '@/plugins/websocket/subscriber';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
// 创建WebSocket实例的函数
|
||||
export function createWebSocket(config: WebSocketConfig) {
|
||||
function createWebSocket(config: WebSocketConfig) {
|
||||
// 响应式数据
|
||||
const socket = ref<WebSocket | null>(null);
|
||||
const isConnected = ref(false);
|
||||
@ -51,9 +22,12 @@ export function createWebSocket(config: WebSocketConfig) {
|
||||
// 计算属性
|
||||
const statusClass = computed(() => {
|
||||
switch (connectionStatus.value) {
|
||||
case ConnectionStatus.CONNECTED: return 'status-connected';
|
||||
case ConnectionStatus.CONNECTING: return 'status-connecting';
|
||||
default: return 'status-disconnected';
|
||||
case ConnectionStatus.CONNECTED:
|
||||
return 'status-connected';
|
||||
case ConnectionStatus.CONNECTING:
|
||||
return 'status-connecting';
|
||||
default:
|
||||
return 'status-disconnected';
|
||||
}
|
||||
});
|
||||
|
||||
@ -95,17 +69,23 @@ export function createWebSocket(config: WebSocketConfig) {
|
||||
const reconnect = () => {
|
||||
if (reconnectCount.value < config.reconnectAttempts) {
|
||||
reconnectCount.value++;
|
||||
addSystemMessage(`尝试重新连接 (${reconnectCount.value}/${config.reconnectAttempts})...`);
|
||||
addSystemMessage(
|
||||
`尝试重新连接 (${reconnectCount.value}/${config.reconnectAttempts})...`
|
||||
);
|
||||
setTimeout(() => connect(), config.reconnectDelay);
|
||||
} else {
|
||||
addSystemMessage('已达到最大重连次数,连接终止');
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = (content: string) => {
|
||||
if (socket.value && isConnected.value && content) {
|
||||
socket.value.send(content);
|
||||
addMessage(content, MessageType.OUTGOING);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -132,6 +112,8 @@ export function createWebSocket(config: WebSocketConfig) {
|
||||
connectionStatus.value = ConnectionStatus.CONNECTED;
|
||||
reconnectCount.value = 0;
|
||||
addSystemMessage('连接已建立');
|
||||
// 触发连接成功事件
|
||||
subscriptionManager.trigger(WsEvent.CONNECTED, e);
|
||||
};
|
||||
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
@ -163,56 +145,27 @@ export function createWebSocket(config: WebSocketConfig) {
|
||||
if (config.autoConnect) {
|
||||
connect();
|
||||
}
|
||||
|
||||
return {
|
||||
return <PaWebSocket>{
|
||||
// 状态
|
||||
isConnected,
|
||||
isConnecting,
|
||||
connectionStatus,
|
||||
messages,
|
||||
statusClass,
|
||||
|
||||
// 方法
|
||||
connect,
|
||||
disconnect,
|
||||
sendMessage,
|
||||
clearMessages
|
||||
clearMessages,
|
||||
subscription: subscriptionManager
|
||||
};
|
||||
}
|
||||
|
||||
// 插件安装函数
|
||||
const WebSocketPlugin: Plugin = {
|
||||
install(app: App, options: WebSocketPluginOptions = {}) {
|
||||
const {
|
||||
globalConfig = {},
|
||||
injectGlobal = false,
|
||||
globalPropertyName = '$websocket'
|
||||
} = options;
|
||||
export let websocketInstance: PaWebSocket | null = null;
|
||||
|
||||
// 合并默认配置和全局配置
|
||||
const defaultConfig: WebSocketConfig = {
|
||||
url: 'wss://echo.websocket.org',
|
||||
protocols: '',
|
||||
reconnectAttempts: 5,
|
||||
reconnectDelay: 3000,
|
||||
autoConnect: false,
|
||||
...globalConfig
|
||||
};
|
||||
|
||||
// 创建WebSocket实例
|
||||
const websocketInstance = createWebSocket(defaultConfig);
|
||||
|
||||
// 如果需要,注入全局实例
|
||||
if (injectGlobal) {
|
||||
app.config.globalProperties[globalPropertyName] = websocketInstance;
|
||||
}
|
||||
|
||||
// 提供WebSocket实例,以便组件通过inject使用
|
||||
app.provide('websocket', websocketInstance);
|
||||
|
||||
// 注册全局组件(如果需要)
|
||||
// app.component('WebSocketComponent', WebSocketComponent);
|
||||
export function initWebSocket(config: WebSocketConfig): PaWebSocket {
|
||||
if (!websocketInstance) {
|
||||
websocketInstance = createWebSocket(config);
|
||||
}
|
||||
};
|
||||
|
||||
export default WebSocketPlugin;
|
||||
return websocketInstance;
|
||||
}
|
||||
81
z_ele/src/plugins/websocket/subscriber.ts
Normal file
81
z_ele/src/plugins/websocket/subscriber.ts
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 事件订阅管理器
|
||||
* 用于统一管理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<string, EventListener[]>;
|
||||
addListener(event: WsEvent, listener: Function): void;
|
||||
removeListener(event: WsEvent, listenerId: string): void;
|
||||
trigger(event: WsEvent, data: any): void;
|
||||
}
|
||||
|
||||
class SubscriptionManagerImpl implements SubscriptionManager {
|
||||
events: Record<string, EventListener[]> = {
|
||||
// 全局事件名称定义
|
||||
[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();
|
||||
51
z_ele/src/plugins/websocket/type.ts
Normal file
51
z_ele/src/plugins/websocket/type.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { SubscriptionManager } from '@/plugins/websocket/subscriber';
|
||||
|
||||
export enum WsEvent {
|
||||
CONNECTED = 'CONNECTED' // websocket连接成功
|
||||
}
|
||||
|
||||
export interface PaWebSocket {
|
||||
isConnected: Ref<boolean>;
|
||||
connectionStatus: Ref<string>;
|
||||
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<WebSocketConfig>;
|
||||
}
|
||||
@ -9,6 +9,10 @@ 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";
|
||||
/** 直接指定菜单数据 */
|
||||
const USER_MENUS: Menu[] | null = null;
|
||||
|
||||
@ -18,6 +22,7 @@ export interface UserState {
|
||||
authorities: (string | undefined)[];
|
||||
roles: (string | undefined)[];
|
||||
dicts: Record<string, DictionaryData[]>;
|
||||
clientState: any;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
@ -31,7 +36,9 @@ export const useUserStore = defineStore('user', {
|
||||
/** 当前登录用户的角色 */
|
||||
roles: [],
|
||||
/** 字典数据缓存 */
|
||||
dicts: {}
|
||||
dicts: {},
|
||||
/** 客户端状态数据 */
|
||||
clientState: {}
|
||||
}),
|
||||
actions: {
|
||||
/**
|
||||
@ -61,8 +68,25 @@ 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();
|
||||
}
|
||||
);
|
||||
return { menus, homePath };
|
||||
},
|
||||
setClientState(value: any) {
|
||||
this.clientState = value;
|
||||
},
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
|
||||
@ -4,8 +4,11 @@ import { ElMessageBox } from 'element-plus';
|
||||
import { removeToken } from '@/utils/token-util';
|
||||
|
||||
export function generateGUID() {
|
||||
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
||||
(
|
||||
c ^
|
||||
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
|
||||
).toString(16)
|
||||
);
|
||||
}
|
||||
/**
|
||||
@ -14,11 +17,7 @@ export function generateGUID() {
|
||||
* @param from 登录后跳转的地址
|
||||
* @param push 路由跳转方法
|
||||
*/
|
||||
export function logout(
|
||||
route?: boolean,
|
||||
from?: string,
|
||||
push?: Router['push']
|
||||
) {
|
||||
export function logout(route?: boolean, from?: string, push?: Router['push']) {
|
||||
removeToken();
|
||||
if (route && push) {
|
||||
push({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user