基于socket.io的实时在线选座系统(demo)


前言

前段时间公司做一个关于剧院的项目,遇到了这样一种情况。

在高并发多用户同时选座的情况下,假设A用户进入选座页面,正在选择座位,此时还没有提交所选择的座位。

这时B用户进入选座页面,迅速的选择了座位,提交。

而这个时候,A终于选择完毕,提交。 发现座位已经被买了。

当用户越多这样的情况越严重。

具体场景就是如此。

1、简介

本项目是基于jquery.seat-charts在线选座插件。集合socket.io,实现的实时选座系统,可应用于剧院,影院,车票等!

Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它主要是为了实现客户端和服务端的全双工通信。我们传统的http请求(抛开长链接不谈),只实现了一请求一回复的,没有办法做到服务器端向客户端推送数据的情况。而Socket.IO则实现了这一点。

依赖的模块

  • node.js
  • express
  • socket.io
  • jquery.seat-charts

2、安装部署

2.1 部署 express服务器

express是一个小巧的Node.js的Web应用框架,在构建HTTP服务器时经常使用到,所以直接以Socket.IO和express为例子来讲解。

在node.js环境下

npm install express 

express XXX                 *(XXX)是你的项目名字*

cd XXX                      *进入你的项目*

npm install                 *下载依赖*
2.2 添加依赖模块、修改默认的express框架

本项目没有使用express默认的模板引擎jade,采用了ejs模板,对新手来说更友好,学习成本更低。

npm install -D socket.io ejs

虽然简单,但是如果使用在express框架上则需要修改以下几个位置。

> views目录下所有文件

改为ejs后缀。 并把内容改为标准html 5 模板。

app.js 文件

app.set('view engine', 'jade');
修改为 ==> app.set('view engine', 'ejs');

bin > www 文件

因为socket.io需要监听服务,所以我们需要把www文件中的server 抛出
module.exports = server; 添加在www文件最后一行即可

新建 bin > socket.js 文件 (后续添加该处代码)

放置socket.io核心代码。实现模块分离。
bin > www 放置服务配置
bin > socket.js 放置socket.io配置信息

package.json 修改入口配置

"scripts": {
"start": "node ./bin/www"
}
修改为
"scripts": {
"start": "node ./bin/socket.js"
}

3、服务端代码

bin > socket.js

var server = require("./www");
var io = require("socket.io")(server); io.chooseSeat = {}; io.on('connection', function(socket) {
//用户选择的座位
socket.chooseSeat = {};
socket.isSold = false; socket.on('login', function(data) {
io.emit("loginlock",io.chooseSeat);
}); //监听用户选择座位
socket.on('selected', function(data) {
socket.chooseSeat[data.id] = data;
io.chooseSeat[data.id] = data;
io.emit("locking",data);
});
socket.on('cancleselected', function(data) {
delete socket.chooseSeat[data.id];
delete io.chooseSeat[data.id];
io.emit("canclelocking",data)
}); socket.on('sold', function(data) {
// 把售卖的座位信息返给其他用户
socket.isSold = true
io.emit('seatsold', Object.keys(data));
}); //监听用户退出 释放用户选择的未提交座位
socket.on('disconnect', function() {
// 如果没有购买,直接就退出了,才去释放座位 for(var t in socket.chooseSeat){
delete io.chooseSeat[t];
} if (!socket.isSold) {
io.emit('userout', socket.chooseSeat);
}
})
});

4、客户端代码

bin > socket.js

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>基于socket.io的实时在线选座系统(影院版)</title>
<meta name="keywords" content="jQuery在线选座,jQuery选座系统,WebSocket,socket.io,实时选座系统" />
<meta name="description" content="本项目是基于jquery.seat-charts在线选座插件。集合socket.io,实现的实时选座系统,可应用于剧院,影院,车票等" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" type="text/css" href="css/reset.css" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
</head>
<body>
<div class="container">
<h2 class="title"><a href="#">jQuery在线选座(影院版)</a></h2>
<div class="demo clearfix">
<!---左边座位列表-->
<div id="seat_area">
<div class="front">屏幕</div>
</div>
<!---右边选座信息-->
<div class="booking_area">
<p>电影:<span>天将雄师</span></p>
<p>时间:<span>03月20日 22:15</span></p>
<p>座位:</p>
<ul id="seats_chose"></ul>
<p>票数:<span id="tickects_num">0</span></p>
<p>总价:<b>¥<span id="total_price">0</span></b></p>
<input type="button" class="btn" id="commitSeat" value="确定购买" />
<div id="legend"></div>
</div>
</div>
</div>
<script src="js/jquery-3.2.1.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="js/jquery.seat-charts.js"></script>
<script src="js/index.js"></script>
</body>
</html>

pulic > js > index.js

