闲来无事,写一篇文章吧!

腾讯云即时通信 IM SDK 完整使用指南

前言

最近在新公司做了直播项目,以前接触过lviekit,所以做起来还算啥得心应手,因为以前公司的聊天服务,都是自己封装websocket去做的,新公司是直接去接腾讯IM,所以记录一下,接入的话,也比较简单

📋 文档概述

这是一份基于项目代码的腾讯云 IM SDK 完整使用指南。涵盖从环境搭建、API 详解到高级功能实现的所有内容。

🎯 适用场景

  • 🎬 直播应用: 直播间聊天、礼物系统、主播通话
  • 💬 即时通讯: 单聊、群聊、文件传输
  • 📞 音视频通话: 语音/视频通话信令管理
  • 🎮 游戏社交: 游戏内聊天、组队沟通
  • 🏢 企业办公: 内部沟通、会议协作

📑 完整目录

基础篇

  1. 环境要求与安装
  2. 项目配置详解
  3. 核心架构设计
  4. 快速上手指南

API 篇

  1. [TIMService 完整API](#TIMService 完整API "#5-timservice-%E5%AE%8C%E6%95%B4api")
  2. [IMStore 状态管理](#IMStore 状态管理 "#6-imstore-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86")
  3. 消息类型系统
  4. 事件系统详解

高级篇

  1. 错误处理机制
  2. 性能优化策略
  3. 最佳实践指南
  4. 故障排查手册

1. 环境要求与安装

1.1 系统环境要求

bash 复制代码
# Node.js 版本要求
Node.js >= 16.0.0

# 包管理器
npm >= 7.0.0 或 yarn >= 1.22.0 或 pnpm >= 6.0.0

# 浏览器支持
Chrome >= 60
Firefox >= 60  
Safari >= 12
Edge >= 79

1.2 腾讯云 IM 控制台配置

步骤 1: 创建应用
  1. 访问 腾讯云 IM 控制台
  2. 点击"创建新应用"
  3. 填写应用名称,选择数据中心
  4. 记录生成的 SDKAppID
步骤 2: 配置基础信息
javascript 复制代码
// 在控制台获取以下信息
const imConfig = {
    SDKAppID: "your_appId",     // 你的应用 ID
    SecretKey: 'your_secret', // 用于生成 UserSig
    AdminUserID: 'admin'      // 管理员账号
}
步骤 3: 域名白名单配置
bash 复制代码
# 开发环境
http://localhost:3000
http://localhost:8080

# 生产环境
https://yourdomain.com
https://www.yourdomain.com

1.3 依赖包安装

核心依赖
bash 复制代码
# 腾讯云 IM SDK
npm install @tencentcloud/chat

# 文件上传插件
npm install tim-upload-plugin

# 状态管理
npm install pinia

# 事件总线
npm install mitt
开发依赖
bash 复制代码
# TypeScript 支持
npm install -D typescript @types/node

# 构建工具
npm install -D vite @vitejs/plugin-vue

# 代码规范
npm install -D eslint prettier
UI 框架(可选)
bash 复制代码
# Varlet UI(项目中使用)
npm install @varlet/ui

# Element Plus
npm install element-plus

# Ant Design Vue
npm install ant-design-vue

2. 项目配置详解

2.1 目录结构规划

bash 复制代码
src/
├── config/
│   ├── tim.ts              # TIM SDK 配置
│   ├── constants.ts        # 常量定义
│   └── environment.ts      # 环境配置
├── utils/
│   ├── timService.ts       # TIM 服务封装
│   ├── request.ts          # HTTP 请求
│   ├── storage.ts          # 本地存储
│   └── validation.ts       # 数据验证
├── stores/
│   ├── im.ts              # IM 状态管理
│   ├── user.ts            # 用户状态
│   ├── conversation.ts    # 会话状态
│   └── message.ts         # 消息状态
├── types/
│   ├── tim.d.ts           # TIM 类型定义
│   ├── api.d.ts           # API 类型定义
│   └── common.d.ts        # 通用类型
├── components/
│   ├── Chat/              # 聊天组件
│   ├── Message/           # 消息组件
│   └── Common/            # 通用组件
└── api/
    ├── im.ts              # IM 相关 API
    ├── user.ts            # 用户 API
    └── upload.ts          # 文件上传 API

2.2 配置文件详解

2.2.1 TIM 基础配置
typescript 复制代码
// src/config/tim.ts
export interface TIMConfig {
    SDKAppID: number
    logLevel: number
    timeout: {
        login: number
        message: number
        invite: number
    }
    retry: {
        maxRetries: number
        retryDelay: number
    }
    features: {
        translation: boolean
        fileUpload: boolean
        signaling: boolean
    }
}

export const timConfig: TIMConfig = {
    // 基础配置
    SDKAppID: Number(import.meta.env.VITE_TIM_SDK_APP_ID) || 1400430140,
    
    // 日志级别
    // 0: 普通级别,日志量较少
    // 1: 详细级别,日志量较多,推荐开发调试时使用
    // 2: 告警级别,只有告警和错误日志
    // 3: 错误级别,只有错误日志
    // 4: 无日志级别,不输出任何日志
    logLevel: import.meta.env.DEV ? 1 : 3,
    
    // 超时配置
    timeout: {
        login: 10000,      // 登录超时(毫秒)
        message: 10000,    // 消息发送超时
        invite: 30000      // 信令邀请超时
    },
    
    // 重试配置
    retry: {
        maxRetries: 3,     // 最大重试次数
        retryDelay: 1000   // 重试延迟(毫秒)
    },
    
    // 功能开关
    features: {
        translation: true,  // 消息翻译功能
        fileUpload: true,   // 文件上传功能
        signaling: true     // 信令功能
    }
}

// 消息类型配置
export const messageTypes = {
    // 显示的消息类型
    DISPLAY_TYPES: [
        'TIMTextElem',      // 文本消息
        'TIMImageElem',     // 图片消息
        'TIMSoundElem',     // 语音消息
        'TIMVideoFileElem', // 视频消息
        'TIMFileElem',      // 文件消息
        'TIMCustomElem'     // 自定义消息
    ],
    
    // 自定义消息业务类型
    CUSTOM_BUSINESS_IDS: {
        GIFT: 'gift',                    // 礼物消息
        LOCATION: 'location',            // 位置消息
        VOICE_CALL: 'voice_call',        // 语音通话
        VIDEO_CALL: 'video_call',        // 视频通话
        RECHARGE: 'ask_recharge',        // 充值请求
        OFFICIAL: 'official_message',    // 官方消息
        DYNAMIC_EMOJI: 'dynamic_emoji'   // 动态表情
    }
}

// 群组配置
export const groupConfig = {
    // 群组类型
    TYPES: {
        WORK: 'Work',               // 好友工作群
        PUBLIC: 'Public',           // 陌生人社交群  
        MEETING: 'Meeting',         // 会议群
        AVCHATROOM: 'AVChatRoom'    // 直播群
    },
    
    // 加群方式
    JOIN_OPTIONS: {
        FREE: 'FreeAccess',         // 自由加入
        NEED_PERMISSION: 'NeedPermission', // 需要验证
        DISABLE_APPLY: 'DisableApply'      // 禁止加群
    },
    
    // 群成员角色
    MEMBER_ROLES: {
        OWNER: 'Owner',     // 群主
        ADMIN: 'Admin',     // 管理员
        MEMBER: 'Member'    // 普通成员
    }
}
2.2.2 环境配置
bash 复制代码
# .env.development
VITE_TIM_SDK_APP_ID=1400430140
VITE_TIM_LOG_LEVEL=1
VITE_API_BASE_URL=http://localhost:3000
VITE_UPLOAD_BASE_URL=http://localhost:3001

# .env.production
VITE_TIM_SDK_APP_ID=1400430140
VITE_TIM_LOG_LEVEL=3
VITE_API_BASE_URL=https://api.yourdomain.com
VITE_UPLOAD_BASE_URL=https://upload.yourdomain.com

# .env.test
VITE_TIM_SDK_APP_ID=1400430140
VITE_TIM_LOG_LEVEL=2
VITE_API_BASE_URL=https://test-api.yourdomain.com
2.2.3 TypeScript 类型定义
typescript 复制代码
// src/types/tim.d.ts

// 扩展全局 TIM 类型
declare module '@tencentcloud/chat' {
    namespace TIM {
        interface Message {
            ID: string
            type: string
            payload: any
            conversationID: string
            from: string
            to: string
            time: number
            status: 'unSend' | 'success' | 'fail'
            sendStatus?: 'pending' | 'success' | 'fail'
            businessStatus?: 'translated' | 'recalled'
            nick?: string
            avatar?: string
            nameCard?: string
        }
        
        interface Conversation {
            conversationID: string
            type: 'C2C' | 'GROUP'
            lastMessage: Message
            unreadCount: number
            isPinned: boolean
            userProfile?: UserProfile
            groupProfile?: GroupProfile
        }
        
        interface UserProfile {
            userID: string
            nick: string
            avatar: string
            selfSignature: string
            gender: 'TIM_GENDER_MALE' | 'TIM_GENDER_FEMALE' | 'TIM_GENDER_UNKNOWN'
            birthday: number
            location: string
            language: number
            level: number
            role: number
            allowType: 'TIM_FRIEND_ALLOW_ANY' | 'TIM_FRIEND_NEED_CONFIRM' | 'TIM_FRIEND_DENY_ANY'
        }
        
        interface GroupProfile {
            groupID: string
            name: string
            avatar: string
            type: string
            introduction: string
            notification: string
            ownerID: string
            createTime: number
            memberCount: number
            maxMemberCount: number
            joinOption: string
            muteAllMembers: boolean
        }
    }
}

// 业务类型定义
export interface CustomMessagePayload {
    businessID: string
    type?: number
    data?: any
    timestamp: number
}

export interface GiftMessageData extends CustomMessagePayload {
    businessID: 'gift'
    type: 3
    giftId: number
    giftName: string
    giftCount: number
    giftValue: number
}

export interface CallInviteData extends CustomMessagePayload {
    businessID: 'video_call' | 'voice_call'
    actionType: 1 | 2 | 3 | 4 | 5  // 邀请|取消|接受|拒绝|超时
    callType: 'voice' | 'video'
    roomId: string
    inviter: string
    invitee: string
}

export interface LocationMessageData extends CustomMessagePayload {
    businessID: 'location'
    latitude: number
    longitude: number
    address: string
    description?: string
}

// API 响应类型
export interface APIResponse<T = any> {
    code: number
    message: string
    data: T
}

export interface LoginResponse {
    userID: string
    userSig: string
    token: string
    expires: number
}

// Store 状态类型
export interface IMStoreState {
    // 连接状态
    isSDKReady: boolean
    isLogin: boolean
    connectionState: 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
    
    // 用户信息
    userInfo: {
        userID: string
        userSig: string
        profile?: TIM.UserProfile
    } | null
    
    // 会话相关
    conversationList: TIM.Conversation[]
    currentConversationID: string | null
    
    // 消息相关
    messageList: Record<string, TIM.Message[]>
    unreadCount: number
    
    // 群组相关
    memberCount: number
    groupOnlineMemberList: any[]
    anchorInRoom: boolean
    anchorUserID: string | null
    
    // 信令相关
    activeInvitation: any | null
    targetGroupId: string | null
}

3. 核心架构设计

3.1 整体架构图

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                     Vue 3 应用层                            │
├─────────────────────────────────────────────────────────────┤
│  Chat Components  │  Message Components  │  UI Components   │
├─────────────────────────────────────────────────────────────┤
│                   Pinia 状态管理层                           │
├─────────────────────────────────────────────────────────────┤
│    IMStore     │   UserStore    │  ConversationStore        │
├─────────────────────────────────────────────────────────────┤
│                   业务逻辑封装层                             │
├─────────────────────────────────────────────────────────────┤
│               TIMService (单例服务)                         │
├─────────────────────────────────────────────────────────────┤
│                   SDK 适配层                                │
├─────────────────────────────────────────────────────────────┤
│  TIM SDK  │  Upload Plugin  │  Event System  │  Utils      │
└─────────────────────────────────────────────────────────────┘

3.2 TIMService 单例设计

typescript 复制代码
// src/utils/timService.ts
import TIM from '@tencentcloud/chat'
import TIMUploadPlugin from 'tim-upload-plugin'
import { timConfig } from '@/config/tim'

class TIMService {
    private static instance: TIMService | null = null
    public tim: ReturnType<typeof TIM.create>
    private isInitialized: boolean = false
    
    private constructor() {
        this.initializeSDK()
    }
    
    /**
     * 获取单例实例
     */
    public static getInstance(): TIMService {
        if (!TIMService.instance) {
            TIMService.instance = new TIMService()
        }
        return TIMService.instance
    }
    
    /**
     * 初始化 SDK
     */
    private initializeSDK() {
        if (this.isInitialized) return
        
        try {
            // 创建 SDK 实例
            this.tim = TIM.create({
                SDKAppID: timConfig.SDKAppID
            })
            
            // 设置日志级别
            this.tim.setLogLevel(timConfig.logLevel)
            
            // 注册上传插件
            this.tim.registerPlugin({
                'tim-upload-plugin': TIMUploadPlugin
            })
            
            this.isInitialized = true
            console.log('✅ TIM SDK 初始化成功')
            
        } catch (error) {
            console.error('❌ TIM SDK 初始化失败:', error)
            throw error
        }
    }
    
    /**
     * 检查 SDK 是否已初始化
     */
    public isReady(): boolean {
        return this.isInitialized && !!this.tim
    }
    
    /**
     * 销毁 SDK 实例
     */
    public destroy() {
        if (this.tim) {
            this.tim.destroy()
            this.tim = null as any
        }
        this.isInitialized = false
        TIMService.instance = null
    }
}

// 导出单例实例
export default TIMService.getInstance()

3.3 状态管理架构

typescript 复制代码
// src/stores/im.ts
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import TIM from '@tencentcloud/chat'
import timService from '@/utils/timService'
import type { IMStoreState } from '@/types/tim'

export const useIMStore = defineStore('im', () => {
    // ===== 状态定义 =====
    
    // 连接状态
    const isSDKReady = ref(false)
    const isLogin = ref(false)
    const connectionState = ref<'connecting' | 'connected' | 'disconnected' | 'reconnecting'>('disconnected')
    
    // 用户信息
    const userInfo = ref<IMStoreState['userInfo']>(null)
    
    // 会话相关
    const conversationList = ref<TIM.Conversation[]>([])
    const currentConversationID = ref<string | null>(null)
    
    // 消息相关
    const messageList = ref<Record<string, TIM.Message[]>>({})
    const unreadCount = ref(0)
    
    // 群组相关
    const memberCount = ref(0)
    const groupOnlineMemberList = ref<any[]>([])
    const anchorInRoom = ref(false)
    const anchorUserID = ref<string | null>(null)
    
    // 信令相关
    const activeInvitation = ref<any | null>(null)
    const targetGroupId = ref<string | null>(null)
    
    // ===== 计算属性 =====
    
    // 当前会话消息
    const currentMessages = computed(() => {
        if (!currentConversationID.value) return []
        return messageList.value[currentConversationID.value] || []
    })
    
    // 总未读消息数
    const totalUnreadCount = computed(() => {
        return conversationList.value.reduce((total, conv) => {
            return total + conv.unreadCount
        }, 0)
    })
    
    // 置顶会话列表
    const pinnedConversations = computed(() => {
        return conversationList.value
            .filter(conv => conv.isPinned)
            .sort((a, b) => b.lastMessage.time - a.lastMessage.time)
    })
    
    // 普通会话列表
    const normalConversations = computed(() => {
        return conversationList.value
            .filter(conv => !conv.isPinned)
            .sort((a, b) => b.lastMessage.time - a.lastMessage.time)
    })
    
    // ===== 监听器 =====
    
    // 监听当前会话变化,自动标记已读
    watch([currentConversationID, isSDKReady], ([convID, sdkReady]) => {
        if (convID && sdkReady && isLogin.value) {
            markMessageAsRead(convID)
        }
    })
    
    // 监听连接状态变化
    watch(connectionState, (newState) => {
        console.log(`🔌 连接状态变化: ${newState}`)
        
        if (newState === 'connected') {
            // 连接成功后的处理
            updateUnreadCount()
            loadConversationList()
        } else if (newState === 'disconnected') {
            // 连接断开后的处理
            console.warn('⚠️ IM 连接已断开')
        }
    })
    
    // ===== 核心方法 =====
    
    /**
     * 初始化事件监听
     */
    function initializeEventListeners() {
        // SDK 就绪事件
        timService.on(TIM.EVENT.SDK_READY, handleSDKReady)
        
        // 消息接收事件
        timService.on(TIM.EVENT.MESSAGE_RECEIVED, handleMessageReceived)
        
        // 会话更新事件
        timService.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, handleConversationUpdate)
        
        // 网络状态变化
        timService.on(TIM.EVENT.NET_STATE_CHANGE, handleNetworkStateChange)
        
        // 被踢下线
        timService.on(TIM.EVENT.KICKED_OUT, handleKickedOut)
        
        // 信令事件监听
        initializeSignalingListeners()
    }
    
    /**
     * 销毁事件监听
     */
    function destroyEventListeners() {
        timService.off(TIM.EVENT.SDK_READY, handleSDKReady)
        timService.off(TIM.EVENT.MESSAGE_RECEIVED, handleMessageReceived)
        timService.off(TIM.EVENT.CONVERSATION_LIST_UPDATED, handleConversationUpdate)
        timService.off(TIM.EVENT.NET_STATE_CHANGE, handleNetworkStateChange)
        timService.off(TIM.EVENT.KICKED_OUT, handleKickedOut)
        
        destroySignalingListeners()
    }
    
    // ===== 导出 =====
    return {
        // 状态
        isSDKReady,
        isLogin,
        connectionState,
        userInfo,
        conversationList,
        currentConversationID,
        messageList,
        unreadCount,
        memberCount,
        groupOnlineMemberList,
        anchorInRoom,
        anchorUserID,
        activeInvitation,
        targetGroupId,
        
        // 计算属性
        currentMessages,
        totalUnreadCount,
        pinnedConversations,
        normalConversations,
        
        // 方法
        initializeEventListeners,
        destroyEventListeners,
        // ... 其他方法
    }
})

4. 快速上手指南

4.1 最小化集成示例

步骤 1: 创建基础 Vue 应用
typescript 复制代码
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')
步骤 2: 创建聊天组件
vue 复制代码
<!-- ChatDemo.vue -->
<template>
  <div class="chat-demo">
    <!-- 登录区域 -->
    <div v-if="!imStore.isLogin" class="login-section">
      <h3>IM 登录</h3>
      <input v-model="loginForm.userID" placeholder="用户 ID" />
      <input v-model="loginForm.userSig" placeholder="用户签名" />
      <button @click="handleLogin" :disabled="isLogging">
        {{ isLogging ? '登录中...' : '登录' }}
      </button>
    </div>
    
    <!-- 聊天区域 -->
    <div v-else class="chat-section">
      <div class="chat-header">
        <span>当前用户: {{ imStore.userInfo?.userID }}</span>
        <button @click="handleLogout">登出</button>
      </div>
      
      <!-- 消息列表 -->
      <div class="message-list" ref="messageListRef">
        <div
          v-for="message in imStore.currentMessages"
          :key="message.ID"
          class="message-item"
          :class="{ 'my-message': isMyMessage(message) }"
        >
          <div class="message-info">
            <span class="sender">{{ message.nick || message.from }}</span>
            <span class="time">{{ formatTime(message.time) }}</span>
          </div>
          
          <!-- 文本消息 -->
          <div v-if="message.type === 'TIMTextElem'" class="text-content">
            {{ message.payload.text }}
          </div>
          
          <!-- 图片消息 -->
          <div v-else-if="message.type === 'TIMImageElem'" class="image-content">
            <img :src="message.payload.imageInfoArray[1].url" alt="图片" />
          </div>
          
          <!-- 消息状态 -->
          <div v-if="isMyMessage(message)" class="message-status">
            <span v-if="message.sendStatus === 'pending'">⏳</span>
            <span v-else-if="message.sendStatus === 'success'">✅</span>
            <span v-else-if="message.sendStatus === 'fail'" @click="resendMessage(message)">❌</span>
          </div>
        </div>
      </div>
      
      <!-- 输入区域 -->
      <div class="input-section">
        <input
          v-model="messageText"
          @keyup.enter="sendMessage"
          placeholder="输入消息..."
          :disabled="!targetUserID"
        />
        <input v-model="targetUserID" placeholder="接收方 ID" />
        <button @click="sendMessage" :disabled="!messageText.trim() || !targetUserID">
          发送
        </button>
        
        <!-- 文件上传 -->
        <input
          type="file"
          ref="fileInputRef"
          @change="handleFileSelect"
          accept="image/*"
          style="display: none"
        />
        <button @click="$refs.fileInputRef.click()">📷</button>
      </div>
    </div>
    
    <!-- 状态指示器 -->
    <div class="status-bar">
      <span :class="connectionClass">{{ connectionText }}</span>
      <span v-if="imStore.unreadCount > 0" class="unread-count">
        未读: {{ imStore.unreadCount }}
      </span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, nextTick, watch, onMounted, onUnmounted } from 'vue'
