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
  );
};
相关推荐
米丘1 天前
vite8 vite preview 命令做了什么?
node.js·vite
米丘1 天前
Vite 构建工具
vite
七夜zippoe4 天前
DolphinDB WebSocket接入:实时数据流
网络·websocket·网络协议·dolphindb·实时数据流
于先生吖5 天前
从零搭建Java萌宠社交系统:WebSocket实时聊天+动态发布模块实现
java·开发语言·websocket
moMo5 天前
我用的脚手架到底是什么——Vite 主要功能
vite
Zhan8611245 天前
WebSocket心跳与断线重连实战:芬兰赫尔辛基指数行情数据接口接入记录
网络·websocket·网络协议
colofullove6 天前
实时游玩页与 WebSocket 状态管理实现
websocket·网络协议·状态模式
小短腿的代码世界6 天前
WebSocket协议在Qt中的工业级实现:5层架构设计与万级并发压测验证
qt·websocket·网络协议
葡萄皮sandy6 天前
SSE和WebSocket
网络·websocket·网络协议
To_OC6 天前
通义千问多模态生图踩坑记:我是如何把两个报错逐个干翻的
前端·aigc·vite