findme-miniprogram-frontend/utils/websocket-manager-v2.js

969 lines
25 KiB
JavaScript
Raw Normal View History

2025-12-27 17:16:03 +08:00
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;