import { useIMStore } from '@/stores/im'

// Store
const imStore = useIMStore()

// 响应式数据
const loginForm = ref({
  userID: 'testUser' + Math.floor(Math.random() * 1000),
  userSig: ''
})
const messageText = ref('')
const targetUserID = ref('testUser2')
const isLogging = ref(false)
const messageListRef = ref<HTMLElement>()
const fileInputRef = ref<HTMLInputElement>()

// 计算属性
const connectionClass = computed(() => {
  const state = imStore.connectionState
  return {
    'status-connected': state === 'connected',
    'status-connecting': state === 'connecting',
    'status-disconnected': state === 'disconnected',
    'status-reconnecting': state === 'reconnecting'
  }
})

const connectionText = computed(() => {
  const stateMap = {
    connected: '已连接',
    connecting: '连接中',
    disconnected: '已断开',
    reconnecting: '重连中'
  }
  return stateMap[imStore.connectionState] || '未知状态'
})

// 方法
async function handleLogin() {
  if (!loginForm.value.userID || !loginForm.value.userSig) {
    alert('请填写用户 ID 和用户签名')
    return
  }
  
  isLogging.value = true
  
  try {
    await imStore.login(loginForm.value.userID, loginForm.value.userSig)
    console.log('✅ 登录成功')
    
    // 设置当前会话
    if (targetUserID.value) {
      imStore.setCurrentConversation(`C2C${targetUserID.value}`)
    }
    
  } catch (error) {
    console.error('❌ 登录失败:', error)
    alert('登录失败: ' + error.message)
  } finally {
    isLogging.value = false
  }
}

