You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

493 lines
14 KiB

// utils/websocket.js
// WebSocket连接管理器
class WebSocketManager {
constructor() {
this.socket = null;
this.url = '';
this.isConnected = false;
this.isAuthenticated = false; // 新增:认证状态标记
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 3000; // 3秒后重连
this.heartbeatInterval = null;
this.heartbeatTime = 30000; // 30秒心跳
this.messageQueue = []; // 未发送的消息队列
this.listeners = {}; // 事件监听器
this.lastHeartbeatTime = 0; // 最后一次心跳响应时间
this.isManualDisconnect = false; // 是否手动断开连接
// 清理可能导致端口错误的存储配置
this._cleanupStorage();
}
// 清理可能导致端口错误的存储配置
_cleanupStorage() {
try {
// 尝试在小程序环境中清理存储
if (typeof wx !== 'undefined' && wx.removeStorageSync) {
wx.removeStorageSync('__TEST_MODE__');
wx.removeStorageSync('__TEST_SERVER_IP__');
wx.removeStorageSync('__DEVICE_TYPE__');
console.log('WebSocket: 已清理可能导致端口错误的本地存储配置');
}
} catch (e) {
console.warn('WebSocket: 清理存储时出错:', e);
}
}
/**
* 初始化WebSocket连接
* @param {string} url - WebSocket服务器地址
* @param {object} options - 配置选项
*/
connect(url, options = {}) {
if (this.socket && this.isConnected) {
console.log('WebSocket已经连接');
return;
}
this.url = url;
this.maxReconnectAttempts = options.maxReconnectAttempts || this.maxReconnectAttempts;
this.reconnectInterval = options.reconnectInterval || this.reconnectInterval;
this.heartbeatTime = options.heartbeatTime || this.heartbeatTime;
this.isManualDisconnect = false; // 重置手动断开标志
try {
console.log('尝试连接WebSocket:', url);
this._trigger('status', { type: 'connecting', message: '正在连接服务器...' });
this.socket = wx.connectSocket({
url: url,
success: () => {
console.log('WebSocket连接请求已发送');
},
fail: (error) => {
console.error('WebSocket连接请求失败:', error);
this._trigger('error', error);
this._trigger('status', { type: 'error', message: '连接服务器失败' });
this._reconnect();
}
});
this._setupEventHandlers();
} catch (error) {
console.error('WebSocket初始化失败:', error);
this._trigger('error', error);
this._trigger('status', { type: 'error', message: '连接异常' });
this._reconnect();
}
}
/**
* 设置WebSocket事件处理器
*/
_setupEventHandlers() {
if (!this.socket) return;
// 连接成功
this.socket.onOpen(() => {
console.log('WebSocket连接已打开');
this.isConnected = true;
this.isAuthenticated = false; // 重置认证状态
this.reconnectAttempts = 0;
this.lastHeartbeatTime = Date.now(); // 记录最后心跳时间
this._trigger('open');
this._trigger('status', { type: 'connected', message: '连接成功' });
// 连接成功后立即进行认证
this.authenticate();
this._startHeartbeat();
});
// 接收消息
this.socket.onMessage((res) => {
try {
let data = JSON.parse(res.data);
// 处理心跳响应
if (data.type === 'pong') {
this.lastHeartbeatTime = Date.now(); // 更新心跳时间
return;
}
// 处理认证响应
if (data.type === 'auth_response') {
if (data.success) {
console.log('WebSocket认证成功');
this.isAuthenticated = true;
// 触发认证成功事件,并传递用户类型信息
this._trigger('authenticated', { userType: data.userType || 'customer' });
// 认证成功后发送队列中的消息
this._flushMessageQueue();
} else {
console.error('WebSocket认证失败:', data.message);
this.isAuthenticated = false;
this._trigger('authFailed', { message: data.message, userType: data.userType || 'unknown' });
}
return;
}
// 处理客服状态更新消息
if (data.type === 'customerServiceStatusUpdate') {
console.log('处理客服状态更新:', data);
this._trigger('customerServiceStatusUpdate', data);
return;
}
console.log('接收到消息:', data);
this._trigger('message', data);
} catch (error) {
console.error('消息解析失败:', error);
this._trigger('error', error);
}
});
// 连接关闭
this.socket.onClose((res) => {
console.log('WebSocket连接已关闭:', res);
this.isConnected = false;
this._stopHeartbeat();
this._trigger('close', res);
this._trigger('status', { type: 'disconnected', message: '连接已关闭' });
// 尝试重连
if (res.code !== 1000 && !this.isManualDisconnect) { // 非正常关闭且不是手动断开
this._reconnect();
}
});
// 连接错误
this.socket.onError((error) => {
console.error('WebSocket错误:', error);
this._trigger('error', error);
this._trigger('status', { type: 'error', message: '连接发生错误' });
});
}
/**
* 发送认证消息
* @param {string} userType - 用户类型'user''customerService'
* @param {string} userId - 用户ID
*/
authenticate(userType = null, userId = null) {
try {
// 获取登录用户信息或token
const app = getApp();
const globalUserInfo = app.globalData.userInfo || {};
// 如果传入了参数,优先使用传入的参数
const finalUserType = userType || globalUserInfo.userType || globalUserInfo.type || 'customer';
const finalUserId = userId || globalUserInfo.userId || wx.getStorageSync('userId') || `temp_${Date.now()}`;
console.log('发送WebSocket认证消息:', { userId: finalUserId, userType: finalUserType });
// 构建认证消息
const authMessage = {
type: 'auth',
timestamp: Date.now(),
data: {
userId: finalUserId,
userType: finalUserType,
// 可以根据实际需求添加更多认证信息
}
};
// 直接发送认证消息,不经过常规消息队列
if (this.isConnected && this.socket) {
this.socket.send({
data: JSON.stringify(authMessage),
success: () => {
console.log('认证消息发送成功');
},
fail: (error) => {
console.error('认证消息发送失败:', error);
// 认证失败后尝试重新认证
setTimeout(() => {
this.authenticate(userType, userId);
}, 2000);
}
});
}
} catch (error) {
console.error('发送认证消息异常:', error);
}
}
/**
* 发送消息
* @param {object} data - 要发送的数据
* @returns {boolean} 消息是否已成功放入发送队列不保证实际发送成功
*/
send(data) {
// 验证消息格式
if (!data || typeof data !== 'object') {
console.error('WebSocket发送消息失败: 消息格式不正确');
return false;
}
// 为消息添加时间戳
if (!data.timestamp) {
data.timestamp = Date.now();
}
// 如果是认证消息或连接未建立,直接处理
if (data.type === 'auth' || data.type === 'ping') {
// 认证消息和心跳消息不需要等待认证
if (this.isConnected && this.socket) {
try {
this.socket.send({
data: JSON.stringify(data),
success: () => {
console.log('特殊消息发送成功:', data);
this._trigger('sendSuccess', data);
},
fail: (error) => {
console.error('特殊消息发送失败:', error);
this._trigger('sendError', error);
}
});
return true;
} catch (error) {
console.error('发送特殊消息异常:', error);
this._trigger('error', error);
return false;
}
}
} else if (this.isConnected && this.socket) {
// 非特殊消息需要检查认证状态
if (!this.isAuthenticated) {
console.log('WebSocket未认证,消息已加入队列等待认证');
this.messageQueue.push(data);
// 如果未认证,尝试重新认证
if (!this.isAuthenticated) {
this.authenticate();
}
return true;
}
try {
this.socket.send({
data: JSON.stringify(data),
success: () => {
console.log('消息发送成功:', data);
this._trigger('sendSuccess', data);
},
fail: (error) => {
console.error('消息发送失败:', error);
// 将失败的消息加入队列
this.messageQueue.push(data);
this._trigger('sendError', error);
}
});
return true;
} catch (error) {
console.error('发送消息异常:', error);
this.messageQueue.push(data);
this._trigger('error', error);
return false;
}
} else {
// 连接未建立,加入消息队列
console.log('WebSocket未连接,消息已加入队列');
this.messageQueue.push(data);
// 尝试重连
if (!this.isConnected) {
this._reconnect();
}
return true;
}
}
/**
* 关闭WebSocket连接
*/
close() {
if (this.socket) {
this._stopHeartbeat();
this.isManualDisconnect = true; // 标记为手动断开
this.socket.close();
this.socket = null;
this.isConnected = false;
this.isAuthenticated = false; // 重置认证状态
console.log('WebSocket已主动关闭');
this._trigger('status', { type: 'disconnected', message: '连接已断开' });
}
}
/**
* 开始心跳检测
*/
_startHeartbeat() {
this._stopHeartbeat();
this.heartbeatInterval = setInterval(() => {
// 检查是否超过3倍心跳间隔未收到心跳响应
if (Date.now() - this.lastHeartbeatTime > this.heartbeatTime * 3) {
console.warn('WebSocket心跳超时,可能已断开连接');
this._stopHeartbeat();
this._reconnect();
return;
}
if (this.isConnected) {
this.send({ type: 'ping', timestamp: Date.now() });
console.log('发送心跳包');
}
}, this.heartbeatTime);
}
/**
* 停止心跳检测
*/
_stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
/**
* 尝试重新连接
*/
_reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('WebSocket重连次数已达上限,停止重连');
this._trigger('reconnectFailed');
this._trigger('status', {
type: 'error',
isWarning: true,
message: `已达到最大重连次数(${this.maxReconnectAttempts}次)`
});
return;
}
this.reconnectAttempts++;
// 增加重连时间间隔(指数退避)
const currentInterval = this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1);
console.log(`WebSocket第${this.reconnectAttempts}次重连... 间隔: ${currentInterval}ms`);
this._trigger('reconnecting', this.reconnectAttempts);
this._trigger('status', {
type: 'reconnecting',
message: `正在重连(${this.reconnectAttempts}/${this.maxReconnectAttempts})`
});
setTimeout(() => {
this.connect(this.url);
}, currentInterval);
}
/**
* 发送队列中的消息
*/
_flushMessageQueue() {
if (this.messageQueue.length > 0) {
console.log('发送队列中的消息,队列长度:', this.messageQueue.length);
// 循环发送队列中的消息,使用小延迟避免消息发送过快
const sendMessage = () => {
if (this.messageQueue.length === 0 || !this.isConnected) {
return;
}
const messageData = this.messageQueue.shift();
const message = JSON.stringify(messageData);
this.socket.send({
data: message,
success: () => {
console.log('队列消息发送成功:', messageData);
// 继续发送下一条消息,添加小延迟
setTimeout(sendMessage, 50);
},
fail: (error) => {
console.error('队列消息发送失败:', error);
// 发送失败,重新加入队列
this.messageQueue.unshift(messageData);
}
});
};
// 开始发送队列中的第一条消息
sendMessage();
}
}
/**
* 触发事件
* @param {string} event - 事件名称
* @param {*} data - 事件数据
*/
_trigger(event, data = null) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`事件处理错误 [${event}]:`, error);
}
});
}
}
/**
* 监听事件
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
/**
* 移除事件监听
* @param {string} event - 事件名称
* @param {Function} callback - 回调函数不传则移除所有该事件的监听器
*/
off(event, callback) {
if (!this.listeners[event]) return;
if (callback) {
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
} else {
this.listeners[event] = [];
}
}
/**
* 获取连接状态
* @returns {boolean} 是否连接
*/
getConnectionStatus() {
return this.isConnected;
}
/**
* 获取认证状态
* @returns {boolean} 是否已认证
*/
getAuthStatus() {
return this.isAuthenticated;
}
/**
* 获取重连次数
* @returns {number} 重连次数
*/
getReconnectAttempts() {
return this.reconnectAttempts;
}
/**
* 清空消息队列
*/
clearMessageQueue() {
this.messageQueue = [];
}
}
// 导出单例
export default new WebSocketManager();