小程式中建立webSocket連線
需求
- 列表頁需要實時獲取新訊息提示,詳情頁(聊天室)實現使用者實時聊天
頁面邏輯
列表頁
首先在列表頁開啟ws服務,並監聽開啟/關閉事件,ws開啟後,在wx.onSocketMessage
監聽訊息
// 列表頁
onLoad(options) {
this.init();
// 監聽接受訊息
wx.onSocketMessage((res) => {})
},
init() {
//# 開啟ws
this.openWS()
},
// 開啟服務
openWS() {
const token = wx.getStorageSync('token');
const app = getApp();
wx.connectSocket({
url: app.globalData.socketPath + '?sec-websocket-protocol=' + encodeURIComponent(token),
header: {
'content-type': 'application/json',
'from': 'wechatmini'
},
success() {},
fail() {}
})
}
複製程式碼
- ws要確保只連一個,統一在列表頁監聽服務開啟/關閉,
- 服務斷開後要重連服務,利用小程式進入詳情後列表頁不銷燬特性,在列表頁或詳情頁斷線後都會執行列表頁的重連事件
// # 列表頁
init() {
// # 1.ws要確保只連一個,用全域性變數判斷
!this.data.socketOpened && this.openWS();
// # 監聽服務
wx.onSocketOpen((res) => {
console.log('WebSocket 已開啟!')
this.data.socketOpened = true;
})
// # 重連
wx.onSocketClose((res) => {
console.log('WebSocket 已關閉!')
this.data.socketOpened = false;
// # 在列表頁面重連服務
this.openWS()
})
}
複製程式碼
重連服務
為了減輕伺服器的壓力,防止連不上服務會一直請求連線服務,做了個延時處理,用reconnectDelay
控制,成功後再重置。斷線有2種情況,一是連線失敗,二是連線成功後斷線:
init() {
...
wx.onSocketOpen((res) => {
console.log('WebSocket 已開啟!')
this.data.socketOpened = true;
this.data.reconnectDelay = 0;
})
wx.onSocketClose((res) => {
console.log('WebSocket 已關閉!')
this.data.socketOpened = false;
// 在列表頁面重連服務,重連失敗呼叫時間+1s
setTimeout(() => {
this.openWS()
this.data.reconnectDelay += 1000
}, this.data.reconnectDelay);
})
...
}
openWS() {
...
wx.connectSocket({
...
fail(res) {
console.log('連線失敗');
// 在列表頁面重連服務,重連失敗呼叫時間+1s
setTimeout(() => {
this.openWS()
this.data.reconnectDelay += 1000
}, this.data.reconnectDelay);
}
})
},
複製程式碼
重連成功後,需要給服務端傳送reconnect
訊息,根據和服務端約定,需要傳送每個聊天的usif和該聊天最後一條訊息id
// # 列表頁
init(){
...
wx.onSocketOpen((res) => {
...
// # msgList儲存的是每個聊天最後一條訊息物件
let len = this.data.msgList.length;
// # 傳送重連訊息
for (let i = len - 1; i >= 0; i--) {
let lastMsg = this.data.msgList[i];
// # util.sendMsg是公共方法,向服務端傳送訊息
util.sendMsg({
action: 'reconnect',
data: {
index: lastMsg.id
},
usif: lastMsg.usif
})
}
})
}
// # util.js
function sendMsg({action,data,usif}) {
data['usif'] = usif
let msg = {
action: action,
data: data
}
wx.sendSocketMessage({
data: JSON.stringify(msg),
success: function () {},
})
}
複製程式碼
訊息處理
列表頁只要標誌新訊息,不涉及傳送,只需要對接收的訊息做處理
服務端約定的message自定義事件返回型別說明
操作 含義 fail 連線成功,但是獲取使用者資訊失敗(一般在redis連線失敗,或者資料被清空時出現,此時需要重連) online 醫生上線通知,此通知會廣播所有使用者(醫生/普通使用者) push 訊息推送通知,收到此訊息時可把資料插入到訊息列表 pushback 訊息推送反饋,通知使用者訊息是否傳送成功,成功則可插入列表,失敗則需要重發 read 訊息已讀通知,通知發訊息的物件,對方已讀此訊息 reconnect 斷線重連時,此操作會返回對方最新傳送的50條未讀訊息,可插入訊息列表 online 使用者下線通知,此通知會廣播所有使用者,需要前端根據ID自行判斷下線使用者是否是在聊天的物件
// # 列表頁
// # 監聽接受訊息
wx.onSocketMessage((res) => {
that.msgOperation({})
})
msgOperation({msgType,errcode = 0,data, errmsg}) {
let that = this
// # 根據不同訊息型別做對應處理
switch (msgType) {
case 'push':
console.log('列表收到新訊息');
// # Message是自定義類,用於對訊息做初始化
data = new Message(data);
if (errcode == 0) {
// # taskList是儲存聊天訂單
for (let i = 0; i < that.data.taskList.length; i++) {
// # 收到相應訂單的新訊息則對該訂單進行hasnews標識
if (data.booking_id == that.data.taskList[i].id) {
// # 如何只改變陣列中某個值的屬性值,中括號法
var hasnews = 'taskList[' + i + '].hasnews'
that.setData({
[hasnews]: 1,
})
}
}
// # 將新訊息作為last_msg加到msgList,這一步是為斷線重連傳送訊息做準備
that.data.msgList.forEach((item, idx) => {
if (item.booking_id == data.booking_id) {
item = Object.assign({}, item, data)
that.data.msgList.splice(idx, 1)
that.data.msgList.push(item)
}
})
that.setData({
msgList: that.data.msgList
})
}
break;
case 'pushback':
// # 列表頁不需要對傳送反饋做處理
break;
case 'read':
//訊息已讀反饋,根據id標記訊息為已讀
data = new Message(data);
var msgid = data.id;
var length = that.data.msgList.length
// # msgList更新是為了??
for (let i = length - 1; i >= 0; i--) {
if (that.data.msgList[i].id == msgid) {
let is_read = "msgList[" + i + "].is_read";
that.setData({
[is_read]: 1
})
}
}
// # 更新is_read, 訊息已讀/未讀狀態是根據taskList中的last_message來判斷的
for (let i = that.data.taskList.length - 1; i >= 0; i--) {
if (that.data.taskList[i].last_message.id == msgid) {
let is_read = "taskList[" + i + "].last_message.is_read";
that.setData({
[is_read]: 1
})
}
}
break;
case 'online':
console.log('醫生上線');
break;
case 'reconnect':
console.log('斷線重連', data);
// # 將收到的新訊息加入msgList,作新訊息處理
if (data.length) {
that.data.taskList.forEach((item, index) => {
for (let i = 0; i < data.length; i++) {
if (data[i].booking_id == item.id) {
var hasnews = 'taskList[' + index + '].hasnews'
that.setData({
[hasnews]: 1,
})
}
}
});
}
default:
}
},
複製程式碼
列表頁主要負責服務開啟/關閉,及對push/reconnect/read
訊息作處理
聊天室-詳情頁
服務端約定的send傳送訊息事件說明
操作 含義 push 訊息推送(對方會在message事件中收到此訊息) read 訊息已讀通知(對方會在message事件中收到此訊息) reconnect 斷線重連(會在自己客戶端message事件中收到斷線時未收到的訊息)
操作:
- 進入聊天室介面獲取歷史訊息列表
- 開啟聊天室,對未讀訊息傳送
read
- 點選傳送後,傳送
push
- 斷線後重連成功,傳送
reconnect
(列表頁執行)
// # 聊天室
onLoad() {
this.getMessageList();// 獲取解讀訊息列表
// # 監聽接受訊息
wx.onSocketMessage((res) => {
var data = JSON.parse(res.data);
// # 聊天框開啟時才執行
if (this.data.chatOpened) {
this.msgOperation({
msgType: data.action,
data: data.data,
errmsg: data.errmsg,
errcode: data.errmsg
})
}
})
},
onShow() {
this.setData({
chatOpened: true,
})
},
onUnload() {
this.setData({
chatOpened: false
})
},
// # 點選傳送按鈕
sendData(e) {
let data = {
msg_type: 1,
messages: e.detail.content,
usif: this.data.usif
};
wx.sendSocketMessage({
data: JSON.stringify({data, action: 'push'}),
success() {},
fail() {
wx.showToast({
title: '訊息傳送失敗',
icon: 'none',
duration: 200
})
}
})
},
複製程式碼
- 歷史訊息作已讀處理,訊息型別有文字,圖片,語音,此處不另做分類處理
// 聊天室
getMessageList(){
...
module.messageList().then(res => {
...
if (res.data.data.length) {
let msgList = [];
for (let i = 0; i < res.data.data.length; i++) {
let msg = new Message(res.data.data[i]);
// # 標記已讀 c_user_type==1 為接收訊息
if (msg.is_read == 0 && msg.c_user_type == 1) {
msg.is_read = 1;
util.sendMsg({
action: 'read',
data: {
id: msg.id
},
usif: this.data.usif
});
}
msgList.push(msg);
}
this.setData({ msgList })
}
})
}
複製程式碼
- 監聽訊息及處理,重點在於
pushback
和reconnect
的處理 pushback
後將訊息推送到列表reconnect
時要將收到的訊息追加到訊息列表中
msgOperation({msgType,errcode = 0,data,errmsg}) {
let that = this
console.log('收到新訊息', data);
switch (msgType) {
case 'push':
data = new Message(data);
// # 收到新訊息push到訊息列表
if (data.booking_id == that.data.booking_id) {
let msgList = that.data.msgList
msgList.push(data)
that.setData({
msgList: msgList,
})
console.log('若聊天框開啟,則發訊息告知已讀');
util.sendMsg({
action: 'read',
data: data,
usif: that.data.usif
});
}
break;
case 'pushback':
data = new Message(data);
//訊息傳送成功的返回
if (errcode == 0) {
let msgList = that.data.msgList
//push到訊息列表
msgList.push(data)
that.setData({msgList})
} else {
console.log('傳送失敗:', errmsg)
}
break;
case 'read':
// # 不處理
break;
case 'online':
console.log('醫生上線');
break;
case 'reconnect':
if (errcode == 0) {
data.forEach(msg => {
that.data.msgList.push(msg)
});
that.setData({
msgList: that.data.msgList
})
};
default:
}
},
複製程式碼
其實,實時聊天前端部分邏輯還是相對簡單的,主要在於
- 控制服務的開啟/關閉,斷線後的處理
- 對各種訊息事件的處理
- 傳送各類訊息