WebSocket是一个长连接,客户端可以给服务端发送消息,服务端也可以给客户端发送消息,是全双工通信。
平时的前后端分离项目,是前端请求结果获取后端服务器数据,从而渲染到页面,服务端无法主动向客户端发起消息,某些需求场景下,一旦客户端需要知道服务端的频繁状态变化,就要由客户端长轮询的多次请求服务端数据从而获得最新状态,很明显这种方法比较笨重,也比较浪费资源。WebSocket应运而生。
我们使用WebSocket模拟一下商家机器人自动回复功能。使用nodeJS启动后端,使用serve启动前端页面,先看效果:
const WebSocket = require('ws')
const autoData = require('./js/auto')
const ws = new WebSocket.Server({ port: 5001 })
ws.on('connection', (ws) => {
console.log('server connection')
ws.on('message', (msg) => {
msg = msg.toString()
if (msg === 'link') {
ws.send(`RedDog旗舰店>>${autoData[msg]}`)
} else if (Object.keys(autoData).indexOf(msg) != -1) {
ws.send(`RedDog旗舰店>>${autoData[msg]}`)
} else {
ws.send(`RedDog旗舰店>>${autoData['未知']}`)
}
})
ws.on('open', () => {
console.log('open')
})
ws.on('close', () => {
console.log('websocket server: 客户端已关闭连接')
})
ws.onerror = function () {
console.log('websocket server: 出错了')
}
})
module.exports = {
"谢谢": '不客气呢萌主大人,我是您的爱宠营养师,能为您效力是我的荣幸哦',
"好的": "好的萌主大人,还有其他可以为您效劳的嘛",
"没有了": "好的萌主大人,那我先退下啦",
"我不开心": '摸摸头',
"link": '小主您好,欢迎来到小店,请问有什么可以帮您?',
"你好": '在的萌主大人,恭迎小主大驾光临,给萌主大人请安!猜你想问【维生素,卵磷脂,驱虫,狗粮】',
"您好": '在的萌主大人,恭迎小主大驾光临,给萌主大人请安!猜你想问【维生素,卵磷脂,驱虫,狗粮】',
"在吗": '在的萌主大人,恭迎小主大驾光临!',
"在么": '在的萌主大人,恭迎小主大驾光临!',
"维生素": '狗狗体内维生素缺乏容易导致挑食厌食、体重减轻、掉毛褪色、皮肤痒、出现泪痕、不爱运动、经常生病、生长缓慢、发育不良等症状,建议狗狗及时补充维生素哈萌主大人!',
"卵磷脂": '卵磷脂的作用有很多,分别有以下几项。首先,保护皮肤,预防皮肤病,可以有效改善狗狗皮肤干燥、瘙痒、过敏等症状。其次,可以补充营养,提高狗狗免疫力,促进骨骼的生长,对老年狗还有保养作用,延缓狗狗衰老。最后,吃卵磷脂可以帮助狗狗美毛,增加毛发的光泽度哈萌主大人!',
"驱虫": '幼犬一般在满月左右第一次驱虫,六个月之前的幼犬,每一个月驱虫一次,六个月之后的幼犬以及成年犬,每三个月驱虫一次哈萌主大人!',
"未知": '哎呀,回答不上来了!换下边的词问问吧【维生素,卵磷脂,驱虫,狗粮】',
"狗粮": '一只成年的柯基犬,每天约需要吃自身体重1.5%左右的狗粮,且这些狗粮至少需要分2餐喂食,具体可以根据是情分体重增分量的哈萌主大人。',
"柯基": '柯基犬是属于英国的品种。柯基犬性格温顺、勇敢大胆、聪明机警、对人友善、精力旺盛、活泼可爱,作为一种小型犬已经深受人们喜欢,越来越多的人选择其作为家庭伴侣犬。'
}
node index.js
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
<link rel="stylesheet" href="./css/index.css" />
head>
<body>
<div id="app">
<p class="title">PLUS专属客服p>
<div class="chatContainer" id="chatContainer">
<div class="contentDiv" v-for="(item, key) in dataList" :key="key">
<p class="time">{{getFormat(key)}}p>
<div v-for="(itemDetail, index) in item" :key="index">
<div class="shop content" v-if="itemDetail.type === 0">
<img :src="itemDetail.imgSrc" />
<div>
<span>{{itemDetail.shopName}}span>
<p>{{itemDetail.content}}p>
div>
div>
<div class="user content" v-if="itemDetail.type === 1">
<img :src="itemDetail.imgSrc" />
<div>
<span>{{itemDetail.nickName}}span>
<p>{{itemDetail.content}}p>
div>
div>
div>
div>
div>
<div class="sendDiv">
<input v-model="sendMsg" placeholder="请输入您的问题" />
<button @click="send">发送button>
div>
div>
body>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.css" />
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js">script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js">script>
<script>
let LinkStatus = {
CLOSE: 0,
SUCCESS: 1,
ERROR: 2,
}
let UserType = {
SHOP: 0,
USER: 1,
}
let app = new Vue({
el: '#app',
data: function () {
return {
dataList: {},
sendMsg: '',
lastTimeStamp: 1664258797000,
webSocket: null,
linkStatus: LinkStatus.CLOSE, // 0 关闭,
}
},
methods: {
send() {
if (this.linkStatus !== LinkStatus.SUCCESS) {
return vant.Notify('失去链接,请刷新页面');
}
let time = new Date().getTime()
let sendMsg = this.sendMsg
this.sendMsg = ''
let obj = {
type: UserType.USER, //用户
content: sendMsg,
nickName: '小球2海绵宝宝',
imgSrc: './img/user.jpg',
}
if (time - this.lastTimeStamp < 2 * 60 * 1000) {
this.dataList[this.lastTimeStamp].push(obj)
} else {
this.lastTimeStamp = time
this.$set(this.dataList, time, [obj])
}
this.handleScroll()
setTimeout(() => {
this.webSocket.send(sendMsg)
}, 800)
},
initWebSocket() {
this.webSocket = new WebSocket('ws://localhost:5001')
this.webSocket.onopen = () => {
this.linkStatus = LinkStatus.SUCCESS
this.webSocket.send('link')
}
this.webSocket.onerror = () => {
this.linkStatus = LinkStatus.ERROR
console.log('连接失败...')
}
this.webSocket.onmessage = (event) => {
let msg = event.data.split('>>')
let time = new Date().getTime()
let obj = {
type: UserType.SHOP, //商家
content: msg[1],
shopName: msg[0],
imgSrc: './img/shop.jpg',
}
if (time - this.lastTimeStamp < 2 * 60 * 1000) {
this.dataList[this.lastTimeStamp].push(obj)
} else {
this.lastTimeStamp = time
this.$set(this.dataList, time, [obj])
}
this.handleScroll()
}
this.webSocket.onclose = () => {
this.linkStatus = LinkStatus.CLOSE
console.log('连接已关闭...')
}
},
handleScroll() {
let screenHeight = window.screen.height
let clientHeight = document.getElementById('chatContainer').clientHeight
if(clientHeight + 150 > screenHeight){
// 滚动
$('#chatContainer').animate({'scrollTop': clientHeight + 150}, 300)
}
},
getFormat(d) {
var now = new Date(parseFloat(d))
var year = now.getFullYear()
var month = now.getMonth() + 1
var date = now.getDate()
if (month >= 1 && month <= 9) {
month = '0' + month
}
if (date >= 0 && date <= 9) {
date = '0' + date
}
var hour = now.getHours()
var minute = now.getMinutes()
var second = now.getSeconds()
if (hour >= 1 && hour <= 9) {
hour = '0' + hour
}
if (minute >= 0 && minute <= 9) {
minute = '0' + minute
}
if (second >= 0 && second <= 9) {
second = '0' + second
}
return (
year +
'-' +
month +
'-' +
date +
' ' +
hour +
':' +
minute +
':' +
second
)
},
},
mounted() {
this.initWebSocket()
},
})
script>
html>
* {
margin: 0;
padding: 0;
}
body{
background-color: rgb(239, 239, 239);
}
.chatContainer {
overflow-y: scroll;
width: 100%;
position: fixed;
top: 45px;
bottom: 50px;
margin-bottom: 10px;
}
.title{
text-align: center;
width: 100%;
height:45px;
line-height: 45px;
border-bottom: 1px solid #d9d9d9;
background: rgba(255, 255, 255, 0.8);
position: fixed;
top: 0;
left: 0;
}
/* 聊天块 */
.contentDiv{
padding: 20px 10px 0px 10px;
}
.contentDiv .time{
text-align: center;
color: #999;
font-size: 13px;
margin-bottom: 10px;
}
img{
width: 40px;
height: 40px;
border-radius: 6px;
margin-top: 4px;
}
.content{
width: 80%;
display: flex;
margin-bottom: 16px;
}
.shop div{
margin: 0 5px;
}
.content div span{
font-size: 12px;
color: #999;
}
.content div p{
margin-top: 4px;
font-size: 14px;
background-color: #fff;
padding: 10px;
border-radius:5px;
line-height: 23px;
text-align: justify;
}
/* 用户话语 */
.user{
width: 80%;
margin-left: 20%;
display: flex;
flex-direction: row-reverse;
}
.user div{
margin-right: 8px;
}
.user div span{
display: block;
text-align: right;
}
.sendDiv{
margin-top: 30px;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
background-color: rgb(246, 246, 246);
border-top: 1px solid #d9d9d9;
display: flex;
align-items: center;
padding: 0 10px;
box-sizing: border-box;
}
.sendDiv input{
width: 75%;
border-radius: 27px;
background-color: #fff;
height: 35px;
line-height: 35px;
border: 1px solid #fff;
padding-left: 10px;
font-size: 13px;
margin-right: 10px;
}
.sendDiv button{
flex: 1;
background-color: green;
color: #fff;
border-radius: 24px;
border: none;
height: 30px;
line-height: 30px;
font-size: 13px;
}
注意: websocket是项目名,启动的时候注意路径
serve websocket
浏览器访问http://locahost:5000
,即可看到页面,第一次连接成功以后,页面上会显示“欢迎语”,证明已经成功连接,并收到后端推送的欢迎信息,
至此:完成前后端通讯。