基于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. JMeter元件的作用域和执行顺序

    元件的作用域 配置元件:会影响其作用范围内的所有元件,作用范围是最大的,只要创建就对所有元件起作用. 前置处理器:在其作用范围内的每一个Sample元件之前执行: 定时器:对其作用范围内的每一个Sam ...

  2. async简单使用

    node的异步io虽然好用,但是控制异步流程确实一个比较麻烦的事情,比如在爬虫中控制并发数量,避免并发过大导致网站宕机或被加入黑名单.因此需要一个工具来控制并发,这个工具可以自己写或者使用async( ...

  3. HTK语音识别示例(Ubuntu)

    一.简介 HTK(Hidden Markov Model Toolkit)是一款语音识别工具包,诞生于Cambridge University Engineering Department (CUED ...

  4. 【Java学习笔记之二十五】初步认知Java内部类

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...

  5. JavaScript练习题 全局变量 局部变量 作用域

    前沿:大家好~我是阿飞~本次 任何简单的事情都可以复杂化,本次让我们来做下搞事情的练习题吧 例题1: var a = 1; function fn1(){ var a = 2; alert(a); / ...

  6. 数据结构-单向链表 C和C++的实现

    数据结构,一堆数据的存放方式. 今天我们学习数据结构中的 链表: 链表的结构: 链表是一种特殊的数组,它的每个元素称为节点,每个节点包括两个部分: 数据域:存放数据,此部分与数组相同 指针域:存放了下 ...

  7. VS2013禁用Browser Link

    禁用原因 VS2013新增的Browser Link功能虽然“强大”,但我并不需要. 但默认是开启的,会在页面中自动添加如下的代码,查看AJAX时造成很大的干扰. <!-- Visual Stu ...

  8. JAVA实用案例之文件导入导出(POI方式)

    1.介绍 java实现文件的导入导出数据库,目前在大部分系统中是比较常见的功能了,今天写个小demo来理解其原理,没接触过的同学也可以看看参考下. 目前我所接触过的导入导出技术主要有POI和iRepo ...

  9. 从送外卖到建站售主机还有共享自行车说起-2017年8月江西IDC排行榜与发展报告

    曾几何时,送外卖,这样的"低技术含量"工作,很难被互联网公司看上,直到百度将其当作连接终端用户与大数据的管道. 同样,销售主机域名和建站业务,本也是"微小体量" ...

  10. 【DDD】领域驱动设计实践 —— 框架实现

    本文主要了在社区服务系统(ECO)中基于SpringMVC+mybatis框架对DDD的落地实现.本文为系列文章中的其中一篇,其他内容可参考:通过业务系统的重构实践DDD. 框架实现图 该框架实现基本 ...