重构websocket
This commit is contained in:
@@ -2,8 +2,7 @@
|
||||
"backendBaseUrl": "__BACKEND_BASE_URL__",
|
||||
"apiBaseUrl": "/api",
|
||||
"noEncryptUrls": [
|
||||
"/crypto/sm2/public-key",
|
||||
"/algorithm-api/rainfall/grid"
|
||||
"/crypto/sm2/public-key"
|
||||
],
|
||||
"tdMapToken": [
|
||||
"fc6cb1139b8eed4f79439130eb34eb00",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* WebSocket连接配置选项
|
||||
*/
|
||||
export interface WebSocketConfig {
|
||||
/** WebSocket路径 */
|
||||
url: string;
|
||||
/** 自动重连间隔(毫秒,默认3000) */
|
||||
reconnectDelay?: number;
|
||||
/** 最大重连次数(默认5,-1表示无限重连) */
|
||||
maxReconnectAttempts?: number;
|
||||
/** 心跳间隔(毫秒,默认50000) */
|
||||
heartbeatIncoming?: number;
|
||||
/** 连接超时时间(毫秒,默认30000) */
|
||||
heartbeatOutgoing?: number;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* WebSocket连接配置选项
|
||||
*/
|
||||
export interface WebSocketOptions {
|
||||
/** WebSocket路径 */
|
||||
path?: string;
|
||||
/** 是否启用加密(默认true) */
|
||||
enableEncrypt?: boolean;
|
||||
/** 自动重连间隔(毫秒,默认5000) */
|
||||
reconnectInterval?: number;
|
||||
/** 最大重连次数(默认10,-1表示无限重连) */
|
||||
maxReconnectAttempts?: number;
|
||||
/** 心跳间隔(毫秒,默认30000) */
|
||||
heartbeatInterval?: number;
|
||||
/** 连接超时时间(毫秒,默认10000) */
|
||||
connectionTimeout?: number;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import type { WebSocketConfig } from '@/types/websocket/WebSocketConfig';
|
||||
import { Client, type IMessage } from '@stomp/stompjs';
|
||||
import SockJS from 'sockjs-client';
|
||||
|
||||
export class WebSocketService {
|
||||
private stompClient: Client | null = null;
|
||||
private connected = false;
|
||||
private reconnectAttempts = 0;
|
||||
private config: Required<WebSocketConfig>;
|
||||
|
||||
// 存储各个主题的回调列表
|
||||
private subscribers: Map<string, Set<(data: unknown) => void>> = new Map();
|
||||
|
||||
// 连接状态回调
|
||||
onConnected?: () => void;
|
||||
onDisconnected?: () => void;
|
||||
onError?: (error: unknown) => void;
|
||||
|
||||
constructor(config: WebSocketConfig) {
|
||||
this.config = {
|
||||
url: config.url,
|
||||
reconnectDelay: config.reconnectDelay || 3000,
|
||||
maxReconnectAttempts: config.maxReconnectAttempts || 5,
|
||||
heartbeatIncoming: config.heartbeatIncoming || 30000,
|
||||
heartbeatOutgoing: config.heartbeatOutgoing || 30000,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接 WebSocket
|
||||
*/
|
||||
connect() {
|
||||
if (this.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.stompClient = new Client({
|
||||
webSocketFactory: () => new SockJS(this.config.url),
|
||||
reconnectDelay: this.config.reconnectDelay,
|
||||
heartbeatIncoming: this.config.heartbeatIncoming,
|
||||
heartbeatOutgoing: this.config.heartbeatOutgoing,
|
||||
});
|
||||
|
||||
// 连接成功回调
|
||||
this.stompClient.onConnect = () => {
|
||||
this.connected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.onConnected?.();
|
||||
};
|
||||
|
||||
// 断开连接回调
|
||||
this.stompClient.onDisconnect = () => {
|
||||
this.connected = false;
|
||||
this.onDisconnected?.();
|
||||
};
|
||||
|
||||
// 错误回调
|
||||
this.stompClient.onStompError = (frame) => {
|
||||
console.error('STOMP 错误:', frame.headers['message']);
|
||||
console.error('详细错误:', frame.body);
|
||||
this.onError?.(frame);
|
||||
};
|
||||
|
||||
try {
|
||||
this.stompClient.activate();
|
||||
} catch (error) {
|
||||
console.error('WebSocket 连接失败:', error);
|
||||
this.handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅某个主题,并注册回调
|
||||
*/
|
||||
subscribe<T = unknown>(topic: string, callback: (data: T) => void) {
|
||||
if (!this.stompClient || !this.connected) {
|
||||
console.error('WebSocket 未连接');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果还没有该主题的订阅者集合,则创建一个新的
|
||||
if (!this.subscribers.has(topic)) {
|
||||
this.subscribers.set(topic, new Set());
|
||||
}
|
||||
|
||||
// 添加当前回调到订阅者集合
|
||||
this.subscribers.get(topic)!.add(callback as (data: unknown) => void);
|
||||
|
||||
// 实际订阅 STOMP 主题
|
||||
this.stompClient.subscribe(topic, (message: IMessage) => {
|
||||
try {
|
||||
const data = JSON.parse(message.body);
|
||||
// 调用所有注册在该主题上的回调
|
||||
this.subscribers.get(topic)?.forEach((cb) => cb(data));
|
||||
} catch (error) {
|
||||
console.error(`解析消息失败 [${topic}]:`, error);
|
||||
this.onError?.(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息到指定目的地
|
||||
*/
|
||||
send(destination: string, body: unknown) {
|
||||
if (!this.stompClient || !this.connected) {
|
||||
console.error('WebSocket 未连接');
|
||||
return;
|
||||
}
|
||||
|
||||
this.stompClient.publish({
|
||||
destination,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect() {
|
||||
if (this.stompClient) {
|
||||
this.stompClient.deactivate();
|
||||
this.stompClient = null;
|
||||
this.connected = false;
|
||||
this.subscribers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查连接状态
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理错误并重连
|
||||
*/
|
||||
private handleError(error: unknown) {
|
||||
this.reconnectAttempts++;
|
||||
console.error(
|
||||
`连接失败 (尝试 ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}):`,
|
||||
error
|
||||
);
|
||||
|
||||
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, this.config.reconnectDelay * this.reconnectAttempts);
|
||||
} else {
|
||||
console.error('达到最大重连次数,请检查网络连接');
|
||||
this.onError?.(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user