《拍拍二手》微信小程序之環信接入

拍拍二手閒置平臺,可以將自己的閒置物品進行轉讓或者捐贈。想和賣家達成共識就需要涉及IM聊天。拍拍二手閒置平臺目前接入的是環信IM聊天。下面我將從三個階段帶大家玩轉環信IM會話。
前期
初識IM聊天
帶着問題去調研

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

調研分析
必須接入環信嗎?除了環信是否可以接入其他即時通信?

現狀: 微信小程序API 提供了WebSocket 方法。
擴展: 如果服務端支持scoket通信,ios\android\H5 也全都支持Im聊天了
備註:專業第三方Im有融雲、環信、雲之訊等,底層實現均是基於scoket 通信。明白scoket通信後也可以自己寫即時通信。

環信目前有哪些功能呢?支持微信小程序嗎?
錯誤想法: 環信就是做im聊天的,咱們上去按照接入文檔,開發就能搞定!!!
這種想法是很致命的。在所有的第三方組件接入中,如果我們不能跳出來看待問題,只是爲了完成任務而完成任務。那麼我們永遠是最底層的低級碼農。
環信目前是同行業裏面做的算不錯的。那麼他的官網、接入規範都應該有的。微信小程序也是支持的。在後面小編會帶領大家一切怎麼去閱讀一個官網

如何接入小程序?
接入小程序是否需要申請一個賬號呢?我直接運行他們的demo可以嗎? 怎麼去測試呢? 此時我們可以有很多的猜想。我認爲在開始接入之前我們應該很好的進行一些思考,答案顯而易見。
環信接入思考篇
快即時慢
在工作中,大家會經常遇到第三方組件的接入。當接收到任務後,爲了儘快完成任務。上來就google,找攻略,找技巧。往往認爲這樣做速度是最快的。結果適得其反,做了很多無用的功。我們意識中的快,結果卻變成了慢
慢即時快
在這裏插入圖片描述
逆向思維: 任何一個第三方的組件,特別是一個大點的平臺,他們爲了推出自己的產品,一定會有各種各樣的功能支持,接入文檔說明。我們放慢速度,將這些資源用上半天的時間進行簡單的梳理。後期的開發進度會有很大的提升。
上圖是我在接入環信Im後進行的反思。因爲在接入環信之前,其他團隊成員用了很長的時間聯調。假如他們在接入環信聊天之前,瞭解環信擁有自己的後臺,可以直接給用戶端發送測試消息;可以直接創建用戶、創建聊天室、創建羣組。他們還會花費那麼久的時間去聯調嗎?完全不用依賴服務端。不用依賴ios,依賴android。自己使用環信後臺,輕輕鬆鬆完成各種測試。
環信接入
環信官網註冊自己的即時通訊雲,並登陸後臺
在這裏插入圖片描述
創建自己的應用,並記錄關鍵信息
在這裏插入圖片描述
以下是關鍵信息哦!!!
在這裏插入圖片描述
備註:
應用標識 應用接入時會使用
IM 用戶 可以創建、刪除用戶、發送消息
羣組 可以創建、刪除羣組信息、發送消息
聊天室 可以創建、刪除聊天室、發送消息
tip 通過這個後臺管理系統,就可以玩轉環信的接入測試了。
從環信下載小程序demo,替換 appkey 進行聯調測試
在這裏插入圖片描述
測試走起
1.用戶測試
在環信後臺創建用戶,在小程序端登錄 (用戶demo1 密碼:123456)
在這裏插入圖片描述2.一對一會話測試
① 在環信後臺創建用戶demo2
② 點擊操作,查看用戶好友將demo1和demo2 添加爲好友。
③ 在小程序端用demo1給demo2發送測試消息。
④ 退出demo1用戶,登錄demo2查看是否會接收到demo1發送的會話
在這裏插入圖片描述
由於環信工程師們相信碼農的實力,在羣組測試和聊天室測試這塊爲大家留下了想象空間。demo 中羣組測試和聊天室測試爲明確寫出。讓我繼續帶大家飛
3.羣組測試
① 創建羣組記錄羣組id,並給羣組添加成員(demo2)
在這裏插入圖片描述
② 環信後臺給羣組發送測試消息
在這裏插入圖片描述
③ 控制檯能收到羣組測試消息,怎麼展示呢? 請閱讀源碼解析篇
4.聊天室測試
① 創建聊天室記錄聊天室id,將demo1 設置爲超級管理員,demo2設置爲管理員
② 聊天室這裏沒有聊天室消息的發送。請閱讀源碼解析篇
通過以上4個簡單的測試,android、ios、h5、小程序的聊天測試均可以參照以上4點進行順利的測試。初期就此結束。下面帶代價進行源碼的解析
中期
看源碼前期思考
在這裏插入圖片描述
核心源碼閱讀
在這裏插入圖片描述
以上是環信sdk 基礎代碼結構。 通過簡單閱讀會發現:
環信的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有對應的scoket通信)
環信的api包裝在connection.js 組件中,如果某些api沒有,咱們可以擴展connection 中的方法
環信核心代碼閱讀完成後,發現沒有涉及到緩存。看來緩存的處理是在對應的業務邏輯中。
設想:
消息應該在哪裏緩存
哪裏進行會話鏈接的監聽註冊

