[MODE: RESEARCH]
快乐程序员,我来详细讲解 useIMManager.ts 中的核心方法!这个文件是腾讯云IM SDK的封装,提供了完整的即时通讯功能。
🏗️ 整体架构设计
1. 单例模式设计
export class IMManager {
private static chatInstance: IMManager
public static getInstance(): IMManager {
if (!this.chatInstance) {
this.chatInstance = new IMManager()
}
return this.chatInstance
}
}
- 设计目的: 确保全局只有一个IM实例,避免重复初始化
- 优势: 节省内存,统一管理连接状态
- 使用场景: 多组件共享同一个IM连接
2. 状态管理系统
export interface IMState {
connectionState: ConnectionState // 连接状态
networkState: NetworkState // 网络状态
isReady: boolean // SDK是否就绪
isLoggedIn: boolean // 是否已登录
errorCount: number // 错误计数
reconnectAttempts: number // 重连次数
lastHeartbeat: number // 最后心跳时间
timestamp: number // 状态更新时间
}
🔧 核心方法详解
1. 初始化与连接管理
构造函数 - 系统初始化
constructor() {
try {
// 创建腾讯云IM实例
this.chat = TencentCloudChat.create({
SDKAppID,
unlimitedAVChatRoom: true
})
// 设置日志级别
this.chat.setLogLevel(0)
// 注册上传插件
this.chat.registerPlugin({ 'tim-upload-plugin': TIMUploadPlugin })
// 初始化状态
this.state = this.createInitialState()
// 注册事件监听器
this.registerEventListeners()
// 启动健康检查
this.startHealthCheck()
} catch (error) {
throw new IMError('IM SDK 初始化失败', 'INIT_FAILED', error)
}
}
核心功能:
- 🎯 SDK初始化: 创建腾讯云IM实例
- 🔧 插件注册: 支持文件上传功能
- 📊 状态初始化: 设置初始状态
- 👂 事件监听: 注册所有必要的事件处理器
- ❤️ 健康检查: 启动心跳和健康监控
事件监听器注册
private registerEventListeners(): void {
// 基础消息事件
this.chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, this.handleMessageReceived.bind(this))
this.chat.on(TencentCloudChat.EVENT.SDK_READY, this.handleSDKReady.bind(this))
this.chat.on(TencentCloudChat.EVENT.MESSAGE_REVOKED, (revokedMessage: MessageRevokedEvent) => {
this.emitter.emit(IMEventType.MSG_REVOKED, revokedMessage.data)
})
// 网络状态监控
this.chat.on(TencentCloudChat.EVENT.NET_STATE_CHANGE, this.handleNetworkStateChanged.bind(this))
// 错误事件监控
this.chat.on(TencentCloudChat.EVENT.ERROR, this.handleError.bind(this))
this.chat.on(TencentCloudChat.EVENT.KICKED_OUT, this.handleKickedOut.bind(this))
// 群组相关事件
this.chat.on(TencentCloudChat.EVENT.GROUP_ATTRIBUTES_UPDATED, this.handleGroupAttributesUpdated.bind(this))
}
2. 用户认证系统
登录方法 - 用户身份验证
async login(userID: string, userSig: string): Promise<void> {
try {
// 参数验证
this.validateNotEmpty(userID)
this.validateNotEmpty(userSig)
// 检查是否已登录
if (this.isUserLoggedIn) {
console.log(LOG_PRIFIX, 'IM 已经登录')
return
}
// 执行登录
await this.chat.login({ userID, userSig })
// 更新状态
this.isUserLoggedIn = true
this.currentUserID = userID
this.updateState({
isLoggedIn: true,
})
console.log(LOG_PRIFIX, 'IM 登录成功')
} catch (error) {
// 错误处理
this.isUserLoggedIn = false
this.currentUserID = null
this.updateState({
isLoggedIn: false,
})
// 处理腾讯云IM的错误码
if (error && typeof error === 'object' && 'code' in error) {
const errorCode = (error as any).code
let message = '登录失败'
switch (errorCode) {
case 7001: message = '用户签名错误'; break
case 7002: message = '用户ID格式错误'; break
case 7003: message = '用户已登录'; break
case 7004: message = '网络连接失败'; break
default: message = `登录失败: ${errorCode}`
}
throw new IMError(message, 'LOGIN_FAILED', error)
}
throw new IMError('登录失败', 'LOGIN_FAILED', error)
}
}
核心功能:
- 🔐 身份验证: 使用userID和userSig进行身份验证
- 🛡️ 重复登录检查: 防止重复登录
- 📊 状态更新: 实时更新登录状态
- 🚨 错误处理: 详细的错误码处理和用户友好的错误信息
登出方法 - 安全退出
async logout(): Promise<void> {
if (!this.isUserLoggedIn) {
console.log(LOG_PRIFIX, 'IM 未登录,无需登出')
return
}
try {
// 清理已加入群组
this.joinedGroups.clear()
// 执行登出
await this.chat.logout()
// 更新状态
this.isUserLoggedIn = false
this.currentUserID = null
this.connectionState = ConnectionState.DISCONNECTED
this.updateState({
isLoggedIn: false,
connectionState: ConnectionState.DISCONNECTED,
})
console.log(LOG_PRIFIX, 'IM 退出成功')
} catch (error) {
// 错误处理...
throw new IMError('退出失败', 'LOGOUT_FAILED', error)
}
}
3. 群组管理系统
创建群组 - 建立聊天室
public async createGroup(groupID: string): Promise<any> {
try {
// 参数验证
this.validateGroupID(groupID)
this.confirmReady()
// 创建群组
const group = await this.chat.createGroup({
type: TencentCloudChat.TYPES.GRP_AVCHATROOM, // 音视频聊天室
name: groupID,
isSupportTopic: true, // 支持话题功能
groupID,
})
console.log(LOG_PRIFIX, '直播群组创建成功', group)
return group
} catch (error) {
// 错误处理
if (error && typeof error === 'object' && 'code' in error) {
const errorCode = (error as any).code
let message = '创建群组失败'
switch (errorCode) {
case 10013: message = '群组已存在'; break
case 10014: message = '群组ID格式错误'; break
case 10015: message = '群组名称过长'; break
default: message = `创建群组失败: ${errorCode}`
}
throw new IMError(message, 'CREATE_GROUP_FAILED', error)
}
throw new IMError('创建群组失败', 'CREATE_GROUP_FAILED', error)
}
}
核心功能:
- 🏗️ 群组创建: 创建音视频聊天室
- 🎯 类型选择: 支持不同类型的群组
- 🔧 功能配置: 支持话题、音视频等功能
- 🚨 错误处理: 详细的错误码处理
加入群组 - 参与聊天
public async joinGroup(groupID: string): Promise<any> {
try {
// 参数验证
this.validateGroupID(groupID)
this.confirmReady()
// 检查是否已加入
if (this.joinedGroups.has(groupID)) {
console.log(LOG_PRIFIX, `已加入群组 ${groupID},跳过重复加入`)
return
}
// 加入群组
const result = await this.chat.joinGroup({
groupID,
type: TencentCloudChat.TYPES.GRP_AVCHATROOM
})
// 记录已加入群组
this.joinedGroups.add(groupID)
console.log(LOG_PRIFIX, `已加入群组 ${groupID}`)
return result
} catch (error) {
// 错误处理...
throw new IMError('加入群组失败', 'JOIN_GROUP_FAILED', error)
}
}
退出群组 - 离开聊天
public async quitGroup(groupID: string): Promise<void> {
try {
// 参数验证
this.validateGroupID(groupID)
this.confirmReady()
// 检查是否已加入
if (!this.joinedGroups.has(groupID)) {
console.log(LOG_PRIFIX, `未加入群组 ${groupID},无需退出`)
return
}
// 从记录中移除
this.joinedGroups.delete(groupID)
// 执行退出
await this.chat.quitGroup(groupID)
console.log(LOG_PRIFIX, `已退出群组 ${groupID}`)
} catch (error) {
throw new IMError('退出群组失败', 'QUIT_GROUP_FAILED', error)
}
}
4. 消息系统
发送群组消息 - 群聊功能
async sendGroupTextMessage(groupID: string, text: string): Promise<number> {
try {
// 参数验证
this.validateGroupID(groupID)
this.confirmReady()
if (!text || typeof text !== 'string' || text.trim().length === 0) {
throw new IMError('消息内容不能为空', 'INVALID_MESSAGE_TEXT', { text })
}
// 创建消息对象
const message = this.chat.createTextMessage({
to: groupID,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: { text },
})
// 发送消息
await this.chat.sendMessage(message)
console.log(LOG_PRIFIX, `群组消息发送成功: ${groupID}`)
return 0
} catch (error) {
console.error(LOG_PRIFIX, `群组消息发送失败: ${groupID}`, error)
throw new IMError('群组消息发送失败', 'SEND_GROUP_MESSAGE_FAILED', error)
}
}
发送私聊消息 - 一对一聊天
async sendPrivateTextMessage(userID: string, text: string): Promise<number> {
try {
// 参数验证
this.validateNotEmpty(userID)
this.confirmReady()
if (!text || typeof text !== 'string' || text.trim().length === 0) {
throw new IMError('消息内容不能为空', 'INVALID_MESSAGE_TEXT', { text })
}
// 创建私聊消息
const message = this.chat.createTextMessage({
to: userID,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: { text },
})
// 发送消息
await this.chat.sendMessage(message)
console.log(LOG_PRIFIX, `私聊消息发送成功: ${userID}`)
return 0
} catch (error) {
console.error(LOG_PRIFIX, `私聊消息发送失败: ${userID}`, error)
throw new IMError('私聊消息发送失败', 'SEND_PRIVATE_MESSAGE_FAILED', error)
}
}
消息接收处理 - 实时消息
private handleMessageReceived(event: { data: Message[] }): void {
try {
if (!event || !event.data || !Array.isArray(event.data)) {
console.warn(LOG_PRIFIX, '收到无效的消息事件:', event)
return
}
console.log('DEBUG_LOG:call handleMessageReceived', event.data)
// 处理每条消息
event.data.forEach(message => {
try {
const conversationType = message.conversationType
// 群组消息处理
if (conversationType === TencentCloudChat.TYPES.CONV_GROUP) {
// 群组消息处理逻辑
}
// 触发消息接收事件
this.emitter.emit(IMEventType.CHAT_MSG, message)
} catch (error) {
console.error(LOG_PRIFIX, '处理单条消息失败:', error, message)
}
})
} catch (error) {
console.error(LOG_PRIFIX, '处理消息事件失败:', error)
}
}
5. 历史消息系统
获取历史消息 - 消息记录
async getHistoryMessages(options: HistoryMessageOptions): Promise<{
messageList: Message[]
isCompleted: boolean
nextReqMessageID?: string
}> {
try {
this.confirmReady()
this.validateConversationID(options.conversationID)
const { conversationID, nextReqMessageID } = options
console.log(LOG_PRIFIX, `开始获取历史消息: ${conversationID}`, options)
// 构建获取参数
const getMessageListParams: any = {
conversationID: `${conversationID}`,
}
// 分页参数
if (nextReqMessageID) {
getMessageListParams.nextReqMessageID = nextReqMessageID
}
// 获取消息列表
const result = await this.chat.getMessageList(getMessageListParams)
console.log(LOG_PRIFIX, `获取历史消息成功: ${conversationID}`, {
messageList: result.data.messageList,
isCompleted: result.data.isCompleted,
nextReqMessageID: result.data.nextReqMessageID,
})
return {
messageList: result.data.messageList || [],
isCompleted: result.data.isCompleted,
nextReqMessageID: result.data.nextReqMessageID,
}
} catch (error) {
console.error(LOG_PRIFIX, `获取历史消息失败: ${options.conversationID}`, error)
throw new IMError('获取历史消息失败', 'GET_HISTORY_MESSAGES_FAILED', error)
}
}
获取最近消息 - 智能消息获取
async getRecentMessages(
conversationID: string,
targetCount: number = 50,
maxRetries: number = 10,
): Promise<Message[]> {
try {
this.confirmReady()
this.validateConversationID(conversationID)
if (targetCount <= 0) {
throw new IMError('目标消息数量必须大于0', 'INVALID_TARGET_COUNT', { targetCount })
}
if (maxRetries < 0) {
throw new IMError('最大重试次数不能为负数', 'INVALID_MAX_RETRIES', { maxRetries })
}
const allMessages: Message[] = []
let nextReqMessageID: string | undefined
let retryCount = 0
console.log(LOG_PRIFIX, `开始获取最近 ${targetCount} 条消息: ${conversationID}`)
// 循环获取消息直到达到目标数量
while (allMessages.length < targetCount) {
try {
const result = await this.getHistoryMessages({
conversationID,
nextReqMessageID,
})
// 如果没有获取到消息,退出循环
if (!result.messageList || result.messageList.length === 0) {
console.log(LOG_PRIFIX, `没有更多消息,停止获取: ${conversationID}`)
break
}
// 添加消息到列表
allMessages.push(...result.messageList)
console.log(LOG_PRIFIX, `已获取 ${allMessages.length} 条消息,目标: ${targetCount}`)
// 如果已经完成或没有下一页标识,退出循环
if (result.isCompleted || !result.nextReqMessageID) {
console.log(LOG_PRIFIX, `消息获取完成: ${conversationID}`)
break
}
// 设置下一页标识
nextReqMessageID = result.nextReqMessageID
} catch (error) {
retryCount++
console.warn(LOG_PRIFIX, `获取消息失败,重试第 ${retryCount} 次: ${conversationID}`, error)
// 如果达到最大重试次数,抛出异常
if (retryCount >= maxRetries) {
console.error(LOG_PRIFIX, `达到最大重试次数 ${maxRetries},停止重试: ${conversationID}`)
throw new IMError('获取消息重试次数超限', 'MAX_RETRIES_EXCEEDED', {
conversationID,
retryCount,
maxRetries,
error,
})
}
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount))
}
}
console.log(LOG_PRIFIX, `最终获取到 ${allMessages.length} 条消息: ${conversationID}`)
return allMessages
} catch (error) {
console.error(LOG_PRIFIX, `获取最近消息失败: ${conversationID}`, error)
throw new IMError('获取最近消息失败', 'GET_RECENT_MESSAGES_FAILED', error)
}
}
6. 状态监控系统
健康检查机制
// 启动健康检查
private startHealthCheck(): void {
// 心跳检查 - 每30秒
this.heartbeatInterval = setInterval(() => {
this.performHeartbeat()
}, 30000)
// 健康状态检查 - 每60秒
this.healthCheckInterval = setInterval(() => {
this.performHealthCheck()
}, 60000)
// 错误计数重置 - 每分钟
setInterval(() => {
this.errorCount = 0
}, this.errorWindow)
}
// 执行心跳检查
private performHeartbeat(): void {
const now = Date.now()
this.lastHeartbeat = now
// 更新状态
this.updateState({
lastHeartbeat: now,
})
// 检查连接状态
if (this.connectionState !== ConnectionState.CONNECTED) {
console.warn(LOG_PRIFIX, '心跳检查:连接状态异常', this.connectionState)
}
// 触发心跳事件
this.emitter.emit(IMEventType.HEARTBEAT, {
timestamp: now,
connectionState: this.connectionState,
networkState: this.networkState,
})
}
// 执行健康检查
private performHealthCheck(): void {
const healthStatus = {
connectionState: this.connectionState,
networkState: this.networkState,
isReady: this.ready,
isLoggedIn: this.isUserLoggedIn,
errorCount: this.errorCount,
reconnectAttempts: this.reconnectAttempts,
lastHeartbeat: this.lastHeartbeat,
timestamp: Date.now(),
}
console.log(LOG_PRIFIX, '健康检查状态:', healthStatus)
// 触发健康检查事件
this.emitter.emit(IMEventType.HEALTH_CHECK, healthStatus)
// 如果状态异常,触发警告
if (this.connectionState === ConnectionState.ERROR || this.errorCount > this.maxErrorCount / 2) {
this.emitter.emit(IMEventType.HEALTH_WARNING, healthStatus)
}
}
网络状态监控
private handleNetworkStateChanged(event: any): void {
const { state } = event.data
const previousState = this.networkState
const previousConnectionState = this.connectionState
// 使用腾讯云IM的网络状态常量
switch (state) {
case TencentCloudChat.TYPES.NET_STATE_CONNECTED:
this.networkState = NetworkState.WIFI
this.connectionState = ConnectionState.CONNECTED
this.reconnectAttempts = 0
this.errorCount = 0
console.log(LOG_PRIFIX, 'IM 网络已连接')
break
case TencentCloudChat.TYPES.NET_STATE_CONNECTING:
this.networkState = NetworkState.MOBILE
this.connectionState = ConnectionState.CONNECTING
console.log(LOG_PRIFIX, 'IM 网络正在连接...')
break
case TencentCloudChat.TYPES.NET_STATE_DISCONNECTED:
this.networkState = NetworkState.NONE
this.connectionState = ConnectionState.DISCONNECTED
console.warn(LOG_PRIFIX, 'IM 网络已断开')
this.handleDisconnection()
break
default:
this.networkState = NetworkState.UNKNOWN
this.connectionState = ConnectionState.ERROR
console.error(LOG_PRIFIX, 'IM 网络状态异常:', state)
}
// 更新状态
this.updateState({
networkState: this.networkState,
connectionState: this.connectionState,
reconnectAttempts: this.reconnectAttempts,
errorCount: this.errorCount,
})
// 触发网络状态变化事件
this.emitter.emit(IMEventType.NETWORK_STATE_CHANGED, {
previousState,
currentState: this.networkState,
})
// 触发连接状态变化事件
this.emitter.emit(IMEventType.CONNECTION_STATE_CHANGED, {
previousState: previousConnectionState,
currentState: this.connectionState,
reconnectAttempts: this.reconnectAttempts,
})
}
7. 群组权限管理
踢出群组成员 - 权限控制
public async kickGroupMember(
groupID: string,
memberIDList: string[],
operatorID?: string,
): Promise<KickMemberResult> {
try {
this.validateGroupID(groupID)
this.confirmReady()
// 如果没有指定操作者,使用当前登录用户
const currentOperatorID = operatorID || this.getCurrentUserID()
if (!currentOperatorID) {
return {
success: false,
message: '未找到当前用户ID',
errorCode: 10001,
}
}
// 检查权限
const hasPermission = await this.checkAdminPermission(groupID, currentOperatorID)
if (!hasPermission) {
return {
success: false,
message: '权限不足,只有群主和管理员可以踢人',
errorCode: 10002,
details: {
operatorID: currentOperatorID,
groupID,
requiredRoles: [GroupRole.OWNER, GroupRole.ADMIN],
},
}
}
// 检查是否尝试踢出群主
const groupInfo = await this.getGroupInfo(groupID)
const isKickingOwner = memberIDList.includes(groupInfo.ownerID)
if (isKickingOwner) {
return {
success: false,
message: '不能踢出群主',
errorCode: 10003,
details: { ownerID: groupInfo.ownerID },
}
}
// 检查是否尝试踢出管理员(只有群主可以踢出管理员)
const operatorRole = await this.getGroupMemberRole(groupID, currentOperatorID)
if (operatorRole !== GroupRole.OWNER) {
// 检查要踢出的用户中是否有管理员
const membersToKick = await Promise.all(memberIDList.map(userID => this.getGroupMemberRole(groupID, userID)))
const hasAdminInList = membersToKick.some(role => role === GroupRole.ADMIN)
if (hasAdminInList) {
return {
success: false,
message: '只有群主可以踢出管理员',
errorCode: 10004,
details: { operatorRole, targetRoles: membersToKick },
}
}
}
// 执行踢人操作
const result = await this.chat.deleteGroupMember({
groupID,
userIDList: memberIDList,
reason: '被管理员踢出群组',
})
console.log(LOG_PRIFIX, `踢出群组成员成功: ${groupID}`, {
operatorID: currentOperatorID,
memberIDList,
result,
})
return {
success: true,
message: `成功踢出 ${memberIDList.length} 名成员`,
details: result,
}
} catch (error) {
// 错误处理...
return {
success: false,
message: '踢出群组成员失败',
errorCode: 10000,
details: error,
}
}
}
8. Vue Composable 集成
useIMManager - Vue集成
export function useIMManager(userID?: string, userSig?: string) {
const imManager = IMManager.getInstance()
const state = ref<IMState>(imManager.getState())
try {
// 清理旧的事件监听器
imManager.offAllListeners()
// 设置状态变化监听
imManager.onStateChange((newState: IMState) => {
state.value = newState
})
// 组件挂载时初始化
onMounted(async () => {
try {
// 先登出
if (imManager.getLoginState()) {
await imManager.logout()
}
// 如果有 userID 和 userSig,则登录
if (userID && userSig) {
await imManager.login(userID, userSig)
}
} catch (error) {
console.error(LOG_PRIFIX, 'IM 初始化失败:', error)
throw new IMError('IM 初始化失败', 'INIT_FAILED', error)
}
})
// 组件卸载时清理资源
onUnmounted(() => {
imManager.offAllListeners()
})
} catch (error) {
console.error(LOG_PRIFIX, 'useIMManager 初始化失败:', error)
throw error
}
return {
imManager,
state: readonly(state),
// 便捷的状态访问方法
isReady: computed(() => state.value.isReady),
isLoggedIn: computed(() => state.value.isLoggedIn),
connectionState: computed(() => state.value.connectionState),
networkState: computed(() => state.value.networkState),
errorCount: computed(() => state.value.errorCount),
reconnectAttempts: computed(() => state.value.reconnectAttempts),
}
}
🎯 核心设计模式
1. 单例模式
- 确保全局只有一个IM实例
- 避免重复初始化和资源浪费
2. 观察者模式
- 使用EventEmitter进行事件通信
- 状态变化自动通知所有监听者
3. 策略模式
- 不同错误码采用不同处理策略
- 网络状态变化采用不同重连策略
4. 代理模式
- 封装腾讯云IM SDK的复杂API
- 提供更简单易用的接口
🚀 使用示例
// 在Vue组件中使用
const { imManager, isReady, isLoggedIn, connectionState } = useIMManager(userID, userSig)
// 监听事件
imManager.on(IMEventType.CHAT_MSG, (message) => {
console.log('收到消息:', message)
})
// 发送消息
await imManager.sendGroupTextMessage('group123', 'Hello World!')
// 加入群组
await imManager.joinGroup('group123')
这个IM管理器提供了完整的即时通讯功能,包括用户认证、群组管理、消息收发、状态监控等,是一个功能强大且易于使用的IM解决方案!🎉