拍拍二手閒置平臺,可以將自己的閒置物品進行轉讓或者捐贈。想和賣家達成共識就需要涉及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
來源:簡書