小程序中创建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.jsfunction 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: }},复制代码其实,实时聊天前端部分逻辑还是相对简单的,主要在于
- 控制服务的开启/关闭,断线后的处理
- 对各种消息事件的处理
- 发送各类消息














