基于socket.io的实时在线选座系统
基于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的实时在线选座系统的更多相关文章
- 基于socket.io的实时消息推送
用户访问Web站点的过程是基于HTTP协议的,而HTTP协议的工作模式是:请求-响应,客户端发出访问请求,服务器端以资源数据响应请求. 也就是说,服务器端始终是被动的,即使服务器端的资源数据发生变化, ...
- 在线白板,基于socket.io的多人在线协作工具
首发:个人博客,更新&纠错&回复 是昨天这篇博文留的尾巴,socket.io库的使用练习,成品地址在这里. 代码已经上传到github,传送门.可以开俩浏览器看效果. 现实意义是俩人在 ...
- Node+Express+MongoDB + Socket.io搭建实时聊天应用
Node+Express+MongoDB + Socket.io搭建实时聊天应用 前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战 ...
- jQuery在线选座订座(影院篇)
原文:jQuery在线选座订座(影院篇) 我们在线购票时(如电影票.车票等)可以自己选座.开发者会在页面上列出座次席位,用户可以一目了然的看到可以选择的座位及支付.本文以电影院购票为例,为您展示如何选 ...
- Node+Express+MongoDB + Socket.io搭建实时聊天应用实战教程(二)--node解析与环境搭建
前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战.写教程一方面在自己写的过程中需要考虑更多的东西,另一方面希望能对node入门者有 ...
- 基于 socket.io 的 AI 服务 杂谈
为什么会想到来聊下这个话题. 前几天在公司的项目中,开发一个基于 socket.io 的直播 IM 功能. 直播分为两部分,一部分是比较昂贵的 视频推流, 另外一部分是 IM 即时聊天服务. 从这里开 ...
- Node+Express+MongoDB+Socket.io搭建实时聊天应用实战教程(一)--MongoDB入门
前言 本文并不是网上流传的多少天学会MongoDB那种全面的教程,而意在总结这几天使用MongoDB的心得,给出一个完整的Node+Express+MongoDB+Socket.io搭建实时聊天应用实 ...
- jQuery在线选座订座(高铁版)
除了电影院在线选座,我们还会接触到飞机机舱选座,当然也有汽车票火车票选座的.假如有一天买火车票也提供在线选座,那么今天我来给大家介绍下如何使用jQuery选座插件完成高铁列车座位布置.选座.不同等级座 ...
- 基于 socket.io, 简单实现多平台类似你猜我画 socket 数据传输
一.前言 socket.io 实现了实时双向的基于事件的通讯机制,是基于 webSocket 的封装,但它不仅仅包括 webSocket,还对轮询(Polling)机制以及其它的实时通信方式封装成了通 ...
随机推荐
- android炫酷动画源码,QQ菜单、瀑布流、二维码源码
Android精选源码 自定义弹框封装,ProgressDialog,StatusDialog和Toast,支持自定义颜色 有深度感的fragment代码 在屏幕顶部或者底部显示提示 短信转发工具,自 ...
- Struts2传参碰到的奇怪问题
在使用Struts2框架,前端页面提交参数为日期时,Action中获取类型直接是Date 出现的问题是,局域网中无法访问,外网可正常访问. 把Action中Date类型换成String类型都可正常访问 ...
- Orleans的集群构建
Orleans的集群构建 这是Orleans系列文章中的一篇.首篇文章在此 听闻一周前,微软公布了.net core2.0,以及各种各样的其他core2.0.大家都很兴奋.微妈的诚意真是满满的.这次开 ...
- canvas图形处理和进阶用法
前面的话 上一篇博客介绍了canvas基础用法,本文将更进一步,介绍canvas的图形处理和进阶用法 图形变换 图形变换是指用数学方法调整所绘形状的物理属性,其实质是坐标变形.所有的变换都依赖于后台的 ...
- ffmpeg音频播放代码示例-avcodec_decode_audio4
一.概述 最近在学习ffmpeg解码的内容,参考了官方的教程http://dranger.com/ffmpeg/tutorial03.html,结果发现这个音频解码的教程有点问题.参考了各种博客,并同 ...
- 前端开发中一些好用的chrome插件总结
下载了很多chrome插件和应用,有些是常用的,有些偶尔用一次,有些是不止开发中用的,现做一下总结: 红色是个人认为比较好用的,排名不分先后 1.AppJump管理应用的插件 安装了许多应用之后肯定 ...
- Struts2使用自定义拦截器导致Action注入参数丢失、url参数
写struts2项目时发现前台超链接中的参数无法传到action, 所有带有传递参数的均无法正常使用了,在Action中所有的参数无法被注入. 后来经过debug发现其中的页面都要先经过拦截器,而后再 ...
- 求链表内环的入口节点-Java
步骤: 1.设置快慢两个指针,slow和fast,每次slow走一步slow.next,而fast走两步fast.next.next. 2.如果链表有环肯定可以在环内的一个节点相遇. 3.此时,slo ...
- CDH-5.7.1离线安装
CDH-5.7.1离线安装 参考自:http://blog.csdn.net/jdplus/article/details/45920733 1.文件下载 CDH (Cloudera's Dist ...
- 融会贯通——最常用的“合成复用原则”技能点Get
复用一个类的时候,多使用对象的组合/聚合的关联关系,而不是继承. 之前提到的"依赖倒转原则",是以里氏代换原则为基础的实现开闭原则目标的手段,这一条路线涉及到的是类的继承(包括单继 ...