refactor: ♻️ websocket 功能重构
This commit is contained in:
@@ -9,7 +9,7 @@ VITE_APP_API_URL=https://api.youlai.tech # 线上
|
|||||||
# VITE_APP_API_URL=http://localhost:8989 # 本地
|
# VITE_APP_API_URL=http://localhost:8989 # 本地
|
||||||
|
|
||||||
# WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws
|
# WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws
|
||||||
VITE_APP_WS_ENDPOINT=ws://localhost:8989/ws
|
VITE_APP_WS_ENDPOINT=
|
||||||
|
|
||||||
# 启用 Mock 服务
|
# 启用 Mock 服务
|
||||||
VITE_MOCK_DEV_SERVER=false
|
VITE_MOCK_DEV_SERVER=false
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ref, watch, onMounted } from "vue";
|
|
||||||
import { Client, IMessage, StompSubscription } from "@stomp/stompjs";
|
import { Client, IMessage, StompSubscription } from "@stomp/stompjs";
|
||||||
import { getAccessToken } from "@/utils/auth";
|
import { getAccessToken } from "@/utils/auth";
|
||||||
|
|
||||||
@@ -14,26 +13,18 @@ export interface UseStompOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useStomp(options: UseStompOptions = {}) {
|
export function useStomp(options: UseStompOptions = {}) {
|
||||||
// 默认值:brokerURL 从环境变量中获取,token 从 getAccessToken() 获取
|
|
||||||
const defaultBrokerURL = import.meta.env.VITE_APP_WS_ENDPOINT || "";
|
const defaultBrokerURL = import.meta.env.VITE_APP_WS_ENDPOINT || "";
|
||||||
const defaultToken = getAccessToken();
|
const defaultToken = getAccessToken();
|
||||||
|
|
||||||
// 将 brokerURL 定义为响应式 ref,便于动态修改
|
|
||||||
const brokerURL = ref(options.brokerURL ?? defaultBrokerURL);
|
const brokerURL = ref(options.brokerURL ?? defaultBrokerURL);
|
||||||
const token = options.token ?? defaultToken;
|
const token = options.token ?? defaultToken;
|
||||||
|
|
||||||
// 连接状态标记
|
|
||||||
const isConnected = ref(false);
|
const isConnected = ref(false);
|
||||||
// 存储所有订阅
|
|
||||||
const subscriptions = new Map<string, StompSubscription>();
|
const subscriptions = new Map<string, StompSubscription>();
|
||||||
|
|
||||||
// 用于保存 STOMP 客户端的实例
|
const client = ref<Client | null>(null);
|
||||||
let client: Client | null = null;
|
|
||||||
|
|
||||||
/**
|
// 初始化 STOMP 客户端
|
||||||
* 初始化 STOMP 客户端
|
|
||||||
* 只有在 brokerURL 非空时才会初始化客户端
|
|
||||||
*/
|
|
||||||
const initializeClient = () => {
|
const initializeClient = () => {
|
||||||
if (!brokerURL.value) {
|
if (!brokerURL.value) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -42,8 +33,8 @@ export function useStomp(options: UseStompOptions = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client) {
|
if (!client.value) {
|
||||||
client = new Client({
|
client.value = new Client({
|
||||||
brokerURL: brokerURL.value,
|
brokerURL: brokerURL.value,
|
||||||
reconnectDelay: options.reconnectDelay ?? 5000,
|
reconnectDelay: options.reconnectDelay ?? 5000,
|
||||||
debug: options.debug ? (msg) => console.log("[STOMP]", msg) : () => {},
|
debug: options.debug ? (msg) => console.log("[STOMP]", msg) : () => {},
|
||||||
@@ -54,17 +45,17 @@ export function useStomp(options: UseStompOptions = {}) {
|
|||||||
heartbeatOutgoing: 4000,
|
heartbeatOutgoing: 4000,
|
||||||
});
|
});
|
||||||
|
|
||||||
client.onConnect = (frame) => {
|
client.value.onConnect = (frame) => {
|
||||||
isConnected.value = true;
|
isConnected.value = true;
|
||||||
console.log("STOMP connected", frame);
|
console.log("STOMP connected", frame);
|
||||||
};
|
};
|
||||||
|
|
||||||
client.onStompError = (frame) => {
|
client.value.onStompError = (frame) => {
|
||||||
console.error("Broker reported error: " + frame.headers["message"]);
|
console.error("Broker reported error: " + frame.headers["message"]);
|
||||||
console.error("Additional details: " + frame.body);
|
console.error("Additional details: " + frame.body);
|
||||||
};
|
};
|
||||||
|
|
||||||
client.onWebSocketClose = (evt) => {
|
client.value.onWebSocketClose = (evt) => {
|
||||||
isConnected.value = false;
|
isConnected.value = false;
|
||||||
console.warn("WebSocket closed", evt);
|
console.warn("WebSocket closed", evt);
|
||||||
};
|
};
|
||||||
@@ -75,9 +66,8 @@ export function useStomp(options: UseStompOptions = {}) {
|
|||||||
watch(brokerURL, (newURL, oldURL) => {
|
watch(brokerURL, (newURL, oldURL) => {
|
||||||
if (newURL !== oldURL) {
|
if (newURL !== oldURL) {
|
||||||
console.log(`brokerURL changed from ${oldURL} to ${newURL}`);
|
console.log(`brokerURL changed from ${oldURL} to ${newURL}`);
|
||||||
// 断开当前连接,重新激活客户端
|
if (client.value && client.value.connected) {
|
||||||
if (client && client.connected) {
|
client.value.deactivate();
|
||||||
client.deactivate();
|
|
||||||
}
|
}
|
||||||
brokerURL.value = newURL;
|
brokerURL.value = newURL;
|
||||||
initializeClient(); // 重新初始化客户端
|
initializeClient(); // 重新初始化客户端
|
||||||
@@ -89,36 +79,39 @@ export function useStomp(options: UseStompOptions = {}) {
|
|||||||
initializeClient();
|
initializeClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
// 激活连接(如果已经连接或正在激活则直接返回)
|
||||||
* 激活连接(如果已经连接或正在激活则直接返回)
|
|
||||||
*/
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
if (client && (client.connected || client.active)) {
|
if (client.value && (client.value.connected || client.value.active)) {
|
||||||
console.log("Already connected or connecting, skipping connect() call.");
|
console.log("Already connected or connecting, skipping connect() call.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client?.activate();
|
if (client.value) {
|
||||||
|
client.value.activate();
|
||||||
|
} else {
|
||||||
|
console.warn("Client is not initialized.");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 订阅指定主题,连接成功后自动订阅
|
||||||
* 订阅指定主题
|
|
||||||
* @param destination 目标主题地址
|
|
||||||
* @param callback 接收到消息时的回调函数
|
|
||||||
* @returns 返回订阅 id,用于后续取消订阅
|
|
||||||
*/
|
|
||||||
const subscribe = (destination: string, callback: (message: IMessage) => void): string => {
|
const subscribe = (destination: string, callback: (message: IMessage) => void): string => {
|
||||||
if (client) {
|
if (!client.value) {
|
||||||
const subscription = client.subscribe(destination, callback);
|
console.error("STOMP client is not initialized.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还没有连接,就先激活连接
|
||||||
|
if (!isConnected.value) {
|
||||||
|
console.log("Not connected yet. Connecting...");
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接成功后订阅主题
|
||||||
|
const subscription = client.value.subscribe(destination, callback);
|
||||||
subscriptions.set(subscription.id, subscription);
|
subscriptions.set(subscription.id, subscription);
|
||||||
return subscription.id;
|
return subscription.id;
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 取消指定订阅
|
||||||
* 取消指定订阅
|
|
||||||
* @param subscriptionId 要取消的订阅 id
|
|
||||||
*/
|
|
||||||
const unsubscribe = (subscriptionId: string) => {
|
const unsubscribe = (subscriptionId: string) => {
|
||||||
const subscription = subscriptions.get(subscriptionId);
|
const subscription = subscriptions.get(subscriptionId);
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
@@ -127,15 +120,17 @@ export function useStomp(options: UseStompOptions = {}) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 主动断开连接(如果未连接则不执行)
|
||||||
* 主动断开连接(如果未连接则不执行)
|
|
||||||
*/
|
|
||||||
const disconnect = () => {
|
const disconnect = () => {
|
||||||
if (client && !(client.connected || client.active)) {
|
if (client.value && !(client.value.connected || client.value.active)) {
|
||||||
console.log("Already disconnected, skipping disconnect() call.");
|
console.log("Already disconnected, skipping disconnect() call.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client?.deactivate();
|
if (client.value) {
|
||||||
|
client.value.deactivate();
|
||||||
|
} else {
|
||||||
|
console.warn("Client is not initialized.");
|
||||||
|
}
|
||||||
isConnected.value = false;
|
isConnected.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,7 +138,7 @@ export function useStomp(options: UseStompOptions = {}) {
|
|||||||
client,
|
client,
|
||||||
isConnected,
|
isConnected,
|
||||||
connect,
|
connect,
|
||||||
subscribe,
|
subscribe, // 订阅函数放到这里
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
disconnect,
|
disconnect,
|
||||||
brokerURL, // 暴露 brokerURL 以便组件中动态修改
|
brokerURL, // 暴露 brokerURL 以便组件中动态修改
|
||||||
|
|||||||
@@ -168,7 +168,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NoticeAPI, { NoticePageVO } from "@/api/system/notice";
|
import NoticeAPI, { NoticePageVO } from "@/api/system/notice";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { useStomp } from "@/hooks/useStomp";
|
|
||||||
|
|
||||||
const activeTab = ref("notice");
|
const activeTab = ref("notice");
|
||||||
const notices = ref<NoticePageVO[]>([]);
|
const notices = ref<NoticePageVO[]>([]);
|
||||||
@@ -176,20 +175,16 @@ const messages = ref<any[]>([]);
|
|||||||
const tasks = ref<any[]>([]);
|
const tasks = ref<any[]>([]);
|
||||||
const noticeDetailRef = ref();
|
const noticeDetailRef = ref();
|
||||||
|
|
||||||
// 初始化 useStomp hook(这里仅用于订阅通知消息),同时调用 connect 建立连接
|
import { useStomp } from "@/hooks/useStomp";
|
||||||
const { connect, subscribe, disconnect } = useStomp({
|
|
||||||
|
const { subscribe, disconnect } = useStomp({
|
||||||
debug: true,
|
debug: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取未读消息列表并连接 WebSocket
|
/**
|
||||||
onMounted(() => {
|
* 订阅通知消息
|
||||||
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 5, isRead: 0 }).then((data) => {
|
*/
|
||||||
notices.value = data.list;
|
function subscribeNotice() {
|
||||||
});
|
|
||||||
|
|
||||||
// 建立连接
|
|
||||||
connect();
|
|
||||||
|
|
||||||
subscribe("/user/queue/message", (message) => {
|
subscribe("/user/queue/message", (message) => {
|
||||||
console.log("收到消息:", message);
|
console.log("收到消息:", message);
|
||||||
const data = JSON.parse(message.body);
|
const data = JSON.parse(message.body);
|
||||||
@@ -210,7 +205,16 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的通知公告
|
||||||
|
*/
|
||||||
|
function featchMyNotice() {
|
||||||
|
NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 5, isRead: 0 }).then((data) => {
|
||||||
|
notices.value = data.list;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 阅读通知公告
|
// 阅读通知公告
|
||||||
function handleReadNotice(id: string) {
|
function handleReadNotice(id: string) {
|
||||||
@@ -233,8 +237,13 @@ function markAllAsRead() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取未读消息列表并连接 WebSocket
|
||||||
|
onMounted(() => {
|
||||||
|
featchMyNotice();
|
||||||
|
subscribeNotice();
|
||||||
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// 如果需要取消订阅,可以在这里调用 disconnect 或 unsubscribe(本示例直接断开连接)
|
|
||||||
disconnect();
|
disconnect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 广播消息发送部分 -->
|
<!-- 广播消息发送部分 -->
|
||||||
<el-card class="mt-5">
|
<el-card class="mt-5">
|
||||||
<el-form label-width="90px">
|
<el-form label-width="90px">
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 点对点消息发送部分 -->
|
<!-- 点对点消息发送部分 -->
|
||||||
<el-card class="mt-5">
|
<el-card class="mt-5">
|
||||||
<el-form label-width="90px">
|
<el-form label-width="90px">
|
||||||
@@ -60,6 +62,7 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<!-- 消息接收显示部分 -->
|
<!-- 消息接收显示部分 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card>
|
<el-card>
|
||||||
@@ -96,10 +99,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStomp } from "@/hooks/useStomp";
|
import { useStomp } from "@/hooks/useStomp";
|
||||||
import { getAccessToken } from "@/utils/auth"; // 此处可与 hook 内 getToken 保持一致
|
import { getAccessToken } from "@/utils/auth"; // 用于获取token
|
||||||
import { useUserStoreHook } from "@/store/modules/user";
|
import { useUserStoreHook } from "@/store/modules/user"; // 获取用户信息
|
||||||
|
|
||||||
const userStore = useUserStoreHook();
|
const userStore = useUserStoreHook();
|
||||||
|
|
||||||
// 用于手动调整 WebSocket 地址
|
// 用于手动调整 WebSocket 地址
|
||||||
const socketEndpoint = ref(import.meta.env.VITE_APP_WS_ENDPOINT);
|
const socketEndpoint = ref(import.meta.env.VITE_APP_WS_ENDPOINT);
|
||||||
// 同步连接状态
|
// 同步连接状态
|
||||||
@@ -175,8 +179,8 @@ function disconnectWebSocket() {
|
|||||||
|
|
||||||
// 发送广播消息
|
// 发送广播消息
|
||||||
function sendToAll() {
|
function sendToAll() {
|
||||||
if (client && client.connected) {
|
if (client.value && isConnected.value) {
|
||||||
client.publish({
|
client.value.publish({
|
||||||
destination: "/topic/notice",
|
destination: "/topic/notice",
|
||||||
body: topicMessage.value,
|
body: topicMessage.value,
|
||||||
});
|
});
|
||||||
@@ -189,8 +193,8 @@ function sendToAll() {
|
|||||||
|
|
||||||
// 发送点对点消息
|
// 发送点对点消息
|
||||||
function sendToUser() {
|
function sendToUser() {
|
||||||
if (client && client.connected) {
|
if (client.value && isConnected.value) {
|
||||||
client.publish({
|
client.value.publish({
|
||||||
destination: "/app/sendToUser/" + receiver.value,
|
destination: "/app/sendToUser/" + receiver.value,
|
||||||
body: queneMessage.value,
|
body: queneMessage.value,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user