微信小程序之環(huán)信接入

IM 聊天接入思考過程

前期

初識IM聊天

帶著問題去調(diào)研

  • 必須接入環(huán)信嗎?除了環(huán)信是否可以接入其他即時通信?
  • 環(huán)信目前有哪些功能呢?支持微信小程序嗎棋嘲?
  • 如何接入小程序呢?

調(diào)研分析

  1. 必須接入環(huán)信嗎矩桂?除了環(huán)信是否可以接入其他即時通信?

現(xiàn)狀: 微信小程序API 提供了WebSocket 方法沸移。
擴展: 如果服務(wù)端支持scoket通信,ios\android\H5 也全都支持Im聊天了
備注:專業(yè)第三方Im有融云、環(huán)信、云之訊等雹锣,底層實現(xiàn)均是基于scoket 通信网沾。明白scoket通信后也可以自己寫即時通信。

  1. 環(huán)信目前有哪些功能呢蕊爵?支持微信小程序嗎辉哥?
    錯誤想法: 環(huán)信就是做im聊天的,咱們上去按照接入文檔攒射,開發(fā)就能搞定4椎!会放!

這種想法是很致命的饲齐。在所有的第三方組件接入中,如果我們不能跳出來看待問題鸦概,只是為了完成任務(wù)而完成任務(wù)箩张。那么我們永遠是最底層的低級碼農(nóng)。
環(huán)信目前是同行業(yè)里面做的算不錯的窗市。那么他的官網(wǎng)先慷、接入規(guī)范都應(yīng)該有的。微信小程序也是支持的咨察。在后面小編會帶領(lǐng)大家一切怎么去閱讀一個官網(wǎng)

  1. 如何接入小程序论熙?

接入小程序是否需要申請一個賬號呢?我直接運行他們的demo可以嗎摄狱? 怎么去測試呢? 此時我們可以有很多的猜想脓诡。我認為在開始接入之前我們應(yīng)該很好的進行一些思考,答案顯而易見媒役。

環(huán)信接入思考篇

快即時慢

在工作中祝谚,大家會經(jīng)常遇到第三方組件的接入。當(dāng)接收到任務(wù)后酣衷,為了盡快完成任務(wù)交惯。上來就google,找攻略穿仪,找技巧席爽。往往認為這樣做速度是最快的。結(jié)果適得其反啊片,做了很多無用的功只锻。我們意識中的快,結(jié)果卻變成了慢

慢即時快

image

逆向思維: 任何一個第三方的組件紫谷,特別是一個大點的平臺齐饮,他們?yōu)榱送瞥鲎约旱漠a(chǎn)品捐寥,一定會有各種各樣的功能支持,接入文檔說明沈矿。我們放慢速度上真,將這些資源用上半天的時間進行簡單的梳理咬腋。后期的開發(fā)進度會有很大的提升羹膳。
上圖是我在接入環(huán)信Im后進行的反思。因為在接入環(huán)信之前根竿,其他團隊成員用了很長的時間聯(lián)調(diào)陵像。假如他們在接入環(huán)信聊天之前,了解環(huán)信擁有自己的后臺寇壳,可以直接給用戶端發(fā)送測試消息;可以直接創(chuàng)建用戶醒颖、創(chuàng)建聊天室、創(chuàng)建群組壳炎。他們還會花費那么久的時間去聯(lián)調(diào)嗎?完全不用依賴服務(wù)端泞歉。不用依賴ios,依賴android匿辩。自己使用環(huán)信后臺腰耙,輕輕松松完成各種測試。

環(huán)信接入

  1. 環(huán)信官網(wǎng)注冊自己的即時通訊云铲球,并登陸后臺


    image
  2. 創(chuàng)建自己的應(yīng)用挺庞,并記錄關(guān)鍵信息


    image

    以下是關(guān)鍵信息哦!<诓 选侨!


    image

