uniapp接入融云IM

背景:在最近接的一个app项目中,需要有即时聊天的功能;经过技术选型后敲定使用融云IM

融云IM聊天会话、聊天记录默认存到本地,和微信同理

据我接入后经验来说,融云IM还是做的不错的,即时聊天的功能也一应俱全,但就是uniapp的文档写的很不友好。

版本: uniapp 5.x, 无UI集成(IMLib)

文档:https://doc.rongcloud.cn/im/uni-app/5.X/prepare

1. 安装相关插件、依赖

文档地址:https://doc.rongcloud.cn/im/uni-app/5.X/quick/include/web

github地址:https://github.com/rongcloud/rongcloud-uniapp-imlib (哪里不明白了,可以参考此)

  1. 去uniapp插件市场安装RCUnilM原生插件(我是自动安装,非手动安装此插件)

    RCUnilM原生插件地址:https://ext.dcloud.net.cn/plugin?id=6120

    插件市场

  2. manifest.json -> APP 原生插件配置 -> 加入原生插件 RCUniIM

    原生插件

  3. 制作自定义调试基座

    使用原生插件需要先制作基座, 制作完成后, 运行前 选刚刚打包好的基座哦

    运行基座

  4. 安装依赖包

    npm i @rongcloud/imlib-uni -S
    
  5. 项目集成引用

    import { init, connect } from '@rongcloud/imlib-uni'
    

2. 初始化IM、全局监听IM状态、连接IM

初始化IM

只需全局初始化一次IM 即可,切勿多次初始化,比如在用户登录后,或首页进行一次

appKey: 在融云开发者账号后台获取

import { init } from '@rongcloud/imlib-uni'
init('<Your-App-Key>');

全局监听IM状态

初始化完成后,应在建立连接之前添加事件监听器,及时获取相关事件通知!, 监听器仅支持添加一次,多次添加后前者会被覆盖。

我添加的监听有如下(还有许多监听状态可添加,见文档):

 // 连接状态监听
 addConnectionStatusListener(listener => {
     if (listener.data.status === 3 || listener.data.status === 4) {
         let _t = listener.data.status === 3 ? '您的账号在其他设备已登录' : '登录过期,请重新登录'
         uni.showToast({title: _t, icon: 'none'})
         uni.reLaunch({url: '/pages/login/login'})
     }
     if (listener.data.status === -1) {
         uni.showToast({title: "当前网络不可用"})
     }
     if (listener.data.status === 14) {
         uni.showToast({title: "连接超时"})
     }
     if (listener.data.status === 4) {
         uni.showToast({title: "Token 过期"})
     }
     if (listener.data.status === 0) {
         console.log("连接成功")
     }
     if (listener.data.status === 2) {
         console.log("未链接  去链接")
     }
 })
 
