前言

Nodejs + WebSocket + Vue 一对一、一对多聊天室 - 第三章

如果你看到这篇文章,还没有了解前面2篇文章的同学,可以先去了解一波,这样上手更快。
推荐文章:

《Nodejs + WebSocket简单介绍及示例 - 第一章》
《Nodejs + WebSocket + Vue 实现多人聊天室WebIM功能 - 第二章》

这篇文章都是前面文章的加强版,功能升级。给大家提供一个循序渐进的学习过程,一步一步的来。

在第二篇文章结束时,我们就已经可以一对多的聊天了,就是多人群聊。这次,我们进行扩展来实现一对一、一对多功能。

WebSocket客户端UI界面更改

有了一对一,一对多,我们就需要对直接的界面做出调整了。左边显示聊天人员列表,右边是具体消息列表。

Nodejs + WebSocket + Vue 一对一、一对多聊天室 - 第三章

<div class="web-im"> <div class="dis-flex"> <div class="user-list"> <div class="user" @click="triggerGroup">群1</div> <div class="user" @click="triggerPersonal(item)" v-if="item.uid!=uid" v-for="item in users">{{item.nickname}}</div> </div> <div class="msg-content"> <div class="header im-title">{{title}}</div> <div class="content im-record"> <div class="li" :class="{user: item.uid == uid}" v-for="item in currentMessage"> <template v-if="item.type===1"> <p class="join-tips">{{item.msg}}</p> </template> <template v-else> <div class="img">{{item.nickname}}</div> <p class="message-box">{{item.msg}}</p> </template> </div> </div> <div class="footer im-input"> <input type="text" v-model="msg" placeholder="请输入内容"> <button @click="send">发送</button> </div> </div> </div> </div> ...

这里我们就写死了一个群,叫群1,默认是所用用户进去群聊。

WebSocket服务端

var ws = require("nodejs-websocket"); var moment = require('moment'); console.log("开始建立连接...") let users = []; let conns = {}; function boardcast(obj) { // bridge用来实现一对一的主要参数 if(obj.bridge && obj.bridge.length){ obj.bridge.forEach(item=>{ conns[item].sendText(JSON.stringify(obj)); }) return; } server.connections.forEach((conn, index) => { conn.sendText(JSON.stringify(obj)); }) } var server = ws.createServer(function(conn){ conn.on("text", function (obj) { obj = JSON.parse(obj); // 将所有uid对应的连接conn存到一个对象里面 conns[''+obj.uid+''] = conn; if(obj.type===1){ let isuser = users.some(item=>{ return item.uid === obj.uid }) if(!isuser){ users.push({ nickname: obj.nickname, uid: obj.uid }); } boardcast({ type: 1, date: moment().format('YYYY-MM-DD HH:mm:ss'), msg: obj.nickname+'加入聊天室', users: users, uid: obj.uid, nickname: obj.nickname, // 增加参数 bridge: obj.bridge }); } else { boardcast({ type: 2, date: moment().format('YYYY-MM-DD HH:mm:ss'), msg: obj.msg, uid: obj.uid, nickname: obj.nickname, // 增加参数 bridge: obj.bridge }); } }) conn.on("close", function (code, reason) { console.log("关闭连接") }); conn.on("error", function (code, reason) { console.log("异常关闭") }); }).listen(8001) console.log("WebSocket建立完毕")

主体结构还是和第二章类型,不同的是:
1、每次将uid对应的conn存储到一个对象conns上
2、根据客户端传入的参数bridge来判断,是群发还是一对一发送
3、群发还是第二章逻辑即可

server.connections.forEach((conn, index) => { conn.sendText(JSON.stringify(obj)); })

4、一对一发送,bridge里面是一对一的两个用户uid,这样就可以在conns对象上找到uid对应的连接conn,并用conn发送信息即可

if(obj.bridge && obj.bridge.length){ obj.bridge.forEach(item=>{ conns[item].sendText(JSON.stringify(obj)); }) return; }

WebSocket客户端

export default { ... data(){ return { title: '群聊', uid: '', nickname: '', socket: '', msg: '', // 当前用户所有消息 messageList: [], users: [], bridge: [] } }, mounted() { ... }, computed: { // 当前对话渲染的msg列表 currentMessage() { let vm = this; // 筛选只有bridge相同的对话,展示出来 // 数组比较,先转成字符串 let data = vm.messageList.filter(item=>{ return item.bridge.sort().join(',') == vm.bridge.sort().join(',') }) return data; } }, methods: { // 切换到群聊 triggerGroup() { this.bridge = []; this.title = '群聊'; }, // 切到具体个人 triggerPersonal(item) { if(this.uid === item.uid){ return; } // 将当前用户uid,和需要对话的uid放入bridge this.bridge = [this.uid, item.uid]; this.title = '和' + item.nickname + '聊天'; }, send(){ if(!this.msg){ return } this.sendMessage(2, this.msg) }, sendMessage(type, msg){ this.socket.send(JSON.stringify({ uid: this.uid, type: type, nickname: this.nickname, msg: msg, // 增加bridge参数 bridge: this.bridge })); this.msg = ''; }, conWebSocket(){ let vm = this; if(window.WebSocket){ vm.socket = new WebSocket('ws://localhost:8001'); let socket = vm.socket; socket.onopen = function(e){ console.log("连接服务器成功"); if(!vm.uid){ ... } // 这里将sendMessage方法if外面 vm.sendMessage(1) } socket.onclose = function(e){ console.log("服务器关闭"); } socket.onerror = function(){ console.log("连接出错"); } // 接收服务器的消息 socket.onmessage = function(e){ let message = JSON.parse(e.data); vm.messageList.push(message); if(message.users) { vm.users = message.users; } } } }, login(){ ... } } }

上方…的代码区域都是和第二篇文章一样的地方,所有就省略了。

1、默认是群发,即bridge是空数组,向所有用户发送消息
2、点击用户列表,赋予bridge当前用户uid,和需要对话的uid。
3、在第二篇文章中,渲染的消息列表是messageList。现在不是,是通过计算属性computed,只需要bridge相等的消息,得出currentMessage当前对话的消息列表
4、因为所有消息都是通过后台socket返回,也不需要考虑发送者/接收者是谁,判断bridge是否相等,可以用sort()方法排序并转换成字符串后进行对比。

快速预览效果

Nodejs + WebSocket + Vue 一对一、一对多聊天室 - 第三章

Nodejs + WebSocket + Vue 一对一、一对多聊天室 - 第三章

Nodejs + WebSocket + Vue 一对一、一对多聊天室 - 第三章

总结

一对一和一对多的核心,就是知道是那个用户与那个用户对话。当前目前一对多是不用考虑,因为是写死的,所有用户,后面来做不同群,随意加群聊天,就需要考虑了。也就是服务端的conn不要弄错,不然收不到消息也接收不到消息。

源码地址:源码地址