前言
如果你看到这篇文章,还没有了解前面2篇文章的同学,可以先去了解一波,这样上手更快。
推荐文章:
《Nodejs + WebSocket简单介绍及示例 - 第一章》
《Nodejs + WebSocket + Vue 实现多人聊天室WebIM功能 - 第二章》
这篇文章都是前面文章的加强版,功能升级。给大家提供一个循序渐进的学习过程,一步一步的来。
在第二篇文章结束时,我们就已经可以一对多的聊天了,就是多人群聊。这次,我们进行扩展来实现一对一、一对多功能。
WebSocket客户端UI界面更改
有了一对一,一对多,我们就需要对直接的界面做出调整了。左边显示聊天人员列表,右边是具体消息列表。
<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()
方法排序并转换成字符串后进行对比。
快速预览效果
总结
一对一和一对多的核心,就是知道是那个用户与那个用户对话。当前目前一对多是不用考虑,因为是写死的,所有用户,后面来做不同群,随意加群聊天,就需要考虑了。也就是服务端的conn不要弄错,不然收不到消息也接收不到消息。
源码地址:源码地址