$(function(){
var price = 100; //电影票价
var initData = {
socket: io.connect('http://192.168.1.96:3000'),
mapData:[ //座位结构图 a 代表座位; 下划线 "_" 代表过道
'cccccccccc',
'cccccccccc',
'__________',
'cccccccc__',
'cccccccccc',
'cccccccccc',
'cccccccccc',
'cccccccccc',
'cccccccccc',
'cc__cc__cc'
],
iconStatus:[ // 座位状态
['c', 'available', '可选座'],
['c', 'selected', '已选中'],
['c', 'locking', '已锁定'],
['c', 'unavailable', '已售出'],
],
selectedSeat:{}
}
var interaction = {
initMap:function(){
var _this = this;
var $cart = $('#seats_chose'), //座位区
$tickects_num = $('#tickects_num'), //票数
$total_price = $('#total_price'); //票价总额
var sc = $('#seat_area').seatCharts({
map:initData.mapData,
naming: { //设置行列等信息
top: false, //不显示顶部横坐标(行)
getLabel: function(character, row, column) { //返回座位信息
return column;
}
},
legend: { //定义图例
node: $('#legend'),
items: initData.iconStatus
},
click: function() {
if (this.status() == 'available') { //若为可选座状态,添加座位
$('<li>' + (this.settings.row + 1) + '排' + this.settings.label + '座</li>')
.attr('id', 'cart-item-' + this.settings.id)
.data('seatId', this.settings.id)
.appendTo($cart);
$tickects_num.text(sc.find('selected').length + 1); //统计选票数量
$total_price.text(_this.getTotalPrice(sc) + price); //计算票价总金额 // 向服务器发送消息,座位被我选中
_this.emit("selected",{
firetype:'selected',
firetime:new Date().toLocaleString(),
character:this.settings.character,
column:this.settings.column,
data:this.settings.data,
id:this.settings.id,
label:this.settings.label,
row:this.settings.row
})
initData.selectedSeat[this.settings.id] = this.settings;
return 'selected';
} else if (this.status() == 'selected') { //若为选中状态
$tickects_num.text(sc.find('selected').length - 1); //更新票数量
$total_price.text(_this.getTotalPrice(sc) - price); //更新票价总金额
$('#cart-item-' + this.settings.id).remove(); //删除已预订座位 // 向服务器发送消息,座位被我取消
_this.emit("cancleselected",{
firetype:'cancleselected',
firetime:new Date().toLocaleString(),
character:this.settings.character,
column:this.settings.column,
data:this.settings.data,
id:this.settings.id,
label:this.settings.label,
row:this.settings.row
})
delete initData.selectedSeat[this.settings.id];
return 'available';
} else if (this.status() == 'unavailable') { //若为已售出状态
return 'unavailable';
} else {
return this.style();
}
}
});
//设置已售出的座位
sc.get(['1_3', '1_4', '4_4', '4_5', '4_6', '4_7', '4_8']).status('unavailable');
interaction.commitSeat();
},
getTotalPrice:function(sc){//计算票价总额
var total = 0;
sc.find('selected').each(function() {
total += price;
});
return total;
},
emit:function(type,msg){
initData.socket.emit(type,msg);
},
socketEvent:function(){
this.emit("login","用户进入选座页面");
initData.socket.on("loginlock",function(loginlock){
for(var t in loginlock){
var isMine = interaction.isMineFire(t,"selected");
if (!isMine) {
$('#'+t).addClass("locking");
}
}
})
initData.socket.on("locking",function(data){
var isMine = interaction.isMineFire(data.id,"selected");
if (!isMine) {
$('#'+data.id).addClass("locking")
}
})
initData.socket.on("canclelocking",function(data){
$('#'+data.id).removeClass("locking");
})
initData.socket.on("userout",function(outuser){
// outuser 为退出用户所选择的座位。
for(var t in outuser){
$('#'+t).removeClass("locking");
}
})
initData.socket.on("seatsold",function(soldseat){
// soldseat 为用户已经购买的座位。 客户端更新座位状态
$.each(soldseat,function(index,item){
$('#'+item).addClass('unavailable');
})
})
},
isMineFire:function(id,type){
return $('#'+id).attr('class').indexOf(type) > 0;
},
commitSeat:function(){
$("#commitSeat").click(function(){
if (JSON.stringify(initData.selectedSeat) === "{}") {
alert("请至少选择一个座位再提交!")
return false;
}
//$.post("http://XXXXXXXX",座位数据,function(){
// 延迟2秒模拟生成订单的ajax请求,请求成功跳转订单页。
setTimeout(function() {
interaction.emit("sold",initData.selectedSeat);
location.href = "/order";
}, 2000);
//})
})
}
}
interaction.initMap();
interaction.socketEvent();
})

5、查看效果

打开浏览器,输入localhost:3000
多打开几个浏览器,可查看实时响应效果

6、注意事项

此处需要修改为你自己的端口,否则会出现监听不到的情况。


作者 HoChine

2017 年 09月 03日

项目演示: http://hochine.cn/demo/realTimeChooseSeat

GitHub地址: https://github.com/HoChine/RealTime-chooseSeat

