Files
claude_dev 918bbed0fc fix: 修复登录500错误和移除明文密码提示
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service)
- 移除登录页面上的明文密码提示
- 改进前端错误处理,避免数据加载失败导致登录显示错误
2026-07-02 12:23:55 +08:00

271 lines
6.3 KiB
JavaScript

/**
* WebSocket 连接管理
* 处理实时数据推送、重连机制
*/
const WS_URL = `ws://${window.location.host}/ws`;
/**
* WebSocket 客户端类
*/
class WebSocketClient {
constructor() {
this.ws = null;
this.reconnectInterval = null;
this.reconnectDelay = 3000; // 3秒后重连
this.maxReconnectAttempts = 10;
this.reconnectAttempts = 0;
this.isConnected = false;
this.subscriptions = new Set();
this.messageHandlers = new Map();
}
/**
* 连接 WebSocket
*/
connect(token) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
console.log('WebSocket already connected');
return;
}
const url = token ? `${WS_URL}?token=${token}` : WS_URL;
this.ws = new WebSocket(url);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
this.clearReconnectInterval();
// 重新订阅之前的内容
if (this.subscriptions.size > 0) {
this.subscribe(Array.from(this.subscriptions));
}
// 触发连接事件
this.emit('connected');
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
} catch (e) {
console.error('Failed to parse WebSocket message:', e);
}
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.isConnected = false;
this.emit('disconnected');
// 尝试重连
this.scheduleReconnect(token);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
/**
* 断开连接
*/
disconnect() {
this.clearReconnectInterval();
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
}
/**
* 安排重连
*/
scheduleReconnect(token) {
if (this.reconnectInterval) {
return;
}
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnect attempts reached');
this.emit('reconnect-failed');
return;
}
this.reconnectAttempts++;
console.log(`Scheduling reconnect attempt ${this.reconnectAttempts}`);
this.reconnectInterval = setTimeout(() => {
this.reconnectInterval = null;
this.connect(token);
}, this.reconnectDelay);
}
/**
* 清除重连定时器
*/
clearReconnectInterval() {
if (this.reconnectInterval) {
clearTimeout(this.reconnectInterval);
this.reconnectInterval = null;
}
}
/**
* 处理消息
*/
handleMessage(message) {
const { type, data } = message;
// 根据消息类型调用对应的处理器
if (this.messageHandlers.has(type)) {
this.messageHandlers.get(type).forEach(handler => handler(data));
}
// 通用消息处理器
if (this.messageHandlers.has('*')) {
this.messageHandlers.get('*').forEach(handler => handler(message));
}
}
/**
* 发送消息
*/
send(message) {
if (!this.isConnected) {
console.warn('WebSocket not connected, message not sent');
return false;
}
try {
this.ws.send(JSON.stringify(message));
return true;
} catch (e) {
console.error('Failed to send WebSocket message:', e);
return false;
}
}
/**
* 订阅消息类型
*/
subscribe(types) {
types = Array.isArray(types) ? types : [types];
types.forEach(type => {
this.subscriptions.add(type);
});
if (this.isConnected) {
this.send({
action: 'subscribe',
types: types
});
}
}
/**
* 取消订阅
*/
unsubscribe(types) {
types = Array.isArray(types) ? types : [types];
types.forEach(type => {
this.subscriptions.delete(type);
});
if (this.isConnected) {
this.send({
action: 'unsubscribe',
types: types
});
}
}
/**
* 订阅品种
*/
subscribeSymbol(symbols) {
symbols = Array.isArray(symbols) ? symbols : [symbols];
if (this.isConnected) {
this.send({
action: 'subscribe_symbol',
symbols: symbols
});
}
}
/**
* 取消订阅品种
*/
unsubscribeSymbol(symbols) {
symbols = Array.isArray(symbols) ? symbols : [symbols];
if (this.isConnected) {
this.send({
action: 'unsubscribe_symbol',
symbols: symbols
});
}
}
/**
* 注册消息处理器
*/
on(type, handler) {
if (!this.messageHandlers.has(type)) {
this.messageHandlers.set(type, new Set());
}
this.messageHandlers.get(type).add(handler);
}
/**
* 移除消息处理器
*/
off(type, handler) {
if (this.messageHandlers.has(type)) {
this.messageHandlers.get(type).delete(handler);
}
}
/**
* 触发事件
*/
emit(type, data) {
if (this.messageHandlers.has(type)) {
this.messageHandlers.get(type).forEach(handler => handler(data));
}
}
/**
* 获取连接状态
*/
getConnectionState() {
if (!this.ws) {
return 'disconnected';
}
switch (this.ws.readyState) {
case WebSocket.CONNECTING:
return 'connecting';
case WebSocket.OPEN:
return 'connected';
case WebSocket.CLOSING:
return 'closing';
case WebSocket.CLOSED:
return 'closed';
default:
return 'unknown';
}
}
}
// 创建全局 WebSocket 客户端实例
const wsClient = new WebSocketClient();