新增聊天系统
This commit is contained in:
547
utils/socket.js
Normal file
547
utils/socket.js
Normal file
@@ -0,0 +1,547 @@
|
||||
// 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
|
||||
};
|
||||
Reference in New Issue
Block a user