async function handleLogout() {
  try {
    await imStore.logout()
    console.log('✅ 登出成功')
  } catch (error) {
    console.error('❌ 登出失败:', error)
  }
}

async function sendMessage() {
  if (!messageText.value.trim() || !targetUserID.value) return
  
  try {
    await imStore.sendMessage(targetUserID.value, messageText.value, 'C2C')
    messageText.value = ''
    
    // 滚动到底部
    await nextTick()
    scrollToBottom()
    
  } catch (error) {
    console.error('❌ 发送消息失败:', error)
    alert('发送失败: ' + error.message)
  }
}

async function handleFileSelect(event: Event) {
  const file = (event.target as HTMLInputElement).files?.[0]
  if (!file || !targetUserID.value) return
  
  try {
    await imStore.sendImageMessage(targetUserID.value, file, 'C2C')
    
    // 清空文件选择
    if (fileInputRef.value) {
      fileInputRef.value.value = ''
    }
    
    await nextTick()
    scrollToBottom()
    
  } catch (error) {
    console.error('❌ 发送图片失败:', error)
    alert('发送图片失败: ' + error.message)
  }
}

async function resendMessage(message: any) {
  try {
    await imStore.resendMessage(message)
    console.log('✅ 重发消息成功')
  } catch (error) {
    console.error('❌ 重发消息失败:', error)
  }
}

