import TIM from 'tim-wx-sdk'; class TIMManager { constructor() { this.tim = null; this.isLogin = false; this.userId = null; this.messageCallbacks = []; this.messageReadCallbacks = []; // 🔥 添加已读回执回调列表 } /** * 初始化 TIM SDK */ init(sdkAppID) { // 创建 SDK 实例 this.tim = TIM.create({ SDKAppID: sdkAppID }); // 设置日志级别(开发时设为 0,生产设为 1) this.tim.setLogLevel(0); // 注册监听事件 this.registerEvents(); console.log('✅ TIM SDK 初始化完成'); } /** * 注册事件监听 */ registerEvents() { // SDK 进入 ready 状态 this.tim.on(TIM.EVENT.SDK_READY, this.onSdkReady.bind(this)); // SDK 未 ready this.tim.on(TIM.EVENT.SDK_NOT_READY, this.onSdkNotReady.bind(this)); // 收到新消息 this.tim.on(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived.bind(this)); // 会话列表更新 this.tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated.bind(this)); // 🔥 消息已读回执(对方已读我发送的消息) this.tim.on(TIM.EVENT.MESSAGE_READ_BY_PEER, this.onMessageReadByPeer.bind(this)); // 被踢下线 this.tim.on(TIM.EVENT.KICKED_OUT, this.onKickedOut.bind(this)); // 网络状态变化 this.tim.on(TIM.EVENT.NET_STATE_CHANGE, this.onNetStateChange.bind(this)); } /** * SDK Ready */ onSdkReady() { console.log('✅ TIM SDK Ready'); this.isLogin = true; } /** * SDK Not Ready */ onSdkNotReady() { console.log('⚠️ TIM SDK Not Ready'); this.isLogin = false; } /** * 收到新消息 */ onMessageReceived(event) { const messageList = event.data; console.log('📥 收到新消息:', messageList.length, '条'); // 同步接收到的消息到MySQL(双重保障) messageList.forEach(message => { this.syncReceivedMessageToMySQL(message); }); // 触发回调 this.messageCallbacks.forEach(callback => { callback(messageList); }); } /** * 同步接收到的消息到MySQL */ async syncReceivedMessageToMySQL(timMessage) { try { // 获取消息类型 const getMessageType = (msg) => { const typeMap = { 'TIMTextElem': 1, 'TIMImageElem': 2, 'TIMSoundElem': 3, 'TIMVideoFileElem': 4, 'TIMFileElem': 5 }; // 处理自定义消息(我们的语音消息) if (msg.type === 'TIMCustomElem' && msg.payload) { try { const customData = JSON.parse(msg.payload.data); if (customData.type === 'voice') { return 3; // 语音消息 } } catch (e) { console.error('解析自定义消息类型失败:', e); } } return typeMap[msg.type] || 1; }; // 获取消息内容 const getMessageContent = (msg) => { switch (msg.type) { case 'TIMTextElem': return msg.payload.text || ''; case 'TIMImageElem': return '[图片]'; case 'TIMSoundElem': return '[语音]'; case 'TIMVideoFileElem': return '[视频]'; case 'TIMFileElem': return '[文件]'; case 'TIMCustomElem': // 处理自定义消息 try { const customData = JSON.parse(msg.payload.data); if (customData.type === 'voice') { return '[语音]'; } } catch (e) { console.error('解析自定义消息内容失败:', e); } return '[自定义消息]'; default: return '[未知消息]'; } }; // 使用V2接口,支持红娘消息(TIM用户ID可能是 m_xxx 格式) const syncData = { messageId: timMessage.ID, fromTimUserId: String(timMessage.from), // 保持字符串格式,支持 m_xxx toTimUserId: String(timMessage.to), // 保持字符串格式,支持 m_xxx messageType: getMessageType(timMessage), content: getMessageContent(timMessage), sendTime: timMessage.time }; // 如果是语音消息,添加语音信息 if (timMessage.type === 'TIMSoundElem' && timMessage.payload) { syncData.mediaUrl = timMessage.payload.url || timMessage.payload.remoteAudioUrl; syncData.duration = timMessage.payload.second || 0; syncData.mediaSize = timMessage.payload.size || 0; } // 如果是自定义消息(我们的语音消息) if (timMessage.type === 'TIMCustomElem' && timMessage.payload) { try { const customData = JSON.parse(timMessage.payload.data); if (customData.type === 'voice') { syncData.mediaUrl = customData.url; syncData.duration = customData.duration; syncData.mediaSize = customData.size || 0; } } catch (e) { console.error('解析自定义消息失败:', e); } } // 如果是图片消息,添加图片信息 if (timMessage.type === 'TIMImageElem' && timMessage.payload.imageInfoArray) { const imageInfo = timMessage.payload.imageInfoArray[0]; syncData.mediaUrl = imageInfo.imageUrl; syncData.thumbnailUrl = imageInfo.imageUrl; } // 调用后端同步接口(V2版本,支持红娘消息) const res = await uni.request({ url: 'http://localhost:8083/api/chat/syncTIMMessageV2', method: 'POST', data: syncData, header: { 'Content-Type': 'application/json' } }); if (res[1].data.code === 200) { const msgType = res[1].data.type === 'matchmaker' ? '红娘消息' : '用户消息'; console.log(`✅ 接收消息已同步到MySQL (${msgType}):`, timMessage.ID); } } catch (error) { console.error('❌ 同步接收消息失败:', error); // 同步失败不影响主流程 } } /** * 消息已读回执(对方已读我发送的消息) */ onMessageReadByPeer(event) { console.log('=== 📖 [全局] 收到已读回执事件 MESSAGE_READ_BY_PEER ==='); console.log(' - 触发时间:', new Date().toLocaleString()); console.log(' - 事件数据:', JSON.stringify(event.data)); // 触发所有已读回执回调 this.messageReadCallbacks.forEach(callback => { try { callback(event); } catch (error) { console.error('❌ 已读回执回调执行失败:', error); } }); } /** * 会话列表更新 */ onConversationListUpdated(event) { console.log('📋 会话列表更新:', event.data.length, '个'); // 计算总未读数 const conversationList = event.data; let totalUnread = 0; conversationList.forEach(conversation => { totalUnread += conversation.unreadCount || 0; }); console.log('📊 总未读消息数:', totalUnread); // 更新到全局状态(Vuex)- 多种方式确保更新成功 try { // 方式1: 使用 getApp() 获取全局实例 const app = getApp(); if (app && app.$store) { app.$store.dispatch('updateTotalUnread', totalUnread); console.log('✅ 已更新全局未读数到 Vuex (getApp):', totalUnread); } else { console.warn('⚠️ getApp() 未获取到 $store'); } // 方式2: 直接导入 store(备用方案) try { const store = require('../store/index.js').default; if (store) { store.dispatch('updateTotalUnread', totalUnread); console.log('✅ 已更新全局未读数到 Vuex (require):', totalUnread); } } catch (e) { console.warn('⚠️ require store 失败:', e.message); } // 方式3: 触发自定义事件,通知所有页面 uni.$emit('conversationUnreadUpdate', totalUnread); console.log('✅ 已触发 conversationUnreadUpdate 事件:', totalUnread); } catch (error) { console.error('❌ 更新全局未读数失败:', error); } } /** * 被踢下线 */ onKickedOut(event) { console.log('❌ 被踢下线:', event.data.type); uni.showModal({ title: '下线通知', content: '您的账号在其他设备登录', showCancel: false }); } /** * 网络状态变化 */ onNetStateChange(event) { console.log('🌐 网络状态:', event.data.state); } /** * 登录 */ async login(userID, userSig) { try { console.log('📱 开始登录 TIM, userID:', userID); const res = await this.tim.login({ userID: String(userID), userSig: userSig }); console.log('✅ TIM 登录成功'); this.userId = userID; return res; } catch (error) { console.error('❌ TIM 登录失败:', error); throw error; } } /** * 登出 */ async logout() { try { await this.tim.logout(); console.log('✅ TIM 登出成功'); this.isLogin = false; this.userId = null; } catch (error) { console.error('❌ TIM 登出失败:', error); } } /** * 发送文本消息 */ async sendTextMessage(toUserId, text) { try { // 验证参数 if (!toUserId || toUserId === 'undefined' || toUserId === 'null') { console.error('❌ 接收者ID无效:', toUserId); throw new Error('接收者ID无效: ' + toUserId); } if (!this.userId || this.userId === 'undefined' || this.userId === 'null') { console.error('❌ 发送者ID无效:', this.userId); throw new Error('发送者ID未登录或无效'); } const toUserIdStr = String(toUserId); console.log('📤 准备发送消息:'); console.log(' - 发送者ID:', this.userId, '(类型:', typeof this.userId, ')'); console.log(' - 接收者ID:', toUserIdStr, '(类型:', typeof toUserIdStr, ')'); console.log(' - 消息内容:', text); // 创建文本消息 const message = this.tim.createTextMessage({ to: toUserIdStr, conversationType: TIM.TYPES.CONV_C2C, // 单聊 payload: { text: text } }); console.log('📤 消息已创建,准备发送...'); // 发送消息 const res = await this.tim.sendMessage(message); console.log('✅ 消息发送成功:', res.data.message); return res.data.message; } catch (error) { console.error('❌ 消息发送失败:', error); console.error(' - 错误详情:', error.message || error); console.error(' - 错误代码:', error.code || 'N/A'); throw error; } } /** * 发送图片消息 */ async sendImageMessage(toUserId, filePath) { try { const message = this.tim.createImageMessage({ to: String(toUserId), conversationType: TIM.TYPES.CONV_C2C, payload: { file: filePath }, onProgress: (percent) => { console.log('📊 上传进度:', percent); } }); const res = await this.tim.sendMessage(message); console.log('✅ 图片发送成功'); return res.data.message; } catch (error) { console.error('❌ 图片发送失败:', error); throw error; } } /** * 发送语音消息(通过自定义消息携带MinIO URL) * @param {String} toUserId 接收者ID * @param {String} voiceUrl MinIO语音文件URL * @param {Number} duration 语音时长(秒) * @param {Number} fileSize 文件大小(字节) */ async sendVoiceMessage(toUserId, voiceUrl, duration, fileSize) { try { console.log('🎤 准备发送语音消息:'); console.log(' - 接收者ID:', toUserId); console.log(' - 语音URL:', voiceUrl); console.log(' - 时长:', duration, '秒'); console.log(' - 文件大小:', fileSize, '字节'); // 使用自定义消息发送语音信息 const message = this.tim.createCustomMessage({ to: String(toUserId), conversationType: TIM.TYPES.CONV_C2C, payload: { data: JSON.stringify({ type: 'voice', url: voiceUrl, duration: duration, size: fileSize }), description: '[语音]', extension: voiceUrl } }); const res = await this.tim.sendMessage(message); console.log('✅ 语音发送成功'); return res.data.message; } catch (error) { console.error('❌ 语音发送失败:', error); throw error; } } /** * 获取会话列表 */ async getConversationList() { try { const res = await this.tim.getConversationList(); console.log('📋 会话列表:', res.data.conversationList.length, '个'); return res.data.conversationList; } catch (error) { console.error('❌ 获取会话列表失败:', error); throw error; } } /** * 获取聊天记录 */ async getMessageList(conversationID, count = 15) { try { const res = await this.tim.getMessageList({ conversationID: conversationID, count: count }); console.log('💬 聊天记录:', res.data.messageList.length, '条'); return res.data.messageList; } catch (error) { console.error('❌ 获取聊天记录失败:', error); throw error; } } /** * 将消息设为已读 */ async setMessageRead(conversationID) { try { await this.tim.setMessageRead({ conversationID }); console.log('✅ 消息已读'); } catch (error) { console.error('❌ 设置已读失败:', error); } } /** * 撤回消息 */ async revokeMessage(message) { try { await this.tim.revokeMessage(message); console.log('✅ 消息已撤回'); } catch (error) { console.error('❌ 撤回失败:', error); throw error; } } /** * 监听消息 */ onMessage(callback) { this.messageCallbacks.push(callback); } /** * 移除监听 */ offMessage(callback) { const index = this.messageCallbacks.indexOf(callback); if (index > -1) { this.messageCallbacks.splice(index, 1); } } /** * 监听已读回执 */ onMessageRead(callback) { this.messageReadCallbacks.push(callback); console.log('✅ 已注册已读回执回调,当前回调数:', this.messageReadCallbacks.length); } /** * 移除已读回执监听 */ offMessageRead(callback) { const index = this.messageReadCallbacks.indexOf(callback); if (index > -1) { this.messageReadCallbacks.splice(index, 1); console.log('✅ 已移除已读回执回调,剩余回调数:', this.messageReadCallbacks.length); } } /** * 获取 TIM 实例 */ getTim() { return this.tim; } /** * 获取当前登录的 IM 用户ID */ getCurrentUserId() { return this.userId ? String(this.userId) : null; } } // 导出单例 const timManager = new TIMManager(); export default timManager;