環信demo 代碼閱讀
會話、羣組
通過前面提到的方式,大家可以在小程序控制臺抓取到用戶收到的會話和羣組消息
會話
app.js
環信scoket 註冊監聽代碼在app.js 中

核心代碼如下:
{
//調用API從本地緩存中獲取數據
var that = this
var logs = wx.getStorageSync(‘logs’) || []
logs.unshift(Date.now())
wx.setStorageSync(‘logs’, logs)

    WebIM.conn.listen({
        onOpened: function (message) {//連接成功回調
        // 如果isAutoLogin設置爲false,那麼必須手動設置上線,否則無法收消息
        // 手動上線指的是調用conn.setPresence(); 如果conn初始化時已將isAutoLogin設置爲true
        // 則無需調用conn.setPresence();             
            WebIM.conn.setPresence()
        },
        onPresence: function (message) { //處理“廣播”或“發佈-訂閱”消息,如聯繫人訂閱請求、處理羣組、聊天室被踢解散等消息
            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 從本地緩存中獲取用戶的消息  發消息+來源 適用於單人會話 msgData.yourname + message.to+當前登錄人 羣組/聊天室
                     
                    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;
            }
        },
    })

    
}

實際開發過程中,在微信中,退出小程序,重新進入時,webscoket 通信並沒有重新創建鏈接。存在用戶收到不到消息的情況。可以將以上代碼封裝,例如addHXLIstener(…)。當用戶重新打開後,再次註冊環信監聽即可。

拍拍二手閒置交易平臺,主要集成的是文本聊天功能。
環信登錄 例如 initLoginHX();
var uin=wx.getStorageSync(‘hxuin’);
var pwd=wx.getStorageSync(‘hxpwd’);
console.log(‘initHX:’ + uin+"||"+pwd);
var options = {
apiUrl: ‘服務器url’,
user: ‘用戶名’,// 用戶名要是字符
pwd: ‘密碼’,
grant_type: ‘password’,
appKey: ‘appkey’,
success: function (res) {
console.log(“環信創建連接成功”)
},
error: function (res) {
console.log(“環信創建連接失敗”)
}
};
WebIM.conn.open(options);

chat 會話
環信的會話列表存儲在本地,並沒有調用服務器端數據
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
})

通過以上代碼得出結論: 環信的會話是通過遍歷用戶id+對方id 構成的數據。
那羣組和聊天室的怎麼處理呢?

環信小程序demo中只提供了聊天室列表的獲取接口我們可以輕鬆實現聊天室列表,並沒有提供羣組列表的獲取方式。我們需要在conection中擴展調用羣組列表的接口,來實現羣組列表。參照聊天室列表獲取即可實現。聊天室列表實現方式如下:
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
    };
    // 想要實現羣組列表,修改對應接口即可
    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
從本地緩存中獲取聊天記錄,並展示
// 環信demo 發送消息

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);
    // 消息發送完成

    if (msg) {
        var value = WebIM.parseEmoji(msg.value.replace(/\n/mg, '')) // 環信表情處理
        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 對方環信uin+自己的uin
        // 注: 羣組聊天 key 羣組id\聊天室id+對方環信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: ''
        })
    }
},

// 環信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’){
// 如果是音頻則請求服務器轉碼
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 對方環信uin+自己的uin
        // 注: 羣組聊天 key 羣組id\聊天室id+對方環信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)
            }
        })
    }
},

環信聊天頁面,聊天數據全部存儲在緩存當中,跟進聊天類型的不同,主要需要調整緩存的key。詳情如下:

單對單聊天 對方uin+自己的uin
羣組聊天(針對某個商品,不需要好友關係,只需要臨時聊天) 羣組id+對方uin+自己的uin
聊天室(同羣組聊天)

問題大雜燴
羣組聊天緩存如何存儲?
答: 緩存key 設置爲 羣組id+對方uin+自己的uin
聊天時,如何在聊天中攜帶擴展信息
答: 消息內容中,ext 支持用戶自定義參數傳遞
var option = {
msg: data.userMessage.trim(), // 消息內容
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’);
}
};

**會話列表如何實現?**
答: 通過接口獲取環信的羣組列表,通過自己的服務器端補全對應的會話信息。
**回顧**
整個環信接入,整體圍繞 假設-->猜想-->實踐完成的。仔細閱讀官網,會爲大姐節約很多時間

作者:賈慧斌
鏈接:https://www.jianshu.com/p/8919316d26b8
來源:簡書

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章