版本
- "vite": "7.1.5",
- "@stomp/rx-stomp": "2.0.1 ",
- "sockjs-client": "1.6.1",
目录结构
- subscribe
- modules
- useMessageSubscribe.ts [消息订阅]
- useOnlineUsersSubscribe.tsx [用户在线状态订阅]
- index.ts [主逻辑]
- key.ts [Destination变量]
- modules
keys
ts
// 消息
export const MESSAGE_KEY = '/user/message';
// 在线用户
export const ONCE_ONLINE_USERS_KEY = '/user/topic/online/user';
useOnlineUsersSubscribe
ts
import { defineSubscribe } from '~/subscribe';
import { ONCE_ONLINE_USERS_KEY } from '../key';
export const useOnlineUsersSubscribe = defineSubscribe(
ONCE_ONLINE_USERS_KEY,
({ action, data: contact }) => {
console.log('useOnlineUsersSubscribe: ', action, contact);
// TODO ....
}
);
useMessageSubscribe
ts
import { defineSubscribe } from '~/subscribe';
import { MESSAGE_KEY } from '../key';
export const useMessageSubscribe = defineSubscribe<MessageEntity>(
MESSAGE_KEY,
(message, rawMessage) => {
// TODO
}
);
创建 STOMP 连接
ts
import type { App } from 'vue';
export default (_: App) => {
console.log('Stomp: 插件进行加载中');
const accessToken = 'xx'
createRxStomp({
brokerURL: 'http://localhost:8080/ws',
heartbeatIncoming: 2000,
heartbeatOutgoing: 2000,
reconnectDelay: 5000,
connectHeaders: {
Authorization: `Bearer ${accessToken}`,
},
debug: (str) => {
console.log(str);
},
});
};
index.ts
主要内容在 loadDirectorySubscriptions 方法下, 通过 import.meta.glob
获取所有 Subscribe结尾的 js、ts、tsx 模块暴露的方法并调用
ts
import { IMessage, RxStomp, RxStompState } from '@stomp/rx-stomp';
import SockJS from 'sockjs-client/dist/sockjs.min.js';
import type { RxStompConfig } from '@stomp/rx-stomp/src/rx-stomp-config';
import { StompHeaders } from '@stomp/stompjs/src/stomp-headers';
let rxStompInstance: RxStomp | null;
const subscribeModules = import.meta.glob(
'~/subscribe/**/*Subscribe.{ts,tsx,js}'
);
interface StompOptions extends RxStompConfig {
authenticate?: string;
include?: string;
}
export const createRxStomp = ({
brokerURL,
authenticate,
beforeConnect,
connectHeaders,
...option
}: StompOptions) => {
// 存在复用
if (rxStompInstance) {
return rxStompInstance;
}
rxStompInstance = new RxStomp();
rxStompInstance.configure({
webSocketFactory: () => new SockJS(brokerURL || 'http://localhost:8080/ws'),
connectHeaders: {
...connectHeaders,
},
...option,
});
// 激活连接
rxStompInstance.activate();
console.log('Stomp: 默认连接头', rxStompInstance.stompClient.connectHeaders);
// 加载目录下所有订阅
loadDirectorySubscriptions();
return rxStompInstance;
};
export const useRxStomp = () => {
if (!rxStompInstance) {
throw new Error(
'RxStomp instance not created. Use createRxStomp to create an instance first.'
);
}
return rxStompInstance;
};
/**
* 订阅
*/
export const defineSubscribe = <T>(
topic: string,
callback: (
message: T,
rawMessage: IMessage
) => void
) => {
const rxStomp = useRxStomp();
return rxStomp.watch(topic).subscribe((message) => {
callback(JSON.parse(message.body), {
...message,
});
});
};
/**
* 加载目录下所有订阅
*/
export const loadDirectorySubscriptions = async () => {
/**
* 从路径中提取模块名称
*
* @param filePath 文件路径
* @returns 模块名称
*/
const extractModuleName = (filePath: string): string => {
// 使用正则表达式匹配文件名部分并去掉扩展名
const match = /\/([^/]+)\.([tj]sx?)$/.exec(filePath);
if (match && match[1]) {
return match[1];
}
throw new Error(`无法从路径中提取模块名称: ${filePath}`);
};
for (const path in subscribeModules) {
await subscribeModules[path]();
console.log(`🧩 Scan Subscribe: ${extractModuleName(path)}`);
}
};
/**
* 刷新连接
*
* @param headers
*/
export const refreshConnectHeaders = async (headers: StompHeaders) => {
if (!rxStompInstance) {
throw new Error('STOMP实例未初始化');
}
// 断开连接
await rxStompInstance.deactivate();
// 更新连接头
rxStompInstance.stompClient.configure({
connectHeaders: {
...rxStompInstance.stompClient.connectHeaders,
...headers,
},
});
// 重新请求
rxStompInstance.activate();
console.log(
'Stomp: 连接头已更新',
rxStompInstance.stompClient.connectHeaders
);
};