基于socket.io的实时在线选座系统的更多相关文章

  1. 基于socket.io的实时消息推送

    用户访问Web站点的过程是基于HTTP协议的,而HTTP协议的工作模式是:请求-响应,客户端发出访问请求,服务器端以资源数据响应请求. 也就是说,服务器端始终是被动的,即使服务器端的资源数据发生变化, ...

  2. 在线白板,基于socket.io的多人在线协作工具

    首发:个人博客,更新&纠错&回复 是昨天这篇博文留的尾巴,socket.io库的使用练习,成品地址在这里. 代码已经上传到github,传送门.可以开俩浏览器看效果. 现实意义是俩人在 ...

  3. Node+Express+MongoDB + Socket.io搭建实时聊天应用

    Node+Express+MongoDB + Socket.io搭建实时聊天应用 前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战 ...

  4. jQuery在线选座订座(影院篇)

    原文:jQuery在线选座订座(影院篇) 我们在线购票时(如电影票.车票等)可以自己选座.开发者会在页面上列出座次席位,用户可以一目了然的看到可以选择的座位及支付.本文以电影院购票为例,为您展示如何选 ...

  5. Node+Express+MongoDB + Socket.io搭建实时聊天应用实战教程(二)--node解析与环境搭建

    前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战.写教程一方面在自己写的过程中需要考虑更多的东西,另一方面希望能对node入门者有 ...

  6. 基于 socket.io 的 AI 服务 杂谈

    为什么会想到来聊下这个话题. 前几天在公司的项目中,开发一个基于 socket.io 的直播 IM 功能. 直播分为两部分,一部分是比较昂贵的 视频推流, 另外一部分是 IM 即时聊天服务. 从这里开 ...

  7. Node+Express+MongoDB+Socket.io搭建实时聊天应用实战教程(一)--MongoDB入门

    前言 本文并不是网上流传的多少天学会MongoDB那种全面的教程,而意在总结这几天使用MongoDB的心得,给出一个完整的Node+Express+MongoDB+Socket.io搭建实时聊天应用实 ...

  8. jQuery在线选座订座(高铁版)

    除了电影院在线选座,我们还会接触到飞机机舱选座,当然也有汽车票火车票选座的.假如有一天买火车票也提供在线选座,那么今天我来给大家介绍下如何使用jQuery选座插件完成高铁列车座位布置.选座.不同等级座 ...

  9. 基于 socket.io, 简单实现多平台类似你猜我画 socket 数据传输

    一.前言 socket.io 实现了实时双向的基于事件的通讯机制,是基于 webSocket 的封装,但它不仅仅包括 webSocket,还对轮询(Polling)机制以及其它的实时通信方式封装成了通 ...

随机推荐

  1. MySQL GROUP BY多个字段分组用法详解

    mysql语句中group by 很容易理解 是分组查询.比如 select sum(score) from user group by name 意思是查询每个人的分数总和但是, select su ...

  2. JAVA 验证码生成(转)

    最近做了一下验证码的功能,网上找了一篇还不错,引用下:http://blog.csdn.net/ruixue0117/article/details/22829557 这篇文章非常好,但是web和js ...

  3. mint-ui —— navbar和tab-container的区别

    navbar的具体实现 <template> <div class="page-navbar"> <div class="page-titl ...

  4. MySQL 账户管理

    一.当前日期.当前用户.当前数据库版本 mysql> select curdate(),version(),database(),user(); +------------+---------- ...

  5. 【Ubuntu 16】启动Eclipse Indigo报错 error code1 jdk没有配置好

    在/etc/profile下配置好JAVA_HOME CLASSPATH PATH 这些变量后 eclipse启动jvm并不能直接按照这些变量来启动 需要使用命令 update-alternative ...

  6. HashMap和ConcurrentHashMap流程图

    本文表达HashMap和ConcurrentHashMap中的put()方法的执行流程图,基于JDK1.8的源码执行过程. HashMap的put()方法: ConcurrentHashMap的put ...

  7. Python金融行业必备工具

    有些国外的平台.社区.博客如果连接无法打开,那说明可能需要"科学"上网 量化交易平台 国内在线量化平台: BigQuant - 你的人工智能量化平台 - 可以无门槛地使用机器学习. ...

  8. django全文检索

    -------------------linux下配置操作1.在虚拟环境中依次安装包 1.pip install django-haystack haystack:django的一个包,可以方便地对m ...

  9. C++中const关键字用法

    为什么使用const?采用符号常量写出的代码更容易维护:指针常常是边读边移动,而不是边写边移动:许多函数参数是只读不写的.const最常见用途是作为数组的界和switch分情况标号(也可以用枚举符代替 ...

  10. 新建github项目,邀请成员

    创建一个项目(repository) 进入项目,点击 SETTINGS 点击左侧导航的 Collaborators 在 Teams 里有个下拉菜单,里面你可以给你的 team 选择 write(写)权 ...