Files
xo_user_client/utils/socket.js
2025-06-16 17:51:09 +08:00

547 lines
12 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.
// socket.js - 优化后的全局唯一socket管理模块支付宝小程序兼容版
// 事件总线实现
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return () => this.off(event, callback);
}
off(event, callback) {
if (!this.events[event]) return;
if (callback) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
} else {
this.events[event] = [];
}
}
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => callback(data));
}
}
// 创建全局事件总线
const eventBus = new EventEmitter();
// 唯一的socket实例
let socketTask = null;
let connectTimer = null;
let heartbeatTimer = null;
let reconnectCount = 0;
let isConnected = false;
let lastUrl = '';
let isConnecting = false;
let isAlipayMiniProgram = false;
// 支付宝小程序特有状态
let globalEventHandlersRegistered = false;
// 初始化时检测环境
(function detectEnvironment() {
try {
const systemInfo = uni.getSystemInfoSync();
isAlipayMiniProgram = systemInfo.platform === 'alipay' || systemInfo.environment === 'alipay' || systemInfo
.app == 'alipay';
console.log('当前运行平台===', systemInfo);
console.log(`当前运行环境: ${isAlipayMiniProgram ? '支付宝小程序' : '其他平台'}`);
} catch (e) {
console.error('检测运行环境失败:', e);
isAlipayMiniProgram = false;
}
})();
const MAX_RECONNECT_COUNT = 5;
const RECONNECT_DELAY = 3000; // 3秒重连间隔
const HEARTBEAT_INTERVAL = 10000; // 10秒心跳间隔
// 连接socket
function connectSocket(url = 'wss://store.sxczgkj.com/shopser') {
// 如果已经连接到相同URL直接返回
if (isConnected && lastUrl === url) {
console.log('socket已连接到:', url);
return Promise.resolve();
}
// 如果正在连接中返回pending状态的Promise
if (isConnecting) {
console.log('socket连接中等待连接完成...');
return new Promise((resolve, reject) => {
const unsubscribeConnected = eventBus.on('connected', () => {
unsubscribeConnected();
unsubscribeError();
resolve();
});
const unsubscribeError = eventBus.on('error', (err) => {
unsubscribeConnected();
unsubscribeError();
reject(err);
});
});
}
// 如果已有不同URL的连接先关闭
if ((socketTask || globalEventHandlersRegistered) && lastUrl !== url) {
console.log('关闭旧连接准备连接新URL:', url);
closeSocket();
}
// 记录当前URL
lastUrl = url;
isConnecting = true;
reconnectCount = 0; // 重置重连计数
console.log('开始连接socket:', url);
return new Promise((resolve, reject) => {
// 支付宝小程序特殊处理
if (isAlipayMiniProgram) {
// 确保只注册一次全局事件处理程序
if (!globalEventHandlersRegistered) {
registerGlobalEventHandlers();
globalEventHandlersRegistered = true;
}
// 调用连接方法
uni.connectSocket({
url,
success: () => {
console.log('socket连接请求已发送');
},
fail: (err) => {
console.error('socket连接失败:', err);
isConnecting = false;
// 特殊处理:如果是"WebSocket已存在"错误,尝试重用现有连接
if (err.errMsg.includes('WebSocket 已存在')) {
console.warn('检测到重复连接,尝试重用现有连接');
// 触发已连接事件(模拟连接成功)
handleSocketOpen(null);
resolve();
return;
}
eventBus.emit('error', err);
handleReconnect(url);
reject(err);
}
});
} else {
// H5和其他平台使用socketTask对象方法
socketTask = uni.connectSocket({
url,
success: () => {
console.log('socket连接请求已发送');
},
fail: (err) => {
console.error('socket连接失败:', err);
isConnecting = false;
eventBus.emit('error', err);
handleReconnect(url);
reject(err);
}
});
// 检查socketTask是否正确返回
if (!socketTask) {
console.error('uni.connectSocket() 未返回 socketTask 对象');
isConnecting = false;
eventBus.emit('error', new Error('无法创建socket连接'));
handleReconnect(url);
reject(new Error('无法创建socket连接'));
return;
}
// 监听socket连接成功
if (socketTask.onOpen) {
socketTask.onOpen((res) => {
handleSocketOpen(res, resolve);
});
} else {
console.error('socketTask对象缺少onOpen方法');
isConnecting = false;
eventBus.emit('error', new Error('socketTask对象不完整'));
handleReconnect(url);
reject(new Error('socketTask对象不完整'));
}
// 监听socket消息
if (socketTask.onMessage) {
socketTask.onMessage((res) => {
handleSocketMessage(res);
});
}
// 监听socket关闭
if (socketTask.onClose) {
socketTask.onClose((res) => {
handleSocketClose(res);
});
}
// 监听socket错误
if (socketTask.onError) {
socketTask.onError((err) => {
handleSocketError(err, reject);
});
}
}
});
}
// 注册支付宝小程序全局事件监听
function registerGlobalEventHandlers() {
console.log('注册支付宝小程序全局socket事件监听');
// 连接成功
uni.onSocketOpen((res) => {
console.log('支付宝小程序: socket连接成功');
handleSocketOpen(res);
});
// 接收消息
uni.onSocketMessage((res) => {
handleSocketMessage(res);
});
// 连接关闭
uni.onSocketClose((res) => {
handleSocketClose(res);
});
// 连接错误
uni.onSocketError((err) => {
handleSocketError(err);
});
}
// 处理socket连接成功
function handleSocketOpen(res, resolve) {
console.log('socket连接成功:', res);
reconnectCount = 0;
isConnected = true;
isConnecting = false;
// 标记socket已创建支付宝小程序无socketTask对象
if (isAlipayMiniProgram) {
socketTask = true; // 使用布尔值标记连接状态
}
eventBus.emit('connected');
const userInfo = uni.getStorageSync('cache_shop_user_info_key');
if (userInfo.id) {
sendMessage({
operate_type: 'init',
type: 'user_msg',
user_id: userInfo.id,
});
}
startHeartbeat();
if (resolve) {
resolve();
}
}
// 处理socket消息
function handleSocketMessage(res) {
try {
let data = JSON.parse(res.data);
console.log('收到socket消息:', data);
eventBus.emit('message', data);
} catch (e) {
console.error('解析socket消息失败:', e);
eventBus.emit('error', e);
}
}
// 处理socket关闭
function handleSocketClose(res) {
console.log('socket已关闭:', res);
isConnected = false;
eventBus.emit('closed', res);
stopHeartbeat();
// 仅在非支付宝小程序环境下重置socketTask
if (!isAlipayMiniProgram) {
socketTask = null;
}
handleReconnect(lastUrl);
}
// 处理socket错误
function handleSocketError(err, reject) {
console.error('socket发生错误:', err);
isConnected = false;
isConnecting = false;
eventBus.emit('error', err);
stopHeartbeat();
// 仅在非支付宝小程序环境下重置socketTask
if (!isAlipayMiniProgram) {
socketTask = null;
}
handleReconnect(lastUrl);
if (reject) {
reject(err);
}
}
// 发送消息
function sendMessage(data) {
if (!isConnected) {
console.error('socket未连接无法发送消息');
// 尝试重连
connectSocket().then(() => {
sendMessage(data);
}).catch(err => {
console.error('重连失败,无法发送消息:', err);
});
return;
}
try {
const messageData = JSON.stringify(data);
if (isAlipayMiniProgram) {
uni.sendSocketMessage({
data: messageData,
success: () => {
console.log('消息发送成功');
},
fail: (err) => {
console.error('消息发送失败:', err);
eventBus.emit('error', err);
}
});
} else {
if (!socketTask) {
console.error('socketTask不存在无法发送消息');
return;
}
// 检查socketTask是否有send方法
if (socketTask.send) {
socketTask.send({
data: messageData,
success: () => {
console.log('消息发送成功');
},
fail: (err) => {
console.error('消息发送失败:', err);
eventBus.emit('error', err);
}
});
} else {
console.error('socketTask缺少send方法');
eventBus.emit('error', new Error('socketTask对象不完整'));
}
}
} catch (e) {
console.error('消息序列化失败:', e);
eventBus.emit('error', e);
}
}
// 发送心跳
function sendHeartbeat() {
sendMessage({
type: 'ping_interval'
});
}
// 开始心跳
function startHeartbeat() {
stopHeartbeat();
heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
}
// 停止心跳
function stopHeartbeat() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
}
// 处理重连
function handleReconnect(url) {
if (reconnectCount >= MAX_RECONNECT_COUNT) {
console.error('达到最大重连次数,停止重连');
return;
}
// 检查是否需要重连(避免重复触发连接)
if (isConnecting || isConnected) {
return;
}
reconnectCount++;
console.log(`准备第${reconnectCount}次重连,${RECONNECT_DELAY/1000}秒后...`);
if (connectTimer) {
clearTimeout(connectTimer);
}
connectTimer = setTimeout(() => {
connectSocket(url);
}, RECONNECT_DELAY);
}
// 关闭socket
function closeSocket() {
console.log('正在关闭socket连接...');
try {
if (isAlipayMiniProgram) {
uni.closeSocket({
success: () => {
console.log('主动关闭socket成功');
},
fail: (err) => {
console.error('主动关闭socket失败:', err);
}
});
} else {
if (!socketTask) return;
if (socketTask.close) {
socketTask.close({
success: () => {
console.log('主动关闭socket成功');
},
fail: (err) => {
console.error('主动关闭socket失败:', err);
}
});
} else {
console.error('socketTask缺少close方法');
isConnected = false;
}
}
} catch (e) {
console.error('关闭socket时发生异常:', e);
isConnected = false;
}
stopHeartbeat();
if (connectTimer) {
clearTimeout(connectTimer);
connectTimer = null;
}
isConnected = false;
isConnecting = false;
// 仅在非支付宝小程序环境下重置socketTask
if (!isAlipayMiniProgram) {
socketTask = null;
}
// 重置支付宝小程序的全局事件标记
globalEventHandlersRegistered = false;
}
// 监听消息
function onMessage(callback) {
return eventBus.on('message', callback);
}
// 监听连接状态
function onConnected(callback) {
return eventBus.on('connected', callback);
}
// 监听断开状态
function onClosed(callback) {
return eventBus.on('closed', callback);
}
// 监听错误
function onError(callback) {
return eventBus.on('error', callback);
}
// 提供一个自动管理生命周期的组件选项
function createSocketMixin() {
return {
data() {
return {
socketUnsubscribes: []
};
},
beforeDestroy() {
// 自动取消所有订阅
this.socketUnsubscribes.forEach(unsubscribe => unsubscribe());
this.socketUnsubscribes = [];
},
methods: {
// 订阅消息并自动管理生命周期
socketOnMessage(callback) {
const unsubscribe = onMessage(callback);
this.socketUnsubscribes.push(unsubscribe);
return unsubscribe;
},
socketOnConnected(callback) {
const unsubscribe = onConnected(callback);
this.socketUnsubscribes.push(unsubscribe);
return unsubscribe;
},
socketOnClosed(callback) {
const unsubscribe = onClosed(callback);
this.socketUnsubscribes.push(unsubscribe);
return unsubscribe;
},
socketOnError(callback) {
const unsubscribe = onError(callback);
this.socketUnsubscribes.push(unsubscribe);
return unsubscribe;
}
}
};
}
// 导出状态检查函数
function isSocketConnected() {
return isConnected;
}
// 导出连接状态
function getConnectionStatus() {
return {
isConnected,
isConnecting,
lastUrl,
reconnectCount,
platform: isAlipayMiniProgram ? 'alipay' : 'other'
};
}
export default {
connectSocket,
sendMessage,
closeSocket,
onMessage,
onConnected,
onClosed,
onError,
createSocketMixin,
isSocketConnected,
getConnectionStatus
};