1. 医生端开方2.0测试
This commit is contained in:
424
utils/chat/chatRoomManager.js
Normal file
424
utils/chat/chatRoomManager.js
Normal file
@@ -0,0 +1,424 @@
|
||||
/**
|
||||
* 聊天室管理器
|
||||
* 封装聊天室相关操作,包括进入房间、离开房间、发送消息、接收消息、管理未读数等
|
||||
*/
|
||||
import { chatConfig } from '@/config/chat.js';
|
||||
import { sendWebSocketMessage, isWebSocketConnected } from '@/utils/ws/initWebSocket.js';
|
||||
import { getChatRegisterInfoApi, getMessagesByRoomIdApi } from '@/request/api/im.js';
|
||||
|
||||
/**
|
||||
* 聊天室管理器类
|
||||
*/
|
||||
class ChatRoomManager {
|
||||
constructor() {
|
||||
// 当前房间ID
|
||||
this.currentRoomId = null;
|
||||
|
||||
// 最后一条消息ID(用于分页加载)
|
||||
this.lastMessageId = 0;
|
||||
|
||||
// 未读数(按房间ID存储)
|
||||
this.unreadCounts = {};
|
||||
|
||||
// 消息缓存(按房间ID存储)
|
||||
this.roomMessages = {};
|
||||
|
||||
// 消息处理器列表
|
||||
this.messageHandlers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入聊天室
|
||||
* @param {string} roomId - 房间ID
|
||||
* 职责:设置当前房间,重置未读数,保存到本地存储
|
||||
*/
|
||||
enterRoom(roomId) {
|
||||
if (!roomId) {
|
||||
console.warn('房间ID不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentRoomId = roomId;
|
||||
uni.setStorageSync('currentRoomId', roomId);
|
||||
this.resetUnreadCount(roomId);
|
||||
|
||||
console.log('进入聊天室:', roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开聊天室
|
||||
* 职责:清空当前房间,移除本地存储
|
||||
*/
|
||||
leaveRoom() {
|
||||
this.currentRoomId = null;
|
||||
uni.removeStorageSync('currentRoomId');
|
||||
console.log('离开聊天室');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前房间ID
|
||||
* @returns {string|null} 当前房间ID
|
||||
*/
|
||||
getCurrentRoomId() {
|
||||
return this.currentRoomId || uni.getStorageSync('currentRoomId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加未读数
|
||||
* @param {string} roomId - 房间ID
|
||||
* 职责:如果房间不是当前房间,则增加未读数并通知
|
||||
*/
|
||||
increaseUnreadCount(roomId) {
|
||||
if (!roomId) return;
|
||||
|
||||
// 如果是当前房间,不增加未读数
|
||||
if (this.getCurrentRoomId() === roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.unreadCounts[roomId] = (this.unreadCounts[roomId] || 0) + 1;
|
||||
this.notifyUnreadChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置未读数
|
||||
* @param {string} roomId - 房间ID
|
||||
* 职责:将指定房间的未读数重置为0并通知
|
||||
*/
|
||||
resetUnreadCount(roomId) {
|
||||
if (this.unreadCounts[roomId]) {
|
||||
this.unreadCounts[roomId] = 0;
|
||||
this.notifyUnreadChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读数
|
||||
* @param {string} roomId - 房间ID
|
||||
* @returns {number} 未读数
|
||||
*/
|
||||
getUnreadCount(roomId) {
|
||||
return this.unreadCounts[roomId] || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有未读数总和
|
||||
* @returns {number} 总未读数
|
||||
*/
|
||||
getAllUnreadCount() {
|
||||
return Object.values(this.unreadCounts).reduce((sum, count) => sum + count, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化未读数显示
|
||||
* @param {string} roomId - 房间ID
|
||||
* @returns {string} 格式化后的未读数
|
||||
*/
|
||||
getFormattedUnreadCount(roomId) {
|
||||
const count = this.getUnreadCount(roomId);
|
||||
return chatConfig.formatUnreadCount(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知未读数变化
|
||||
* 职责:触发未读数更新事件
|
||||
*/
|
||||
notifyUnreadChange() {
|
||||
uni.$emit('unread-message-update', {
|
||||
total: this.getAllUnreadCount(),
|
||||
byRoom: { ...this.unreadCounts }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消息到房间
|
||||
* @param {string} roomId - 房间ID
|
||||
* @param {Object} message - 消息对象
|
||||
* 职责:将消息添加到指定房间的消息列表
|
||||
*/
|
||||
addMessageToRoom(roomId, message) {
|
||||
if (!roomId || !message) return;
|
||||
|
||||
if (!this.roomMessages[roomId]) {
|
||||
this.roomMessages[roomId] = [];
|
||||
}
|
||||
|
||||
// 检查消息是否已存在(避免重复)
|
||||
const existingIndex = this.roomMessages[roomId].findIndex(m => m.id === message.id);
|
||||
if (existingIndex !== -1) {
|
||||
this.roomMessages[roomId][existingIndex] = message;
|
||||
} else {
|
||||
this.roomMessages[roomId].push(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间消息列表
|
||||
* @param {string} roomId - 房间ID
|
||||
* @returns {Array} 消息列表
|
||||
*/
|
||||
getRoomMessages(roomId) {
|
||||
return this.roomMessages[roomId] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载房间消息
|
||||
* @param {string} roomId - 房间ID
|
||||
* @param {number} lastMessageId - 最后一条消息ID(可选,用于分页)
|
||||
* @returns {Promise<Array>} 消息列表
|
||||
*/
|
||||
async loadRoomMessages(roomId, lastMessageId = 0) {
|
||||
if (!roomId) {
|
||||
console.warn('房间ID不能为空');
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getMessagesByRoomIdApi({
|
||||
room_id: roomId,
|
||||
last_message_id: lastMessageId || 0
|
||||
});
|
||||
|
||||
if (res && res.data && res.data.result) {
|
||||
const messages = this.processMessages(res.data.result.list || []);
|
||||
this.lastMessageId = res.data.result.last_message_id || 0;
|
||||
|
||||
// 如果是首次加载,替换消息列表;否则追加到前面
|
||||
if (lastMessageId === 0) {
|
||||
this.roomMessages[roomId] = messages;
|
||||
} else {
|
||||
this.roomMessages[roomId] = [...messages, ...(this.roomMessages[roomId] || [])];
|
||||
}
|
||||
|
||||
uni.$emit('load-room-message', lastMessageId === 0 ? 1 : 0);
|
||||
return messages;
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('加载房间消息失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息数据
|
||||
* @param {Array|Object} messages - 消息数据(数组或单个消息)
|
||||
* @returns {Array|Object} 处理后的消息
|
||||
*/
|
||||
processMessages(messages) {
|
||||
if (!Array.isArray(messages)) {
|
||||
return this.processSingleMessage(messages);
|
||||
}
|
||||
|
||||
return messages.map((item) => {
|
||||
const result = {
|
||||
id: item.id,
|
||||
sender_user_id: item.sender_user_id,
|
||||
receiver_user_id: item.receiver_user_id,
|
||||
message_type: item.message_type,
|
||||
message_content: item.message_content,
|
||||
duration: item.duration,
|
||||
room_id: item.room_id,
|
||||
created_at: item.created_at || item.send_time,
|
||||
created_at_text: item.created_at_text || this.formatTime(item.send_time || item.created_at),
|
||||
timestamp: item.send_time || item.created_at
|
||||
};
|
||||
|
||||
// 如果是挂号消息(type=10),异步加载详细信息
|
||||
if (item.message_type === 10) {
|
||||
this.loadRegisterInfo(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个消息
|
||||
* @param {Object} message - 消息对象
|
||||
* @returns {Object} 处理后的消息
|
||||
*/
|
||||
processSingleMessage(message) {
|
||||
return {
|
||||
id: message.id,
|
||||
sender_user_id: message.sender_user_id,
|
||||
receiver_user_id: message.receiver_user_id,
|
||||
message_type: message.message_type,
|
||||
message_content: message.message_content,
|
||||
duration: message.duration,
|
||||
room_id: message.room_id,
|
||||
created_at: message.created_at || message.send_time,
|
||||
created_at_text: message.created_at_text || this.formatTime(message.send_time || message.created_at),
|
||||
timestamp: message.send_time || message.created_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param {number} timestamp - 时间戳(秒)
|
||||
* @returns {string} 格式化后的时间(HH:mm)
|
||||
*/
|
||||
formatTime(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const date = new Date(timestamp * 1000);
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载挂号信息(异步)
|
||||
* @param {Object} message - 消息对象
|
||||
* 职责:为挂号消息加载详细信息
|
||||
*/
|
||||
loadRegisterInfo(message) {
|
||||
const parsedContent = this.parseMessageContent(message.message_content);
|
||||
if (parsedContent._loaded || !parsedContent.id) return;
|
||||
|
||||
getChatRegisterInfoApi({
|
||||
id: parsedContent.id,
|
||||
message_id: message.id || ''
|
||||
}).then((res) => {
|
||||
if (res && res.data && res.data.result) {
|
||||
const resultWithFlag = { ...res.data.result, _loaded: true };
|
||||
const roomId = message.room_id;
|
||||
const roomMessages = this.roomMessages[roomId];
|
||||
|
||||
if (roomMessages) {
|
||||
const index = roomMessages.findIndex(m => m.id === message.id);
|
||||
if (index !== -1) {
|
||||
roomMessages[index].message_content = JSON.stringify(resultWithFlag);
|
||||
uni.$emit('message-updated', { roomId, messageId: message.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载挂号信息失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息内容
|
||||
* @param {string|Object} content - 消息内容
|
||||
* @returns {Object|string} 解析后的内容
|
||||
*/
|
||||
parseMessageContent(content) {
|
||||
try {
|
||||
return typeof content === 'object' ? content : JSON.parse(content);
|
||||
} catch (e) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param {Object} messageData - 消息数据
|
||||
* @param {string} messageData.room_id - 房间ID
|
||||
* @param {string} messageData.sender_user_id - 发送者ID
|
||||
* @param {string} messageData.receiver_user_id - 接收者ID
|
||||
* @param {number|string} messageData.message_type - 消息类型
|
||||
* @param {string} messageData.message_content - 消息内容
|
||||
* @param {number} messageData.duration - 语音时长(可选)
|
||||
* @returns {boolean} 是否发送成功
|
||||
*/
|
||||
sendMessage(messageData) {
|
||||
if (!isWebSocketConnected()) {
|
||||
console.error('WebSocket未连接,无法发送消息');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保消息类型是数字
|
||||
let messageType = messageData.message_type;
|
||||
if (typeof messageType === 'string') {
|
||||
messageType = chatConfig.getMessageType(messageType);
|
||||
}
|
||||
|
||||
const message = {
|
||||
request_type: 'send_message',
|
||||
room_id: messageData.room_id,
|
||||
sender_user_id: messageData.sender_user_id,
|
||||
receiver_user_id: messageData.receiver_user_id,
|
||||
message_type: messageType,
|
||||
message_content: messageData.message_content || '',
|
||||
duration: messageData.duration || 0
|
||||
};
|
||||
|
||||
return sendWebSocketMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册消息处理器
|
||||
* @param {Function} handler - 消息处理函数
|
||||
* 职责:注册消息处理器,当收到WebSocket消息时调用
|
||||
*/
|
||||
onMessage(handler) {
|
||||
if (typeof handler !== 'function') {
|
||||
console.warn('消息处理器必须是函数');
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageHandlers.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除消息处理器
|
||||
* @param {Function} handler - 消息处理函数
|
||||
*/
|
||||
offMessage(handler) {
|
||||
const index = this.messageHandlers.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
this.messageHandlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理收到的WebSocket消息
|
||||
* @param {Object} data - WebSocket消息数据
|
||||
* 职责:处理收到的消息,更新未读数,调用注册的处理器
|
||||
*/
|
||||
handleWebSocketMessage(data) {
|
||||
// 只处理聊天消息
|
||||
if (data.request_type !== 'receive_message' && !data.room_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const roomId = data.room_id;
|
||||
if (!roomId) return;
|
||||
|
||||
// 处理消息
|
||||
const message = this.processSingleMessage(data);
|
||||
|
||||
// 添加到房间消息列表
|
||||
this.addMessageToRoom(roomId, message);
|
||||
|
||||
// 如果不是当前房间,增加未读数
|
||||
if (this.getCurrentRoomId() !== roomId) {
|
||||
this.increaseUnreadCount(roomId);
|
||||
}
|
||||
|
||||
// 调用所有注册的处理器
|
||||
this.messageHandlers.forEach(handler => {
|
||||
try {
|
||||
handler(message, roomId);
|
||||
} catch (error) {
|
||||
console.error('消息处理器执行失败:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空房间消息缓存
|
||||
* @param {string} roomId - 房间ID(可选,不传则清空所有)
|
||||
*/
|
||||
clearRoomCache(roomId = null) {
|
||||
if (roomId) {
|
||||
delete this.roomMessages[roomId];
|
||||
} else {
|
||||
this.roomMessages = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const chatRoomManager = new ChatRoomManager();
|
||||
118
utils/ws/initWebSocket.js
Normal file
118
utils/ws/initWebSocket.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* WebSocket初始化工具
|
||||
* 统一管理WebSocket的初始化、连接、断开等操作
|
||||
*/
|
||||
import { webSocketManager } from './websocket.js';
|
||||
|
||||
/**
|
||||
* 检查是否已登录
|
||||
* @returns {boolean} 是否已登录
|
||||
*/
|
||||
function checkLoginStatus() {
|
||||
const token = uni.getStorageSync('token');
|
||||
const doctorId = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
|
||||
return !!(token && doctorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化WebSocket
|
||||
* 职责:检查登录状态,如果已登录则连接WebSocket并绑定用户
|
||||
* @returns {boolean} 是否初始化成功
|
||||
*/
|
||||
export function initWebSocket() {
|
||||
// 检查登录状态
|
||||
if (!checkLoginStatus()) {
|
||||
console.log('未登录,跳过WebSocket初始化');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果已经连接,不需要重复连接
|
||||
if (webSocketManager.isConnected) {
|
||||
console.log('WebSocket已连接,跳过初始化');
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('开始初始化WebSocket...');
|
||||
webSocketManager.connect();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('WebSocket初始化失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开WebSocket连接
|
||||
* 职责:断开WebSocket连接,清理资源
|
||||
*/
|
||||
export function disconnectWebSocket() {
|
||||
try {
|
||||
console.log('断开WebSocket连接...');
|
||||
webSocketManager.disconnect();
|
||||
} catch (error) {
|
||||
console.error('断开WebSocket失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新绑定用户
|
||||
* 职责:在token刷新或用户信息变更后,重新绑定用户
|
||||
* @returns {boolean} 是否绑定成功
|
||||
*/
|
||||
export function rebindUser() {
|
||||
if (!checkLoginStatus()) {
|
||||
console.warn('未登录,无法重新绑定用户');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('重新绑定WebSocket用户...');
|
||||
return webSocketManager.rebindUser();
|
||||
} catch (error) {
|
||||
console.error('重新绑定用户失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebSocket连接状态
|
||||
* @returns {boolean} 是否已连接
|
||||
*/
|
||||
export function isWebSocketConnected() {
|
||||
return webSocketManager.isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebSocket认证状态
|
||||
* @returns {boolean} 是否已认证
|
||||
*/
|
||||
export function isWebSocketAuthenticated() {
|
||||
return webSocketManager.isAuthenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消息处理器
|
||||
* @param {Function} handler - 消息处理函数
|
||||
*/
|
||||
export function addMessageHandler(handler) {
|
||||
webSocketManager.addMessageHandler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除消息处理器
|
||||
* @param {Function} handler - 消息处理函数
|
||||
*/
|
||||
export function removeMessageHandler(handler) {
|
||||
webSocketManager.removeMessageHandler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送WebSocket消息
|
||||
* @param {Object} message - 消息对象
|
||||
* @param {boolean} requireAuth - 是否需要认证
|
||||
* @returns {boolean} 是否发送成功
|
||||
*/
|
||||
export function sendWebSocketMessage(message, requireAuth = true) {
|
||||
return webSocketManager.send(message, requireAuth);
|
||||
}
|
||||
314
utils/ws/websocket.js
Normal file
314
utils/ws/websocket.js
Normal file
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in New Issue
Block a user