備注:

  1. 應(yīng)用標識 應(yīng)用接入時會使用
  2. IM 用戶 可以創(chuàng)建、刪除用戶然走、發(fā)送消息
  3. 群組 可以創(chuàng)建援制、刪除群組信息、發(fā)送消息
  4. 聊天室 可以創(chuàng)建芍瑞、刪除聊天室晨仑、發(fā)送消息
    tip 通過這個后臺管理系統(tǒng),就可以玩轉(zhuǎn)環(huán)信的接入測試了啄巧。
  1. 從環(huán)信下載小程序demo寻歧,替換 appkey 進行聯(lián)調(diào)測試


    image
  2. 測試走起
  1. 用戶測試 在環(huán)信后臺創(chuàng)建用戶,在小程序端登錄 (用戶demo1 密碼:123456)


    image
  2. 一對一會話測試
    ① 在環(huán)信后臺創(chuàng)建用戶demo2
    ② 點擊操作秩仆,查看用戶好友將demo1和demo2 添加為好友码泛。
    ③ 在小程序端用demo1給demo2發(fā)送測試消息。
    ④ 退出demo1用戶澄耍,登錄demo2查看是否會接收到demo1發(fā)送的會話


    image

由于環(huán)信工程師們相信碼農(nóng)的實力噪珊,在群組測試和聊天室測試這塊為大家留下了想象空間晌缘。demo 中群組測試和聊天室測試為明確寫出。讓我繼續(xù)帶大家飛

  1. 群組測試
    ① 創(chuàng)建群組記錄群組id痢站,并給群組添加成員(demo2)


    image

    ② 環(huán)信后臺給群組發(fā)送測試消息


    image

    ③ 控制臺能收到群組測試消息磷箕,怎么展示呢? 請閱讀源碼解析篇
  2. 聊天室測試
    ① 創(chuàng)建聊天室記錄聊天室id,將demo1 設(shè)置為超級管理員,demo2設(shè)置為管理員
    ② 聊天室這里沒有聊天室消息的發(fā)送阵难。請閱讀源碼解析篇

通過以上4個簡單的測試岳枷,android、ios呜叫、h5空繁、小程序的聊天測試均可以參照以上4點進行順利的測試。初期就此結(jié)束朱庆。下面帶代價進行源碼的解析

中期

看源碼前期思考

image

核心源碼閱讀

image

以上是環(huán)信sdk 基礎(chǔ)代碼結(jié)構(gòu)盛泡。 通過簡單閱讀會發(fā)現(xiàn):

  1. 環(huán)信的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有對應(yīng)的scoket通信)
  2. 環(huán)信的api包裝在connection.js 組件中娱颊,如果某些api沒有傲诵,咱們可以擴展connection 中的方法

環(huán)信核心代碼閱讀完成后,發(fā)現(xiàn)沒有涉及到緩存箱硕∷┲瘢看來緩存的處理是在對應(yīng)的業(yè)務(wù)邏輯中。

設(shè)想:

  1. 消息應(yīng)該在哪里緩存
  2. 哪里進行會話鏈接的監(jiān)聽注冊

環(huán)信demo 代碼閱讀

會話颅痊、群組

通過前面提到的方式殖熟,大家可以在小程序控制臺抓取到用戶收到的會話和群組消息

會話

app.js

環(huán)信scoket 注冊監(jiān)聽代碼在app.js 中
核心代碼如下:

