저작권 안내
- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
자동 확장 가능한 콘서트 티켓 예매 사이트 구축하기
이재홍 http://www.pyrasis.com 2014.03.24 ~ 2014.06.30
Node.js로 웹 서버 작성하기
필요한 AWS 리소스를 생성하고 설정하였습니다. 이제 Node.js로 웹 서버를 작성해보겠습니다. 다음 예제 코드는 저의 GitHub 저장소에 있는 예제 코드를 받아서 사용합니다.
app.js
var express = require('express')
, Sequelize = require('sequelize')
, redis = require('redis')
, EC2Metadata = require('ec2metadata')
, http = require('http')
, fs = require('fs')
, app = express()
, server = http.createServer(app)
, io = require('socket.io').listen(server);
var redisEndpoint = {
host: 'exampleticket.o5nouc.0001.apne1.cache.amazonaws.com',
port: 6379
};
var rdsEndpoint = {
host: 'exampleticket.cnlconsezo7y.ap-northeast-1.rds.amazonaws.com',
port: 3306
};
// Redis Pub/Sub
var publisher = redis.createClient(redisEndpoint.port, redisEndpoint.host);
var subscriber = redis.createClient(redisEndpoint.port, redisEndpoint.host);
// MySQL DB 이름, 계정, 암호
var sequelize = new Sequelize('exampleticket', 'admin', 'adminpassword', {
host: rdsEndpoint.host,
port: rdsEndpoint.port,
maxConcurrentQuries: 1024,
logging: false
});
// MySQL DB 테이블 정의
var Seat = sequelize.define('Seat', {
seatId: { type: Sequelize.STRING, allowNull: false, unique: true },
actionType: { type: Sequelize.STRING, allowNull: false },
userId: Sequelize.STRING
});
// MySQL DB 테이블 생성
sequelize.sync();
var ipAddress;
app.get(['/', '/index.html'], function (req, res) {
fs.readFile('./index.html', function (err, data) {
res.contentType('text/html');
res.send(data);
});
});
// 좌석 예약, 결제 상태 출력
app.get('/seats', function (req, res) {
Seat.findAll({
where: { actionType: { ne: 'cancel' } }
}).success(function (seats) {
var data = [];
seats.map(function (seat) { return seat.values; }).forEach(function (e) {
seat = e.seatId.split('-');
data.push({
row: seat[0],
col: seat[1],
actionType: e.actionType,
userId: e.userId
});
});
res.header('Cache-Control', 'max-age=0, s-maxage=0, public');
res.send(data);
});
});
// socket.io에 접속할 IP 주소 전달
app.get('/ip', function (req, res) {
res.header('Cache-Control', 'max-age=0, s-maxage=0, public');
if (!ipAddress) {
EC2Metadata.get(['public-ipv4'], function (err, data) {
ipAddress = data.publicIpv4;
res.send(ipAddress);
});
}
else {
res.send(ipAddress);
}
});
// 좌석 예약, 결제 처리
io.sockets.on('connection', function (socket) {
socket.on('action', function (data) {
Seat.find({
where: { seatId: data.row + '-' + data.col }
}).success(function (seat) {
if (seat == null ||
seat.userId == data.userId ||
seat.actionType == 'cancel') {
if (seat == null)
seat = Seat.build();
seat.seatId = data.row + '-' + data.col;
seat.userId = data.userId;
seat.actionType = data.actionType;
seat.save().success(function () {
publisher.publish('seat', JSON.stringify(data));
});
}
});
});
});
// 실시간으로 좌석 상태 개신
subscriber.subscribe('seat');
subscriber.on('message', function (channel, message) {
io.sockets.emit('result', JSON.parse(message));
});
server.listen(80);
다음은 app.js에서 사용한 모듈들의 버전을 정의한 파일입니다.
package.json
{
"name": "ExampleTicketWebServer",
"version": "0.0.1",
"description": "ExampleTicketWebServer",
"dependencies": {
"express": "4.4.x",
"ec2metadata": "0.1.x",
"socket.io": "1.0.x",
"sequelize": "1.7.x",
"mysql": "2.3.2",
"redis": "0.10.x"
}
}
다음 내용을 index.html로 저장합니다.
index.html
<!DOCTYPE HTML>
<html>
<head>
<title>ExampleTicket</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<div id="stage" class="btn btn-default" style="width:600px; margin-left:265px;">Stage</div>
<div><span>ID: </span><span id="userId"></span></div>
<div id="seats" class="col-xs-12"></div>
<div id="dialog" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">결제</h4>
</div>
<div class="modal-body">
<p><span id="seatId"></span> 좌석을 결제하시겠습니까?</p>
</div>
<div class="modal-footer">
<button id="pay" type="button"
class="btn btn-primary" data-dismiss="modal">결제</button>
<button id="cancel" type="button"
class="btn btn-default" data-dismiss="modal">취소</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
var currentSeat;
var socket;
var userId = prompt('ID를 입력하세요', '');
$('#userId').text(userId);
function action(seat, actionType) {
socket.emit('action', {
actionType: actionType,
userId: userId,
row: seat.data('row'),
col: seat.data('col')
});
}
function openDialog(seat) {
$('#seatId').text(seat.data('row') + '-' + seat.data('col'));
$('#dialog').modal({ keyboard: false, backdrop: 'static' });
}
function updateSeat(data) {
var seat = $('div[data-row="' + data.row + '"][data-col="' + data.col + '"]');
if (data.actionType == 'reserve')
seat.removeClass('btn-default').addClass('btn-warning');
else if (data.actionType == 'cancel')
seat.removeClass('btn-warning').addClass('btn-default');
else if (data.actionType == 'pay')
seat.removeClass('btn-warning').addClass('btn-success');
if (data.userId == userId && (data.actionType == 'reserve' || data.actionType == 'pay'))
seat.addClass('active');
else
seat.removeClass('active');
}
function drawSeats() {
var seat;
var rowSymbols = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'];
$('#seats').css({ 'height': '550px', 'width': '1100px' });
var colDiv = $('<div>');
colDiv.css({ 'width': '35px', 'height': '550px', 'float': 'left', });
for (var row = 1; row <= 26; row++) {
var rowSymbol = $('<div>');
rowSymbol.css({
'width': '30px',
'height': '25px',
'padding-top': '7px',
'margin-top': '12px',
'margin-bottom': '1px' });
rowSymbol.addClass('badge').text(rowSymbols[row - 1]);
colDiv.append(rowSymbol);
}
$('#seats').append(colDiv);
for (var col = 1; col <= 32; col++) {
var colDiv = $('<div>');
for (var row = 1; row <= 26; row++) {
colDiv.css({ 'height': '550px', 'width': '30px', 'float': 'left' });
seat = $('<div>');
seat.css({ 'width': '30px',
'height': '30px',
'padding-left': '5px',
'padding-top': '5px',
'margin-bottom': '8px' })
.addClass('btn btn-default seat').text(col);
seat.attr('data-row', rowSymbols[row - 1]).attr('data-col', col);
colDiv.append(seat);
}
if (col <= 8)
colDiv.css('margin-top', col * 10);
else if (col <= 24 && col > 8)
colDiv.css('margin-top', '80px');
else if (col > 24)
colDiv.css('margin-top', (33 - col) * 10);
$('#seats').append(colDiv);
if (col % 4 == 0) {
var way = $('<div>');
way.css({ 'width': '10px', 'height': '550px', 'float': 'left' });
$('#seats').append(way);
}
}
}
$.get('/ip', function (ip) {
socket = io.connect(ip);
socket.on('connect', function () {
$('.seat').click(function () {
if ($(this).hasClass('btn-warning') || $(this).hasClass('btn-success'))
return;
currentSeat = $(this);
action($(this), 'reserve');
});
$('#pay').click(function () {
action(currentSeat, 'pay');
currentSeat = null;
});
$('#cancel').click(function () {
action(currentSeat, 'cancel');
currentSeat = null;
});
socket.on('result', function (data) {
if (currentSeat && data.userId == userId && data.actionType == 'reserve')
openDialog(currentSeat);
updateSeat(data);
});
});
});
drawSeats();
$.getJSON('/seats', function (data) {
$.each(data, function (i, e) {
updateSeat(e);
});
});
});
</script>
</body>
</html>
앞에서 생성한 <프로젝트 이름>.src S3 버킷에 ExampleTicketWebServer라는 디렉터리를 생성하고 app.js, package.json, index.html 파일을 올립니다.
저작권 안내
이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.- 블로그, 게시판 등에 퍼가는 것을 금지합니다.
- 비공개 포스트에 퍼가는 것을 금지합니다.
- 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
- 링크 및 SNS 공유는 허용합니다.
Published
2014-09-30