Vue+原生App混合开发手记#2 融云即时通讯

  最近开发的一个医药项目中要求加入即时通讯,最后选择了融云IM即时通讯服务,融云即时通讯包含Android SDK,iOS SDK以及Web SDK,为了节省开发时间,使用了Web SDK,这样在Android平台和iOS平台上都能表现一致。这是部分界面的效果,

分为两类用户,一类是医生,接受患者的咨询,一类是患者,可以与医生交流:

医生用户看到的界面 患者用户看到的界面 聊天界面
     

获取App Key

  首先进入融云官网,找到Web SDK开发指南,按照提示先注册一个账号,拿到AppKey:

使用即时通讯的用户都会有各自的token,所以接下来就是获取用户的token,由于所有页面都是由webview加载的,所以获取token这一部分交给后端完成即可,前端可以将其存入本地缓存中方便调取。

在vue-cli中使用融云SDK

扫描二维码关注公众号,回复: 4671624 查看本文章

  官方提供了一份基于vue的demo,参考后可以知道,需要在vue-cli项目中先引入SDK,找到index.html文件,添加本地SDK的引用(也可使用CDN):

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>融云WebSDK</title>
    <script src="./static/js/RongIMLib-2.3.4.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

官方已经写好了一个init.js来初始化,基本上拿过来直接用即可,由于是在vue-cli中使用,所以需要做一些小改动,我在 src/common/js 目录中新建了一个rongInit.js文件:

export default function init(params, callbacks, modules){    
  //省略的代码请参考init.js

  //开始链接
    RongIMClient.connect(token, {
        onSuccess: function(userId) {
            callbacks.getCurrentUser && callbacks.getCurrentUser({userId:userId});
            console.log("链接成功,用户id:" + userId);

            RongIMClient.getInstance().getConversationList({
                onSuccess: function (list) {
                    // list => 会话列表集合。
                    //这一行是新加的代码,用于更新更新会话列表
                    callbacks.updateList && callbacks.updateList(list)
                },
                onError: function (error) {
                    // do something...
                }
            }, null);

        },
        onTokenIncorrect: function() {
            console.log('token无效');
        },
        onError:function(errorCode){
            console.log(errorCode);
        }
    }, params.userId);
}

然后在会话列表页初始化融云:

import rongInit from '../common/js/rongInit'

created() {
    this.connectRongCloud()
},

methods: {
    //连接融云
    connectRongCloud() {
        let params = {
            appKey: this.appKey,
            token: this.token
        };

        let that = this

        let callbacks = {
            getInstance: function(instance) {
           window.rongInstance = instance   //将融云实例保存到全局对象
            },
            getCurrentUser: function(userInfo) {
                that.id = userInfo.userId
            },
            receiveNewMessage: function(message) { //接收到新消息
                that.$root.$data.newMsg = message
            },
            updateList: function(list) { //获取会话列表
                that.convrList = list
            }
        };

        rongInit(params, callbacks);
    }
} 

如果配置正确,这个页面会看到输出:

现在可以通过后台模拟用户发送消息,假设模拟用户2向用户1发送消息:

发送成功后,会执行这一段代码:

updateList: function(list) { //获取会话列表
    that.convrList = list
}

convrList保存的是该用户的会话列表,一旦数据发生变化,就会立即反映在页面上,这是Vue非常强大的特性,因此基本上什么都不用做,只要在页面上遍历convrList就可以了:

<ul class="list">
  <li v-for="item in convrList">
      <div>用户{{item.targetId}} <i v-show="item.unreadMessageCount > 0">{{item.unreadMessageCount}}</i></div>
      <div>{{item.latestMessage.content.content}}</div>
  </li>
</ul>

上图是后台模拟消息发送后的效果,其中红色的圆圈代表未读消息的条数,点击后设置为已读,后面的文字是发送方最后一条发送的信息。至此,融云SDK就基本整合进Vue里了,本篇文章主要梳理一下单聊这一部分,群聊、黑名单、消息撤回等高级功能暂不涉及。

浏览器前进后退时会话列表被清空的问题

  在继续下一步之前,先来踩个坑。在会话列表点击返回,再点击浏览器的前进按钮,会发现会话列表没了,初步估计是由于融云在整个使用过程中只会初始化一次,只要应用没有退出,融云的实例就一直在内存中存在,因此再次进入会话列表页面时,convrList就被还原成默认值了,而获取会话列表的代码并没有执行。如何销毁融云实例官网并没有说明,于是我采用了断开重连的方法:

//一定要disconnect,不然重新连接的代码不会执行
destroyed() {
    window.rongInstance.disconnect()
},

created() {
    if (!window.rongInstance) {
        this.connectRongCloud()
    } else {
        RongIMClient.reconnect({
            onSuccess: function(userId) {
                console.log("重新链接成功,用户id:" + userId);
                RongIMClient.getInstance().getConversationList({
                    onSuccess: function(list) {
                        //重新拿到会话列表
                        that.convrList = list
                    },
                    onError: function(error) {
                        // do something...
                    }
                },
                null);
            },
            onTokenIncorrect: function() {
                console.log('token无效');
            },
            onError: function(errorCode) {
                var info = '';
                switch (errorCode) {
                case RongIMLib.ErrorCode.TIMEOUT:
                    info = '超时';
                    break;
                case RongIMLib.ErrorCode.UNKNOWN_ERROR:
                    info = '未知错误';
                    break;
                case RongIMLib.ErrorCode.UNACCEPTABLE_PROTOCOL_VERSION:
                    info = '不可接受的协议版本';
                    break;
                case RongIMLib.ErrorCode.IDENTIFIER_REJECTED:
                    info = 'appkey不正确';
                    break;
                case RongIMLib.ErrorCode.SERVER_UNAVAILABLE:
                    info = '服务器不可用';
                    break;
                }
                console.log(info);
            }
        })
    }
}