{
        //調(diào)用API從本地緩存中獲取數(shù)據(jù)
        var that = this
        var logs = wx.getStorageSync('logs') || []
        logs.unshift(Date.now())
        wx.setStorageSync('logs', logs)

        WebIM.conn.listen({
            onOpened: function (message) {//連接成功回調(diào)
            // 如果isAutoLogin設(shè)置為false,那么必須手動設(shè)置上線斑响,否則無法收消息
            // 手動上線指的是調(diào)用conn.setPresence(); 如果conn初始化時已將isAutoLogin設(shè)置為true
            // 則無需調(diào)用conn.setPresence();             
                WebIM.conn.setPresence()
            },
            onPresence: function (message) { //處理“廣播”或“發(fā)布-訂閱”消息菱属,如聯(lián)系人訂閱請求、處理群組舰罚、聊天室被踢解散等消息
                switch(message.type){
                    case "unsubscribe":
                        pages[0].moveFriend(message);
                        break;
                    case "subscribe":
                        if (message.status === '[resp:true]') {
                            return
                        } else {
                            pages[0].handleFriendMsg(message)
                        }
                        break;
                    case "joinChatRoomSuccess":
                        console.log('Message: ', message);
                        wx.showToast({
                            title: "JoinChatRoomSuccess",
                        });
                        break;
                    case "memberJoinChatRoomSuccess":
                        console.log('memberMessage: ', message);
                        wx.showToast({
                            title: "memberJoinChatRoomSuccess",
                        });
                        break;
                    case "memberLeaveChatRoomSuccess":
                        console.log("LeaveChatRoom");
                        wx.showToast({
                            title: "leaveChatRoomSuccess",
                        });
                        break;
                }
            },
            onRoster: function (message) { //處理好友申請
                var pages = getCurrentPages()
                if (pages[0]) {
                    pages[0].onShow()
                }
            },

            onVideoMessage: function(message){ //視頻處理
                console.log('onVideoMessage: ', message);
                var page = that.getRoomPage()
                if (message) {
                    if (page) {
                        page.receiveVideo(message, 'video')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'video',
                                data: message.url
                            },
                            style: '',
                            time: time,
                            mid: 'video' + message.id
                        }
                        msgData.style = ''
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },

            onAudioMessage: function (message) { // 音頻處理
                console.log('onAudioMessage', message)
                var page = that.getRoomPage()
                console.log(page)
                if (message) {
                    if (page) {
                        page.receiveMsg(message, 'audio')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'audio',
                                data: value
                            },
                            style: '',
                            time: time,
                            mid: 'audio' + message.id
                        }
                        console.log("Audio msgData: ", msgData);
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },

            onLocationMessage: function (message) { // 收到位置信息
                console.log("Location message: ", message);
            },

            onTextMessage: function (message) {//收到文本消息
                var page = that.getRoomPage()
                console.log(page)
                if (message) {
                    if (page) {
                        page.receiveMsg(message, 'txt')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'txt',
                                data: value
                            },
                            style: '',
                            time: time,
                            mid: 'txt' + message.id
                        }
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },
            onEmojiMessage: function (message) { //收到表情信息
                //console.log('onEmojiMessage',message)
                var page = that.getRoomPage()
                //console.log(pages)
                if (message) {
                    if (page) {
                        page.receiveMsg(message, 'emoji')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'emoji',
                                data: message.data
                            },
                            style: '',
                            time: time,
                            mid: 'emoji' + message.id
                        }
                        msgData.style = ''
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || [] //tip 從本地緩存中獲取用戶的消息  發(fā)消息+來源 適用于單人會話 msgData.yourname + message.to+當(dāng)前登錄人 群組/聊天室
                         
                        chatMsg.push(msgData)
                        //console.log(chatMsg)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },
            onPictureMessage: function (message) {//收到圖片信息
                //console.log('Picture',message);
                var page = that.getRoomPage()
                if (message) {
                    if (page) {
                        //console.log("wdawdawdawdqwd")
                        page.receiveImage(message, 'img')
                    } else {
                        var chatMsg = that.globalData.chatMsg || []
                        var time = WebIM.time()
                        var msgData = {
                            info: {
                                from: message.from,
                                to: message.to
                            },
                            username: message.from,
                            yourname: message.from,
                            msg: {
                                type: 'img',
                                data: message.url
                            },
                            style: '',
                            time: time,
                            mid: 'img' + message.id
                        }
                        msgData.style = ''
                        chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
                        chatMsg.push(msgData)
                        wx.setStorage({
                            key: msgData.yourname + message.to,
                            data: chatMsg,
                            success: function () {
                                //console.log('success')
                            }
                        })
                    }
                }
            },
            // 各種異常
            onError: function (error) {
                // 16: server-side close the websocket connection
                if (error.type == WebIM.statusCode.WEBIM_CONNCTION_DISCONNECTED) {
                    if (WebIM.conn.autoReconnectNumTotal < WebIM.conn.autoReconnectNumMax) {
                        return;
                    }

                    wx.showToast({
                        title: 'server-side close the websocket connection',
                        duration: 1000
                    });
                    wx.redirectTo({
                        url: '../login/login'
                    });
                    return;
                }

                // 8: offline by multi login
                if (error.type == WebIM.statusCode.WEBIM_CONNCTION_SERVER_ERROR) {
                    wx.showToast({
                        title: 'offline by multi login',
                        duration: 1000
                    })
                    wx.redirectTo({
                        url: '../login/login'
                    })
                    return;
                }
            },
        })

        
    }

實際開發(fā)過程中纽门,在微信中,退出小程序赏陵,重新進入時,webscoket 通信并沒有重新創(chuàng)建鏈接饲漾。存在用戶收到不到消息的情況蝙搔。可以將以上代碼封裝考传,例如addHXLIstener(...)吃型。當(dāng)用戶重新打開后,再次注冊環(huán)信監(jiān)聽即可僚楞。

環(huán)信登錄 例如 initLoginHX();

    var uin=wx.getStorageSync('hxuin');
    var pwd=wx.getStorageSync('hxpwd');
    console.log('initHX:' + uin+"||"+pwd);
    var options = {
      apiUrl: '服務(wù)器url',
      user: '用戶名',// 用戶名要是字符
      pwd: '密碼',
      grant_type: 'password',
      appKey: 'appkey',
      success: function (res) {
        console.log("環(huán)信創(chuàng)建連接成功")
      },
      error: function (res) {
        console.log("環(huán)信創(chuàng)建連接失敗")
      }
    };
    WebIM.conn.open(options);

chat 會話

環(huán)信的會話列表存儲在本地勤晚,并沒有調(diào)用服務(wù)器端數(shù)據(jù)

 var that = this
        var member = wx.getStorageSync('member')
        var myName = wx.getStorageSync('myUsername')
        var array = []
        for (var i = 0; i < member.length; i++) {
            if (wx.getStorageSync(member[i].name + myName) != '') {
                array.push(wx.getStorageSync(member[i].name + myName)[wx.getStorageSync(member[i].name + myName).length - 1])
            }
        }
        //console.log(array枉层,'1')
        this.setData({
            arr: array
        })

通過以上代碼得出結(jié)論: 環(huán)信的會話是通過遍歷用戶id+對方id 構(gòu)成的數(shù)據(jù)。

那群組和聊天室的怎么處理呢赐写?
環(huán)信小程序demo中只提供了聊天室列表的獲取接口我們可以輕松實現(xiàn)聊天室列表鸟蜡,并沒有提供群組列表的獲取方式。我們需要在conection中擴展調(diào)用群組列表的接口挺邀,來實現(xiàn)群組列表揉忘。參照聊天室列表獲取即可實現(xiàn)。聊天室列表實現(xiàn)方式如下:

connection.prototype.getChatRooms = function (options) {

    var conn = this,
        token = options.accessToken || this.context.accessToken;

    if (token) {
        var apiUrl = this.apiUrl;
        var appName = this.context.appName;
        var orgName = this.context.orgName;

        if (!appName || !orgName) {
            conn.onError({
                type: _code.WEBIM_CONNCTION_AUTH_ERROR
            });
            return;
        }

        var suc = function (data, xhr) {
            typeof options.success === 'function' && options.success(data);
        };

        var error = function (res, xhr, msg) {
            if (res.error && res.error_description) {
                conn.onError({
                    type: _code.WEBIM_CONNCTION_LOAD_CHATROOM_ERROR,
                    msg: res.error_description,
                    data: res,
                    xhr: xhr
                });
            }
        };

        var pageInfo = {
            pagenum: parseInt(options.pagenum) || 1,
            pagesize: parseInt(options.pagesize) || 20
        };
        // 想要實現(xiàn)群組列表悠夯,修改對應(yīng)接口即可
        var opts = {
            url: apiUrl + '/' + orgName + '/' + appName + '/chatrooms',
            dataType: 'json',
            type: 'GET',
            header: {'Authorization': 'Bearer ' + token},
            data: pageInfo,
            success: suc || _utils.emptyfn,
            fail: error || _utils.emptyfn
        };
        wx.request(opts);
    } else {
        conn.onError({
            type: _code.WEBIM_CONNCTION_TOKEN_NOT_ASSIGN_ERROR
        });
    }

chatroom

從本地緩存中獲取聊天記錄癌淮,并展示

// 環(huán)信demo 發(fā)送消息

 sendMessage: function () {

        if (!this.data.userMessage.trim()) return;


        var that = this
        // //console.log(that.data.userMessage)
        // //console.log(that.data.sendInfo)
        var myName = wx.getStorageSync('myUsername')
        var id = WebIM.conn.getUniqueId();
        var msg = new WebIM.message('txt', id);
        msg.set({
            msg: that.data.sendInfo,
            to: that.data.yourname,
            roomType: false,
            success: function (id, serverMsgId) {
                console.log('send text message success')
            }
        });
        // //console.log(msg)
        console.log("Sending textmessage")
        msg.body.chatType = 'singleChat'; // 群組聊天 groupRoom
        WebIM.conn.send(msg.body);
        // 消息發(fā)送完成

        if (msg) {
            var value = WebIM.parseEmoji(msg.value.replace(/\n/mg, '')) // 環(huán)信表情處理
            var time = WebIM.time()
            var msgData = {
                info: {
                    to: msg.body.to
                },
                username: that.data.myName,
                yourname: msg.body.to,
                msg: {
                    type: msg.type,
                    data: value
                },
                style: 'self',
                time: time,
                mid: msg.id
            }
            that.data.chatMsg.push(msgData)
            // console.log(that.data.chatMsg)
            
            // 存儲聊天記錄 
            // 注: 單獨單聊天 key 對方環(huán)信uin+自己的uin
            // 注: 群組聊天 key 群組id\聊天室id+對方環(huán)信uin+自己的uin
            wx.setStorage({
                key: that.data.yourname + myName,
                data: that.data.chatMsg,
                success: function () {
                    //console.log('success', that.data)
                    that.setData({
                        chatMsg: that.data.chatMsg,
                        emojiList: [],
                        inputMessage: ''
                    })
                    setTimeout(function () {
                        that.setData({
                            toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
                        })
                    }, 100)
                }
            })
            that.setData({
                userMessage: ''
            })
        }
    },

// 環(huán)信demo 收到消息
    receiveMsg: function (msg, type) {
        var that = this
        var myName = wx.getStorageSync('myUsername')
        if (msg.from == that.data.yourname || msg.to == that.data.yourname) {
            if (type == 'txt') {
                var value = WebIM.parseEmoji(msg.data.replace(/\n/mg, ''))
            } else if (type == 'emoji') {
                var value = msg.data
            } else if(type == 'audio'){
                // 如果是音頻則請求服務(wù)器轉(zhuǎn)碼
                console.log('Audio Audio msg: ', msg);
                var token = msg.accessToken;
                console.log('get token: ', token)
                var options = {
                    url: msg.url,
                    header: {
                        'X-Requested-With': 'XMLHttpRequest',
                        'Accept': 'audio/mp3',
                        'Authorization': 'Bearer ' + token
                    },
                    success: function(res){
                        console.log('downloadFile success Play', res);
                        // wx.playVoice({
                            // filePath: res.tempFilePath
                        // })
                        msg.url = res.tempFilePath
                        var msgData = {
                            info: {
                                from: msg.from,
                                to: msg.to
                            },
                            username: '',
                            yourname: msg.from,
                            msg: {
                                type: type,
                                data: value,
                                url: msg.url
                            },
                            style: '',
                            time: time,
                            mid: msg.type + msg.id
                        }

                        if (msg.from == that.data.yourname) {
                            msgData.style = ''
                            msgData.username = msg.from
                        } else {
                            msgData.style = 'self'
                            msgData.username = msg.to
                        }

                        var msgArr = that.data.chatMsg;
                        msgArr.pop();
                        msgArr.push(msgData);

                        that.setData({
                            chatMsg: that.data.chatMsg,
                        })
                        console.log("New audio");
                    },
                    fail: function(e){
                        console.log('downloadFile failed', e);
                    }
                };
                console.log('Download');
                wx.downloadFile(options);
            }
            //console.log(msg)
            //console.log(value)
            var time = WebIM.time()
            var msgData = {
                info: {
                    from: msg.from,
                    to: msg.to
                },
                username: '',
                yourname: msg.from,
                msg: {
                    type: type,
                    data: value,
                    url: msg.url
                },
                style: '',
                time: time,
                mid: msg.type + msg.id
            }
            console.log('Audio Audio msgData: ', msgData);
            if (msg.from == that.data.yourname) {
                msgData.style = ''
                msgData.username = msg.from
            } else {
                msgData.style = 'self'
                msgData.username = msg.to
            }
            //console.log(msgData, that.data.chatMsg, that.data)
            that.data.chatMsg.push(msgData)

            // 存儲聊天記錄 
            // 注: 單獨單聊天 key 對方環(huán)信uin+自己的uin
            // 注: 群組聊天 key 群組id\聊天室id+對方環(huán)信uin+自己的uin

            wx.setStorage({
                key: that.data.yourname + myName,
                data: that.data.chatMsg,
                success: function () {
                    if(type == 'audio')
                        return;
                    //console.log('success', that.data)
                    that.setData({
                        chatMsg: that.data.chatMsg,
                    })
                    setTimeout(function () {
                        that.setData({
                            toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
                        })
                    }, 100)
                }
            })
        }
    },

環(huán)信聊天頁面躺坟,聊天數(shù)據(jù)全部存儲在緩存當(dāng)中沦补,跟進聊天類型的不同,主要需要調(diào)整緩存的key咪橙。詳情如下:

  1. 單對單聊天 對方uin+自己的uin
  2. 群組聊天(針對某個商品夕膀,不需要好友關(guān)系,只需要臨時聊天) 群組id+對方uin+自己的uin
  3. 聊天室(同群組聊天)

問題大雜燴

  1. 群組聊天緩存如何存儲美侦?

答: 緩存key 設(shè)置為 群組id+對方uin+自己的uin

  1. 聊天時产舞,如何在聊天中攜帶擴展信息

答: 消息內(nèi)容中,ext 支持用戶自定義參數(shù)傳遞

var option = {
  msg: data.userMessage.trim(),          // 消息內(nèi)容
  to: data.groupId,            // 接收消息對象(聊天室id)
  roomType: true,
  chatType: 'groupRoom',
  from: data.myuin,
  ext: {
   //todo 需要補充的字符哦
  },
  success: function () {
    console.log('send room text success');
  },
  fail: function () {
    console.log('failed');
  }
};
```
  1. 會話列表如何實現(xiàn)菠剩?

答: 通過接口獲取環(huán)信的群組列表易猫,通過自己的服務(wù)器端補全對應(yīng)的會話信息。

回顧

整個環(huán)信接入具壮,整體圍繞 假設(shè)-->猜想-->實踐完成的准颓。仔細閱讀官網(wǎng),會為大家節(jié)約很多時間

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棺妓,一起剝皮案震驚了整個濱河市攘已,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怜跑,老刑警劉巖样勃,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件性芬,死亡現(xiàn)場離奇詭異峡眶,居然都是意外死亡,警方通過查閱死者的電腦和手機植锉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門辫樱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汽煮,你說我怎么就攤上這事搏熄∨锼簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵心例,是天一觀的道長宵凌。 經(jīng)常有香客問我,道長止后,這世上最難降的妖魔是什么瞎惫? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮译株,結(jié)果婚禮上瓜喇,老公的妹妹穿的比我還像新娘。我一直安慰自己歉糜,他們只是感情好乘寒,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匪补,像睡著了一般伞辛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夯缺,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蚤氏,我揣著相機與錄音,去河邊找鬼踊兜。 笑死竿滨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捏境。 我是一名探鬼主播于游,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼典蝌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸠澈?” 一聲冷哼從身側(cè)響起笑陈,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涵妥,失蹤者是張志新(化名)和其女友劉穎蓬网,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吵取,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡皮官,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剪撬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婿奔。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖如叼,靈堂內(nèi)的尸體忽然破棺而出笼恰,到底是詐尸還是另有隱情社证,我是刑警寧澤追葡,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布宜肉,位于F島的核電站谬返,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏佑刷。R本人自食惡果不足惜瘫絮,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一蝇裤、第九天 我趴在偏房一處隱蔽的房頂上張望栓辜。 院中可真熱鬧藕甩,春花似錦周荐、人聲如沸概作。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽济竹。三九已至送浊,卻和暖如春袭景,著一層夾襖步出監(jiān)牢的瞬間浴讯,已是汗流浹背榆纽。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工饥侵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衣屏,地道東北人膨疏。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓佃却,卻偏偏與公主長得像饲帅,于是被迫代替她去往敵國和親灶泵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容