findme-miniprogram-frontend/utils/websocket-manager-v2.js
2025-12-27 17:16:03 +08:00

968 lines
25 KiB
JavaScript
Raw 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.

const config = require('../config/config.js');
/**
* 微信小程序专用WebSocket管理器 V2
* 针对小程序环境优化,解决连接问题
*/
class WebSocketManagerV2 {
constructor() {
this.ws = null;
this.isConnected = false;
this.isConnecting = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 8; // 增加重连次数
this.reconnectInterval = 2000; // 2秒
this.heartbeatInterval = null;
this.heartbeatTimeout = 30000; // 30秒心跳
this.connectionTimeout = null;
// 认证信息
this.token = null;
this.deviceId = null;
// 事件处理器
this.messageHandlers = new Map();
this.eventHandlers = new Map();
// 消息队列
this.messageQueue = [];
this.messageIdCounter = 0;
// 性能监控
this.performanceStats = {
connectionAttempts: 0,
successfulConnections: 0,
failedConnections: 0,
messagesSent: 0,
messagesReceived: 0,
reconnectCount: 0,
lastConnectTime: null,
lastDisconnectTime: null,
averageLatency: 0,
latencyHistory: []
};
// 连接质量监控
this.connectionQuality = {
score: 100, // 0-100分
factors: {
stability: 100, // 连接稳定性
latency: 100, // 延迟
reliability: 100 // 可靠性
}
};
// 智能重连配置
this.smartReconnect = {
enabled: true,
adaptiveDelay: true,
maxDelay: 60000, // 最大延迟60秒
backoffFactor: 1.5,
jitterRange: 0.1 // 10%的随机抖动
};
// 初始化设备ID
this.initDeviceId();
// 绑定方法上下文
this.onOpen = this.onOpen.bind(this);
this.onMessage = this.onMessage.bind(this);
this.onError = this.onError.bind(this);
this.onClose = this.onClose.bind(this);
}
// 初始化设备ID
initDeviceId() {
try {
let deviceId = wx.getStorageSync('device_id');
if (!deviceId) {
deviceId = 'mp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
wx.setStorageSync('device_id', deviceId);
} else {
}
this.deviceId = deviceId;
} catch (error) {
console.error('❌ 设备ID初始化失败:', error);
this.deviceId = 'mp_unknown_' + Date.now();
}
}
// 设置认证token
setToken(token) {
this.token = token;
}
// 获取认证token
getToken() {
if (this.token) {
return this.token;
}
try {
// 方式1从app.globalData获取
const app = getApp();
if (app?.globalData?.userInfo?.token) {
this.token = app.globalData.userInfo.token;
return this.token;
}
// 方式2从本地存储获取userInfo
const userInfo = wx.getStorageSync('userInfo');
if (userInfo?.token) {
this.token = userInfo.token;
return this.token;
}
// 方式3直接从本地存储获取token
const directToken = wx.getStorageSync('token');
if (directToken) {
this.token = directToken;
return this.token;
}
console.warn('⚠️ 未找到有效的token');
return null;
} catch (error) {
console.error('❌ 获取token失败:', error);
return null;
}
}
// 连接WebSocket
async connect() { if (this.ws) {
// 0: CONNECTING, 1: OPEN, 2: CLOSING, 3: CLOSED
}
if (this.isConnected) {
return Promise.resolve();
}
if (this.isConnecting) {
return this.waitForConnection();
}
return new Promise((resolve, reject) => {
this.isConnecting = true;
// 记录连接尝试
this.performanceStats.connectionAttempts++;
const connectStartTime = Date.now();
// 获取token
const token = this.getToken();
if (!token) {
console.error('❌ 缺少认证token无法连接WebSocket');
this.isConnecting = false;
this.performanceStats.failedConnections++;
reject(new Error('缺少认证token'));
return;
}
// 构建WebSocket URL
const wsUrl = config.websocket?.url || 'wss://api.faxianwo.me/api/v1/ws';
const fullUrl = `${wsUrl}?device_id=${encodeURIComponent(this.deviceId)}`;
try {
// 小程序WebSocket连接配置
const connectOptions = {
url: fullUrl,
header: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 15000
};
this.ws = wx.connectSocket(connectOptions);
// 设置连接超时
this.connectionTimeout = setTimeout(() => {
if (this.isConnecting && !this.isConnected) {
console.error('❌ WebSocket连接超时');
this.cleanup();
reject(new Error('连接超时'));
}
}, 20000);
// 事件监听
this.ws.onOpen((res) => {
const connectTime = Date.now() - connectStartTime;
console.log(`🎉 WebSocket连接成功 (${connectTime}ms)`);
// 更新性能统计
this.performanceStats.successfulConnections++;
this.performanceStats.lastConnectTime = Date.now();
this.updateConnectionQuality('connect_success', connectTime);
this.onOpen(res);
resolve();
});
this.ws.onMessage((res) => {
this.performanceStats.messagesReceived++;
console.log('🔥 WebSocket原始接收:', {
data: res.data,
dataType: typeof res.data,
dataLength: res.data ? res.data.length : 0,
timestamp: new Date().toLocaleTimeString()
});
try {
this.onMessage(res);
} catch (error) {
console.error('🔥 this.onMessage调用失败:', error);
}
});
this.ws.onError((error) => {
console.error('💥 WebSocket连接错误:', error);
this.performanceStats.failedConnections++;
this.updateConnectionQuality('connect_error');
this.onError(error);
if (this.isConnecting) {
reject(error);
}
});
this.ws.onClose((res) => {
this.performanceStats.lastDisconnectTime = Date.now();
this.updateConnectionQuality('disconnect', res.code);
this.onClose(res);
if (this.isConnecting) {
reject(new Error('连接被关闭'));
}
});
} catch (error) {
console.error('❌ WebSocket连接创建失败:', error);
this.cleanup();
// 检查域名配置问题
if (error.errMsg?.includes('url not in domain list')) {
console.error('💡 请在微信公众平台配置WebSocket域名: wss://api.faxianwo.me');
}
reject(error);
}
});
}
// 等待连接完成
waitForConnection() {
return new Promise((resolve, reject) => {
const checkConnection = () => {
if (this.isConnected) {
resolve();
} else if (!this.isConnecting) {
reject(new Error('连接失败'));
} else {
setTimeout(checkConnection, 100);
}
};
checkConnection();
});
}
// 清理连接状态
cleanup() {
this.isConnecting = false;
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
this.connectionTimeout = null;
}
}
// 连接成功处理
onOpen(res) {
console.log('📊 连接详情:', {
deviceId: this.deviceId,
hasToken: !!this.token,
reconnectAttempts: this.reconnectAttempts,
timestamp: new Date().toLocaleTimeString()
});
this.isConnected = true;
this.isConnecting = false;
this.reconnectAttempts = 0;
this.cleanup();
this.startHeartbeat();
this.processMessageQueue();
this.triggerEvent('connected', res);
}
// 消息处理
onMessage(event) {
try {
const message = JSON.parse(event.data);
console.log('📨 WebSocket收到原始消息:', {
type: message.type || 'unknown',
hasData: !!message.data,
messageKeys: Object.keys(message),
deviceId: this.deviceId,
timestamp: new Date().toLocaleTimeString()
});
// 心跳响应
if (message.type === 'pong' || message.type === 'heartbeat_response') {
return;
}
// 🔥 根据消息类型分发到对应的事件处理器
switch (message.type) {
case 'new_message':
this.triggerEvent('new_message', message);
break;
case 'message_status':
this.triggerEvent('message_status', message);
break;
case 'message_sent':
// 标准响应:消息发送成功(包括撤回成功)
this.triggerEvent('message_sent', message);
break;
case 'message_recalled':
// 收到其他用户撤回消息的通知
this.triggerEvent('message_recalled', message);
break;
case 'error':
// 错误响应(包括撤回失败)
this.triggerEvent('error', message);
break;
case 'unread_count_update':
this.triggerEvent('unread_count_update', message);
break;
// 🔥 在线状态相关presence
case 'user_online':
this.triggerEvent('user_online', message);
this.triggerEvent('message', message); // 兼容通用监听
break;
case 'user_offline':
this.triggerEvent('user_offline', message);
this.triggerEvent('message', message); // 兼容通用监听
break;
case 'presence_update':
this.triggerEvent('presence_update', message);
this.triggerEvent('message', message); // 兼容通用监听
break;
case 'friend_request':
this.triggerEvent('friend_request', message);
break;
case 'notification':
this.triggerEvent('notification', message);
break;
case 'chat_message':
this.triggerEvent('chat_message', message);
break;
default:
// 尝试查找特定类型的处理器
const handler = this.messageHandlers.get(message.type);
if (handler) {
handler(message);
} else {
// 触发通用消息事件
this.triggerEvent('message', message);
}
}
} catch (error) {
console.error('❌ 消息解析失败:', error);
}
}
// 错误处理
onError(error) {
console.error('❌ WebSocket错误:', error);
this.isConnected = false;
this.cleanup();
this.triggerEvent('error', error);
this.scheduleReconnect();
}
// 连接关闭处理
onClose(event) {
this.isConnected = false;
this.cleanup();
this.stopHeartbeat();
this.triggerEvent('disconnected', event);
// 非正常关闭时重连
if (event.code !== 1000) {
this.scheduleReconnect();
}
}
// 发送消息(带性能监控)
send(message) {
if (!this.isConnected) {
console.warn('⚠️ WebSocket未连接消息加入队列');
this.messageQueue.push(message);
this.connect().catch(console.error);
return false;
}
try {
const messageStr = typeof message === 'string' ? message : JSON.stringify(message);
const sendTime = Date.now();
this.ws.send({
data: messageStr,
success: () => {
this.performanceStats.messagesSent++;
// 如果是心跳消息,记录延迟
if (message.type === 'heartbeat') {
this.recordLatency(sendTime);
}
},
fail: (error) => {
console.error('❌ 消息发送失败:', error);
this.updateConnectionQuality('send_error');
}
});
return true;
} catch (error) {
console.error('❌ 消息发送失败:', error);
this.updateConnectionQuality('send_exception');
return false;
}
}
// 处理消息队列
processMessageQueue() {
while (this.messageQueue.length > 0 && this.isConnected) {
const message = this.messageQueue.shift();
this.send(message);
}
}
// 开始心跳 - 使用WebSocket原生ping/pong
startHeartbeat() {
this.stopHeartbeat();
this.heartbeatInterval = setInterval(() => {
console.log("发送心跳---->");
if (this.isConnected && this.ws) {
try {
// 使用微信小程序的ping方法如果支持
if (typeof this.ws.ping === 'function') {
this.ws.ping();
} else {
// 降级:发送应用层心跳(使用正确的格式)
const heartbeatMessage = {
type: 'heartbeat',
id: `heartbeat_${Date.now()}`,
data: {
timestamp: Date.now()
}
};
this.send(heartbeatMessage);
}
} catch (error) {
console.error('❌ 心跳发送失败:', error);
}
}
}, this.heartbeatTimeout);
}
// 停止心跳
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
// 智能重连机制
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('❌ 达到最大重连次数,停止重连');
this.triggerEvent('max_reconnect_reached');
return;
}
// 智能延迟计算
let delay;
if (this.smartReconnect.adaptiveDelay) {
// 基于连接质量调整延迟
const qualityFactor = this.connectionQuality.score / 100;
const baseDelay = this.reconnectInterval * Math.pow(this.smartReconnect.backoffFactor, this.reconnectAttempts);
delay = Math.min(baseDelay / qualityFactor, this.smartReconnect.maxDelay);
// 添加随机抖动,避免雷群效应
const jitter = delay * this.smartReconnect.jitterRange * (Math.random() - 0.5);
delay = Math.max(1000, delay + jitter); // 最小1秒
} else {
delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts);
}
console.log(`🔄 ${Math.round(delay)}ms后尝试第${this.reconnectAttempts + 1}次重连 (质量分数: ${this.connectionQuality.score})`);
// 更新统计
this.performanceStats.reconnectCount++;
setTimeout(() => {
this.reconnectAttempts++;
this.connect().catch((error) => {
console.error('重连失败:', error);
this.scheduleReconnect(); // 继续重连
});
}, delay);
}
// 断开连接
disconnect() {
this.stopHeartbeat();
this.cleanup();
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
this.reconnectAttempts = this.maxReconnectAttempts; // 阻止重连
}
// 注册消息处理器
registerMessageHandler(type, handler) {
this.messageHandlers.set(type, handler);
}
// 发送聊天消息 - 符合API文档规范
sendChatMessage(receiverId, content, msgType = 0, chatType = 0, options = {}) {
// 生成客户端消息ID
const clientMsgId = this.generateMessageId();
// 🔥 消息类型映射 - 修正为与API文档一致
const msgTypeMap = {
'text': 0, // 文字消息
'image': 1, // 图片消息
'voice': 2, // 语音消息
'video': 3, // 视频消息
'file': 4, // 文件消息
'emoji': 6, // 表情消息
'location': 5 // 位置消息
};
// 🔥 详细记录消息类型转换过程
// 如果msgType是字符串转换为数字
if (typeof msgType === 'string') {
const convertedMsgType = msgTypeMap[msgType] || 0;
msgType = convertedMsgType;
}
const message = {
type: 'send_message',
id: clientMsgId,
data: {
receiverId: receiverId,
chatType: chatType,
msgType: msgType,
content: content,
atUsers: options.atUsers || [],
replyTo: options.replyTo || '',
extra: options.extra || ''
}
};
console.log('📤 发送聊天消息:', {
type: 'send_message',
id: clientMsgId,
receiverId,
chatType,
msgType,
contentLength: content ? content.length : 0,
contentPreview: content ? (content.substring(0, 50) + (content.length > 50 ? '...' : '')) : 'null'
});
return this.send(message);
}
// 生成消息ID
generateMessageId() {
this.messageIdCounter++;
return `client_msg_${Date.now()}_${this.messageIdCounter}`;
}
// 发送图片消息
sendImageMessage(receiverId, imageUrl, chatType = 0, options = {}) {
return this.sendChatMessage(receiverId, imageUrl, 1, chatType, options);
}
// 发送语音消息
sendVoiceMessage(receiverId, voiceUrl, duration, chatType = 0, options = {}) {
const content = JSON.stringify({
url: voiceUrl,
duration: duration
});
return this.sendChatMessage(receiverId, content, 2, chatType, options);
}
// 发送视频消息
sendVideoMessage(receiverId, videoUrl, duration, thumbnail, chatType = 0, options = {}) {
const content = JSON.stringify({
url: videoUrl,
duration: duration,
thumbnail: thumbnail
});
return this.sendChatMessage(receiverId, content, 3, chatType, options);
}
// 发送位置消息
sendLocationMessage(receiverId, latitude, longitude, address, locationName, chatType = 0, options = {}) {
const message = {
type: 'send_location',
id: this.generateMessageId(),
data: {
receiverId: receiverId,
chatType: chatType,
latitude: latitude,
longitude: longitude,
address: address,
locationName: locationName
}
};
return this.send(message);
}
// 撤回消息
recallMessage(messageId) {
const message = {
type: 'recall_message',
id: this.generateMessageId(),
data: {
messageId: messageId
}
};
return this.send(message);
}
// 标记消息已读
markMessageRead(messageIds) {
const message = {
type: 'mark_read',
id: this.generateMessageId(),
data: {
messageIds: Array.isArray(messageIds) ? messageIds : [messageIds]
}
};
return this.send(message);
}
// 注册事件处理器
on(event, handler) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, []);
}
this.eventHandlers.get(event).push(handler);
}
// 移除事件处理器
off(event, handler) {
if (!this.eventHandlers.has(event)) {
return;
}
const handlers = this.eventHandlers.get(event);
if (handler) {
// 移除特定的处理器
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
} else {
// 移除所有处理器
this.eventHandlers.set(event, []);
}
// 如果没有处理器了,删除整个事件
if (handlers.length === 0) {
this.eventHandlers.delete(event);
}
}
// 触发事件
triggerEvent(event, data) {
const handlers = this.eventHandlers.get(event);
const handlerCount = Array.isArray(handlers) ? handlers.length : 0;
if (handlerCount > 0) {
handlers.forEach((handler, index) => {
try {
handler(data);
} catch (error) {
console.error(`❌ 事件处理器错误 [${event}]:`, error);
}
});
} else {
}
}
// 获取连接状态
getStatus() {
return {
isConnected: this.isConnected,
isConnecting: this.isConnecting,
reconnectAttempts: this.reconnectAttempts,
hasToken: !!this.token,
deviceId: this.deviceId
};
}
// 兼容性方法 - 为了向后兼容
getConnectionStatus() {
return {
connected: this.isConnected,
connecting: this.isConnecting
};
}
// ===== 聊天功能相关方法 =====
// 发送输入状态
sendTypingStatus(conversationId, isTyping) {
try {
if (!this.isConnected) {
console.warn('⚠️ WebSocket未连接无法发送输入状态');
return false;
}
const message = {
type: 'typing_status',
id: `typing_${Date.now()}`,
data: {
conversationId: conversationId,
isTyping: isTyping,
timestamp: Date.now()
}
};
console.log('📝 发送输入状态:', {
conversationId,
isTyping,
timestamp: Date.now()
});
return this.send(message);
} catch (error) {
console.error('❌ 发送输入状态失败:', error);
return false;
}
}
// 发送已读回执
sendReadReceipt(messageId) {
try {
if (!this.isConnected) {
console.warn('⚠️ WebSocket未连接无法发送已读回执');
return false;
}
const message = {
type: 'mark_read',
id: `read_${Date.now()}`,
data: {
messageId: messageId,
timestamp: Date.now()
}
};
console.log('✅ 发送已读回执:', {
messageId,
timestamp: Date.now()
});
return this.send(message);
} catch (error) {
console.error('❌ 发送已读回执失败:', error);
return false;
}
}
// 聊天消息处理器注册
onChatMessage(handler) {
this.on('new_message', handler);
this.on('chat_message', handler);
}
// 未读数更新处理器注册
onUnreadCountUpdate(handler) {
this.on('unread_count_update', handler);
this.on('unread_update', handler);
}
// 通知处理器注册
onNotification(handler) {
this.on('notification', handler);
}
// 好友请求处理器注册
onFriendRequest(handler) {
this.on('friend_request', handler);
}
// 发送聊天消息(兼容旧版本接口)- 重定向到标准方法
sendChatMessageLegacy(targetId, content, messageType = 'text', extra = {}) {
// 转换消息类型
const msgTypeMap = {
'text': 1,
'image': 2,
'voice': 3,
'video': 4,
'file': 5,
'emoji': 6,
'location': 7
};
const msgType = msgTypeMap[messageType] || 1;
const chatType = extra.chatType || 0;
return this.sendChatMessage(targetId, content, msgType, chatType, extra);
}
// 生成消息ID
generateMessageId() {
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// ===== 性能监控方法 =====
// 记录延迟
recordLatency(sendTime) {
const latency = Date.now() - sendTime;
this.performanceStats.latencyHistory.push(latency);
// 只保留最近50次的延迟记录
if (this.performanceStats.latencyHistory.length > 50) {
this.performanceStats.latencyHistory.shift();
}
// 计算平均延迟
const sum = this.performanceStats.latencyHistory.reduce((a, b) => a + b, 0);
this.performanceStats.averageLatency = sum / this.performanceStats.latencyHistory.length;
// 更新连接质量
this.updateConnectionQuality('latency', latency);
}
// 更新连接质量评分
updateConnectionQuality(event, value = null) {
const factors = this.connectionQuality.factors;
switch (event) {
case 'connect_success':
factors.reliability = Math.min(100, factors.reliability + 5);
if (value < 3000) factors.latency = Math.min(100, factors.latency + 2);
break;
case 'connect_error':
factors.reliability = Math.max(0, factors.reliability - 10);
factors.stability = Math.max(0, factors.stability - 5);
break;
case 'disconnect':
if (value !== 1000) { // 非正常关闭
factors.stability = Math.max(0, factors.stability - 8);
}
break;
case 'send_error':
case 'send_exception':
factors.reliability = Math.max(0, factors.reliability - 3);
break;
case 'latency':
if (value > 5000) {
factors.latency = Math.max(0, factors.latency - 5);
} else if (value < 1000) {
factors.latency = Math.min(100, factors.latency + 1);
}
break;
}
// 计算总分
this.connectionQuality.score = Math.round(
(factors.stability + factors.latency + factors.reliability) / 3
);
}
// 获取性能统计
getPerformanceStats() {
return {
...this.performanceStats,
connectionQuality: this.connectionQuality,
uptime: this.performanceStats.lastConnectTime ?
Date.now() - this.performanceStats.lastConnectTime : 0
};
}
// 重置性能统计
resetPerformanceStats() {
this.performanceStats = {
connectionAttempts: 0,
successfulConnections: 0,
failedConnections: 0,
messagesSent: 0,
messagesReceived: 0,
reconnectCount: 0,
lastConnectTime: null,
lastDisconnectTime: null,
averageLatency: 0,
latencyHistory: []
};
this.connectionQuality = {
score: 100,
factors: {
stability: 100,
latency: 100,
reliability: 100
}
};
}
// ===== 兼容性别名方法 =====
// 兼容旧版本的 sendMessage 方法
sendMessage(message) {
return this.send(message);
}
}
// 创建全局实例
const wsManager = new WebSocketManagerV2();
module.exports = wsManager;