微信小程式-實現實時聊天功能 前端部分

哦shi小公舉發表於2018-09-20

小程式中建立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() {}
  })
}
複製程式碼
  1. ws要確保只連一個,統一在列表頁監聽服務開啟/關閉,
  2. 服務斷開後要重連服務,利用小程式進入詳情後列表頁不銷燬特性,在列表頁或詳情頁斷線後都會執行列表頁的重連事件
// # 列表頁
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 })
        }
    })    
}
複製程式碼
  • 監聽訊息及處理,重點在於pushbackreconnect的處理
  • 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:
    }
},
複製程式碼

其實,實時聊天前端部分邏輯還是相對簡單的,主要在於

  1. 控制服務的開啟/關閉,斷線後的處理
  2. 對各種訊息事件的處理
  3. 傳送各類訊息

相關文章