Vue+原生App混合開發手記#2 融雲即時通訊
最近開發的一個醫藥專案中要求加入即時通訊,最後選擇了融雲IM即時通訊服務,融雲即時通訊包含Android SDK,iOS SDK以及Web SDK,為了節省開發時間,使用了Web SDK,這樣在Android平臺和iOS平臺上都能表現一致。這是部分介面的效果,
分為兩類使用者,一類是醫生,接受患者的諮詢,一類是患者,可以與醫生交流:
醫生使用者看到的介面 | 患者使用者看到的介面 | 聊天介面 |
獲取App Key
首先進入融雲官網,找到
使用即時通訊的使用者都會有各自的token,所以接下來就是獲取使用者的token,由於所有頁面都是由webview載入的,所以獲取token這一部分交給後端完成即可,前端可以將其存入本地快取中方便調取。
在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="./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 方便以後回顧,只能傳送文字訊息,可以多裝置同時收發資訊。