function isMyMessage(message: any): boolean {
  return message.from === imStore.userInfo?.userID
}

function formatTime(timestamp: number): string {
  return new Date(timestamp * 1000).toLocaleTimeString()
}

function scrollToBottom() {
  if (messageListRef.value) {
    messageListRef.value.scrollTop = messageListRef.value.scrollHeight
  }
}

// 监听消息变化,自动滚动
watch(() => imStore.currentMessages, () => {
  nextTick(() => scrollToBottom())
}, { deep: true })

// 监听目标用户变化,切换会话
watch(targetUserID, (newUserID) => {
  if (newUserID && imStore.isLogin) {
    imStore.setCurrentConversation(`C2C${newUserID}`)
  }
})

// 生命周期
onMounted(() => {
  // 尝试自动登录
  imStore.tryAutoLogin().catch(() => {
    console.log('自动登录失败,需要手动登录')
  })
})

onUnmounted(() => {
  // 清理会话
  imStore.setCurrentConversation('')
})
</script>

<style scoped>
.chat-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.login-section {
  text-align: center;
}

.login-section input {
  display: block;
  width: 100%;
  margin: 10px 0;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.login-section button {
  padding: 10px 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.login-section button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.chat-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}

.message-list {
  height: 400px;
  overflow-y: auto;
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}

.message-item {
  margin-bottom: 15px;
  padding: 10px;
  border-radius: 8px;
  background: #f5f5f5;
}

.message-item.my-message {
  background: #007bff;
  color: white;
  margin-left: 20%;
}

.message-info {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  margin-bottom: 5px;
  opacity: 0.7;
}

.text-content {
  word-break: break-word;
}

.image-content img {
  max-width: 200px;
  border-radius: 4px;
}

.message-status {
  text-align: right;
  margin-top: 5px;
}

.message-status span {
  cursor: pointer;
  font-size: 14px;
}

.input-section {
  display: flex;
  gap: 10px;
  padding: 10px 0;
}

.input-section input {
  flex: 1;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.input-section button {
  padding: 8px 15px;
  background: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.input-section button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.status-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  font-size: 12px;
}

.status-connected { color: #28a745; }
.status-connecting { color: #ffc107; }
.status-disconnected { color: #dc3545; }
.status-reconnecting { color: #17a2b8; }

.unread-count {
  background: #dc3545;
  color: white;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
}
</style>
步骤 3: 在主应用中使用
vue 复制代码
<!-- App.vue -->
<template>
  <div id="app">
    <h1>腾讯 IM SDK 演示</h1>
    <ChatDemo />
  </div>
</template>

<script setup lang="ts">
import ChatDemo from './components/ChatDemo.vue'
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  padding: 20px;
}
</style>

4.2 获取 UserSig 的方法

方法 1: 控制台生成(开发测试用)
  1. 登录腾讯云 IM 控制台
  2. 进入应用
  3. 点击"开发辅助工具" → "UserSig生成&校验"
  4. 输入用户标识符,点击生成
方法 2: 服务端生成(生产推荐)
javascript 复制代码
// Node.js 示例
const TLSSigAPIv2 = require('tls-sig-api-v2')

const api = new TLSSigAPIv2.Api(SDKAppID, secretKey)

// 生成 UserSig,有效期 7 天
const userSig = api.genUserSig(userID, 7 * 24 * 3600)
python 复制代码
# Python 示例
from TLSSigAPIv2 import TLSSigAPIv2

api = TLSSigAPIv2(SDKAppID, secretKey)

# 生成 UserSig,有效期 7 天
userSig = api.gen_user_sig(userID, 7 * 24 * 3600)
方法 3: 前端临时生成(仅开发测试)
typescript 复制代码
// 注意:密钥不能暴露在前端代码中,此方法仅供开发测试
import { genTestUserSig } from '@/utils/generateTestUserSig'

const { userSig } = genTestUserSig(userID)

5. TIMService 完整API

5.1 认证相关 API

login(userID: string, userSig: string): Promise<void>

功能描述: 用户登录腾讯云 IM 系统,建立与服务器的连接。

详细参数:

  • userID (string): 用户唯一标识符

    • 长度限制: 1-32 字符
    • 字符限制: 字母、数字、下划线
    • 区分大小写
    • 示例: 'user123', 'anchor_001', 'customer_service'
  • userSig (string): 用户身份验证签名

    • 由 App 服务端使用密钥生成
    • 包含用户 ID、过期时间等信息
    • 建议有效期设为 7 天
    • 格式: Base64 编码的 JSON Web Token

返回值: Promise<void> - 登录成功时 resolve,失败时 reject 并抛出错误

可能的错误码:

typescript 复制代码
interface LoginError {
    code: number
    message: string
}

// 常见错误码
const LOGIN_ERRORS = {
    2000: '参数错误',
    3050: '用户已登录,无需重复登录',
    6014: '用户 ID 不合法',
    6015: 'SDKAppID 不匹配',
    70001: 'UserSig 已过期',
    70002: 'UserSig 长度为 0',
    70003: 'UserSig 格式错误',
    70005: 'UserSig 验证失败',
    70009: 'UserSig 验证失败,请使用官网工具重新生成',
    70013: '用户 ID 包含敏感词',
    70014: '用户 ID 不能为空',
    70016: '用户 ID 长度超限',
    70020: 'SDKAppID 未找到,请在腾讯云即时通信 IM 控制台创建应用'
}

完整使用示例:

typescript 复制代码
import timService from '@/utils/timService'

async function performLogin(userID: string, userSig: string) {
    // 参数验证
    if (!userID || !userSig) {
        throw new Error('用户 ID 和 UserSig 不能为空')
    }
    
    if (userID.length > 32) {
        throw new Error('用户 ID 长度不能超过 32 个字符')
    }
    
    if (!/^[a-zA-Z0-9_]+$/.test(userID)) {
        throw new Error('用户 ID 只能包含字母、数字和下划线')
    }
    
    try {
        console.log(`🔐 开始登录,用户 ID: ${userID}`)
        
        await timService.login(userID, userSig)
        
        console.log('✅ 登录成功')
        
        // 登录成功后的处理
        await postLoginSetup()
        
    } catch (error) {
        console.error('❌ 登录失败:', error)
        
        // 错误处理
        handleLoginError(error)
        
        throw error
    }
}

async function postLoginSetup() {
    // 获取用户资料
    const userProfile = await timService.getCurrentUser()
    console.log('👤 用户资料:', userProfile.data)
    
    // 获取会话列表
    const conversations = await timService.getConversationList()
    console.log('💬 会话列表:', conversations.data.conversationList)
}

function handleLoginError(error: any) {
    const errorMessages: Record<number, string> = {
        3050: '您已经登录,无需重复操作',
        70001: 'UserSig 已过期,请重新获取',
        70005: 'UserSig 验证失败,请检查签名是否正确',
        70020: 'SDKAppID 不存在,请检查配置'
    }
    
    const message = errorMessages[error.code] || error.message || '登录失败'
    
    // 显示用户友好的错误信息
    showErrorMessage(message)
    
    // 特殊错误处理
    switch (error.code) {
        case 70001:
            // UserSig 过期,可以尝试自动刷新
            refreshUserSigAndRetry()
            break
            
        case 70020:
            // SDKAppID 错误,需要检查配置
            console.error('请检查 timConfig.SDKAppID 配置是否正确')
            break
    }
}

async function refreshUserSigAndRetry() {
    try {
        // 从服务端获取新的 UserSig
        const newUserSig = await fetchUserSigFromServer()
        
        // 重新登录
        await performLogin(getCurrentUserID(), newUserSig)
        
    } catch (error) {
        console.error('自动刷新 UserSig 失败:', error)
    }
}

logout(): Promise<void>

功能描述: 用户登出 IM 系统,断开与服务器的连接并清理本地数据。

执行流程:

  1. 向服务器发送登出请求
  2. 断开 WebSocket 连接
  3. 清理本地缓存的消息和会话数据
  4. 停止接收新消息推送
  5. 清理事件监听器

返回值: Promise<void> - 登出成功时 resolve,失败时 reject

注意事项:

  • 登出后需要重新登录才能使用 IM 功能
  • 本地存储的 UserSig 会被清除
  • 正在进行的文件上传会被取消

完整使用示例:

typescript 复制代码
async function performLogout() {
    try {
        console.log('🚪 开始登出...')
        
        // 保存当前状态(可选)
        await saveCurrentState()
        
        // 执行登出
        await timService.logout()
        
        console.log('✅ 登出成功')
        
        // 登出后的清理工作
        await postLogoutCleanup()
        
    } catch (error) {
        console.error('❌ 登出失败:', error)
        
        // 即使登出失败,也要执行本地清理
        await postLogoutCleanup()
        
        throw error
    }
}

async function saveCurrentState() {
    // 保存草稿消息
    const draftMessages = getDraftMessages()
    localStorage.setItem('draftMessages', JSON.stringify(draftMessages))
    
    // 保存其他需要持久化的状态
}

async function postLogoutCleanup() {
    // 清理敏感数据
    localStorage.removeItem('userID')
    localStorage.removeItem('userSig')
    
    // 清理会话数据
    sessionStorage.clear()
    
    // 重置应用状态
    resetApplicationState()
    
    // 跳转到登录页面
    navigateToLogin()
}

function resetApplicationState() {
    // 重置 Pinia store
    const imStore = useIMStore()
    imStore.reset()
    
    // 清理其他全局状态
}

getCurrentUser(): Promise<UserProfileResult>

功能描述: 获取当前登录用户的详细资料信息。

返回值类型:

typescript 复制代码
interface UserProfileResult {
    code: number
    data: {
        userID: string              // 用户 ID
        nick: string               // 昵称
        avatar: string             // 头像 URL
        selfSignature: string      // 个性签名
        gender: 'TIM_GENDER_MALE' | 'TIM_GENDER_FEMALE' | 'TIM_GENDER_UNKNOWN'
        birthday: number           // 生日时间戳(秒)
        location: string           // 位置信息
        language: number           // 语言
        level: number              // 等级
        role: number               // 角色
        allowType: 'TIM_FRIEND_ALLOW_ANY' | 'TIM_FRIEND_NEED_CONFIRM' | 'TIM_FRIEND_DENY_ANY'
        customInfo: Record<string, string>  // 自定义字段
    }
}

详细使用示例:

typescript 复制代码
async function loadUserProfile() {
    try {
        const result = await timService.getCurrentUser()
        const profile = result.data
        
        console.log('👤 用户资料:')
        console.log('用户 ID:', profile.userID)
        console.log('昵称:', profile.nick || '未设置')
        console.log('头像:', profile.avatar || '默认头像')
        console.log('个性签名:', profile.selfSignature || '这个人很懒,什么都没写')
        console.log('性别:', getGenderText(profile.gender))
        console.log('生日:', formatBirthday(profile.birthday))
        console.log('位置:', profile.location || '未设置')
        
        // 更新 UI 显示
        updateUserProfileUI(profile)
        
        return profile
        
    } catch (error) {
        console.error('❌ 获取用户资料失败:', error)
        
        // 使用默认资料
        const defaultProfile = getDefaultProfile()
        updateUserProfileUI(defaultProfile)
        
        throw error
    }
}

function getGenderText(gender: string): string {
    const genderMap = {
        'TIM_GENDER_MALE': '男',
        'TIM_GENDER_FEMALE': '女',
        'TIM_GENDER_UNKNOWN': '未设置'
    }
    return genderMap[gender] || '未知'
}

function formatBirthday(timestamp: number): string {
    if (!timestamp) return '未设置'
    
    const date = new Date(timestamp * 1000)
    return date.toLocaleDateString('zh-CN')
}

function updateUserProfileUI(profile: any) {
    // 更新头像
    const avatarEl = document.querySelector('.user-avatar') as HTMLImageElement
    if (avatarEl) {
        avatarEl.src = profile.avatar || '/default-avatar.png'
    }
    
    // 更新昵称
    const nickEl = document.querySelector('.user-nick')
    if (nickEl) {
        nickEl.textContent = profile.nick || profile.userID
    }
    
    // 更新个性签名
    const signatureEl = document.querySelector('.user-signature')
    if (signatureEl) {
        signatureEl.textContent = profile.selfSignature || '这个人很懒,什么都没写'
    }
}

function getDefaultProfile() {
    return {
        userID: 'unknown',
        nick: '未知用户',
        avatar: '/default-avatar.png',
        selfSignature: '',
        gender: 'TIM_GENDER_UNKNOWN',
        birthday: 0,
        location: '',
        language: 0,
        level: 0,
        role: 0,
        allowType: 'TIM_FRIEND_NEED_CONFIRM'
    }
}

5.2 消息相关 API

createTextMessage(to: string, text: string, conversationType?: ConversationType): Message

功能描述: 创建文本消息对象,用于发送纯文本内容。

参数详解:

  • to (string): 接收方标识符
    • 单聊: 对方的 userID,如 'user123'
    • 群聊: 群组的 groupID,如 'group456'
  • text (string): 消息文本内容
    • 最大长度: 12KB (约 12,000 个字符)
    • 支持 Unicode 字符,包括 Emoji 表情
    • 支持换行符 \n
    • 自动过滤敏感词(如果配置了敏感词过滤)
  • conversationType (ConversationType): 会话类型,可选参数
    • TIM.TYPES.CONV_C2C: 单聊会话(默认)
    • TIM.TYPES.CONV_GROUP: 群聊会话

返回值: 返回一个 Message 对象,包含以下主要属性:

typescript 复制代码
interface TextMessage {
    ID: string                    // 消息 ID(本地生成)
    type: 'TIMTextElem'          // 消息类型
    payload: {
        text: string             // 消息文本
    }
    conversationID: string       // 会话 ID
    to: string                   // 接收方 ID
    from: string                 // 发送方 ID(当前用户)
    time: number                 // 消息时间戳
    status: 'unSend'            // 初始状态
    sendStatus?: 'pending'       // 发送状态(自定义字段)
}

详细使用示例:

typescript 复制代码
// 基础文本消息创建
function createBasicTextMessage() {
    const message = timService.createTextMessage(
        'receiver123',
        '你好!这是一条测试消息 😊'
    )
    
    console.log('📝 创建的消息:', message)
    return message
}

// 创建群聊文本消息
function createGroupTextMessage() {
    const message = timService.createTextMessage(
        'liveRoom456',
        '大家好!欢迎来到直播间 🎉',
        TIM.TYPES.CONV_GROUP
    )
    
    return message
}

// 创建多行文本消息
function createMultilineMessage() {
    const multilineText = `这是第一行
这是第二行
这是第三行

这是空行后的内容`
    
    const message = timService.createTextMessage(
        'user789',
        multilineText
    )
    
    return message
}

// 创建包含特殊字符的消息
function createSpecialCharMessage() {
    const specialText = `特殊字符测试:
🎵 音乐符号
💖 爱心表情
🌟 星星
👍 点赞
@用户名 提及用户
#话题标签 话题
http://example.com 链接`
    
    const message = timService.createTextMessage(
        'user456',
        specialText
    )
    
    return message
}

// 文本长度验证
function createValidatedTextMessage(to: string, text: string) {
    // 验证文本长度
    const maxLength = 12 * 1024 // 12KB
    const textBytes = new TextEncoder().encode(text).length
    
    if (textBytes > maxLength) {
        throw new Error(`消息长度超限,当前: ${textBytes} 字节,最大: ${maxLength} 字节`)
    }
    
    // 验证是否为空
    if (!text.trim()) {
        throw new Error('消息内容不能为空')
    }
    
    // 创建消息
    const message = timService.createTextMessage(to, text)
    
    // 添加自定义属性
    message.sendStatus = 'pending'
    message.createTime = Date.now()
    
    return message
}

// 批量创建消息
function createBatchMessages(to: string, texts: string[]) {
    return texts.map(text => {
        const message = timService.createTextMessage(to, text)
        message.batchId = Date.now() + Math.random()
        return message
    })
}

// 创建回复消息
function createReplyMessage(to: string, replyText: string, originalMessage: any) {
    const replyContent = `回复 @${originalMessage.from}: ${replyText}`
    
    const message = timService.createTextMessage(to, replyContent)
    
    // 添加回复信息
    message.replyTo = {
        messageID: originalMessage.ID,
        sender: originalMessage.from,
        content: originalMessage.payload.text.substring(0, 50) + '...'
    }
    
    return message
}

消息内容处理工具:

typescript 复制代码
// 消息内容处理工具类
class MessageContentProcessor {
    // 过滤敏感词
    static filterSensitiveWords(text: string): string {
        const sensitiveWords = ['敏感词1', '敏感词2'] // 实际项目中从服务端获取
        
        let filteredText = text
        sensitiveWords.forEach(word => {
            const regex = new RegExp(word, 'gi')
            filteredText = filteredText.replace(regex, '*'.repeat(word.length))
        })
        
        return filteredText
    }
    
    // 提取 @ 提及
    static extractMentions(text: string): string[] {
        const mentionRegex = /@([a-zA-Z0-9_]+)/g
        const mentions: string[] = []
        let match
        
        while ((match = mentionRegex.exec(text)) !== null) {
            mentions.push(match[1])
        }
        
        return mentions
    }
    
    // 提取话题标签
    static extractHashtags(text: string): string[] {
        const hashtagRegex = /#([^\s#]+)/g
        const hashtags: string[] = []
        let match
        
        while ((match = hashtagRegex.exec(text)) !== null) {
            hashtags.push(match[1])
        }
        
        return hashtags
    }
    
    // 提取 URL 链接
    static extractUrls(text: string): string[] {
        const urlRegex = /(https?:\/\/[^\s]+)/g
        const urls: string[] = []
        let match
        
        while ((match = urlRegex.exec(text)) !== null) {
            urls.push(match[1])
        }
        
        return urls
    }
    
    // 格式化消息内容
    static formatMessageContent(text: string): {
        originalText: string
        filteredText: string
        mentions: string[]
        hashtags: string[]
        urls: string[]
        hasSpecialContent: boolean
    } {
        const filteredText = this.filterSensitiveWords(text)
        const mentions = this.extractMentions(text)
        const hashtags = this.extractHashtags(text)
        const urls = this.extractUrls(text)
        
        return {
            originalText: text,
            filteredText,
            mentions,
            hashtags,
            urls,
            hasSpecialContent: mentions.length > 0 || hashtags.length > 0 || urls.length > 0
        }
    }
}

// 使用示例
function createEnhancedTextMessage(to: string, text: string) {
    // 处理消息内容
    const processed = MessageContentProcessor.formatMessageContent(text)
    
    // 创建消息
    const message = timService.createTextMessage(to, processed.filteredText)
    
    // 添加扩展信息
    message.mentions = processed.mentions
    message.hashtags = processed.hashtags
    message.urls = processed.urls
    message.hasSpecialContent = processed.hasSpecialContent
    
    console.log('📝 增强消息创建完成:', {
        mentions: processed.mentions,
        hashtags: processed.hashtags,
        urls: processed.urls
    })
    
    return message
}

createCustomMessage(to: string, payload: CustomPayload, conversationType?: ConversationType, priority?: MessagePriority): Message

功能描述: 创建自定义消息对象,用于传输业务相关的结构化数据,如礼物、位置、音视频通话邀请等。

参数详解:

  • to (string): 接收方标识符

  • payload (CustomPayload): 自定义消息载荷

    typescript 复制代码
    interface CustomPayload {
        data: string           // 自定义数据(必填),通常为 JSON 字符串
        description?: string   // 消息描述(可选),用于消息列表显示
        extension?: string     // 扩展字段(可选),用于版本控制等
    }
  • conversationType (ConversationType): 会话类型

  • priority (MessagePriority): 消息优先级

    • TIM.TYPES.MSG_PRIORITY_HIGH: 高优先级,优先传输
    • TIM.TYPES.MSG_PRIORITY_NORMAL: 普通优先级(默认)
    • TIM.TYPES.MSG_PRIORITY_LOW: 低优先级

业务消息类型定义:

typescript 复制代码
// 礼物消息
interface GiftMessageData {
    businessID: 'gift'
    type: 3                    // 消息类型标识
    giftId: number            // 礼物 ID
    giftName: string          // 礼物名称
    giftIcon: string          // 礼物图标
    giftCount: number         // 礼物数量
    giftValue: number         // 礼物价值
    animation?: string        // 动画效果
    timestamp: number         // 时间戳
}

// 位置消息
interface LocationMessageData {
    businessID: 'location'
    latitude: number          // 纬度
    longitude: number         // 经度
    address: string          // 详细地址
    description?: string     // 位置描述
    thumbnail?: string       // 地图缩略图
    timestamp: number
}

// 音视频通话邀请
interface CallInviteData {
    businessID: 'video_call' | 'voice_call'
    actionType: 1 | 2 | 3 | 4 | 5  // 1:邀请 2:取消 3:接受 4:拒绝 5:超时
    callType: 'voice' | 'video'     // 通话类型
    roomId: string                  // 房间 ID
    inviter: string                 // 邀请者 ID
    invitee: string                 // 被邀请者 ID
    duration?: number               // 通话时长(结束时)
    timestamp: number
}

// 充值请求消息
interface RechargeRequestData {
    businessID: 'ask_recharge'
    amount: number            // 请求充值金额
    currency: string          // 货币类型
    reason?: string          // 充值原因
    timestamp: number
}

// 官方消息
interface OfficialMessageData {
    businessID: 'official_message'
    messageType: 'system' | 'promotion' | 'announcement'
    title: string            // 消息标题
    content: {
        text?: string        // 文本内容
        image?: string       // 图片 URL
        uri?: string         // 跳转链接
        action?: string      // 动作类型
    }
    priority: 'high' | 'normal' | 'low'
    timestamp: number
}

详细使用示例:

typescript 复制代码
// 1. 创建礼物消息
async function sendGiftMessage(receiverID: string, giftInfo: any) {
    const giftData: GiftMessageData = {
        businessID: 'gift',
        type: 3,
        giftId: giftInfo.id,
        giftName: giftInfo.name,
        giftIcon: giftInfo.icon,
        giftCount: giftInfo.count,
        giftValue: giftInfo.value,
        animation: giftInfo.animation,
        timestamp: Date.now()
    }
    
    const payload = {
        data: JSON.stringify(giftData),
        description: `${giftInfo.name} x${giftInfo.count}`,
        extension: 'gift_v2.0'
    }
    
    try {
        const message = timService.createCustomMessage(
            receiverID,
            payload,
            TIM.TYPES.CONV_C2C,
            TIM.TYPES.MSG_PRIORITY_HIGH  // 礼物消息使用高优先级
        )
        
        console.log('🎁 礼物消息创建成功:', message)
        
        // 发送消息
        const result = await timService.sendMessage(message)
        
        // 触发礼物动画
        triggerGiftAnimation(giftData)
        
        return result
        
    } catch (error) {
        console.error('❌ 礼物消息发送失败:', error)
        throw error
    }
}

// 2. 创建位置分享消息
async function sendLocationMessage(receiverID: string, locationInfo: any) {
    const locationData: LocationMessageData = {
        businessID: 'location',
        latitude: locationInfo.lat,
        longitude: locationInfo.lng,
        address: locationInfo.address,
        description: locationInfo.description,
        thumbnail: await generateMapThumbnail(locationInfo.lat, locationInfo.lng),
        timestamp: Date.now()
    }
    
    const payload = {
        data: JSON.stringify(locationData),
        description: `位置: ${locationInfo.address}`,
        extension: 'location_v1.0'
    }
    
    const message = timService.createCustomMessage(
        receiverID,
        payload,
        TIM.TYPES.CONV_C2C
    )
    
    return await timService.sendMessage(message)
}

// 3. 创建音视频通话邀请
async function sendCallInvitation(inviteeID: string, callType: 'voice' | 'video') {
    const callData: CallInviteData = {
        businessID: callType === 'video' ? 'video_call' : 'voice_call',
        actionType: 1,  // 邀请
        callType: callType,
        roomId: generateRoomId(),
        inviter: getCurrentUserID(),
        invitee: inviteeID,
        timestamp: Date.now()
    }
    
    const payload = {
        data: JSON.stringify(callData),
        description: `${callType === 'video' ? '视频' : '语音'}通话邀请`,
        extension: 'call_v1.0'
    }
    
    try {
        const message = timService.createCustomMessage(
            inviteeID,
            payload,
            TIM.TYPES.CONV_C2C,
            TIM.TYPES.MSG_PRIORITY_HIGH
        )
        
        // 同时发送信令邀请
        const signalResult = await timService.invite(
            inviteeID,
            JSON.stringify(callData),
            30  // 30秒超时
        )
        
        // 将信令 ID 添加到消息中
        message.signalInviteID = signalResult.inviteID
        
        const result = await timService.sendMessage(message)
        
        console.log('📞 通话邀请发送成功')
        return {
            message: result,
            signalInviteID: signalResult.inviteID
        }
        
    } catch (error) {
        console.error('❌ 通话邀请发送失败:', error)
        throw error
    }
}

// 4. 创建充值请求消息
async function sendRechargeRequest(receiverID: string, amount: number, reason?: string) {
    const rechargeData: RechargeRequestData = {
        businessID: 'ask_recharge',
        amount: amount,
        currency: 'USD',  // 或从配置中获取
        reason: reason,
        timestamp: Date.now()
    }
    
    const payload = {
        data: JSON.stringify(rechargeData),
        description: `充值请求: $${amount}`,
        extension: 'recharge_v1.0'
    }
    
    const message = timService.createCustomMessage(
        receiverID,
        payload,
        TIM.TYPES.CONV_C2C
    )
    
    return await timService.sendMessage(message)
}

// 5. 创建官方系统消息
async function sendOfficialMessage(receiverID: string, messageData: any) {
    const officialData: OfficialMessageData = {
        businessID: 'official_message',
        messageType: messageData.type,
        title: messageData.title,
        content: messageData.content,
        priority: messageData.priority || 'normal',
        timestamp: Date.now()
    }
    
    const payload = {
        data: JSON.stringify(officialData),
        description: messageData.title,
        extension: 'official_v1.0'
    }
    
    const message = timService.createCustomMessage(
        receiverID,
        payload,
        TIM.TYPES.CONV_C2C,
        messageData.priority === 'high' ? TIM.TYPES.MSG_PRIORITY_HIGH : TIM.TYPES.MSG_PRIORITY_NORMAL
    )
    
    return await timService.sendMessage(message)
}

// 辅助函数
function generateRoomId(): string {
    return `room_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}

function getCurrentUserID(): string {
    const imStore = useIMStore()
    return imStore.userInfo?.userID || ''
}

async function generateMapThumbnail(lat: number, lng: number): Promise<string> {
    // 调用地图服务生成缩略图
    const mapService = 'https://maps.googleapis.com/maps/api/staticmap'
    const params = new URLSearchParams({
        center: `${lat},${lng}`,
        zoom: '15',
        size: '200x200',
        markers: `${lat},${lng}`,
        key: 'YOUR_GOOGLE_MAPS_API_KEY'
    })
    
    return `${mapService}?${params.toString()}`
}

function triggerGiftAnimation(giftData: GiftMessageData) {
    // 触发礼物动画效果
    const event = new CustomEvent('giftAnimation', {
        detail: giftData
    })
    document.dispatchEvent(event)
}

自定义消息处理器:

typescript 复制代码
// 自定义消息处理器
class CustomMessageHandler {
    private handlers: Map<string, (data: any) => void> = new Map()
    
    // 注册处理器
    registerHandler(businessID: string, handler: (data: any) => void) {
        this.handlers.set(businessID, handler)
    }
    
    // 处理接收到的自定义消息
    handleCustomMessage(message: any) {
        try {
            const payload = JSON.parse(message.payload.data)
            const handler = this.handlers.get(payload.businessID)
            
            if (handler) {
                handler(payload)
            } else {
                console.warn('未找到对应的消息处理器:', payload.businessID)
            }
        } catch (error) {
            console.error('处理自定义消息失败:', error)
        }
    }
}

// 全局消息处理器实例
const messageHandler = new CustomMessageHandler()

// 注册各种消息处理器
messageHandler.registerHandler('gift', (data: GiftMessageData) => {
    console.log('🎁 收到礼物:', data.giftName)
    showGiftNotification(data)
    updateGiftStatistics(data)
})

messageHandler.registerHandler('location', (data: LocationMessageData) => {
    console.log('📍 收到位置分享:', data.address)
    showLocationOnMap(data)
})

messageHandler.registerHandler('video_call', (data: CallInviteData) => {
    console.log('📞 收到视频通话邀请')
    showCallInviteDialog(data)
})

messageHandler.registerHandler('ask_recharge', (data: RechargeRequestData) => {
    console.log('💰 收到充值请求:', data.amount)
    showRechargeDialog(data)
})

// 在消息接收处理中使用
function handleReceivedMessage(message: any) {
    if (message.type === TIM.TYPES.MSG_CUSTOM) {
        messageHandler.handleCustomMessage(message)
    }
}

相关推荐
keep_di2 小时前
05-vue3+ts中axios的封装
前端·vue.js·ajax·typescript·前端框架·axios
JiKun3 小时前
ECMA 2024(ES15) 新特性
前端·javascript
0__O3 小时前
细说 new Function
javascript
百锦再3 小时前
从 .NET 到 Java 的转型指南:详细学习路线与实践建议
android·java·前端·数据库·学习·.net·数据库架构
i小杨3 小时前
前端埋点(打点)方案
前端·状态模式
前端加油站3 小时前
时间转换那些事
前端·javascript
风清云淡_A3 小时前
【VUECLI】node.js打造自己的前端cli脚手架工具
前端·node.js
Never_Satisfied3 小时前
在JavaScript / Node.js中,SQLite异步查询函数实现
javascript·sqlite·node.js
YuspTLstar3 小时前
一文掌握Redux-toolkit核心原理
前端·react.js