uniapp 實現騰訊云 IM 消息撤回功能實戰指南
一、功能實現原理
騰訊云 IM 的消息撤回功能通過 消息修訂(Message Revision) 機制實現,核心流程如下:
- 發送方調用撤回 API 刪除指定消息
- 云端生成撤回通知消息(類型為
TIM.TYPES.MSG_REVOKED
) - 接收方收到通知后執行本地消息刪除
- 全平臺自動同步消息狀態(需開啟消息漫游)
二、核心實現步驟
1. 發送方撤回邏輯
// services/im.js
export async function revokeMessage(message) {const tim = initIM()try {// 執行消息撤回操作const res = await tim.revokeMessage(message)// 更新本地消息狀態(立即生效)if (res.data.revokeMessage) {const conv = tim.getConversationProfile(message.conversationID)conv.setMessageRevoked(message.clientMsgID)}return res} catch (error) {console.error('撤回失敗:', error)throw new Error('消息撤回失敗,請檢查網絡')}
}
2. 接收方消息處理
// 消息監聽器(全局注冊)
export function setupMessageListener(callback) {const tim = initIM()tim.on(tim.EVENT.MESSAGE_RECEIVED, (event) => {event.data.forEach(msg => {// 處理撤回通知if (msg.type === tim.TYPES.MSG_REVOKED) {handleRevokeNotice(msg)return}callback(msg)})})
}// 撤回通知處理
function handleRevokeNotice(notice) {const { revokedMessageClientMsgID, operator } = notice.payload// 查找本地對應消息const conversation = tim.getConversationProfile(notice.conversationID)const originalMsg = conversation.getMessage(revokedMessageClientMsgID)if (!originalMsg) return// 權限驗證(僅允許發送者撤回)if (originalMsg.from !== operator.userID) {console.warn('非法撤回操作', operator)return}// 執行本地刪除conversation.deleteMessage(revokedMessageClientMsgID)// 觸發UI更新(示例)uni.$emit('message-revoked', {conversationID: notice.conversationID,clientMsgID: revokedMessageClientMsgID})
}
3. UI 層集成示例
<template><view class="message-list"><view v-for="(msg, index) in messages":key="msg.clientMsgID"class="message-item"><!-- 消息內容 --><template v-if="!msg.isRevoked">{{ msg.payload.text }}</template><!-- 撤回提示 --><view v-else class="revoked-tip">"{{ msg.payload.description }}" 已被撤回</view><!-- 長按操作菜單 --><view v-if="canRevoke(msg)"class="action-menu"@longpress="showActionSheet(msg)">?</view></view></view>
</template><script>
export default {data() {return {messages: []}},methods: {// 權限校驗canRevoke(msg) {return msg.from === this.currentUser.userID && !msg.isRevoked &&Date.now() - msg.time < 2 * 60 * 1000 // 2分鐘內可撤回},// 執行撤回async handleRevoke(msg) {try {await revokeMessage(msg)uni.showToast({ title: '撤回成功', icon: 'none' })} catch (error) {uni.showToast({ title: error.message, icon: 'none' })}}}
}
</script>
三、關鍵問題處理
1. 撤回時間限制
// 配置中心(建議)
const IM_CONFIG = {REVOKE_TIME_LIMIT: 2 * 60 * 1000 // 2分鐘
}// 權限校驗時使用
if (Date.now() - msg.time > IM_CONFIG.REVOKE_TIME_LIMIT) {throw new Error('超過可撤回時間')
}
2. 消息狀態同步
// 消息漫游配置(初始化時)
tim = TIM.create({SDKAppID: config.SDKAppID
})// 開啟消息漫游(需在控制臺配置)
tim.setMessageRevokeMode({mode: TIM.TYPES.REVOKE_MODE_SENDER, // 僅發送方可撤回syncOtherMachine: true // 同步到其他端
})
3. 異常場景處理
// 撤回失敗重試機制
export async function revokeWithRetry(msg, retries = 3) {try {return await revokeMessage(msg)} catch (error) {if (retries <= 0) throw errorawait new Promise(resolve => setTimeout(resolve, 1000))return revokeWithRetry(msg, retries - 1)}
}
四、高級功能擴展
1. 富媒體消息撤回
// 自定義撤回描述(圖片/文件等)
function getRevokeDescription(msg) {switch(msg.type) {case TIM.TYPES.MSG_IMAGE:return '[圖片]'case TIM.TYPES.MSG_FILE:return '[文件]'case TIM.TYPES.MSG_CUSTOM:return JSON.parse(msg.payload.data).description || '[自定義消息]'default:return msg.payload.text || '[未知消息]'}
}
2. 撤回動畫效果
/* 添加CSS過渡 */
.message-item.revoking {animation: fadeOut 0.3s forwards;
}@keyframes fadeOut {to {opacity: 0;transform: translateX(20px);}
}
3. 服務端日志記錄
// 撤回事件上報(示例)
async function logRevokeEvent(msg, operator) {await axios.post('/api/im/revoke-log', {sdk_app_id: process.env.SDKAppID,group_id: msg.groupID,operator_id: operator.userID,target_msg_id: msg.clientMsgID,timestamp: Date.now()})
}
五、常見問題排查
-
Q: 撤回后對方仍顯示消息
A: 檢查消息漫游是否開啟,確認雙方客戶端版本 ≥ 2.15.0 -
Q: 無法撤回超過2分鐘的消息
A: 騰訊云默認限制為2分鐘,需在控制臺申請延長權限 -
Q: 群聊中非群主成員撤回失敗
A: 確認群類型是否為 Private(私有群),Public 群需群主操作 -
Q: 撤回通知不顯示描述
A: 檢查自定義消息解析邏輯,確保 payload 格式正確
六、性能優化建議
- 使用
tim.getMessageRevokeStatus()
批量查詢消息狀態 - 對已撤回消息進行本地緩存,避免重復查詢
- 添加防抖處理,防止快速連續撤回導致性能問題