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
  );
};
相关推荐
Tzarevich15 小时前
现代前端开发工程化:从 Vite 到 Vue 3 多页面应用实战
vue.js·vite
济南壹软网络科技有限公司1 天前
深度解构:基于 React 19 + WebSocket 的高性能 SocialFi 社交金融架构
websocket·react.js·金融·即时通讯
韭菜炒大葱2 天前
现代前端开发工程化:Vue3 + Vite 带你从 0 到 1 搭建 Vue3 项目🚀
前端·vue.js·vite
萌虎不虎3 天前
【鸿蒙ETS中WebSocket使用说明】
websocket·华为·harmonyos
爬山算法3 天前
Netty(20)如何实现基于Netty的WebSocket服务器?
服务器·websocket·网络协议
zfj3213 天前
springmvc websocket 的用法
网络·websocket·网络协议·springmvc
zfj3213 天前
websocket为什么需要在tcp连接成功后先发送一个标准的http请求,然后在当前tcp连接上升级协议成websocket
websocket·tcp/ip·http
蜗牛攻城狮3 天前
Vite 项目中 `node_modules/.vite/deps` 文件夹详解
前端·vite·构建工具
无敌最俊朗@4 天前
WebSocket与Webhook:实时通信技术对比
网络·websocket·网络协议
Rover.x4 天前
Netty基于SpringBoot实现WebSocket
spring boot·后端·websocket