315 lines
9.4 KiB
JavaScript
315 lines
9.4 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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();
|