Files
xk-doctor-wx/utils/ws/websocket.js
2026-03-06 13:59:22 +08:00

315 lines
9.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WebSocket管理器
* 负责WebSocket连接、消息收发、重连、心跳等功能
*/
import { websocketConfig } from '@/config/websocket.js';
export class WebSocketManager {
constructor(url = null) {
// 使用配置文件中的URL如果传入了url则使用传入的
this.url = url || websocketConfig.getUrl();
this.socket = null;
this.isConnected = false;
this.isAuthenticated = false;
this.reconnectAttempts = 0;
// 从配置文件读取重连配置
this.maxReconnectAttempts = websocketConfig.reconnect.maxAttempts;
this.reconnectDelay = websocketConfig.reconnect.initialDelay;
this.maxReconnectDelay = websocketConfig.reconnect.maxDelay;
this.messageHandlers = [];
this.pingInterval = null;
this.cachedToken = null;
// 从配置文件读取用户配置
this.userPrefix = websocketConfig.user.prefix;
this.platform = websocketConfig.user.platform;
}
getToken() {
let token = uni.getStorageSync('token');
if (token) {
token = token.replace('Bearer ', '');
this.cachedToken = token;
}
return token || null;
}
clearCachedToken() {
this.cachedToken = null;
}
rebindUser() {
const userId = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
if (!userId) {
console.warn('重新绑定用户失败doctor_id不存在');
return false;
}
this.clearCachedToken();
if (this.isConnected) {
return this.bindUser(userId, true);
} else {
this.connect();
return true;
}
}
connect() {
if (this.isConnected) return;
console.log('正在连接WebSocket...');
this.socket = uni.connectSocket({
url: this.url,
success: () => {
console.log('WebSocket连接成功');
},
fail: (err) => {
console.error('WebSocket连接失败:', err);
this.handleReconnect();
}
});
this.socket.onOpen(() => {
console.log('WebSocket连接已打开');
this.isConnected = true;
this.reconnectAttempts = 0;
this.startHeartbeat();
// 绑定当前用户医生小程序使用doctor-前缀)
const userId = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
if (userId) {
this.bindUser(userId, true);
}
});
this.socket.onMessage((res) => {
try {
const data = JSON.parse(res.data);
console.log('收到消息:', data);
this.handleMessage(data);
} catch (e) {
console.error('解析消息失败:', e);
}
});
this.socket.onClose((res) => {
console.log('WebSocket连接关闭:', res);
this.isConnected = false;
this.stopHeartbeat();
this.handleReconnect();
});
this.socket.onError((err) => {
console.error('WebSocket错误:', err);
});
}
unbindUser(userId) {
const token = this.getToken();
if (!token) {
console.warn('解绑用户失败token不存在');
return false;
}
// 使用配置文件中的用户前缀
const normalizedUserId = userId.toString().startsWith(this.userPrefix) ? userId : `${this.userPrefix}${userId}`;
console.log('发送unbind请求清理旧连接:', normalizedUserId);
return this.send({
request_type: 'unbind',
user_type: 'doctor',
sender_user_id: normalizedUserId,
token: token
}, false);
}
bindUser(userId, cleanOldConnections = true) {
const token = this.getToken();
if (!token) {
console.error('绑定用户失败token不存在');
return false;
}
// 使用配置文件中的用户前缀
const normalizedUserId = userId.toString().startsWith(this.userPrefix) ? userId : `${this.userPrefix}${userId}`;
// 构建bind请求对象
const bindMessage = {
request_type: 'bind',
user_type: 'doctor',
sender_user_id: normalizedUserId,
token: token,
platform: this.platform, // 使用配置文件中的平台标识
clean_old_connections: cleanOldConnections
};
// 输出详细的调试日志
console.log('========== WebSocket Bind 请求 ==========');
console.log('用户ID:', normalizedUserId);
console.log('用户类型:', 'doctor');
console.log('平台标识:', this.platform);
console.log('清理旧连接:', cleanOldConnections);
console.log('Token存在:', !!token);
console.log('完整请求内容:', JSON.stringify(bindMessage, null, 2));
console.log('==========================================');
return this.send(bindMessage);
}
send(message, requireAuth = true) {
if (!this.isConnected) {
console.error('WebSocket未连接');
return false;
}
try {
if (requireAuth && !message.token && message.request_type !== 'ping') {
const token = this.getToken();
if (token) {
message.token = token;
}
}
// 如果是发送消息确保添加platform标识
if (message.request_type === 'send_message' && !message.platform) {
message.platform = this.platform;
}
const messageStr = JSON.stringify(message);
this.socket.send({
data: messageStr,
success: () => {
console.log('消息发送成功:', message);
},
fail: (err) => {
console.error('消息发送失败:', err);
}
});
return true;
} catch (err) {
console.error('发送消息出错:', err);
return false;
}
}
addMessageHandler(handler) {
this.messageHandlers.push(handler);
}
removeMessageHandler(handler) {
const index = this.messageHandlers.indexOf(handler);
if (index !== -1) {
this.messageHandlers.splice(index, 1);
}
}
handleMessage(data) {
if (data.auth_status !== undefined) {
if (data.auth_status === 'success') {
console.log('========== WebSocket 认证成功 ==========');
console.log('认证状态:', data.auth_status);
console.log('完整响应:', JSON.stringify(data, null, 2));
console.log('========================================');
this.isAuthenticated = true;
} else if (data.auth_status === 'failed') {
console.error('========== WebSocket 认证失败 ==========');
console.error('认证状态:', data.auth_status);
console.error('错误消息:', data.message || '未知错误');
console.error('错误代码:', data.code || '无');
console.error('完整响应:', JSON.stringify(data, null, 2));
console.error('当前用户ID:', uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id'));
console.error('Token存在:', !!this.getToken());
console.error('平台标识:', this.platform);
console.error('========================================');
this.isAuthenticated = false;
this.cachedToken = null;
} else if (data.auth_status === 'token_expired') {
console.warn('========== Token 已过期 ==========');
console.warn('认证状态:', data.auth_status);
console.warn('错误消息:', data.message || 'Token已过期');
console.warn('完整响应:', JSON.stringify(data, null, 2));
console.warn('==================================');
this.isAuthenticated = false;
this.cachedToken = null;
uni.$emit('ws-token-expired');
const userId = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
if (userId) {
console.log('尝试重新绑定用户...');
this.bindUser(userId, true);
}
} else {
console.warn('========== 未知的认证状态 ==========');
console.warn('认证状态:', data.auth_status);
console.warn('完整响应:', JSON.stringify(data, null, 2));
console.warn('==================================');
}
return;
}
if (data.clientId) {
const userId = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
if (userId) {
this.bindUser(userId, true);
}
}
this.messageHandlers.forEach(handler => {
try {
handler(data);
} catch (e) {
console.error('消息处理出错:', e);
}
});
}
/**
* 启动心跳
* 职责按照配置的心跳间隔发送ping消息
*/
startHeartbeat() {
this.pingInterval = setInterval(() => {
if (this.isConnected) {
this.send({ request_type: 'ping' }, false);
}
}, websocketConfig.heartbeat.interval);
}
stopHeartbeat() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
/**
* 处理重连
* 职责:按照配置的重连策略进行重连
*/
handleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('已达到最大重连次数');
return;
}
setTimeout(() => {
this.reconnectAttempts++;
// 使用指数退避策略,但不超过最大延迟
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.connect();
}, this.reconnectDelay);
}
disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
}
this.isConnected = false;
this.stopHeartbeat();
}
}
// 全局WebSocket实例
// 使用配置文件中的URL
export const webSocketManager = new WebSocketManager();