// 监听新消息
addReceiveMessageListener(message => {
    console.log(message)
}

连接IM

在登录成功后,接收到服务端传输来的用户token后建立IM连接

import { connect } from '@rongcloud/imlib-uni'
connect(this.selfUser.rcloudToken, (result) => {
    if (result.code === 0) {
    console.log('连接成功')
    // 获取历史会话、未读消息数量
    this.getConversation()
    this.getAllUnRead()
    } else {
        console.log('连接失败')
    }
})

3. 发送消息(文字、表情、多媒体)

表情也是文本,发送表情和文本是一样的,故先介绍如何引入表情

引入表情

此处参考:融云IM web版4.x 文档:https://doc.rongcloud.cn/im/Web/4.X/guide/private/msgmanage/msgsend/web#emoji

// 首先,在聊天室页面引入emoji插件
onLoad() {
    var config = {
        size: 52,
        url: '//f2e.cn.ronghub.com/sdk/emoji-48.png',
        lang: 'en',
        extension: {
            dataSource: {
            u1F914: { // 自定义 u1F914 对应的表情
                en: 'thinking face', // 英文名称
                zh: '思考', // 中文名称
                tag: '🤔', // 原生 Emoji
                position: '0 0' // 所在背景图位置坐标
            }
        },
        url: '//cdn.ronghub.com/thinking-face.png' // 新增 Emoji 背景图 url
        }
    };
    // 初始化
    RongIMEmoji.init(config);
    this.emoji = RongIMEmoji.list;
}

this.emoji是表情列表,拿此值渲染展示即可;(有的表情是一个字符 有的是两个)

发送文本消息

handleSend() {
    var self = this
    // 去除空格和换行
    let _s = self.draft.toString()
    _s = _s.replace(/[\r\n]/g,"");
    _s = _s.replace(/\ +/g,"");
    if (!_s || _s.length === 0) {    // 输入框为空不发送
        return
    }
    const msg = {
    conversationType: ConversationType.PRIVATE,// 会话类型
    targetId: self.friendAccount,// 目标ID
    content: {
        objectName: 'RC:TxtMsg',    // 消息类型
        userInfo: {
            userId: self.selfUser.rongCloudUserId,
            name: self.selfUser.nickName,
            portraitUrl: defaultAvatar,
        },
        content: self.draft,    // 内容
        extra: `附加信息`
        }
    }
    sendMessage(msg,(res) => {
        // 消息发送成功
        if (res.code === 0) {
            // 清空输入框
            self.draft = ''
        } else {
            uni.showToast("消息发送失败")
            console.log(res)
        }
    })
},

发送图片消息

// 选择图片
openPhoto() {
    var self = this
    uni.chooseImage({
        count: 9,
        sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
        sourceType: ['camera', 'album']
        success: function (res) {
        // 发送多媒体
        for(let i=0;i<res.tempFilePaths.length;i++){
            // 这一步重要,必须这样后才可以发送
            uni.getImageInfo({
                src: res.tempFilePaths[i],
                success: (imgInfo) => {
                    self.senMedia(imgInfo, res.tempFilePaths[i])
                }
                })
            }
        }
    })
},

// 发送
senMedia(imgInfo) {
    var self = this
    let avatar = this.selfUser.avatar ? this.selfUser.avatar : this.defaultAvatar
    const msg = {
        conversationType: ConversationType.PRIVATE,// 会话类型
        targetId: this.friendAccount,    // 对方融云账号
        content: {    
            objectName: 'RC:ImgMsg',    // 消息类型
            local: imgInfo.path,    // 图片地址
            userInfo: {
                userId: this.selfUser.rongCloudUserId,
                name: this.selfUser.nickName,
                portraitUrl: avatar,
            },
            extra: `附加信息`
        },
        pushContent: '[图片]',
        pushData: '',
    }
    
    sendMediaMessage(msg, {
        success: (messageId) => {
            // 发送成功回调
            console.log('发送成功回调', messageId)
        },
        progress: (progress, messageId) => {
            // 发送进度回调
            console.log('发送进度回调', messageId, progress)
        },
        cancel: (messageId) => {
            // 发送取消回调
            console.log('发送取消回调', messageId)
        },
        error: (errorCode, messageId) => {
            // 发送错误回调
            console.log('发送错误回调', messageId, errorCode)
        }
    })
}

4. 获取历史消息记录、聊天已读回执

在聊天页面中获取与对方的历史消息

 // 获取本地历史消息记录
 getLocaMessage() {
     const self = this
     const conversationType = ConversationType.PRIVATE // 会话类型
     const targetId = self.friendAccount    // 对方的融云账号
     const objectNames = ['RC:TxtMsg', 'RC:ImgMsg', 'RC:GIFMsg']    // 要获取的消息类型
     const timeStamp = new Date().getTime()
     const count = 1000 // 获取的消息数量
     const isForward = true // 是否向前获取
     getHistoryMessagesByTimestamp(conversationType, targetId, objectNames, timeStamp, count, isForward,
     (result) => {
     // 获取历史消息成功
         if(result.code === 0){
         result.messages.forEach(item => {
             // 根据自己显示需求,来调整该消息对象
             let obj = {}
             obj['objectName'] = item.objectName
             obj['content'] = item.content
             obj['sentTime'] = item.sentTime
             obj['messageId'] = item.messageId
             if (item.senderUserId === self.selfUser.rongCloudUserId) {
                 obj['who'] = 'me'
                 obj['avatar'] = this.selfUser.avatar || this.defaultAvatar
             } else {
                 obj['who'] = 'other'
                 obj['avatar'] = this.friendAvatar
             }
            self.history.unshift(obj)
         })
         // 聊天已读回执
         if (result.messages.length > 0) {
            self.setRead()
         }
        })
     })
 },
 
 
 // 聊天已读回执
 setRead() {
     const conversationType = ConversationType.PRIVATE // 会话类型
     const targetId = this.friendAccount        // 对方的融云账号
     const timestamp  = this.history[this.history.length - 1].sentTime
     // 已读回执
     sendReadReceiptMessage(conversationType, targetId, timestamp)
     // 清除当前会话未读数
     clearMessagesUnreadStatus(conversationType, targetId, timestamp, (res) =>{
     // 清除未读数成功
         console.log("清除未读数:::", res)
     })
 },

5. 获取历史会话记录及显示

直接获取,并显示即可 ps:用户头像昵称在发送消息时已包含到消息体中,渲染时候通过getWhogetAvatargetNickname对应获取

