废话不多说,直接上代码
首先是后台的webSocket.js
const WebSocket = require('ws');
// 定义websocket服务器,3000是端口号
const wsServer = new WebSocket.Server({ port: 3000 });
// 定义连接到的websocket集合
let socketSet = [];
// 连接
wsServer.on('connection', (websocket, req, res) => {
const userid = req.url.split('/');
let isExist = false; // 标记当前用户是否在线
socketSet.forEach(ws => {
if (ws.currentId == userid[2]) isExist = true;
});
if (!isExist) {
socketSet.push({
websocket: websocket,
currentId: userid[1]
});
}
websocket.on('message', function incoming(message) {
// console.log('received: %s', message);
// 收到消息之后推送给目标对象
const msgObj = JSON.parse(message);
socketSet.forEach(ws => {
// console.log(ws);
if (ws.websocket.readyState == 1) {
if (ws.currentId == msgObj.target) {
// 判断当前用户是否为目标对象
ws.websocket.send(
JSON.stringify({
msg: msgObj.msg,
from: msgObj.current
})
);
}
}
});
});
// websocket.send(
// JSON.stringify({
// msg: 'websocket连接成功'
// })
// );
});
/**
readyState属性返回实例对象的当前状态,共有四种。
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败
*/
ws需要npm来安装
然后是前端如何使用?
在vue-cli里面创建一个socket.js
export default {
// 保证整个项目只有一个socket实例
ws: null, // Websocket实例
init(config, onMessage, onError) {
if (!this.ws) {
// 实例化socket对象
this.ws = new WebSocket(`ws://localhost:3000/${config.user.id}`);
}
// 客户端接收消息
this.ws.onmessage = event => {
let message = JSON.parse(event.data);
onMessage && onMessage(message); // 接收到消息触发的回调
};
// 出错
this.ws.onerror = error => {
onError && onError(error);
};
this.ws.onclose = () => {
this.ws = null;
};
},
send(msgObj) {
// 发送消息的时候触发
this.ws.send(JSON.stringify(msgObj));
}
};
到我们的一个vue组件里面,就是聊天的组件
<template>
<div class="chat">
<Header v-if="targetUser" :is-left="true" :title="targetUser.name" btn_icon="ellipsis-h" />
<div class="container">
<!-- 聊天内容 -->
<div class="content_wrap" v-if="targetUser && user" v-for="(item,index) in messageList" :key="index">
<!-- 别人的内容 -->
<div class="left_msg" v-if="item.source == 'other'">
<img :src="targetUser.avatar" alt>
<span>{{item.msg}}</span>
</div>
<!-- 我的内容 -->
<div class="right_msg" v-if="item.source=='self'">
<span>{{item.msg}}</span>
<img :src="user.avatar" alt>
</div>
</div>
</div>
<div class="footer_wrap">
<input type="text" v-model="msgValue">
<button :disabled="msgValue == ''" @click="sendMessage">发送</button>
</div>
</div>
</template>
<script>
import Header from "../components/Header";
import WSocket from "../socket.js";
export default {
data() {
return {
targetUser: null,
msgValue: "",
messageList: []
};
},
components: {
Header
},
beforeRouteEnter(to, from, next) {
next(vm => {
vm.targetUser = to.params.user;
vm.getMessage();
});
},
computed: {
user() {
return this.$store.getters.user;
}
}, // 服务端推送消息给客户端
mounted() {
WSocket.init({
user: this.user
},
message => {
// 收到消息后,将消息存储到数据中
this.messageList.push({
msg: message.msg,
source: "other"
});
// 保存消息
this.saveMsg();
},
error => {
console.log(error);
}
);
},
methods: {
sendMessage() {
// console.log(this.msgValue);
// 需要发送的消息对象
const msgObj = {
target: this.targetUser._id,
current: this.user.id,
msg: this.msgValue
};
WSocket.send(msgObj);
// 本地客户端显示
this.messageList.push({
msg: this.msgValue,
source: "self"
});
// 保存消息
this.saveMsg();
// 清空input
this.msgValue = "";
},
saveMsg() {
// 保存消息
let message = {
target: {
avatar: this.targetUser.avatar,
name: this.targetUser.name,
_id: this.targetUser._id
},
count: 0,
message: this.messageList,
user_id: this.user.id
};
this.$axios
.post("http://localhost:5123/api/profile/addmsg", message)
.then(res => (this.msgValue = ""));
},
getMessage() {
this.$axios(`http://localhost:5123/api/profiles/msg/${this.user.id}`).then(res => {
console.log(res);
// 过滤与当前目标对象的聊天数据
let result = res.data.filter(data => {
// console.log(data.target._id);
// console.log(this.targetUser._id);
return data.target._id == this.targetUser._id;
});
if (result.length > 0) {
this.messageList = result[0].message;
}
});
}
}
};
</script>
<style scoped>
.chat {
width: 100%;
height: 100%;
overflow: hidden;
}
.container {
width: 100%;
height: calc(100% - 100px);
box-sizing: border-box;
background-color: #f1f1f1;
margin-top: 50px;
padding: 8px;
overflow-y: scroll;
}
.footer_wrap {
width: 100%;
height: 50px;
box-sizing: border-box;
border-top: 1px solid #d9d9d9;
position: absolute;
bottom: 0;
padding: 8px;
background-color: #fafafa;
}
.footer_wrap input {
width: 70%;
height: 30px;
outline: none;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.footer_wrap button {
width: 20%;
height: 34px;
border: 1px solid #d9d9d9;
border-radius: 4px;
margin-left: 8px;
outline: none;
background-color: #f1f1f1;
}
.footer_wrap button[disabled] {
background-color: #d9d9d9;
cursor: not-allowed;
pointer-events: none;
}
.left_msg {
justify-content: flex-start;
}
.right_msg {
justify-content: flex-end;
}
.left_msg,
.right_msg {
width: 100%;
display: flex;
margin: 5px 0;
}
.content_wrap img {
width: 3rem;
height: 3rem;
}
.content_wrap span {
display: inline-block;
max-width: 65%;
border: 1px solid #d9d9d9;
border-radius: 4px;
margin: 0 5px;
padding: 8px;
box-sizing: border-box;
word-break: break-all;
}
.left_msg span {
background-color: #fff;
}
.right_msg span {
background-color: #0fce0d;
}
</style>