微信小程序-实现实时聊天功能 前端部分

7,166 阅读5分钟

小程序中创建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. 发送各类消息