| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- import TIM from 'tim-wx-sdk';
- class TIMManager {
- constructor() {
- this.tim = null;
- this.isLogin = false;
- this.userId = null;
- this.messageCallbacks = [];
- }
- /**
- * 初始化 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.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 '[未知消息]';
- }
- };
-
- const syncData = {
- messageId: timMessage.ID,
- fromUserId: timMessage.from,
- toUserId: timMessage.to,
- 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;
- }
-
- // 调用后端同步接口
- const res = await uni.request({
- url: 'http://localhost:8083/api/chat/syncTIMMessage',
- method: 'POST',
- data: syncData,
- header: {
- 'Content-Type': 'application/json'
- }
- });
-
- if (res[1].data.code === 200) {
- console.log('✅ 接收消息已同步到MySQL:', timMessage.ID);
- }
- } 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);
- }
- }
- }
- // 导出单例
- const timManager = new TIMManager();
- export default timManager;
|