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
  );
};
相关推荐
Sylvia33.7 小时前
火星数据体育API|一站式接入足球篮球电竞等18+项目实时数据
java·开发语言·python·websocket·游戏
一条泥憨鱼14 小时前
苍穹外卖【day10| 来电提醒和客户催单】
java·后端·websocket·苍穹外卖
the局外人1 天前
掌控自己的 K 线数据:TradingView 本地部署与数据源接入
前端·vue.js·websocket
小林ixn1 天前
跨域、SSE 与 WebSocket:从一次「请求被拦截」说起
websocket·http
喵喵锤锤你小可爱2 天前
SerialHub:把串口变成 WebSocket 字节管道,浏览器和脚本直接读写(开源工具 SerialHub 实战)
websocket·rust·嵌入式·串口调试·开源工具
只睡四小时2 天前
内网穿透+WebSocket:Node 零依赖远程桌面实战
网络·websocket·网络协议
梦想平凡2 天前
百游棋牌源代码开发搭建教程(七):WebSocket房间同步与断线恢复
网络·websocket·网络协议
AIFQuant3 天前
Python股票实时价格告警系统:WebSocket订阅与REST快照实战
开发语言·python·websocket·a股行情
小肥君3 天前
前端测试websocket
前端·websocket·状态模式
tachibana23 天前
WebSocket 和 SSE 通信的区别及局限性
网络·人工智能·websocket·网络协议·ai·llm·agent