在后续的开发中,我尝试在同一个应用中切换appKey,发现得到的是上一个appKey的数据,虽然在实际情况中应该不会出现,但万一出现了就是一个很严重的问题,相当于一个用户能拿到另一个用户的聊天记录。个人认为这个锅应该融云来背,因为在查看了SDK的源码之后,发现融云在初始化以后对整个实例进行了缓存:

只要不退出应用或者不刷新浏览器,这个_instance 总是驻留在内存中的,即使再次重连,还是会带着上一次的appKey连接,官方虽然提供了 instance.logout() 方法,但并没有清除 _instance ,可能官方觉得在使用期间appKey是不会变的。无论如何,个人认为这是一个漏洞,于是在退出登录时加上了如下方法:

window.RongIMClient.getInstance().logout()
window.RongIMClient._instance = null
window.rongInstance = null

其中第2行就是手动将_instance实例置为null,使其能被垃圾回收器回收,这样再次连接时,就会执行new RongIMClient() 方法,appKey也刷新了。

历史对话记录

  点击会话列表中的一条记录,就会跳转到与对方的会话详情。在这个页面需要将与对方之前的聊天记录读取并展示出来:

//获取历史对话记录
getHistoryDialogue(isContinuous) {
    let that = this
    var conversationType = RongIMLib.ConversationType.PRIVATE; //单聊,其他会话选择相应的消息类型即可。
    var targetId = this.targetId; // 想获取自己和谁的历史消息,targetId 赋值为对方的 Id。
    var timestrap = isContinuous ? null : 0; // 默认传 null,每次获取20条历史记录。 若从头开始获取历史消息,请赋值为 0 ,timestrap = 0;
    var count = 20; // 每次获取的历史消息条数,范围 0-20 条,可以多次获取。
    rongInstance.getHistoryMessages(conversationType, targetId, timestrap, count, {
        onSuccess: function (list, hasMsg) {
                if (isContinuous !== undefined) { //连续获取历史消息时追加数组
                    that.dialogueList = list.concat(that.dialogueList)
                } else { //只获取一次历史消息
                    that.dialogueList = list
                }

                that.hasMore = hasMsg
                    // list => Message 数组。
                    // hasMsg => 是否还有历史消息可以获取。

                console.log(list)
            },
            onError: function (error) {
                console.log("GetHistoryMessages,errorcode:" + error);
            }
    });
}

同时这个方法还支持查看更多历史消息,只需要随便传一个参数就可以了。

收发消息

  点击发送按钮后,会将消息发送给对方,自己的消息记录里也会多一条刚才自己发的消息

给自己的消息记录里添加一条记录比较容易实现:

updateConvrList(message) {
    this.dialogueList.push(message)
}

给对方的消息记录里添加一条记录则需要监听一个全局变量newMsg,然后在聊天界面里使用watch来监听它的变化

main.js

new Vue({
    el: '#app',
    router,
    data() {
        return {
            newMsg: null
        }
    },
    render: h => h(App)
})
watch: {
    '$root.$data.newMsg' (newVal, oldVal) {
        this.dialogueList.push(newVal)
    }
}

回顾一下之前的代码:

receiveNewMessage: function (message) { //接收到新消息
    that.$root.$data.newMsg = message
}

在接收到新消息时,融云脚本内部会执行 receiveNewMessage 方法,此时将新消息赋值给 newMsg ,就能触发watch从而更新对方的消息记录。下图是最终效果:

 发送图片与自定义消息

  融云提供了许多自定义消息的标识:

RC开头的都是官方定义的,也可以自定义格式用于处理不同的业务需求。个人觉得RC:TxtMsg已经可以满足大部分需求了,官方提供的消息格式中除了正文都有一个附加字段extra:

这个extra里可以将图片的base64格式放在里面,大概可以发送不超过100K的图片base64格式(测试环境下),超过以后就会发送失败,100K已经是相当大的文本量了,感觉融云还是挺大方的。不过谨慎起见,我还是选择了先将待发送的图片传到图床,拿到其网络路径再塞到extra字段里。接收方拿到extra里的路径后用v-html指令可以将其转成图片。文章顶部示例图片中的“开药邀请”也是通过将html塞进这个字段发送给对方的。剩下的就是双方的消息处里了,比如 在我的聊天界面 “我向对方发出了邀请” ,此时接收方应该提示 “对方向我发出了邀请,是否同意? 同意 拒绝” ,这种情况与普通的聊天内容不一样,两边的展示文本是不一样的,需要做进一步处理,然后再把处理后的内容统一存到dialogueList里展示出来,不仅是新发送的内容,在读取历史记录的时候也要处理。由于代码量太大,就不一一列举了。

总结

  这个功能做了大概一周,期间踩了不少坑,而且网上的资料很少,遇到问题也不知道上哪问,不过好在最后都一一填平了,有惊无险。虽然不敢保证代码都没问题,但长期的加班已经让我心力交瘁,不想再测了,先把锅甩给测试,等她们反馈bug吧。

  最后做了一个简单的 demo 方便以后回顾,只能发送文字消息,可以多设备同时收发信息。

猜你喜欢

转载自www.cnblogs.com/undefined000/p/vue-cli-rongcloud.html