效果图:
1、首先安装依赖
javascript
npm install express
npm install express-ws
2、在index.js中引用
javascript
import express from "express";
import expressWs from "express-ws";
这边我是用的es6模块化导入语法,可以在package.json中设置"type": "module"
3、使用expressWs
javascript
const app = express()
expressWs(app);
// 每个在线用户所发送的消息
const wss = expressWs(app).getWss('/')
4、发起请求
javascript
app.ws('/socket', (ws, req) => {
console.log('连接成功');
try {
// 监听消息事件
ws.on('message', msg => {
console.log('serve message', msg);
let msgObj = JSON.parse(msg)
let sql = 'insert into chat_messages set ?'
wss.clients.forEach((e) => {
msgObj.date = moment(new Date()).format('YYYY-MM-DD HH:mm:ss')
e.send(JSON.stringify(msgObj))
})
db.query(sql, JSON.parse(msg), (err, data) => {
if (err) return ws.send('sql出错了' + err.message);
if (data.affectedRows !== 1) return res.send({
code: 2,
message: '用户信息保存数据库失败'
})
})
})
ws.on('open', () => {
console.log('serve open');
})
ws.on('error', () => {
console.log('serve error');
})
ws.on('close', () => {
console.log('serve close');
})
} catch (error) {
console.log(error);
}
});
app.listen(3000, () => {
console.log('运行于http://127.0.0.1:3000');
})
不存数据库的话这部可以不要
javascript
db.query(sql, JSON.parse(msg), (err, data) => {
if (err) return ws.send('sql出错了' + err.message);
if (data.affectedRows !== 1) return res.send({
code: 2,
message: '用户信息保存数据库失败'
})
})
5、前端vue代码
javascript
<template>
<div>
<div ref="messagesContainer" :style="{ width: '450px',height: contentHeight + 'px',border: '1px solid #ccc', marginBottom: '16px',overflow: 'auto',padding: '10px' }">
<!-- <p style="text-align: center;font-size: 14px">您正在和{{ username }}聊天</p> -->
<div style="width: 100%" v-for="(item, index) in messageList" :key="index">
<div style="width: 300px;margin-bottom: 10px;" :class="item.userid === userid ? 'right' : ''">
<h3 style="font-size: 14px;margin-bottom: 5px;" :class="item.userid === userid ? 'title_right' : ''">{{ item.username }} {{ moment(item.create_time).format('YYYY-MM-DD HH:mm:ss') }}</h3>
<div style="width: 100%;">
<div :class="item.userid === userid ? 'float_left' : 'float_right'" style="width: 250px;min-height: 40px;background-color: #e8e8e9;padding: 10px;white-space: wrap;border-radius: 10px;">{{ item.message }}</div>
<div :class="item.userid === userid ? 'float_left' : 'float_right'" style="width: 40px;height: 40px;">
<img :src="item.imgUrl" alt="" style="width: 100%;height: 100%;object-fit: cover;">
</div>
<div style="clear: both;"></div>
</div>
</div>
<div style="clear: both;"></div>
</div>
</div>
<Input v-model="message" placeholder="请输入内容" style="width: 280px" @on-enter="sendMessage"/>
<Button type="success" @click="sendMessage" style="margin: 10px;">发送</Button>
<Button type="success" @click="clear">清空记录</Button>
</div>
</template>
<script setup>
import moment from 'moment';
import { onMounted, reactive, ref, nextTick, onUpdated } from 'vue';
import { Message } from 'view-ui-plus'
import { getMessageListApi } from '../../utils/message.js'
let message = ref('')
let messageList = reactive([])
let messagesContainer = ref(null)
let socket = new WebSocket('http://127.0.0.1:3000/socket')
let username = JSON.parse(localStorage.getItem('userinfo')).nickname
let imgUrl = JSON.parse(localStorage.getItem('userinfo')).imgUrl
let userid = JSON.parse(localStorage.getItem('userinfo')).id
let contentHeight = ref(document.documentElement.clientHeight - 240)
onMounted(() => {
initSocket()
nextTick(() => {
// 滚动到messagesContainer的底部
scrollToBottom();
});
getMessageListApi().then(res => {
try {
if(res.code === 0) {
messageList.push(...res.data)
} else {
Message.error(res);
}
} catch (error) {
console.log(error);
}
})
})
onUpdated(() => {
nextTick(() => {
// 滚动到messagesContainer的底部
setTimeout(scrollToBottom, 20)
});
})
const initSocket = () => {
console.log(socket);
// 接收到消息的回调
socket.onmessage = messageHandler
// 连接成功后的回调
socket.onopen = openHandler
// 连接发生错误的回调
socket.onerror = errorHandler
// 关闭的回调
socket.onclose = closeHandler
}
const messageHandler = (e) => {
let msg = JSON.parse(e.data)
console.log(msg);
messageList.push(msg)
}
const openHandler = () => {
console.log('open');
}
const errorHandler = () => {
console.log('发生了错误,重连中...');
}
const closeHandler = function(event) {
console.log('WebSocket connection closed with code: ' + event.code + ' and reason: ' + event.reason);
setTimeout(initSocket, 1500)
}
const sendMessage = () => {
if (!message.value) {
return Message.warning('输入的内容不能为空')
}
let obj = {
userid,
username,
imgUrl,
message: message.value
}
socket.send(JSON.stringify(obj))
message.value = ''
nextTick(() => {
// 滚动到messagesContainer的底部
setTimeout(scrollToBottom, 20)
});
}
const clear = () => {
console.log('清空');
}
const scrollToBottom = () => {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight + 999;
}
</script>
<style lang="less" scoped>
.right {
// margin-left: 125px;
float: right;
}
.title_right {
text-align: right;
}
.float_left {
float: left;
margin-right: 5px;
}
.float_right {
float: right;
margin-left: 5px;
}
</style>