今天和朋友一起吃饭,偶然发现美团的点餐系统竟然是多设备同步的,因为之前都是谁付款就用谁的手机点餐,饶有兴致,正好借此写个demo,希望对于初次接触websocket的同学来说有个帮助,对于事件推送其实有很多方法
1.轮询:优点是一种通用的实现方式,简单粗暴的向服务端发起请求,几乎所有浏览器都支持。缺点就是服务器压力会比较大,如果是高频交互,也存在一定的延时,取决于轮询时间的逻辑
例如网易云的扫码登录,每隔几十毫秒发送登录请求检测是否成功扫码
2.Server-Sent Events (SSE):个人比较喜欢的事件推送机制,因为很多场景下不需要与服务器进行交互,只需要从服务器接收数据就可以,例如消息提示,评论点赞提示等等,例如消息列表小红点,但是并不是进行双向通信,所以缺点也很明显。
3.websocket:双向通信,服务器可以主动推送消息到客户端,客户端也可以主动向服务端推送消息,基本是实时推送,但是ie并不支持,长时间连接对服务器压力较大
首先是web端的实现,采用vue3
首先定义响应式数据,这个一般从后端获取,由于是demo所以我选择写死数据
const list = ref([
{ title: 'title1', price: 10 },
{ title: 'title2', price: 15 },
{ title: 'title3', price: 20 },
{ title: 'title4', price: 25 },
{ title: 'title5', price: 30 },
{ title: 'title6', price: 35 }
]);
然后定义选择每个商品的数量数量
const nums = ref([]);
// 返回几个数据动态添加每项商品的数量控制
list.value.forEach(() => {
nums.value.push(0);
});
定义自增自减函数以及计算总价函数
const jia = (index) => {
nums.value[index]++;
calculateTotalPrice();//每次自增或自减以后计算总价
};
const jian = (index) => {
if (nums.value[index] > 0) {
nums.value[index]--;
calculateTotalPrice();
}
};
const calculateTotalPrice = () => {
let total = 0;
for (let i = 0; i < nums.value.length; i++) {
total += nums.value[i] * list.value[i].price;
}
totalPrice.value = total;
};
接下来完成后端的操作
1.首先安装websocket
npm install ws
2.太长了不想写,直接贴代码
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
// 存储每个房间的客户端
let clients = {};
// 存储每个房间的购物车状态
let cartStates = {};
server.on('connection', (ws, req) => {
const urlParams = new URLSearchParams(req.url.split('?')[1]);
const roomId = urlParams.get('roomId');
if (!clients[roomId]) {
clients[roomId] = [];
cartStates[roomId] = Array(6).fill(0); // 初始购物车状态
}
clients[roomId].push(ws);
ws.send(JSON.stringify({ room: roomId, nums: cartStates[roomId] }));
ws.on('message', (message) => {
const { room, nums } = JSON.parse(message.toString('utf8'));
if (room === roomId) {
cartStates[room] = nums;
// 广播更新后的购物车状态给该房间的所有客户端
clients[room].forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ room, nums }));
}
});
}
});
ws.on('close', () => {
clients[roomId] = clients[roomId].filter(client => client !== ws);
if (clients[roomId].length === 0) {
delete clients[roomId];
delete cartStates[roomId];
}
});
});
console.log('启动成功端口8080');
3.接下来是客户端全部代码(确保连接到服务端)
<template>
<div class="container">
<ul>
<li v-for="(item, index) in list" :key="index">
<div class="title">{{ item.title }} - Price: {{ item.price }} </div>
<div class="btn">
<button @click="jian(index)">-</button>
<span>{{ nums[index] }}</span>
<button @click="jia(index)">+</button>
</div>
</li>
</ul>
<div class="price">Total Price: {{ totalPrice }}</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// 定义房间ID,可以根据实际情况设置
const roomId = 'room1';
const nums = ref([]);
const list = ref([
{ title: 'title1', price: 10 },
{ title: 'title2', price: 15 },
{ title: 'title3', price: 20 },
{ title: 'title4', price: 25 },
{ title: 'title5', price: 30 },
{ title: 'title6', price: 35 }
]);
// 返回几个数据动态添加每项商品的数量
list.value.forEach(() => {
nums.value.push(0);
});
const totalPrice = ref(0);
const ws = new WebSocket(`ws://localhost:8080?roomId=${roomId}`);
ws.onmessage = (event) => {
const { room, nums: updatedNums } = JSON.parse(event.data);
if (room === roomId) {
nums.value = updatedNums;
calculateTotalPrice();
}
};
const jia = (index) => {
nums.value[index]++;
calculateTotalPrice();
ws.send(JSON.stringify({ room: roomId, nums: nums.value }));
};
const jian = (index) => {
if (nums.value[index] > 0) {
nums.value[index]--;
calculateTotalPrice();
ws.send(JSON.stringify({ room: roomId, nums: nums.value }));
}
};
const calculateTotalPrice = () => {
let total = 0;
for (let i = 0; i < nums.value.length; i++) {
total += nums.value[i] * list.value[i].price;
}
totalPrice.value = total;
};
ws.onopen = () => {
console.log('WebSocket connection established.');
};
</script>
<style scoped>
.container{
width: 400px;
height: 700px;
border: solid 3px #000;
border-radius: 30px;
}
ul{
margin: 40px auto;
border: solid 1px #000;
width: 360px;
list-style-type: none;
padding: 0;
}
li{
width: 360px;
height: 40px;
background-color: antiquewhite;
display: flex;
justify-content: space-between;
align-items: center;
}
.title{
font-size: 20px;
font-weight: 600;
}
.btn{
height: 40px;
width: 110px;
display: flex;
justify-content: space-between;
align-items: center;
}
button{
border-radius: 90px;
width: 30px;
height: 30px;
background-color: #f8d63f;
border: none;
font-size: 20px;
}
.price{
font-size: 24px;
font-weight: 600;
}
</style>
效果展示