diff --git a/.env.development b/.env.development index 55b854a1..10bd8d14 100644 --- a/.env.development +++ b/.env.development @@ -9,7 +9,7 @@ VITE_APP_API_URL=https://api.youlai.tech # 线上 # VITE_APP_API_URL=http://localhost:8989 # 本地 # WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws -VITE_APP_WS_ENDPOINT= +VITE_APP_WS_ENDPOINT=ws://localhost:8989/ws # 启用 Mock 服务 VITE_MOCK_DEV_SERVER=false diff --git a/src/hooks/useStomp.ts b/src/hooks/useStomp.ts new file mode 100644 index 00000000..b13d394a --- /dev/null +++ b/src/hooks/useStomp.ts @@ -0,0 +1,151 @@ +import { ref, watch, onMounted } from "vue"; +import { Client, IMessage, StompSubscription } from "@stomp/stompjs"; +import { getAccessToken } from "@/utils/auth"; + +export interface UseStompOptions { + /** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */ + brokerURL?: string; + /** 用于鉴权的 token,不传时使用 getToken() 的返回值 */ + token?: string; + /** 重连延迟,单位毫秒,默认为 5000 */ + reconnectDelay?: number; + /** 是否开启调试日志 */ + debug?: boolean; +} + +export function useStomp(options: UseStompOptions = {}) { + // 默认值:brokerURL 从环境变量中获取,token 从 getAccessToken() 获取 + const defaultBrokerURL = import.meta.env.VITE_APP_WS_ENDPOINT || ""; + const defaultToken = getAccessToken(); + + // 将 brokerURL 定义为响应式 ref,便于动态修改 + const brokerURL = ref(options.brokerURL ?? defaultBrokerURL); + const token = options.token ?? defaultToken; + + // 连接状态标记 + const isConnected = ref(false); + // 存储所有订阅 + const subscriptions = new Map(); + + // 用于保存 STOMP 客户端的实例 + let client: Client | null = null; + + /** + * 初始化 STOMP 客户端 + * 只有在 brokerURL 非空时才会初始化客户端 + */ + const initializeClient = () => { + if (!brokerURL.value) { + console.warn( + "brokerURL is required. Please set the WebSocket URL in your .env file (VITE_APP_WS_ENDPOINT)." + ); + return; + } + + if (!client) { + client = new Client({ + brokerURL: brokerURL.value, + reconnectDelay: options.reconnectDelay ?? 5000, + debug: options.debug ? (msg) => console.log("[STOMP]", msg) : () => {}, + connectHeaders: { + Authorization: `Bearer ${token}`, + }, + heartbeatIncoming: 4000, + heartbeatOutgoing: 4000, + }); + + client.onConnect = (frame) => { + isConnected.value = true; + console.log("STOMP connected", frame); + }; + + client.onStompError = (frame) => { + console.error("Broker reported error: " + frame.headers["message"]); + console.error("Additional details: " + frame.body); + }; + + client.onWebSocketClose = (evt) => { + isConnected.value = false; + console.warn("WebSocket closed", evt); + }; + } + }; + + // 监听 brokerURL 的变化,若地址改变则重新初始化 + watch(brokerURL, (newURL, oldURL) => { + if (newURL !== oldURL) { + console.log(`brokerURL changed from ${oldURL} to ${newURL}`); + // 断开当前连接,重新激活客户端 + if (client && client.connected) { + client.deactivate(); + } + brokerURL.value = newURL; + initializeClient(); // 重新初始化客户端 + } + }); + + // 在组件挂载时检查并初始化客户端 + onMounted(() => { + initializeClient(); + }); + + /** + * 激活连接(如果已经连接或正在激活则直接返回) + */ + const connect = () => { + if (client && (client.connected || client.active)) { + console.log("Already connected or connecting, skipping connect() call."); + return; + } + client?.activate(); + }; + + /** + * 订阅指定主题 + * @param destination 目标主题地址 + * @param callback 接收到消息时的回调函数 + * @returns 返回订阅 id,用于后续取消订阅 + */ + const subscribe = (destination: string, callback: (message: IMessage) => void): string => { + if (client) { + const subscription = client.subscribe(destination, callback); + subscriptions.set(subscription.id, subscription); + return subscription.id; + } + return ""; + }; + + /** + * 取消指定订阅 + * @param subscriptionId 要取消的订阅 id + */ + const unsubscribe = (subscriptionId: string) => { + const subscription = subscriptions.get(subscriptionId); + if (subscription) { + subscription.unsubscribe(); + subscriptions.delete(subscriptionId); + } + }; + + /** + * 主动断开连接(如果未连接则不执行) + */ + const disconnect = () => { + if (client && !(client.connected || client.active)) { + console.log("Already disconnected, skipping disconnect() call."); + return; + } + client?.deactivate(); + isConnected.value = false; + }; + + return { + client, + isConnected, + connect, + subscribe, + unsubscribe, + disconnect, + brokerURL, // 暴露 brokerURL 以便组件中动态修改 + }; +} diff --git a/src/layout/components/NavBar/components/Notification.vue b/src/layout/components/NavBar/components/Notification.vue index bc6069b0..06d55b2c 100644 --- a/src/layout/components/NavBar/components/Notification.vue +++ b/src/layout/components/NavBar/components/Notification.vue @@ -167,8 +167,8 @@