会话类型:https://doc.rongcloud.cn/im/uni-app/5.X/conversation/get/web

template

<view class="message" v-for="(m, index) in messageList" :key="index" @click="toChatPage(m)">
    <view class="avatar">
        <view class="count" v-if="m.unreadMessageCount > 0">{{ m.unreadMessageCount }}</view>
        <image :src="getAvatar(m)"></image>
    </view>
    <view class="content">
        <view class="title">
            <view class="username">{{getNickname(m)}}</view>
            <view class="time">{{$u.timeFrom(m.sentTime, 'yyyy年mm月dd日 hh:MM')}}</view>
        </view>
        <view class="detail">{{getWho(m)}}{{ m.objectName === 'RC:TxtMsg' ? m.latestMessage.content : '[图片]' }}</view>
    </view>
</view>

script

getConversation() {    
const conversationTypes = [ConversationType.PRIVATE] // 会话类型列表
const count = 1000 // 获取的会话数量
const timestamp = 0 // 会话的时间戳(获取这个时间戳之前的会话列表,0 表示从最新开始获取)
    getConversationList(conversationTypes, count, timestamp, (res) => {
        if (res.code === 0 && this.messageList !== res.conversations) {
            this.messageList = res.conversations
            uni.setStorageSync('messageList', this.messageList)
         }
    })
},

6. 监听新消息(好友请求、聊天信息)

思考:1. 监听到新消息后判断其类型,是聊天信息还是好友请求还是系统公告等

在监听新消息返回的对象中, 通过message.objectName字段可判断消息类型

聊天类型:https://doc.rongcloud.cn/rcloud/-/-/objectname

addReceiveMessageListener(message => {
    let data = message.data    // 新消息
    let chatType = ['RC:ImgMsg', 'RC:TxtMsg']    // 普通聊天信息类型
    if (data.message.objectName === 'RC:ContactNtf') {
        console.log("好友请求信息")
    } else if(chatType.includes(data.message.objectName)){
        console.log(普通聊天信息)
    }
})

思考:2. 监听到新消息后如何反馈给用户

通过手机振动+底部tabbar角标来反馈的(没有加下拉提示和顶部弹窗那种)

// 手机震动
uni.vibrateShort({success: function () {console.log('短震动---震动成功!');}})
uni.vibrateLong({success: function () {console.log('长震动--震动成功');}})
// 设置和移除底部tabbar角标
// index:tabbar索引   text:显示内容
uni.setTabBarBadge({index: 0, text: 10})

思考:3. 如何把当前新消息及时更新到当前聊天的界面

在监听到新消息时,主动设置vuex中特定字段, 并在聊天页面监听vuex中该字段的变化而同步都当前聊天界面(app同时只能打开一个页面)

// 获取到时候主动设置
this.$store.commit("setNewMessage", _message)
// 聊天页面监听获取
computed: {
    newMessage() {
        return this.$store.state.currentNewMessage
    },
}

watch: {
    newMessage(n, o) {
        if (n) {
            console.log("有信息")
        }
}

完整代码

listenForNewMessages() {
    // 监听新消息
    addReceiveMessageListener(message => {
        // 普通消息类型
        let chatType = ['RC:ImgMsg', 'RC:TxtMsg']
        let data = message.data    // 新消息
        if (data.message.objectName === 'RC:ContactNtf') {
            // 加好友请求
            if (data.message.content.operation === 'Request') {
                uni.vibrateShort({success: function () {console.log('短--震动成功!');}})
                // 设置底部tabbar角标 以提示
                uni.setTabBarBadge({index: 1, text: 1})
            }
            // 本地存储记录
            uni.setStorageSync('newFrienCount', _c)
        } else if(chatType.includes(data.message.objectName)){
            // 更新会话列表
            this.getConversation()
            let currenChat = this.$store.state.currenChat
            // 当前聊天页面者 不是此新消息发送者,则震动
            if (currenChat !== message.data.message.senderUserId) {
                uni.vibrateLong({success: function () {console.log('震动成功');}})
                uni.setStorageSync('messageUnreadCount', 1)
                // 设置底部tabbar角标 以提示
                uni.setTabBarBadge({index: 0, text: 1})
            } else {    // 当前聊天页面者 是此新消息发送者,同步信息到聊天页面
                let _message = {
                    objectName : data.message.objectName,
                    content :  data.message.content,
                    sentTime :  data.message.sentTime,
                    messageId :  data.message.messageId,
                    who :  'other',
                    avatar :  ''
                }
                this.$store.commit("setNewMessage", _message)
            }
        }
    })
},

zhanghaoran

2021.12.23