1. 程式人生 > >Vue+原生App混合開發手記#2 融雲即時通訊

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

  官方提供了一份基於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 方便以後回顧,只能傳送文字訊息,可以多裝置同時收發資訊。