年终结尾最后一次更新,在不写一次估计年前就没机会再写了(保证一月一次)。突然想起之前一个朋友问起实时订单推送如何实现。在年尾没啥事自己也比较感兴趣,简单实现了一下。
1.开始
直接梭。安装websocket依赖库。
//安装 websocket依赖
npm install websocket
//安装 hashMap依赖
npm install hashmap
2.实现服务端
此处代码是简单实现一下业务场景而已,其中使用很多for循环,真实业务场景千万不要这么使用,可自行找其他代替方案。如Redis或MongoDB都可以很完美实现。(有兴趣或看到的朋友,千万别复制粘贴一把梭了。一定要看注释。在结合自己的想法去优化,取长补短。个人觉得有更好的方式去实现不妨留言探讨一下。谢谢!共同进步!)
const http = require('http');
const WebSocketServer = require("websocket").server
const hashMap = require('hashmap');
const httpServer = http.createServer((request, response) => {
console.log('[' + new Date + '] Received request for ' + request.url);
response.writable(404);
response.end();
})
const wsServer = new WebSocketServer({
httpServer,
autoAcceptConnections: true
})
const driverMap = new hashMap();//使用hashMap,保持键唯一,查询快
const passengerMap = new hashMap();//使用hashMap,保持键唯一,查询快
const driverContent = [];
wsServer.on('connect', function (connection) {
connection.on('message', message => {
console.log(message);
if (message.type == "utf8") {
//转换一下JSON格式。
var ConnectMessage = JSON.parse(message.utf8Data)
if (ConnectMessage.type == "driver") {
console.log("司机连接socket!");
//将司机连接头信息装进hashMap形成一个队列,以openID为Key保证唯一。此为测试用,实际线上环境使用redis。
//(如:使用hashMap在线上环境遍历时导致服务器挂壁,独自负责哈~!)
driverMap.set(ConnectMessage.openId, connection);
//此处将司机信息保存起来。此信息也可以用redis记录,因为3.2版本之后redis也支持地理操作(如距离查询等等)。
//当然使用mongodb也是可以的。redis优先级最高,因为可以缓存,查询起来起码会快很多。
driverContent.push(ConnectMessage)
//司机连接成功给一个提示!
connection.send(JSON.stringify({ code: 200, status: true, msg: "连接成功!等待系统为您指派任务!" }));
}
if (ConnectMessage.type == "passenger") {
console.log("乘客连接socket!");
//将乘客连接头信息装进hashMap形成一个队列。
passengerMap.set(ConnectMessage.openId, connection);
//这里是代替redis或mongodb 经纬度距离查询操作。
for (var i = 0; i < driverContent.length; i++) {
//我这里实现的比较简单,正常是拿到客户经纬度去redis或mongodb查询匹配的距离,如多个结果直接拿最近距离的结果。
var dist = GetDistance(driverContent[i].lng, driverContent[i].lat, ConnectMessage.lng, ConnectMessage.lat)
if (dist < 20) {
console.log(dist);
console.log("订单匹配成功");
driverMap.forEach(function (value, key) {
if (driverContent[i].openId == key) {
//此处当订单派发成功后,司机端给一个提示,是否接单,如接单就将订单持久化放入数据库,如不接单将订单回归socket订单池。
value.send(JSON.stringify({ code: 200, status: true, data: ConnectMessage, msg: "接单成功!" }));
//订单发布成功后,通知客户端
connection.send(JSON.stringify({ code: 200, status: true, data: driverContent, msg: "附近司机成功接揽您的订单!" }));
}
}, driverMap)
}
}
}
}
}).on("close", (reasonCode, description) => {
console.log('[' + new Date() + '] Peer ' + connection.remoteAddress + ' disconnected.')//关闭
// 由于司机 连接socket 就加入的序列,所有用户退出socket需要移除用户序列
driverMap.forEach(function (value, key) {
// 判断关闭socket的连接 是否与序列中的连接一样。如果一样移除
if (connection === value) {
driverMap.remove(key)
//删除司机信息。
for (var i = 1; i < driverContent.length; i++) {
if (driverContent[i].openId == key) {
driverContent.splice(driverContent[i], 1)
}
}
}
}, driverMap)
//乘客
passengerMap.forEach(function (value, key) {
if (connection === value) {
passengerMap.remove(key)
}
}, passengerMap)
})
})
httpServer.listen(666, () => {
console.log((new Date()) + ' Server is listening on port 666');
})
//代替redis 和 Mongodb中的 经纬度地理计算。此方法为其他博客找的,距离查询跟redis mongodb 有很大出入,跟手机APP定位的距离也有出入。
function GetDistance(lat1, lng1, lat2, lng2) {
var radLat1 = lat1 * Math.PI / 180.0;
var radLat2 = lat2 * Math.PI / 180.0;
var a = radLat1 - radLat2;
var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * 6378.137;// EARTH_RADIUS;
s = Math.round(s * 10000) / 10000;
return s;
}
3.实现客户端(包括客户端,司机端,只有简单JS实现,具体UI实现个人有想法可以自己梭一把。)
司机端:
<template>
<div>
<a-button @click="close">断开</a-button>
</div>
</template>
<script>
export default{
name:"socketTest",
data(){
return{
socket:""
}
},
created(){
this.initWebSocket()
},
mounted(){},
methods:{
initWebSocket(){
var url = "ws:192.168.16.90:666";
this.socket = new WebSocket(url);
this.socket.onopen = this.onOpen;
this.socket.onmessage = this.getMessage;
this.socket.onerror = this.error
this.socket.onclose = this.close
},
onOpen(e){
console.log("socket连接成功!",e);
var data = {
openId:"driver_2d14d3f0d84a11e9b5c7a1dd69b6481f",
type:"driver",
name:'chenbd',
lng:"22.5595796300",
lat:"113.8894646400"
}
this.send(JSON.stringify(data));
},
error(err){
console.log("socket连接失败",err);
},
getMessage(message){
console.log("消息接收成功!")
console.log(message.data)
console.log(JSON.parse(message.data))
},
send(params){
this.socket.send(params)
},
close(){
console.log("socket关闭!")
},
},
components:{}
}
</script>
<style lang="scss" scoped>
</style>
客户端:
<template>
<div>
</div>
</template>
<script>
export default{
name:"socketTest2",
data(){
return{
socket:""
}
},
created(){
this.initWebSocket()
},
mounted(){
},
methods:{
initWebSocket(){
var url = "ws:192.168.16.90:666";
this.socket = new WebSocket(url);
this.socket.onopen = this.onOpen;
this.socket.onmessage = this.getMessage;
this.socket.onerror = this.error
this.socket.onclose = this.close
},
onOpen(e){
console.log("socket连接成功!",e);
var data = {
openId:"passenger_2d14d3f0d84a11e9b5c7a1dd69b6481f",
type:"passenger",
name:'波波先生',
lng:"22.6980270000",
lat:"113.9989310000",
address:"我要去深圳北站,我需要30个小时后赶到!"
}
this.send(JSON.stringify(data));
},
error(err){
console.log("socket连接失败",err);
},
getMessage(message){
console.log("消息接收成功!")
console.log(message.data)
console.log(JSON.parse(message.data))
},
send(params){
this.socket.send(params)
},
close(){
console.log("socket关闭!")
},
},
components:{
}
}
</script>
<style lang="scss" scoped>
</style>
4.实现效果图。
1.先让司机端连线。打开页面,自动连接socket服务器。等待系统自动匹配订单
2.客户端发布订单消息!打开页面自动发布订单消息,等待系统交付司机订单消息,等待通知!
5.结尾
做到这就结束了。以上有什么错误或更好的处理方式,请留言我及时更正,避免误人子弟!
同时年尾最后几天了。祝大家明年更好,工作顺心。新年快乐!