// utils/chat-core.js import GoEasy from '../static/lib/goeasy-2.13.24.esm.min'; import { jianquanxian } from './imAuth/jianquanxian'; import { parseOrderCardText } from './group-chat.js'; import { persistGroupMeta, getFreshImUser } from './im-user.js'; let _globalPrivateHandler = null; let _globalGroupHandler = null; let _globalConvHandler = null; function initGlobalMessageSystem(app) { app.updateUnreadCount = (count) => updateUnreadCount(app, count); app.disconnectGoEasy = () => disconnectGoEasy(app); app.ensureConnection = () => ensureConnection(app); app.connectWithIdentity = (identityType, userId, isAutoRestore) => connectWithIdentity(app, identityType, userId, isAutoRestore); app.connectForCurrentRole = () => connectForCurrentRole(app); app.switchRoleAndReconnect = (newRole) => switchRoleAndReconnect(app, newRole); app.toggleDoNotDisturb = (enabled) => toggleDoNotDisturb(app, enabled); app.toggleNotificationMute = (enabled) => toggleNotificationMute(app, enabled); app.closeNotification = () => closeNotification(app); app.loadConversations = () => loadConversations(app); app.updateCurrentPageState = () => updateCurrentPageState(app); app.getSavedConnection = () => getSavedConnection(app); app.clearSavedConnection = () => clearSavedConnection(app); app.saveConnectionState = () => saveConnectionState(app); app.handleNewMessage = (message) => handleNewMessage(app, message); loadUserSettings(app); initNetworkListener(app); initAppStateListener(app); bindGoEasyLifecycle(app); setupEventSystem(app); // 监听器在连接成功 / onShow 时注册,不在 IM 未就绪时空绑 checkAndRestoreConnection(app); } function bindGoEasyLifecycle(app) { if (!wx.goEasy || app._goEasyLifecycleBound) return; app._goEasyLifecycleBound = true; try { wx.goEasy.on('connected', () => { app.globalData.goEasyConnection.status = 'connected'; setupMessageListeners(app); loadConversations(app); }); wx.goEasy.on('disconnected', () => { app.globalData.goEasyConnection.status = 'disconnected'; }); } catch (e) { console.warn('绑定 GoEasy 生命周期失败:', e); } } function getCurrentGoEasyUserId() { return (wx.goEasy && wx.goEasy.im && wx.goEasy.im.userId) || null; } function loadUserSettings(app) { try { const messageSettings = wx.getStorageSync(app.globalData.messageManager.cacheKeys.messageSettings); if (messageSettings) { app.globalData.messageManager = { ...app.globalData.messageManager, ...messageSettings }; } const connectionSettings = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection); if (connectionSettings) { app.globalData.goEasyConnection = { ...app.globalData.goEasyConnection, ...connectionSettings }; } const savedUnread = wx.getStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal); if (savedUnread !== '') { app.globalData.messageManager.unreadTotal = savedUnread; updateTabBarBadge(app, savedUnread); } const savedMessages = wx.getStorageSync(app.globalData.messageManager.cacheKeys.latestMessages); if (savedMessages) { app.globalData.messageManager.latestMessages = savedMessages; } const notificationMuted = wx.getStorageSync(app.globalData.messageManager.cacheKeys.notificationMuted); if (notificationMuted !== '') { app.globalData.messageManager.notificationMuted = notificationMuted; } } catch (error) { console.warn('加载用户设置失败:', error); } } function initNetworkListener(app) { wx.onNetworkStatusChange((res) => { if (res.isConnected) { ensureConnection(app); } else { app.globalData.goEasyConnection.status = 'disconnected'; app.emitEvent('connectionChanged', { status: 'disconnected', reason: 'network' }); } }); } function initAppStateListener(app) { wx.onAppShow(() => { updateCurrentPageState(app); ensureConnection(app); }); wx.onAppHide(() => { pauseHeartbeat(app); saveConnectionState(app); }); } function checkAndRestoreConnection(app) { try { const savedConnection = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection); if (savedConnection && savedConnection.userId && savedConnection.identityType) { const now = Date.now(); const lastTime = savedConnection.lastConnectTime || 0; const hoursDiff = (now - lastTime) / (1000 * 60 * 60); const validityHours = app.globalData.goEasyConnection.config.cacheValidityHours || 12; if (hoursDiff > validityHours) { clearSavedConnection(app); } } ensureConnection(app); } catch (error) { console.warn('检查保存的连接信息失败:', error); } } function setupEventSystem(app) { if (!app.globalData.eventListeners) { app.globalData.eventListeners = {}; } } function isImConnected() { if (!wx.goEasy?.getConnectionStatus) return false; const s = wx.goEasy.getConnectionStatus(); return s === 'connected' || s === 'reconnected'; } // 进入小程序即尝试连接并监听(不依赖历史缓存) function ensureConnection(app) { if (!wx.goEasy?.connect) return; if (app.globalData.chatEnabled === false) return; if (isImConnected()) { setupMessageListeners(app); loadConversations(app); return; } // 连接不存在时尝试恢复 const saved = getSavedConnection(app); if (saved && saved.userId && saved.identityType) { connectWithIdentity(app, saved.identityType, saved.userId, true); return; } const uid = wx.getStorageSync('uid'); if (uid) { connectForCurrentRole(app); } } function getSavedConnection(app) { try { const saved = wx.getStorageSync(app.globalData.goEasyConnection.cacheKeys.savedConnection); if (!saved) return null; if (typeof saved === 'string') return JSON.parse(saved); return saved; } catch (error) { console.error('获取保存的连接信息失败:', error); return null; } } function saveConnectionState(app) { try { const { goEasyConnection } = app.globalData; const connectionState = { userId: goEasyConnection.userId, identityType: goEasyConnection.identityType, lastConnectTime: Date.now(), autoReconnect: goEasyConnection.autoReconnect, status: goEasyConnection.status, }; wx.setStorageSync(goEasyConnection.cacheKeys.savedConnection, JSON.stringify(connectionState)); } catch (error) { console.warn('保存连接状态失败:', error); } } function clearSavedConnection(app) { const { cacheKeys } = app.globalData.goEasyConnection; wx.removeStorageSync(cacheKeys.savedConnection); wx.removeStorageSync(cacheKeys.userId); wx.removeStorageSync(cacheKeys.identityType); app.globalData.goEasyConnection.userId = ''; app.globalData.goEasyConnection.identityType = ''; app.globalData.goEasyConnection.lastConnectTime = 0; } function disconnectGoEasy(app) { return new Promise((resolve) => { app.globalData.goEasyConnection.status = 'disconnected'; app.globalData.goEasyConnection.autoReconnect = false; stopHeartbeat(app); removeGlobalMessageListeners(); if (wx.goEasy && wx.goEasy.disconnect) { wx.goEasy.disconnect({ onSuccess: () => { clearSavedConnection(app); app.globalData.messageManager.unreadTotal = 0; updateTabBarBadge(app, 0); app.emitEvent('connectionChanged', { status: 'disconnected', manual: true }); resolve(); }, onFailed: () => { clearSavedConnection(app); app.globalData.messageManager.unreadTotal = 0; updateTabBarBadge(app, 0); resolve(); } }); } else { clearSavedConnection(app); app.globalData.messageManager.unreadTotal = 0; updateTabBarBadge(app, 0); resolve(); } }); } async function connectWithIdentity(app, identityType, userId, isAutoRestore = false) { if (!wx.goEasy?.connect) { return Promise.reject(new Error('聊天服务未就绪')); } // 冷启动/后台恢复:先连上 IM 才能收消息;完整鉴权留给消息页/发消息 if (!isAutoRestore) { const quanxian = await jianquanxian(app); if (!quanxian.allowed) { return Promise.reject(new Error(quanxian.reason || '无聊天权限')); } } else { const uid = wx.getStorageSync('uid'); if (!uid) { return Promise.reject(new Error('未登录')); } } app.globalData.goEasyConnection.autoReconnect = true; app.globalData.goEasyConnection.status = 'connecting'; app.globalData.goEasyConnection.userId = userId; app.globalData.goEasyConnection.identityType = identityType; if (!isAutoRestore) app.globalData.goEasyConnection.reconnectAttempts = 0; saveConnectionState(app); const uid = wx.getStorageSync('uid'); const role = app.globalData.currentRole || identityType || 'normal'; const freshUser = getFreshImUser(app, role, uid); const currentUser = app.globalData.currentUser || { id: uid, name: freshUser.name, avatar: freshUser.avatar, }; return new Promise((resolve, reject) => { wx.goEasy.connect({ id: userId, data: { name: freshUser.name || currentUser.name, avatar: freshUser.avatar || currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), identityType, }, onSuccess: () => { app.globalData.goEasyConnection.status = 'connected'; app.globalData.goEasyConnection.lastConnectTime = Date.now(); app.globalData.goEasyConnection.reconnectAttempts = 0; saveConnectionState(app); startHeartbeat(app); setupMessageListeners(app); loadConversations(app); // 连接成功立刻拉未读数 app.emitEvent('connectionChanged', { status: 'connected', identityType, userId }); if (!isAutoRestore) { wx.showToast({ title: '连接成功', icon: 'success', duration: 2000 }); } resolve(); }, onFailed: (error) => { console.error('GoEasy连接失败:', error); if (error.code === 900) { app.globalData.goEasyConnection.autoReconnect = false; app.globalData.goEasyConnection.status = 'disconnected'; clearSavedConnection(app); stopHeartbeat(app); wx.showModal({ title: '消息功能暂不可用', content: '当前使用人数爆满,消息功能暂时无法使用。请联系管理员升级套餐后重试。', showCancel: false, confirmText: '我知道了' }); app.emitEvent('connectionChanged', { status: 'disconnected', error, reason: 'overcapacity' }); reject(error); return; } app.globalData.goEasyConnection.status = 'disconnected'; app.emitEvent('connectionChanged', { status: 'disconnected', error, isAutoRestore }); const { autoReconnect, reconnectAttempts, maxReconnectAttempts } = app.globalData.goEasyConnection; if (autoReconnect && reconnectAttempts < maxReconnectAttempts) { app.globalData.goEasyConnection.reconnectAttempts++; setTimeout(() => { if (app.globalData.goEasyConnection.autoReconnect) { connectWithIdentity(app, identityType, userId, true); } }, app.globalData.goEasyConnection.config.reconnectDelay); reject(error); } else { reject(error); } }, }); }); } function connectForCurrentRole(app) { const uid = wx.getStorageSync('uid'); if (!uid) return; const role = app.globalData.currentRole || 'normal'; const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' , kaoheguan: 'Kh'}; const prefix = prefixMap[role] || 'Boss'; const targetUserId = prefix + uid; const currentUserId = getCurrentGoEasyUserId(); if (currentUserId === targetUserId) { loadConversations(app); setupMessageListeners(app); return; } if (currentUserId) { wx.goEasy.disconnect({ onSuccess: () => {}, onFailed: () => {} }); } connectWithIdentity(app, role, targetUserId, true); } async function switchRoleAndReconnect(app, newRole) { try { await disconnectGoEasy(app); app.globalData.currentRole = newRole; wx.setStorageSync('currentRole', newRole); const uid = wx.getStorageSync('uid'); const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' , kaoheguan: 'Kh'}; const prefix = prefixMap[newRole] || 'Boss'; const targetUserId = prefix + uid; await connectWithIdentity(app, newRole, targetUserId, false); return true; } catch (error) { wx.showToast({ title: '聊天功能不可用,可能是人数爆满或未充值会员', icon: 'none' }); throw error; } } function startHeartbeat(app) { const { goEasyConnection } = app.globalData; stopHeartbeat(app); goEasyConnection.heartbeatInterval = setInterval(() => { if (goEasyConnection.status === 'connected' && wx.goEasy?.im) { wx.goEasy.im.ping({ onSuccess: () => {}, onFailed: (error) => { console.error('心跳失败:', error); goEasyConnection.status = 'reconnecting'; attemptReconnect(app); }, }); } }, goEasyConnection.config.heartbeatInterval); } function stopHeartbeat(app) { if (app.globalData.goEasyConnection.heartbeatInterval) { clearInterval(app.globalData.goEasyConnection.heartbeatInterval); app.globalData.goEasyConnection.heartbeatInterval = null; } } function pauseHeartbeat(app) { stopHeartbeat(app); } function attemptReconnect(app) { const { goEasyConnection } = app.globalData; if (!goEasyConnection.autoReconnect) return; if (goEasyConnection.userId && goEasyConnection.identityType) { connectWithIdentity(app, goEasyConnection.identityType, goEasyConnection.userId, true); } } /* ---------- 消息监听器 ---------- */ function removeGlobalMessageListeners() { if (_globalPrivateHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _globalPrivateHandler); _globalPrivateHandler = null; } if (_globalGroupHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _globalGroupHandler); _globalGroupHandler = null; } if (_globalConvHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _globalConvHandler); _globalConvHandler = null; } } function setupMessageListeners(app) { if (!wx.goEasy?.im || typeof wx.goEasy.im.on !== 'function') { return; } removeGlobalMessageListeners(); _globalPrivateHandler = (message) => { app.handleNewMessage(message); }; _globalGroupHandler = (message) => { app.handleNewMessage(message); }; _globalConvHandler = (conversations) => { const unreadTotal = conversations.unreadTotal || 0; app.globalData.messageManager.unreadTotal = unreadTotal; updateTabBarBadge(app, unreadTotal); wx.setStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal, unreadTotal); if (typeof app.emitEvent === 'function') { app.emitEvent('conversationsUpdated', conversations); } }; wx.goEasy.im.on(wx.GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, _globalPrivateHandler); wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, _globalGroupHandler); wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, _globalConvHandler); } function handleNewMessage(app, message) { const messageId = message.messageId || message.id; const fingerprint = `${message.senderId}_${message.timestamp}_${message.type}`; const msgKey = messageId || fingerprint; if (!app._processedMessages) app._processedMessages = new Set(); if (app._processedMessages.has(msgKey)) return; app._processedMessages.add(msgKey); if (app._processedMessages.size > 200) { app._processedMessages.delete(app._processedMessages.values().next().value); } const now = Date.now(); const msgTime = message.timestamp || now; if (now - msgTime > 60000) { cacheMessage(app, message); return; } cacheMessage(app, message); if (shouldShowNotification(app)) { let notificationType = 'private'; let extraData = {}; if (message.groupId) { notificationType = 'group'; extraData = { groupId: message.groupId, orderId: message.orderId || '', groupName: '', groupAvatar: '', isCross: message.isCross || 0 }; const groupInfo = app.globalData.groupInfoMap?.[message.groupId]; if (groupInfo) { extraData.groupName = groupInfo.name || ''; extraData.groupAvatar = groupInfo.avatar || ''; } } else if (message.teamId) { notificationType = 'cs'; extraData = { teamId: message.teamId }; } showNotificationDirect(app, { senderId: message.senderId, senderName: message.senderData?.name || '用户', avatar: message.senderData?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang), content: formatMessageForNotification(message), message, notificationType, ...extraData }); } } function shouldShowNotification(app) { if (app.globalData.messageManager.notificationMuted) return false; if (isDoNotDisturbTime(app)) return false; if (app.globalData.pageState.isInChatPage && app.globalData.pageState.currentChatId) { return false; } return true; } function isDoNotDisturbTime(app) { const { doNotDisturb, doNotDisturbStart, doNotDisturbEnd } = app.globalData.messageManager; if (!doNotDisturb) return false; const now = new Date(); const currentTime = now.getHours() * 60 + now.getMinutes(); const [sH, sM] = doNotDisturbStart.split(':').map(Number); const [eH, eM] = doNotDisturbEnd.split(':').map(Number); const startTime = sH * 60 + sM; const endTime = eH * 60 + eM; if (startTime < endTime) { return currentTime >= startTime && currentTime < endTime; } else { return currentTime >= startTime || currentTime < endTime; } } function isMessageFromCurrentChat(app, message) { const pageState = app.globalData.pageState || {}; if (!pageState.isInChatPage || !pageState.currentChatId) return false; if (message.groupId) { return message.groupId === pageState.currentChatId; } const myId = app.globalData.goEasyConnection?.userId || getCurrentGoEasyUserId(); if (!myId) return false; const peerId = message.senderId === myId ? message.receiverId : message.senderId; return peerId === pageState.currentChatId || message.senderId === pageState.currentChatId; } function showNotificationDirect(app, data) { if (app.globalData.globalNotification?.show) { try { app.globalData.globalNotification.show(data); return; } catch (error) { console.warn('全局通知显示失败:', error); } } app.emitEvent('showNotification', data); } async function updateUnreadCount(app, customCount) { let unreadTotal = customCount; if (unreadTotal === undefined) { try { const result = await getUnreadCount(app); unreadTotal = result.unreadTotal || 0; } catch (error) { console.error('获取未读数失败:', error); return; } } app.globalData.messageManager.unreadTotal = unreadTotal; updateTabBarBadge(app, unreadTotal); wx.setStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal, unreadTotal); app.emitEvent('unreadCountChanged', { unreadTotal }); } function getUnreadCount(app) { return new Promise((resolve, reject) => { if (!getCurrentGoEasyUserId()) { resolve({ unreadTotal: 0 }); return; } wx.goEasy.im.latestConversations({ onSuccess: (result) => resolve({ unreadTotal: result.unreadTotal || 0, conversations: result.content?.conversations || [], }), onFailed: reject, }); }); } function formatMessageForNotification(message) { switch (message.type) { case 'text': return message.payload.text || '[文本]'; case 'image': return '[图片]'; case 'audio': return '[语音]'; case 'video': return '[视频]'; case 'file': return '[文件]'; case 'order': return '[订单]'; default: return '[新消息]'; } } function cacheMessage(app, message) { if (message.groupId && message.type === 'text' && message.payload?.text) { const card = parseOrderCardText(message.payload.text); if (card && card.zhuangtai != null) { if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; const meta = { ...(app.globalData.groupInfoMap[message.groupId] || {}), orderId: card.orderId, orderZhuangtai: card.zhuangtai, orderDesc: card.jieshao, }; app.globalData.groupInfoMap[message.groupId] = meta; persistGroupMeta(app, message.groupId, meta); } } const { latestMessages } = app.globalData.messageManager; latestMessages.unshift({ id: message.messageId, type: message.type, senderId: message.senderId, senderName: message.senderData?.name, content: formatMessageForNotification(message), timestamp: message.timestamp || Date.now(), conversationId: message.conversationId, }); if (latestMessages.length > 50) latestMessages.length = 50; wx.setStorageSync(app.globalData.messageManager.cacheKeys.latestMessages, latestMessages); } function loadConversations(app) { if (!getCurrentGoEasyUserId() || !wx.goEasy?.im?.latestConversations) return; wx.goEasy.im.latestConversations({ onSuccess: (result) => { if (result.unreadTotal !== undefined) { updateUnreadCount(app, result.unreadTotal); } app.emitEvent('conversationsUpdated', result); const conversations = result?.content?.conversations || result?.conversations || []; if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {}; conversations.forEach(c => { if (c.type === 'group' && c.groupId) { app.globalData.groupInfoMap[c.groupId] = c.data || {}; } }); _ensureGroupSubscriptions(app, conversations); }, onFailed: (error) => console.error('加载会话列表失败:', error), }); } function _ensureGroupSubscriptions(app, conversations) { const groupIds = conversations .filter(c => c.type === 'group' && c.groupId) .map(c => c.groupId); if (groupIds.length === 0) return; if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') { console.error('subscribeGroup 不可用'); return; } wx.goEasy.im.subscribeGroup({ groupIds: groupIds, onSuccess: () => {}, onFailed: (error) => console.error('全局订阅群组失败:', error), }); } function updateCurrentPageState(app) { try { const pages = getCurrentPages(); if (pages.length > 0) { const currentPage = pages[pages.length - 1]; const chatPages = ['pages/liaotian/liaotian', 'pages/qunliaotian/qunliaotian', 'pages/kefuliaotian/kefuliaotian']; app.globalData.pageState.currentPage = currentPage.route; app.globalData.pageState.isInChatPage = chatPages.indexOf(currentPage.route) >= 0; app.globalData.pageState.lastPageUpdate = Date.now(); } } catch (error) { console.warn('更新页面状态失败:', error); } } function saveUserSettings(app) { try { wx.setStorageSync(app.globalData.messageManager.cacheKeys.messageSettings, { soundEnabled: app.globalData.messageManager.soundEnabled, vibrationEnabled: app.globalData.messageManager.vibrationEnabled, doNotDisturb: app.globalData.messageManager.doNotDisturb, doNotDisturbStart: app.globalData.messageManager.doNotDisturbStart, doNotDisturbEnd: app.globalData.messageManager.doNotDisturbEnd, notificationMuted: app.globalData.messageManager.notificationMuted, notificationStyle: app.globalData.messageManager.notificationStyle, }); } catch (error) { console.warn('保存用户设置失败:', error); } } function toggleDoNotDisturb(app, enabled) { app.globalData.messageManager.doNotDisturb = enabled; saveUserSettings(app); wx.showToast({ title: `免打扰${enabled ? '开启' : '关闭'}`, icon: 'success', duration: 2000 }); } function toggleNotificationMute(app, enabled) { app.globalData.messageManager.notificationMuted = enabled; wx.setStorageSync(app.globalData.messageManager.cacheKeys.notificationMuted, enabled); saveUserSettings(app); wx.showToast({ title: `通知${enabled ? '静音' : '开启'}`, icon: 'success', duration: 2000 }); } function closeNotification(app) { app.emitEvent('hideNotification'); } function updateTabBarBadge(app, unreadCount) { const { messageManager } = app.globalData; if (!messageManager.showTabBarBadge) return; let badgeText = ''; if (unreadCount > 0) { badgeText = unreadCount > 99 ? '99+' : unreadCount.toString(); } messageManager.tabBarBadgeText = badgeText; app.emitEvent('tabBarBadgeChanged', { index: messageManager.tabBarIndex, badgeText, showRedDot: unreadCount > 0, forceUpdate: true, }); updateTabBarDirectly(app, badgeText); saveTabBarBadgeToStorage(app, badgeText); } function updateTabBarDirectly(app, badgeText) { try { const pages = getCurrentPages(); if (pages.length > 0) { const tabBar = pages[pages.length - 1].selectComponent('#custom-tab-bar'); if (tabBar && tabBar.setData) tabBar.setData({ badgeText }); } } catch (error) { console.warn('直接更新TabBar失败:', error); } } function saveTabBarBadgeToStorage(app, badgeText) { try { wx.setStorageSync('tabBarMessageBadge', badgeText); } catch (error) { console.warn('保存TabBar徽章失败:', error); } } module.exports = { initGlobalMessageSystem };