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

男儿欲遂平生志,五经勤向窗前读。这篇文章主要讲述Vue+原生App混合开发手记#2 融云即时通讯相关的知识,希望能为你提供帮助。
最近开发的一个医药项目中要求加入即时通讯,最后选择了融云IM即时通讯服务,融云即时通讯包含android SDK,ios SDK以及Web SDK,为了节省开发时间,使用了Web SDK,这样在Android平台和iOS平台上都能表现一致。这是部分界面的效果,
分为两类用户,一类是医生,接受患者的咨询,一类是患者,可以与医生交流:

医生用户看到的界面 患者用户看到的界面 聊天界面
Vue+原生App混合开发手记#2 融云即时通讯

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

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

文章图片
 
获取App Key
首先进入融云官网,找到Web SDK开发指南,按照提示先注册一个账号,拿到AppKey:
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

 
使用即时通讯的用户都会有各自的token,所以接下来就是获取用户的token,由于所有页面都是由webview加载的,所以获取token这一部分交给后端完成即可,前端可以将其存入本地缓存中方便调取。
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

 
在vue-cli中使用融云SDK
官方提供了一份基于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="https://www.songbingjia.com/android/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 = thislet 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); } }

如果配置正确,这个页面会看到输出:
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

现在可以通过后台模拟用户发送消息,假设模拟用户2向用户1发送消息:
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

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

文章图片

发送成功后,会执行这一段代码:
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>

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

文章图片

上图是后台模拟消息发送后的效果,其中红色的圆圈代表未读消息的条数,点击后设置为已读,后面的文字是发送方最后一条发送的信息。至此,融云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的源码之后,发现融云在初始化以后对整个实例进行了缓存:
Vue+原生App混合开发手记#2 融云即时通讯

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

文章图片

只要不退出应用或者不刷新浏览器,这个_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); } }); }

同时这个方法还支持查看更多历史消息,只需要随便传一个参数就可以了。
 
收发消息
点击发送按钮后,会将消息发送给对方,自己的消息记录里也会多一条刚才自己发的消息
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

给自己的消息记录里添加一条记录比较容易实现:
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从而更新对方的消息记录。下图是最终效果:
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

 
  发送图片与自定义消息
融云提供了许多自定义消息的标识:
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

RC开头的都是官方定义的,也可以自定义格式用于处理不同的业务需求。个人觉得RC:TxtMsg已经可以满足大部分需求了,官方提供的消息格式中除了正文都有一个附加字段extra:
Vue+原生App混合开发手记#2 融云即时通讯

文章图片

这个extra里可以将图片的base64格式放在里面,大概可以发送不超过100K的图片base64格式(测试环境下),超过以后就会发送失败,100K已经是相当大的文本量了,感觉融云还是挺大方的。不过谨慎起见,我还是选择了先将待发送的图片传到图床,拿到其网络路径再塞到extra字段里。接收方拿到extra里的路径后用v-html指令可以将其转成图片。文章顶部示例图片中的“ 开药邀请” 也是通过将html塞进这个字段发送给对方的。剩下的就是双方的消息处里了,比如 在我的聊天界面 “ 我向对方发出了邀请” ,此时接收方应该提示 “ 对方向我发出了邀请,是否同意? 同意 拒绝” ,这种情况与普通的聊天内容不一样,两边的展示文本是不一样的,需要做进一步处理,然后再把处理后的内容统一存到dialogueList里展示出来,不仅是新发送的内容,在读取历史记录的时候也要处理。由于代码量太大,就不一一列举了。
 
总结
这个功能做了大概一周,期间踩了不少坑,而且网上的资料很少,遇到问题也不知道上哪问,不过好在最后都一一填平了,有惊无险。虽然不敢保证代码都没问题,但长期的加班已经让我心力交瘁,不想再测了,先把锅甩给测试,等她们反馈bug吧。
最后做了一个简单的 demo 方便以后回顾,只能发送文字消息,可以多设备同时收发信息。
【Vue+原生App混合开发手记#2 融云即时通讯】

    推荐阅读