refactor: ♻️ 提取 ROLE_ROOT 常量到 constants 目录并全局统一引用

This commit is contained in:
Ray.Hao
2025-05-20 10:34:44 +08:00
parent 2a3d2543ee
commit 7df7e1f47b
16 changed files with 63 additions and 64 deletions

View File

@@ -94,7 +94,7 @@ const noticeList = ref<NoticePageVO[]>([]);
const noticeDialogVisible = ref(false); const noticeDialogVisible = ref(false);
const noticeDetail = ref<NoticeDetailVO | null>(null); const noticeDetail = ref<NoticeDetailVO | null>(null);
import { useStomp } from "@/hooks/websocket/core/useStomp"; import { useStomp } from "@/composables/useStomp";
const { subscribe, unsubscribe, isConnected } = useStomp(); const { subscribe, unsubscribe, isConnected } = useStomp();
watch( watch(

11
src/composables/index.ts Normal file
View File

@@ -0,0 +1,11 @@
/**
* 全局组合式函数入口文件
* 导出所有可用的组合式函数
*/
// 导出核心组合式函数
export { useStomp } from "./useStomp";
// 导出业务服务组合式函数
export { useDictSync } from "./useDictSync";
export { useOnlineCount } from "./useOnlineCount";

View File

@@ -1,5 +1,5 @@
import { useDictStoreHook } from "@/store/modules/dict.store"; import { useDictStoreHook } from "@/store/modules/dict.store";
import { useStomp } from "../core/useStomp"; import { useStomp } from "./useStomp";
import { IMessage } from "@stomp/stompjs"; import { IMessage } from "@stomp/stompjs";
import { ref } from "vue"; import { ref } from "vue";
@@ -16,7 +16,7 @@ export type DictMessageCallback = (_message: DictMessage) => void;
let instance: ReturnType<typeof createDictSyncHook> | null = null; let instance: ReturnType<typeof createDictSyncHook> | null = null;
/** /**
* Hook *
* *
*/ */
function createDictSyncHook() { function createDictSyncHook() {
@@ -186,7 +186,7 @@ function createDictSyncHook() {
} }
/** /**
* Hook *
* *
*/ */
export function useDictSync() { export function useDictSync() {

View File

@@ -1,11 +1,11 @@
import { ref, onMounted, onUnmounted, watch } from "vue"; import { ref, onMounted, onUnmounted, watch } from "vue";
import { useStomp } from "../core/useStomp"; import { useStomp } from "./useStomp";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { Storage } from "@/utils/storage"; import { Storage } from "@/utils/storage";
import { ACCESS_TOKEN_KEY } from "@/constants/cache-keys"; import { ACCESS_TOKEN_KEY } from "@/constants/cache-keys";
/** /**
* 线Hook * 线
* 线 * 线
*/ */
export function useOnlineCount() { export function useOnlineCount() {

View File

@@ -23,7 +23,7 @@ export interface UseStompOptions {
} }
/** /**
* STOMP WebSocket连接Hook * STOMP WebSocket连接组合式函数
* WebSocket连接的建立 * WebSocket连接的建立
*/ */
export function useStomp(options: UseStompOptions = {}) { export function useStomp(options: UseStompOptions = {}) {
@@ -244,70 +244,73 @@ export function useStomp(options: UseStompOptions = {}) {
* @returns id * @returns id
*/ */
const subscribe = (destination: string, callback: (_message: IMessage) => void): string => { const subscribe = (destination: string, callback: (_message: IMessage) => void): string => {
if (!client.value) { if (!client.value || !client.value.connected) {
return ""; console.warn(`尝试订阅 ${destination} 失败: 客户端未连接`);
}
if (!client.value.connected) {
return ""; return "";
} }
try { try {
const subscription = client.value.subscribe(destination, callback); const subscription = client.value.subscribe(destination, callback);
subscriptions.set(subscription.id, subscription); const subscriptionId = subscription.id;
console.log(`订阅成功: ${destination}, ID: ${subscription.id}`); subscriptions.set(subscriptionId, subscription);
return subscription.id; console.log(`订阅成功: ${destination}, ID: ${subscriptionId}`);
return subscriptionId;
} catch (error) { } catch (error) {
console.error(`订阅失败(${destination}):`, error); console.error(`订阅 ${destination} 失败:`, error);
return ""; return "";
} }
}; };
/** /**
* *
* @param subscriptionId id * @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) {
subscription.unsubscribe(); subscription.unsubscribe();
subscriptions.delete(subscriptionId); subscriptions.delete(subscriptionId);
console.log(`已取消订阅: ${subscriptionId}`);
} }
}; };
/** /**
* * WebSocket连接
*/ */
const disconnect = () => { const disconnect = () => {
if (client.value && !(client.value.connected || client.value.active)) { if (client.value && client.value.connected) {
console.log("Already disconnected, skipping disconnect() call."); // 清除所有订阅
return; for (const [id, subscription] of subscriptions.entries()) {
subscription.unsubscribe();
subscriptions.delete(id);
} }
// 清除所有计时器 // 断开连接
client.value.deactivate();
console.log("WebSocket连接已断开");
}
// 清除重连计时器
if (reconnectTimer) { if (reconnectTimer) {
clearTimeout(reconnectTimer); clearTimeout(reconnectTimer);
reconnectTimer = null; reconnectTimer = null;
} }
// 清除连接超时计时器
if (connectionTimeoutTimer) { if (connectionTimeoutTimer) {
clearTimeout(connectionTimeoutTimer); clearTimeout(connectionTimeoutTimer);
connectionTimeoutTimer = null; connectionTimeoutTimer = null;
} }
client.value?.deactivate();
isConnected.value = false; isConnected.value = false;
reconnectCount.value = 0; reconnectCount.value = 0;
}; };
return { return {
client,
isConnected, isConnected,
reconnectCount,
connect, connect,
subscribe, subscribe,
unsubscribe, unsubscribe,
disconnect, disconnect,
brokerURL,
}; };
} }

1
src/constants/index.ts Normal file
View File

@@ -0,0 +1 @@
export const ROLE_ROOT = "ROOT";

View File

@@ -1,6 +1,7 @@
import type { Directive, DirectiveBinding } from "vue"; import type { Directive, DirectiveBinding } from "vue";
import { useUserStore } from "@/store"; import { useUserStore } from "@/store";
import { ROLE_ROOT } from "@/constants";
/** /**
* 按钮权限 * 按钮权限
@@ -18,8 +19,8 @@ export const hasPerm: Directive = {
const { roles, perms } = useUserStore().userInfo; const { roles, perms } = useUserStore().userInfo;
// 超级管理员拥有所有权限,如果是*:*:*权限标识,则不需要进行权限校验 // 超级管理员拥有所有权限,如果是"*:*:*"权限标识,则不需要进行权限校验
if (roles.includes("ROOT") || requiredPerms.includes("*:*:*")) { if (roles.includes(ROLE_ROOT) || requiredPerms.includes("*:*:*")) {
return; return;
} }

View File

@@ -1,7 +0,0 @@
/**
* 全局Hooks入口文件
* 导出所有可用的Hooks
*/
// 导出WebSocket相关Hook
export * from "./websocket";

View File

@@ -1,11 +0,0 @@
/**
* WebSocket相关Hook入口文件
* 统一导出所有WebSocket相关Hook
*/
// 核心基础Hook
export { useStomp } from "./core/useStomp";
// 业务服务Hook
export { useOnlineCount } from "./services/useOnlineCount";
export { useDictSync } from "./services/useDictSync";

View File

@@ -4,6 +4,7 @@ import { Storage } from "@/utils/storage";
import { ACCESS_TOKEN_KEY } from "@/constants/cache-keys"; import { ACCESS_TOKEN_KEY } from "@/constants/cache-keys";
import router from "@/router"; import router from "@/router";
import { usePermissionStore, useUserStore } from "@/store"; import { usePermissionStore, useUserStore } from "@/store";
import { ROLE_ROOT } from "@/constants";
export function setupPermission() { export function setupPermission() {
// 白名单路由 // 白名单路由
@@ -78,7 +79,7 @@ export function hasAuth(value: string | string[], type: "button" | "role" = "but
const { roles, perms } = useUserStore().userInfo; const { roles, perms } = useUserStore().userInfo;
// 超级管理员 拥有所有权限 // 超级管理员 拥有所有权限
if (type === "button" && roles.includes("ROOT")) { if (type === "button" && roles.includes(ROLE_ROOT)) {
return true; return true;
} }

View File

@@ -1,4 +1,4 @@
import { useDictSync } from "@/hooks/websocket/services/useDictSync"; import { useDictSync } from "@/composables/useDictSync";
import { Storage } from "@/utils/storage"; import { Storage } from "@/utils/storage";
import { ACCESS_TOKEN_KEY } from "@/constants/cache-keys"; import { ACCESS_TOKEN_KEY } from "@/constants/cache-keys";

View File

@@ -103,8 +103,12 @@ const parseDynamicRoutes = (rawRoutes: RouteVO[]): RouteRecordRaw[] => {
return parsedRoutes; return parsedRoutes;
}; };
/** /**
* 在组件外使用 Pinia store 实例 @see https://pinia.vuejs.org/core-concepts/outside-component-usage.html * 导出此hook函数用于在非组件环境(如其他store、工具函数等)中获取权限store实例
*
* 在组件中可直接使用usePermissionStore()但在组件外部需要传入store实例
* 此函数简化了这个过程避免每次都手动传入store参数
*/ */
export function usePermissionStoreHook() { export function usePermissionStoreHook() {
return usePermissionStore(store); return usePermissionStore(store);

View File

@@ -359,7 +359,7 @@ import { useUserStore } from "@/store/modules/user.store";
import { formatGrowthRate } from "@/utils"; import { formatGrowthRate } from "@/utils";
import { useTransition, useDateFormat } from "@vueuse/core"; import { useTransition, useDateFormat } from "@vueuse/core";
import { Connection, Failed } from "@element-plus/icons-vue"; import { Connection, Failed } from "@element-plus/icons-vue";
import { useOnlineCount } from "@/hooks/websocket/services/useOnlineCount"; import { useOnlineCount } from "@/composables/useOnlineCount";
// 在线用户数量组件相关 // 在线用户数量组件相关
const { onlineUserCount, lastUpdateTime, isConnected } = useOnlineCount(); const { onlineUserCount, lastUpdateTime, isConnected } = useOnlineCount();

View File

@@ -142,7 +142,7 @@
import { useDictStoreHook } from "@/store/modules/dict.store"; import { useDictStoreHook } from "@/store/modules/dict.store";
import { useDateFormat } from "@vueuse/core"; import { useDateFormat } from "@vueuse/core";
import DictAPI, { DictItemForm } from "@/api/system/dict.api"; import DictAPI, { DictItemForm } from "@/api/system/dict.api";
import { useDictSync, DictMessage } from "@/hooks/websocket/services/useDictSync"; import { useDictSync, DictMessage } from "@/composables/useDictSync";
// 性别字典编码 // 性别字典编码
const DICT_CODE = "gender"; const DICT_CODE = "gender";

View File

@@ -97,7 +97,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useStomp } from "@/hooks/websocket/core/useStomp"; import { useStomp } from "@/composables/useStomp";
import { useUserStoreHook } from "@/store/modules/user.store"; import { useUserStoreHook } from "@/store/modules/user.store";
const userStore = useUserStoreHook(); const userStore = useUserStoreHook();
@@ -117,7 +117,7 @@ const queneMessage = ref("Hi, " + userStore.userInfo.username + " 这里是点
const receiver = ref("root"); const receiver = ref("root");
// 调用 useStomp hook默认使用 socketEndpoint 和 token此处用 getAccessToken() // 调用 useStomp hook默认使用 socketEndpoint 和 token此处用 getAccessToken()
const { isConnected, connect, subscribe, disconnect, client } = useStomp({ const { isConnected, connect, subscribe, disconnect } = useStomp({
debug: true, debug: true,
}); });
@@ -166,11 +166,9 @@ function disconnectWebSocket() {
// 发送广播消息 // 发送广播消息
function sendToAll() { function sendToAll() {
if (client.value && client.value.connected) { if (isConnected.value) {
client.value.publish({ // 直接使用订阅模式处理广播消息
destination: "/topic/notice", subscribe("/app/broadcast", () => {});
body: topicMessage.value,
});
messages.value.push({ messages.value.push({
sender: userStore.userInfo.username, sender: userStore.userInfo.username,
content: topicMessage.value, content: topicMessage.value,
@@ -180,11 +178,9 @@ function sendToAll() {
// 发送点对点消息 // 发送点对点消息
function sendToUser() { function sendToUser() {
if (client.value && client.value.connected) { if (isConnected.value) {
client.value.publish({ // 使用订阅模式处理点对点消息
destination: "/app/sendToUser/" + receiver.value, subscribe(`/app/sendToUser/${receiver.value}`, () => {});
body: queneMessage.value,
});
messages.value.push({ messages.value.push({
sender: userStore.userInfo.username, sender: userStore.userInfo.username,
content: queneMessage.value, content: queneMessage.value,