Stomp 订阅模块化

版本

  • "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变量]

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
  );
};
相关推荐
Cxiaomu6 小时前
React Native 项目中 WebSocket 的完整实现方案
websocket·react native·react.js
晓得迷路了7 小时前
栗子前端技术周刊第 102 期 - Vite+ 正式发布、React Native 0.82、Nitro v3 alpha 版...
前端·javascript·vite
Arva .20 小时前
WebSocket实现网站点赞通知
网络·websocket·网络协议
火星数据-Tina1 天前
LOL实时数据推送技术揭秘:WebSocket在电竞中的应用
网络·websocket·网络协议
paopaokaka_luck1 天前
基于SpringBoot+Vue的社区诊所管理系统(AI问答、webSocket实时聊天、Echarts图形化分析)
vue.js·人工智能·spring boot·后端·websocket
歪歪1001 天前
使用 Wireshark 进行 HTTP、MQTT、WebSocket 抓包的详细教程
网络·websocket·测试工具·http·wireshark
EndingCoder2 天前
WebSocket实时通信:Socket.io
服务器·javascript·网络·websocket·网络协议·node.js
J_bean2 天前
Spring Boot 集成 WebSocket 的实战案例
spring boot·websocket
m0_611779962 天前
MQTT和WebSocket的差别
网络·websocket·网络协议
paopaokaka_luck2 天前
基于SpringBoot+Vue的数码交流管理系统(AI问答、协同过滤算法、websocket实时聊天、Echarts图形化分析)
vue.js·人工智能·spring boot·websocket·echarts