개발 전 과정
그 동안의 개발과정은 해당 카테고리 + Node.js 카테고리에서 볼 수 있습니다.
백엔드
backend/app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
const history = require('connect-history-api-fallback');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const cors = require("cors");
const mongoose = require('mongoose');
var session = require('express-session');
var indexRouter = require('./routes/index');
require('dotenv').config();
var app = express();
var views = [
path.join(__dirname, 'views')
]
app.set('views', views);
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.cookie_secret));
app.use(session({
secret : process.env.session_secret,
resave : false,
saveUninitialized : false,
cookie : {
maxAge : (3.6e+6)*12,
httpOnly : false
},
store : require('mongoose-session')(mongoose)
}))
app.use('/', indexRouter);
app.use(history());
app.use(express.static(path.join(__dirname, 'public')));
app.use(
cors({
origin : 'http://olrang.shop:3000',
credentials : true,
})
);
app.use(function(req, res, next) {
next(createError(404));
});
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
mongoose.connect(
'mongodb://olrang.shop:27017/mydbname',
{ useNewUrlParser : true, useUnifiedTopology : true,},
function(err) {
if(err){
console.error('mongodb connection error!!!', err);
}
console.log('mongodb connected!!')
}
);
var userControll=require('./routes/index.js');
app.use('/signup',userControll);
app.use('/login', userControll);
app.use('/logout', userControll);
app.use('/aptitude',userControll);
app.use('/secession',userControll);
app.use('/api/mypage',userControll);
app.use('/mypage',userControll);
module.exports = app;
Express, cookie, mongodb, express-session을 사용했습니다.
cookie, session의 secretkey는 dotenv를 사용해서 분리했습니다.
https가 아닌 http를 사용했기 때문에 보안옵션은 사용하지 않았습니다.
backend/routes/index.js
var express = require('express');
var path = require('path');
var router = express.Router();
router.use(express.urlencoded({extended:true}));
router.use(express.json())
var User = require('./mongodbuser');
var Product = require('./mongodbproduct');
var Order = require('./mongodborder');
const crypto = require('crypto');
router.get('/', function(req, res, next) {
res.sendFile(path.join(__dirname, '../public', 'index.html'));
});
router.post('/signup', (request, response) => {
var ck = true;
if(request.body.id == request.body.password){
try{
response.send("<script>alert('아이디와 비밀번호는 서로 달라야 합니다.');location.href='/signup';</script>");
}catch (exception) {
response.redirect('/signup');
}
ck= false;
}
if (request.body.id == ''){
try{
response.send("<script>alert('id가 입력되지 않았습니다.');location.href='/signup';</script>");
}catch (exception) {
response.redirect('/signup');
}
}
if (request.body.password == ''){
try{
response.send("<script>alert('password가 입력되지 않았습니다.');location.href='/signup';</script>");
}catch (exception) {
response.redirect('/signup');
}
}
if (request.body.name == ''){
try{
response.send("<script>alert('name이 입력되지 않았습니다.');location.href='/signup';</script>");
}catch (exception) {
response.redirect('/signup');
}
}
if (request.body.phone == ''){
try{
response.send("<script>alert('phone number가 입력되지 않았습니다.');location.href='/signup';</script>");
}catch (exception) {
response.redirect('/signup');
}
}
User.find((err, users) => {
if(users.id == request.body.id){
ck = false;
try{
response.send("<script>alert('이미 존재하는 아이디입니다.');location.href='/signup';</script>");
}catch (exception) {
response.redirect('/signup');
}
}
});
if(ck==true){
var password = request.body.password;
crypto.randomBytes(64, (err, buf) => {
const salt = buf.toString('base64')
crypto.pbkdf2(password, salt, 107529, 64, 'sha512', (err, key) => {
User.create ({
id:request.body.id,
password:(key.toString('base64')),
salt : salt,
name:request.body.name,
phone:request.body.phone,
});
});
});
response.redirect('/login');
}
});
router.post('/login', (req, res) => {
User.findOne({id : req.body.id},(err, users) => {
if(!users || err){
try{
res.send("<script>alert('아이디가 틀렸습니다.');location.href='/login';</script>");
}catch (exception) {
res.redirect('/login');
}
}
else {
var cpassword = req.body.password;
crypto.randomBytes(64, (err, buf) => {
crypto.pbkdf2(cpassword, users.salt, 107529, 64, 'sha512', (err, key) => {
cpassword = key.toString('base64')
if (users.password == cpassword) {
req.session.userinfo = users.phone;
res.redirect('/');
} else {
try{
res.send("<script>alert('비밀번호가 틀렸습니다.');location.href='/login';</script>");
}catch (exception) {
res.redirect('/login');
}
}
});
});
}
})
})
router.get('/logout', (req, res) => {
if(!req.session) {
res.redirect('/');
} else {
req.session.destroy(()=>{
res.clearCookie('connect.sid');
res.redirect('/');
});
}
})
router.get('/api/mypage', (req, res) => {
var mydata = {phone : req.session.userinfo};
res.json(mydata);
})
router.post('/mypage', (req, res) => {
if(req.body.phone == ''){
try{
res.send("<script>alert('값을 입력해주세요');location.href='/mypage';</script>");
}catch (exception) {
res.redirect('/mypage');
}
} else {
users.updateOne({$set : {phone : req.body.phone}}).exec();
res.redirect('/');
}
res.redirect('/');
})
router.post('/secession', (req, res) => {
User.findOne({phone : req.sesssion.userinfo},(err, users) => {
if(!req.sesssion.userinfo || err) {
res.redirect('/');
} else {
users.deleteOne();
req.session.destroy(()=>{
res.clearCookie('connect.sid');
res.redirect('/');
});
}
})
})
router.post('/aptitude', async (req, res) => {
try {
const imp_uid = req.body.imp_uid;
const merchant_uid = req.body.merchant_uid;
const getToken = await axios({
url: "https://api.iamport.kr/users/getToken",
method: "post",
headers: { "Content-Type": "application/json" },
data: {
imp_key: "2406300329155625",
imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556"
}
});
const { access_token } = getToken.data.response;
const getPaymentData = await axios({
url: `https://api.iamport.kr/payments/${imp_uid}`,
method: "get",
headers: { "Authorization": access_token }
});
const paymentData = getPaymentData.data.response;
const order = await Product.findOne({name :"aptitude"});
const amountToBePaid = order.amount;
const { amount, status } = paymentData;
if (amount === amountToBePaid && req.cookies.session) {
await Order.create({
number : merchant_uid,
name : req.cookies.session,
product : req.body.name,
amount : req.body.amount
});
switch (status) {
case "paid":
console.log("결제 성공");
break;
}
} else {
console.log("위조된 결제 시도");
}
} catch (e) {
res.status(400).send(e);
}
});
router.post('/homelover', async (req, res) => {
try {
const imp_uid = req.body.imp_uid;
const merchant_uid = req.body.merchant_uid;
const getToken = await axios({
url: "https://api.iamport.kr/users/getToken",
method: "post",
headers: { "Content-Type": "application/json" },
data: {
imp_key: "2406300329155625",
imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556"
}
});
const { access_token } = getToken.data.response;
const getPaymentData = await axios({
url: `https://api.iamport.kr/payments/${imp_uid}`,
method: "get",
headers: { "Authorization": access_token }
});
const paymentData = getPaymentData.data.response;
const order = await Product.findOne({name :"homelover"});
const amountToBePaid = order.amount;
const { amount, status } = paymentData;
if (amount === amountToBePaid && req.cookies.session) {
await Order.create({
number : merchant_uid,
name : req.cookies.session,
product : req.body.name,
amount : req.body.amount
});
switch (status) {
case "paid":
console.log("결제 성공");
break;
}
} else {
console.log("위조된 결제 시도");
}
} catch (e) {
res.status(400).send(e);
}
});
router.post('/past', async (req, res) => {
try {
const imp_uid = req.body.imp_uid;
const merchant_uid = req.body.merchant_uid;
const getToken = await axios({
url: "https://api.iamport.kr/users/getToken",
method: "post",
headers: { "Content-Type": "application/json" },
data: {
imp_key: "2406300329155625",
imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556"
}
});
const { access_token } = getToken.data.response;
const getPaymentData = await axios({
url: `https://api.iamport.kr/payments/${imp_uid}`,
method: "get",
headers: { "Authorization": access_token }
});
const paymentData = getPaymentData.data.response;
const order = await Product.findOne({name :"pastpotion"});
const amountToBePaid = order.amount;
const { amount, status } = paymentData;
if (amount === amountToBePaid && req.cookies.session) {
await Order.create({
number : merchant_uid,
name : req.cookies.session,
product : req.body.name,
amount : req.body.amount
});
switch (status) {
case "paid":
console.log("결제 성공");
break;
}
} else {
console.log("위조된 결제 시도");
}
} catch (e) {
res.status(400).send(e);
}
});
router.post('/friendadd', async (req, res) => {
try {
const imp_uid = req.body.imp_uid;
const merchant_uid = req.body.merchant_uid;
const getToken = await axios({
url: "https://api.iamport.kr/users/getToken",
method: "post",
headers: { "Content-Type": "application/json" },
data: {
imp_key: "2406300329155625",
imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556"
}
});
const { access_token } = getToken.data.response;
const getPaymentData = await axios({
url: `https://api.iamport.kr/payments/${imp_uid}`,
method: "get",
headers: { "Authorization": access_token }
});
const paymentData = getPaymentData.data.response;
const order = await Product.findOne({name :"friendadd"});
const amountToBePaid = order.amount;
const { amount, status } = paymentData;
if (amount === amountToBePaid && req.cookies.session) {
await Order.create({
number : merchant_uid,
name : req.cookies.session,
product : req.body.name,
amount : req.body.amount
});
switch (status) {
case "paid":
console.log("결제 성공");
break;
}
} else {
console.log("위조된 결제 시도");
}
} catch (e) {
res.status(400).send(e);
}
});
module.exports = router;
다음주소API를 사용하긴 하지만 개발 도중, 주소 값을 DB에 저장할 필요가 없다고 느껴져서 DB에 주소 값을 저장하지 않았습니다.
(택배 배송 추적을 구현해볼 수 없어서 주소를 저장해야할 필요성을 느끼지 못했습니다.)
마이페이지 접근 제한은 미들웨어를 사용하지 않고 FRONTEND에서 메인 페이지로 이동하도록 구현했습니다.
(아래에 코드가 첨부되어 있습니다.)
i'mport 결제 API를 사용했고 테스트 결제까지 실행되지만 사업자등록번호가 없어서 보안처리(액세스토큰발급, DB접근)는 실행되지 않습니다.
프론트엔드
모든 페이지가 아닌 주요 기능이 포함되어있는 페이지 코드만 있습니다.
또한 가독성을 위해 CSS 코드는 제외했습니다.
회원가입(다음주소API)
<template>
<div class="signuppage">
<h1>회원 가입</h1><br><br><br>
<div class="inputbox">
<form action="/signup" method="post">
<h3>ADDRESS</h3>
<input type="text" id="postcode" name="postcode" placeholder="우편번호">
<button type="button" @click="search()">우편번호 찾기</button><br>
<input type="text" id="roadAddress" name = "roadAddress" placeholder="도로명주소">
<input type="text" id="jibunAddress" name = "jibunAddress" placeholder="지번주소">
<span id="guide" style="color:#000;display:none"></span>
<input type="text" id="detailAddress" name="detailAddress" placeholder="상세주소">
<input type="text" id="extraAddress" name="extraAddress" placeholder="참고항목">
<h3>ID</h3>
<input type="text" name="id" pattern="^(?=.*\d)(?=.*[a-z]).{5,15}" placeholder="최소 5자 이상, 숫자 필수, 영문"> <br>
<h3>PASSWORD</h3>
<input type="text" name="password" pattern="^(?=.*\d)(?=.*[a-z]).{8,15}" placeholder="최소 8자 이상, 숫자 필수, 영문"> <br>
<h3>NAME</h3>
<input type="text" name="name" maxlength="4" pattern="^[가-힣]+$" placeholder="한글만, 최대 4자"> <br>
<h3>PHONE NUMBER</h3>
<input type="text" name="phone" maxlength="11" pattern="^(\d{3}).*(\d{3}).*(\d{4})" placeholder="전화번호 입력"> <br>
<button @click="signup()">회원 가입</button>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name : "SignUpPage",
data (){
return {
id : '',
password : '',
name : '',
address : ''
}
},
methods : {
signup() {
axios.post( "http://olrang.shop:3000/signup", {
id : this.id,
password : this.password,
name : this.name,
phone : this.phone,
}).then((response) => {
this.response = response
}).catch((error) => {
this.error=error
});
},
search(){
new window.daum.Postcode({
oncomplete: (data) => {
var roadAddr = data.roadAddress;
var extraRoadAddr = '';
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraRoadAddr += data.bname;
}
if(data.buildingName !== '' && data.apartment === 'Y'){
extraRoadAddr += (extraRoadAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
if(extraRoadAddr !== ''){
extraRoadAddr = ' (' + extraRoadAddr + ')';
}
document.getElementById('postcode').value = data.zonecode;
document.getElementById("roadAddress").value = roadAddr;
document.getElementById("jibunAddress").value = data.jibunAddress;
if(roadAddr !== ''){
document.getElementById("extraAddress").value = extraRoadAddr;
} else {
document.getElementById("extraAddress").value = '';
}
var guideTextBox = document.getElementById("guide");
if(data.autoRoadAddress) {
var expRoadAddr = data.autoRoadAddress + extraRoadAddr;
guideTextBox.innerHTML = '(예상 도로명 주소 : ' + expRoadAddr + ')';
guideTextBox.style.display = 'block';
} else if(data.autoJibunAddress) {
var expJibunAddr = data.autoJibunAddress;
guideTextBox.innerHTML = '(예상 지번 주소 : ' + expJibunAddr + ')';
guideTextBox.style.display = 'block';
} else {
guideTextBox.innerHTML = '';
guideTextBox.style.display = 'none';
}
}
}).open();
}
}
}
</script>
상품페이지(i'mport 결제 API)
<template>
<div class="pastintro">
<h1>[Best] 원하는 과거로 갈 수 있는 포션</h1><br><br>
<h3>해당 포션은 14세 이상부터 권장합니다.</h3><br><br><br>
<img :src="require('@/assets/pastpotion.jpg')">
<div class="info">
<h2>사용 대상</h2>
<h2 v-for="item in items" :key="item">{{item.message}} </h2><br><br><br>
<h2>사용법</h2>
<h2 v-for="use in list" :key="use">{{use.message}} </h2><br><br><br>
<h2>주의 사항</h2>
<h2 v-for="warn in warns" :key="warn">{{warn.message}} </h2><br><br><br>
<h3>구매 전, 구매하실 포션의 이름과 사진이 맞는 지 확인해주세요.</h3><br><br><br><br>
<img :src="require('@/assets/pastpotion.jpg')">
<div class="purchase">
<h1>[Best] 원하는 과거로 갈 수 있는 포션</h1><br><br>
<h1>1500 원</h1>
<button type="button" @click="past()">구매하기</button>
</div>
</div>
</div>
</template>
<script>
import jQuery from 'jquery'
export default {
name : "PastIntro",
data (){
return {
items : [
{message : '과거가 그리운 사람'},
{message : '어린 시절로 돌아가고 싶은 사람 등'}
],
list : [
{message : '자기 전, 시간 차를 두지 않고 한 번에 끝까지 마시면 됩니다.'},
{message : '눈을 감고 원하는 과거를 떠올리면 됩니다.'},
{message : '꿈 속에서 과거에 머무를 수 있는 시간은 실제 수면 시간과 동일합니다.'},
],
warns : [
{message : '최소 2주에 1번만 마시는 것을 권장합니다.'},
{message : '꿈 속에서의 일은 절대 현재, 미래에 영향을 미치지 않습니다.'}
]
}
},
methods : {
past(){
var IMP = window.IMP;
IMP.init("imp07168267");
IMP.request_pay({
pg: "kakao",
pay_method: "card",
merchant_uid: 'merchant_' + new Date().getTime(),
name: "[Best] 원하는 과거로 갈 수 있는 포션",
amount: 1500,
digital: true,
app_scheme : ''
}, function (rsp) {
if (rsp.success) {
jQuery.ajax({
url: "http://olrang.shop:3000/aptitude",
method: "POST",
headers: { "Content-Type": "application/json" },
data: {
imp_uid: rsp.imp_uid,
merchant_uid: rsp.merchant_uid
}
}).done(function (data) {
switch(data.status){
case "success":
alert("결제 성공!");
break;
}
});
} else {
alert("결제 실패. 에러 내용 : "+rsp.error_msg);
}
});
}
}
}
</script>
마이페이지(페이지 접근 제한)
<template>
<div class = "mypage">
<h2>개인정보 수정</h2> <br>
<h3>기존 번호 : {{mydata}}</h3>
<form action="/mypage" method="post">
<input type="text" name="phone" maxlength="11" pattern="^(\d{3}).*(\d{3}).*(\d{4})" placeholder="전화번호"> <br>
<button @click="update()">수정하기</button>
</form>
</div>
</template>
<script>
import axios from 'axios'
export default {
name : "MyPage",
data(){
return{
mydata:''
}
},
created() {
axios.get('http://olrang.shop:3000/api/mypage', {
withCredentials: true,
}).then((response) => {
this.mydata = response.data.phone
if(this.mydata == undefined)
this.$router.replace('/')
}).catch((error) => {
this.error=error
})
},
method : {
update(){
axios.post('http://olrang.shop:3000/mypage', {
withCredentials: true,
phone : this.phone,
}).then((response) => {
this.response = response
}).catch((error) => {
this.error=error
})
}
}
}
</script>
긴 글 읽어주셔서 감사합니다.