개발 전 과정

그 동안의 개발과정은 해당 카테고리 + 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();



// view engine setup

var views = [
  //path.join(__dirname, '../public', 'index.html')
  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,
  })
);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});




  // mongodb 미들웨어
mongoose.connect(  // DB 연결
  'mongodb://olrang.shop:27017/mydbname',  // DB 주소
  { 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');  // DB 스키마 파일 참조
var Product = require('./mongodbproduct'); // DB 스키마 파일 참조
var Order = require('./mongodborder'); // DB 스키마 파일 참조
const crypto = require('crypto'); // 패스워드 암호화 모듈


/* GET home page. */
router.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '../public', 'index.html'));
  //res.render('index', { title: 'Express' });
});

router.post('/signup', (request, response) => { //회원 가입

  var ck = true;
  if(request.body.id == request.body.password){  //request.body는 웹 input 태그에서 값 가져오는 기능. body 뒤에는 input 태그의 name 값을 써야함
    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) => { //id 중복체크 
      if(users.id == request.body.id){  // users는 DB 유저 목록  
          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 ({ //DB 생성
            id:request.body.id,
            password:(key.toString('base64')),
            salt : 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) => { // id를 먼저 체크해서
    if(!users || err){ //id가 DB에 없으면(즉, id가 틀렸거나 회원가입이 안되어 있거나) 
      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') // DB에 저장했던 동일한 방식으로 암호화 
        
        if (users.password == cpassword) { // 저장된 패스워드와 암호화된 입력받은 패스워드 비교
          // res.header('Access-Control-Allow-Origin', 'olrang.shop:3000');
          // res.header("Access-Control-Allow-Credentials", true);
          /*res.cookie('session', users.name, {
          maxAge : (3.6e+6)*12 //12시간
          // secure : true,  // https 쓸 때 사용 가능한 보안 옵션
          // sameSite : 'none', //secure 쓸 때 사용 가능한 옵션
          // domain : 'olrang.shop:3000/login',
          // httpOnly : true,  // js에서 쿠키에 접근 못하게 하는 옵션
        }); */
            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) => { //마이페이지에 띄울 db 정보 가져오기
    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) => {
  // 프론트에서 결제 api 먼저 실행 후 
  try {
    // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
    
    const imp_uid = req.body.imp_uid;
    const merchant_uid = req.body.merchant_uid;


    // 액세스 토큰(access token) 발급 받기
    const getToken = await axios({
      url: "https://api.iamport.kr/users/getToken",
      method: "post", // POST method
      headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
      data: {
        imp_key: "2406300329155625", // REST API키
        imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
      }
    });
    const { access_token } = getToken.data.response; // 인증 토큰


      // imp_uid로 아임포트 서버에서 결제 정보 조회
      const getPaymentData = await axios({
        url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
        method: "get", // GET method
        headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
      });
      const paymentData = getPaymentData.data.response; // 조회한 결제 정보

      // DB에서 결제되어야 하는 금액 조회
      const order = await Product.findOne({name :"aptitude"});
      const amountToBePaid = order.amount; // 결제 되어야 하는 금액


      // 결제 검증하기
      const { amount, status } = paymentData;
      if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
        await Order.create({  // DB에 결제 정보 저장
          number : merchant_uid,
          name : req.cookies.session,
          product : req.body.name,
          amount : req.body.amount
        }); 


        switch (status) {
          case "paid": // 결제 완료
            console.log("결제 성공");  
          //res.send({ status: "success", message: "일반 결제 성공" });
            break;
        }
      } else { // 결제 금액 불일치. 위/변조 된 결제
        console.log("위조된 결제 시도");
        //throw { status: "forgery", message: "위조된 결제시도" };
      }
    } catch (e) {
      res.status(400).send(e);
    }
  });

  router.post('/homelover', async (req, res) => {
    // 프론트에서 결제 api 먼저 실행 후 
    try {
      // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
      
      const imp_uid = req.body.imp_uid;
      const merchant_uid = req.body.merchant_uid;
  
  
      // 액세스 토큰(access token) 발급 받기
      const getToken = await axios({
        url: "https://api.iamport.kr/users/getToken",
        method: "post", // POST method
        headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
        data: {
          imp_key: "2406300329155625", // REST API키
          imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
        }
      });
      const { access_token } = getToken.data.response; // 인증 토큰
  
  
        // imp_uid로 아임포트 서버에서 결제 정보 조회
        const getPaymentData = await axios({
          url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
          method: "get", // GET method
          headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
        });
        const paymentData = getPaymentData.data.response; // 조회한 결제 정보
  
        // DB에서 결제되어야 하는 금액 조회
        const order = await Product.findOne({name :"homelover"});
        const amountToBePaid = order.amount; // 결제 되어야 하는 금액
  
  
        // 결제 검증하기
        const { amount, status } = paymentData;
        if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
          await Order.create({  // DB에 결제 정보 저장
            number : merchant_uid,
            name : req.cookies.session,
            product : req.body.name,
            amount : req.body.amount
          }); 
  
  
          switch (status) {
            case "paid": // 결제 완료
              console.log("결제 성공");  
            //res.send({ status: "success", message: "일반 결제 성공" });
              break;
          }
        } else { // 결제 금액 불일치. 위/변조 된 결제
          console.log("위조된 결제 시도");
          //throw { status: "forgery", message: "위조된 결제시도" };
        }
      } catch (e) {
        res.status(400).send(e);
      }
    });


    router.post('/past', async (req, res) => {
      // 프론트에서 결제 api 먼저 실행 후 
      try {
        // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
        
        const imp_uid = req.body.imp_uid;
        const merchant_uid = req.body.merchant_uid;
    
    
        // 액세스 토큰(access token) 발급 받기
        const getToken = await axios({
          url: "https://api.iamport.kr/users/getToken",
          method: "post", // POST method
          headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
          data: {
            imp_key: "2406300329155625", // REST API키
            imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
          }
        });
        const { access_token } = getToken.data.response; // 인증 토큰
    
    
          // imp_uid로 아임포트 서버에서 결제 정보 조회
          const getPaymentData = await axios({
            url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
            method: "get", // GET method
            headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
          });
          const paymentData = getPaymentData.data.response; // 조회한 결제 정보
    
          // DB에서 결제되어야 하는 금액 조회
          const order = await Product.findOne({name :"pastpotion"});
          const amountToBePaid = order.amount; // 결제 되어야 하는 금액
    
    
          // 결제 검증하기
          const { amount, status } = paymentData;
          if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
            //const useraddress = User.findOne({name : req.cookies.session});
            await Order.create({  // DB에 결제 정보 저장
              number : merchant_uid,
              name : req.cookies.session,
              product : req.body.name,
              amount : req.body.amount
            }); 
    
    
            switch (status) {
              case "paid": // 결제 완료
                console.log("결제 성공");  
              //res.send({ status: "success", message: "일반 결제 성공" });
                break;
            }
          } else { // 결제 금액 불일치. 위/변조 된 결제
            console.log("위조된 결제 시도");
            //throw { status: "forgery", message: "위조된 결제시도" };
          }
        } catch (e) {
          res.status(400).send(e);
        }
      });


      router.post('/friendadd', async (req, res) => {
        // 프론트에서 결제 api 먼저 실행 후 
        try {
          // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
          
          const imp_uid = req.body.imp_uid;
          const merchant_uid = req.body.merchant_uid;
      
      
          // 액세스 토큰(access token) 발급 받기
          const getToken = await axios({
            url: "https://api.iamport.kr/users/getToken",
            method: "post", // POST method
            headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
            data: {
              imp_key: "2406300329155625", // REST API키
              imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
            }
          });
          const { access_token } = getToken.data.response; // 인증 토큰
      
      
            // imp_uid로 아임포트 서버에서 결제 정보 조회
            const getPaymentData = await axios({
              url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
              method: "get", // GET method
              headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
            });
            const paymentData = getPaymentData.data.response; // 조회한 결제 정보
      
            // DB에서 결제되어야 하는 금액 조회
            const order = await Product.findOne({name :"friendadd"});
            const amountToBePaid = order.amount; // 결제 되어야 하는 금액
      
      
            // 결제 검증하기
            const { amount, status } = paymentData;
            if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
              // const useraddress = User.findOne({name : req.cookies.session});
              await Order.create({  // DB에 결제 정보 저장
                number : merchant_uid,
                name : req.cookies.session,
                // address : useraddress.address,
                product : req.body.name,
                amount : req.body.amount
              }); 
      
      
              switch (status) {
                case "paid": // 결제 완료
                  console.log("결제 성공");  
                //res.send({ status: "success", message: "일반 결제 성공" });
                  break;
              }
            } else { // 결제 금액 불일치. 위/변조 된 결제
              console.log("위조된 결제 시도");
              //throw { status: "forgery", message: "위조된 결제시도" };
            }
          } 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,
                    // address : this.postcode + this.roadAddress + this.jibunAddress,
                    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({ // param
                pg: "kakao", // PG사 선택
                pay_method: "card", // 지불 수단
                merchant_uid: 'merchant_' + new Date().getTime(), //가맹점에서 구별할 수 있는 고유한id
                name: "[Best] 원하는 과거로 갈 수 있는 포션", // 상품명
                amount: 1500, // 가격
                digital: true, // 실제 물품인지 무형의 상품인지(핸드폰 결제에서 필수 파라미터)
                app_scheme : '' // 돌아올 app scheme
            }, function (rsp) { // callback
                
                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) {
                        // 가맹점 서버 결제 API 성공시 로직
                        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>

 

 

긴 글 읽어주셔서 감사합니다.

 

 

여는 글


 

서브 페이지에서 새로 고침하면 404 뜨는 건 개발 초기에 vue 문제라는 걸 구글링해서 알고 있는 상태였다.

 

redirect 에러는 회원 가입 페이지를 구현하면서 회원 가입이 완료되면 로그인 페이지로 이동하도록 구현하고 싶었으나 redirect 명령어를 사용하면 404에러가 뜨고 url 창에 직접 경로를 쳐도 404가 뜬다는 걸 알게됐고 메인 페이지로의 이동만 가능했기 때문에 회원 가입 후 메인 페이지로 이동하도록 구현해놓은 상태다.

 

이번에 새로 고침하면 404뜨는 에러를 고치기 위해서 구글링하다보니 redirect 문제까지 해결할 수 있다는 것을 알았다.

 

미들웨어 connect-history-api-fallback를 사용하는 것.

 

구글링하면서 vue 포럼에서 이 미들웨어를 사용하는 것을 추천하길래 사용하려고 시도했더니 해결됐다.

 

 

 

connect-history-api-fallback 사용하기


 

Node.js에 아래 명령어로 설치한다.

npm install --save connect-history-api-fallback

 

 

backend/routes/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 session = require('express-session');



var indexRouter = require('./routes/index');

var app = express();


// view engine setup

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());


app.use('/', indexRouter);
app.use(history()); //여기

app.use(express.static(path.join(__dirname, 'public'))); // 반드시 여기 위에 use 쓸 것!


/*---------후략-----------*/

 

주석 표시한 곳만 보면 되고 주의해야 할 점은 static 위에 history를 써야한다는 것이다.

 

처음에 안 되길래 구글링해보니 static이 위에 있어서 안됐던 거였다.

 

 

 

++다음날 확인해보니 / 페이지가 백엔드 페이지로 출력되는 것을 확인하고 프론트엔드 페이지로 나오도록 추가 작성 했다.

 

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');


/* GET home page. */
router.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '../public', 'index.html')); // 수정된 부분
});

/* 후략 */

 

 

 

 

닫는 글


 

이젠 서브페이지 새로고침, url 창에서 주소 입력해도 잘 이동한다.

 

이제 세션으로 로그인/로그아웃 구현만 남았다.

 

+ 2021.10.18

해결된 줄 알고 글을 작성했으나 알고보니 안됐다. 아래 포스팅을 보면 내가 왜 착각했는 지 나와있다. 

 

[Node.js, Vue.js, Ajax] -삽질기록- Vue.js alert 넣기

원래는 패치 내용을 따로 포스팅 할 계획이 없었으나 해결이 안되다보니 기록이라도 남기려 작성하게 됐다. 그 김에 앞으로 패치 관련 포스팅은 이 카테고리에서 하도록 하겠다. 내가 올랑상점

codelist.tistory.com

 

 

여는 글


 

먼저 내가 어떤 기능을 구현하고 싶었는 지 간단하게 설명하자면 회원가입 시 DB를 조회한 후 아이디가 이미 존재하면 이미 존재하는 아이디라고 alert를 통해 사용자에게 알리는 기능이었다.

 

그러나 어떻게 해도 Node.js와 Vue.js가 통신하지 못했다.

 

[Vue.js, Node.js] 해결X Node.js에서 보낸 response Vue에서 처리

여는 글 제목에서 보다시피 해결하지 못했다. 저번 포스팅에서 id 중복체크 기능을 구현했고 이를 사용자에게 알리기 위해 팝업창을 띄우는 기능이었다. 3일 동안 해결하려 했지만(사실 3일이면

codelist.tistory.com

 

 

뜻밖에도 아임포트(결제기능)을 구현하면서 힌트를 얻었다.

 

Ajax를 사용하는 것.

 

아임포트 구현 코드에서는 Ajax를 사용했고 alert 창이 뜨는 걸 확인했다.

 

Ajax 사용할 생각을 못했던 이유는 몰라서가 아니라 오히려 알고 있었기 때문이다.

 

앞서 졸업작품(Django)에서 Ajax를 사용해 새로 고침하지 않고 페이지를 로드해서 사용했는데 Ajax는 이런 용도로 쓰는 거구나, 해서 alert랑 전혀 상관없는 줄 알았다.

 

 

 

기존 코드 vs Ajax 사용 코드


 

기존 코드

 

frontend/src/components/SignUpPage.vue

signup() {
            this.axios.post('http://localhost:3000/signup',  { 
            headers : {
                "Content-Type" : "application/x-www-form-urlencoded"
            },
            id : this.id,
            password : this.password,
            name : this.name,
            address : this.postcode,
            phone : this.phone
                
            }).then((response) => { //이 부분이 아예 동작 안함
                    alert(response.data.message); 
                
            
                
            }).catch((error) => {  
                this.error=error    
            
                
            })
        
        }

 

 

backend/routes/index.js

router.post('/signup', (request, response) => { //회원 가입
  console.log('success served');
  var ck = true;
  User.find((err, users) => { //id 중복체크 
      users.forEach((item) => {
        if(item.id == request.body.id){ // 평문끼리비교하는거라 배포시에는 이렇게 하면 안됨 암호화 필수
          ck = false;
          response.json({message : '사용할 수 없는 ID입니다.'})
          //response.redirect('http://localhost:3000');
        }
        
      })
    if(ck==true){
      User.create ({
        id:request.body.id,
        password:request.body.password,
        name:request.body.name,
        address:request.body.postcode + ' ' + request.body.roadAddress + ' ' + request.body.detailAddress + request.body.extraAddress,
        phone:request.body.phone,
      });
      response.redirect('/');
    }
});


});

 

 

Ajax 코드

 

frontend/src/components/SignUpPage.vue

signup() {
            jQuery.ajax({
                url: "http://localhost:3000/signup",
                method: "POST",
                headers: { "Content-Type": "application/json" },
                data: {
                    id : this.id,
                    password : this.password,
                    name : this.name,
                    address : this.postcode,
                    phone : this.phone
                    },
                error : function (){
                        alert("이미 존재하는 아이디입니다.");
                } 
            });
        },

 

 

backend/routes/index.js

router.post('/signup', (request, response) => { //회원 가입
  console.log('success served');
  var ck = true;
  User.find((err, users) => { //id 중복체크 
      users.forEach((item) => {
        if(item.id == request.body.id){ // 평문끼리비교하는거라 배포시에는 이렇게 하면 안됨 암호화 필수
          ck = false;
          response.status(400);
        }
        
      })
    if(ck==true){
      User.create ({
        id:request.body.id,
        password:request.body.password,
        name:request.body.name,
        address:request.body.postcode + ' ' + request.body.roadAddress + ' ' + request.body.detailAddress + request.body.extraAddress,
        phone:request.body.phone,
      });
      response.redirect('/');
    }
});


});

 

 

 

닫는 글


 

아직 고쳐야 하는 부분은 2가지다.

 

로그인/로그아웃 세션으로 구현하기, redirect 서브 페이지 안되는 거 해결하기

 

이 외에는 편의성 개선? 정도 인데 이 부분은 시간이나 여력이 되면 해보는 걸로!

 

 

여는 글


 

기능상으로는 이것이 마지막 포스팅이다.

 

이제는 고쳐야 할 부분만 남은 것 같다.

 

구현하는 데에 오래걸리진 않았고 테스트에서 약간 시간이 걸렸다.

 

테스트에 대해서는 밑에서 더 자세하게 이야기 하겠다.

 

 

 

결제 구현


 

먼저 frontend에 jQuery를 설치해야 한다.

 

npm install jQuery

 

나는 아래 포스팅을 거의 참고 했다.

 

[import] 아임포트 결제 모듈 만들기(코드 포함)

아임포트 결제 모듈 만들기 안녕하세요 이번에 결제 모듈을 만들면서 공부한 내용들을 정리하고자 포스팅합니다. 본인의 서버 환경과 서비스의 특징에 맞게 변경하여 사용하시면 됩니다. 아임

puzzle-puzzle.tistory.com

 

주의!! 코드 작성 후 관리자페이지에서 시스템설정->PG설정(일반결제및정기결제)에서 결제 수단을 등록해야 결제창이 뜬다.

 

내 모든 코드는 내 깃허브에 있다.

 

GitHub - codelistwriter/olrang: 올랑 상점 모든 코드

올랑 상점 모든 코드. Contribute to codelistwriter/olrang development by creating an account on GitHub.

github.com

 

나에게 맞게 고쳐서 작성하긴 했는데 내가 손댄 건 MongoDB에 맞게 DB코드를 바꾼다던지, 전달될 데이터를 수정한다던지 하는 간단한 수정이었고 코드 설명도 친절하셔서 매우 참고가 됐다.

 

 

 

테스트


 

 

앞에서도 언급했지만 의외로 구현보다는 테스트에 시간이 들었다.

 

테스트를 해보니 결제창도 잘 뜨고 결제도 잘 됐는데 주문이 내 DB에 안 들어가서 의아했기 때문이다.

 

DB에 들어가려면 backend 부분이 실행이 되어야 하는데 그냥 frontend에서 결제창->결제->끝 이렇게 되는 것 같다는 생각이 들었고 구글링도 해보고 어디까지 실행이 되는 건지, backend로 전달이 되긴 하는 건지 한참을 찾다가 결론을 내렸다.

 

이게 실제 결제가 아니라 테스트모드라 보안이나 DB 저장 부분까지는 실행이 안되는 건가?

 

물론 실제 결제가 아니더라도 실제 돈은 결제가 된다.

 

아래 사진은 내가 실제로 사용하는 농협 계좌이고 계좌 번호는 혹시 몰라 가렸다.

 

정확하게 말하면 PG사랑 계약을 해야 진짜 결제다.

 

근데 그러려면 사업자등록번호가 있어야한다고 해서 나는 테스트를 여기서 끝내기로 했다.

 

 

 

닫는 글


 

다음에는 프론트엔드에 ALRER 창 삽입하기를 구현할 것이다.

 

저번엔 안됐지만 이번에는 꼭 고쳐야지!

 

아래는 내가 구현한 첫 결제 기념 스크린샷이다.

 

 

여는 글


 

설정하는 데 꽤 헤맸지만 엄청 간단하게 해결됐다.

 

설정은 정말 간단하다.

 

 

 

연동하기


 

참고한 포스팅 (감사합니다..)

https://snepbnt.tistory.com/394

 

[ MongoDB ] MongoDB compass 로 aws mongodb 연결하기

연결하는 방법은 생각보다 간단하다 1. 인스턴스 보안설정에 들어가서 인바운드 규칙을 설정한다. aws 는 인바운드 규칙을 통해 외부 접속을 통제하기 때문에 ec2 에 설치한 mongodb 를 외부에 있는

snepbnt.tistory.com

 

정말 간단했고 compass를 쓰는 나에게 딱이었다.

 

내가 막혔던 부분은 compass에서 ip를 치고 연결하는 부분이었다.

 

연결이 잘 되면 바로 연결하는 데 2초? 정도 걸리는 데 나는 계속 나침반 모양이 표시되더니 안됐었다.

 

하루종일 구글링하다가 갑자기 되길래 깜짝 놀랬는데 앞선 시도들과 뭐가 달랐는 지 생각해보니 이거 하나였다.

 

aws에서 인스턴스 돌리고 cmd에서 리눅스에 ssh 접속한 상태까지만이면 compass랑 연결이 안된다.

 

반드시 npm start 후에 compass랑 연결해야 연결이 된다.

 

연결하고 나면 그 후에는 자동으로 값 생성되고 알아서 잘 된다.

 

늘 그렇듯이 갑자기 되면 한동안은 어안이 벙벙하다.

 

 

 

닫는 글


 

이제는 mongoDB 후기를 남길 때가 된 것 같아서 남긴다.

 

NoSQL을 처음 써봤고 항상 SQL을 써오던 나는 비슷하지만 다른 구조를 가진 NoSQL에 약간의 낯섬과 거부감 같은 게 들었지만 쓰고나니까 왜 NoSQL이 나오게 됐는지, 왜 많이 쓰는 지 깨닫게 되었다.

 

다른 말 필요 없고 진짜 편하다.

 

명령어가 간단하고 SQL에서는 생각할 수 없는 융통성과 연결의 편리성이 있다.

 

무슨 느낌이냐면 나는 C언어와 JAVA를 먼저 배우고 Python을 배웠는데, 딱 이 느낌이었다.

 

사용하면서 이게 돼? 이게 되네? 라는 생각이 계속 들었다.

 

마치 별도로 선언하지 않았는데도 바로 변수를 쓸 수 있는 Python을 봤을 때의 충격 정도.

 

아무튼 mongoDB도 쓰기 좋다는 뜻이다.

 

 

여는 글


 

구글링하면 nodejs만 배포하거나 vue 배포만 하는 글이 많고 둘 다 있다고 하더라도 막 시작하는 (나처럼 코드가 이미 한가득 작성되어 있지 않은) 단계의 글이 많아서 해결하는 데 시간이 오래 걸렸다.

 

그러니 이 글은 아래 경우에 해당되는 사람이 배포 할 때 도움이 되는 글이다.

 

1. nodejs, vuejs 를 동시에 배포해야하고 개발 마무리 단계다.

 

2. 개발 라이브러리가 frontend, backend 따로 구분되어 있다.

 

 

 

AWS 세팅 및 서버 구동하기


 

세팅은 아래 포스팅을 거의 참고했다. (참고하기 전에 아래 글을 먼저 읽고 참고하길 바람)

 

 

GitHub과 Node.js Express를 이용한 자동배포 환경 만들기(feat. AWS-EC2, AWS CodeDeploy, AWS CodePipeline) 1편 - 까

1편에서는 AWS EC2에 Node.js Express 프로젝트를 배포하는 과정에 대해서 설명합니다. https://aws.amazon.com/ko/ 클라우드 서비스 | 클라우드 컴퓨팅 솔루션| Amazon Web Services 제조 AWS를 활용한 Siemens의..

ookm1020.tistory.com

 

이 포스팅을 따라하다가 막힌 부분이 있었고 나와는 다른 구조로 되어 있어서 그렇다는 걸 깨달았다.

 

 

 

ssh 접속 할 때

 

나는 aws 이름이 codewriter였기 때문에 codewriter@ip 로 했는데 계속 실패했다.

 

이거 때문에 며칠을 구글링했는데 그냥 기본이름인 ubuntu로 하니까 바로 됐다...

 

그리고 굳이 가상머신->리눅스로 하지 않아도 윈도우 cmd에서도 충분히 된다.

 

시작 메뉴에서 openssh 설치 후 cmd에서 ssh를 쳐보면 설치확인을 할 수 있고 여기서 바로 접속할 수 있다. (권한 문제가 있다면 구글링하면 많이 나오므로 생략)

 

구글링하다보니 cmd 사용을 꺼리거나 그냥 putty를 많이 사용하던데 본인의 선택이다.

 

난 배포 가능 여부가 급해서 그냥 cmd로 했다.  

 

[미래의 나를 위해 써두는 cmd 실행 명령어 순서]

바탕화면에 pem 파일이 있으므로 먼저 cd Desktop

ssh -i "olrangstore.pem" ubuntu@퍼블릭ip

(코드 갱신 시 git에 업로드 후 rm -rf olrang, git clone https://github.com/codelistwriter/olrang.git)

cd olrang

cd frontend/backend

npm run build/npm run start

 

 

 

GitHub에 파일을 업로드할 때 

 

나는 vscode로 개발했고 .gitignore가 있었다.

 

근데 그대로 올려서 clone으로 내려받았더니 npm start가 안됐다.

 

문제는 .gitignore에 node_modules가 포함되어 있어 GitHub에는 backend, frontend 하위의 저 폴더가 업로드 되지 않아 npm start 명령어가 먹히지 않았던 것이다.

 

구글링해보니 package.json이 저 폴더를 대체할 수 있으며 node_modules는 내용이 많아서 git에 올리지 않는다고 하는 글을 봤다.

 

근데 나는 node_modules 폴더를 올리지 않으니 서버 명령어가 안 먹혔고 올리고 난 후에는 잘 동작한다.

 

 

npm start 명령 수행 위치

 

나는 기본 경로(ubuntu@ip~)에서 npm start하면 안 먹힌다.

 

vscode에서 개발했다면 frontend 폴더에서 npm run build한 후 backend 폴더로 이동해 npm run start 명령어를 사용하는 방식으로 구동했을 텐데 여기서도 그렇게 해야한다.

 

 그러니까 기본 경로에서 cd 명령어를 이용해 frontend, backend 폴더로 이동한 후 vscode에서 하던 것처럼 구동 명령어를 해주면 된다.

 

 

 

닫는 글


 

배포를 진행하면서 진지하게 고민했는데 일단 결제 기능 구현되고 나면 부족했던 로그인, frontend에 alter 삽입하기 등을 다시 잡아볼 생각이다.

 

원래는 빨리 끝내고 포트폴리오를 하나 더 만들 생각이었는데 이것도 제대로 못하는 데 양이 많아봤자 무슨 소용이냐는 생각이 들었다.

 

 

아래는 public ip로 접속한 페이지!

 

 

 

현재는 DB 이용이 안된다.

 

개발 시에 로컬 기준으로 동작하도록 구현했기 때문이다.

 

다음은 배포한 주소에서도 DB사용이 가능하도록 하는 내용의 포스팅이다.

 

 

기록용!

 

구글링하면 해당 폴더 하위에 .git 폴더가 있어서 삭제하고 push하라고 한다.

 

근데 내 눈엔 아무리 봐도 .git 폴더가 안 보여서 삭제를 하고 싶어도 할 수가 없었다.

 

알고보니 파일 탐색기에서 숨김 폴더 보기를 체크해야 볼 수 있었다.

 

.git 폴더는 기본적으로 숨김 되어져 있다. 반드시 기억하자!

'포트폴리오(종료)' 카테고리의 다른 글

웹 애플리케이션 포트폴리오(Backend)  (0) 2021.07.11

 

++세션&쿠키로 로그인 구현

https://codelist.tistory.com/85

 

[Node.js] 세션 구현하기 (express-session)

세션 구현 시도 https://codelist.tistory.com/62 [Vue.js, Node.js] 쿠키로 로그인/로그아웃 구현 여는 글 쿠키는 보안상 좋지 않아서 쿠키만으로는 로그인/로그아웃을 구현하기엔 좋지 않다. 대부분은 세션+

codelist.tistory.com

 

 

여는 글


 

 

쿠키는 보안상 좋지 않아서 쿠키만으로는 로그인/로그아웃을 구현하기엔 좋지 않다.

 

대부분은 세션+쿠키를 사용하는 것 같다.

 

그래서 나도 처음엔 세션부터 구현하려고 했다.

 

세션에는 그냥 메모리에 저장하는 방식, 파일로 저장하는 방식, DB에 저장하는 방식이 있고 실제로는 파일이나 DB에 저장하는 방식이 좋다고 하고 많이 쓰인다고 한다.

 

그래도 일단 세션이 되는 지 안 되는 지 테스트 해볼겸 메모리 저장하는 방식을 택하고 구현을 했는데 안됐다.

 

세션에 값을 저장할 때 KEY를 생성해서 쓰던데, 예를 들어 req.session.user 이런 식으로.

 

근데 나는 user 라는 KEY 생성 자체가 안됐다.

 

그래서 내가 설정을 덜 했나보다, 키를 먼저 명시를 해줘야하나? 라고 생각해서 3일 동안 구글링했는데 다른 사람들은 별도로 명시하지 않고 바로 사용했고 명시하지 않아도 된다는 글도 있어서 그냥 내가 안되는 거구나, 생각하고 그럼 파일에 저장하는 형식으로 바꾸자, 라고 생각한 후 구현하기 시작했다.

 

근데 파일에 저장하는 형식도 안됐다.

 

보통 node.js 하위에 폴더를 생성하고, 로그인 하게되면 그 생성된 폴더 하위에 키나 데이터 등이 저장된 파일이 생기는데 나는 폴더는 생기는 데 파일이 안 생겼다.

 

그래서 DB에 저장하는 방식으로 바꿨다.

 

일단 나는 이미 DB를 쓰고 있기 때문에 구현하기 더 쉬울 것 같다고 생각했는데 로그인하면 값이 DB에 저장은 당연히 된다.

 

원래도 회원가입 때문에 CREATE 기능을 쓰고 있었으니까.

 

근데 세션을 못 쓰니까 로그인 유지를 어떻게 구현할까 고민하다가 쿠키를 사용하기로 했다.

 

쿠키에 값이 존재하면 로그인 된 상태라는 조건으로 구현하는 것으로.

 

그래서 여기까지는 나름 괜찮았다.

 

근데 이제 로그아웃이 문제였다.

 

로그아웃 버튼을 누르면 쿠키는 잘 삭제가 된다.

 

근데 DB에서 로그인할 때 넣은 session 값이 삭제가 안된다.

 

나는 mongoDB 쓰기 때문에 remove, deleteMany, deleteOne 이런 데이터 삭제 함수들을 써야했는데 이거 3개 다 동작하지 않았다.

 

그래서 여러 테스트를 해본 결과 삭제 함수만 동작이 안된다.

 

create, find 다 잘 되는 데 삭제만 안된다.

 

그래서 결론은, 로그인 가지고 일주일을 붙잡고 있었는데 쿠키로만 구현하게 됐다.

 

쿠키에는 보안상 password를 절대 넣어선 안된다.

 

그래서 나도 name을 넣기로 했다.

 

 

 

쿠키로 로그인/로그아웃 구현하기


 

모든 코드는 github에 올려져 있다.

 

 

backend/routes/index.js

router.post('/login', (req, res) => {
  console.log('로그인 중');
  User.find((err, users) => { // 평문끼리비교하는거라 배포시에는 이렇게 하면 안됨 암호화 필수
    users.forEach((list) => {
      if (req.body.id == '' || req.body.password == ''){
        console.log('로그인 불가');
        return;
      }

      if(list.id == req.body.id && list.password == req.body.password) {
          console.log('로그인 성공!');
          res.cookie('session', list.name);
          console.log(req.cookies.session);
          res.redirect('/');

      }else if (list.id != req.body.id && list.password == req.body.password)  {
          console.log('아이디가 틀렸습니다.');

      } else if (list.id == req.body.id && list.password != req.body.password) {
        console.log('비밀번호가 틀렸습니다.');

      } 
    })
  })
})

router.get('/logout', (req, res) => {
  User.find((err, users) => { // 평문끼리비교하는거라 배포시에는 이렇게 하면 안됨 암호화 필수
    users.forEach((list) => {
      if (req.cookies.session == list.name){
        res.clearCookie('session');
        res.redirect('/');
      }
    })
  })

})

 

 

backend/app.js

app.use('/login', userControll);

app.use('/logout', userControll);

 

 

frontend/src/components/LogInPage.vue

<template>
  <div class="loginpage">
      <form action="/login" method="post">
       <h2>LOG IN</h2> <br>
        <input type="text" name="id" placeholder="ID" autofocus pattern="^(?=.*\d)(?=.*[a-z]).{5,15}"> <br>
        <input type="text" name="password" placeholder="PASSWORD" pattern="^(?=.*\d)(?=.*[a-z]).{8,15}"> <br>
        <button @click="login()">LOG IN</button>
    </form>
  </div>
</template>

<script>
export default {
    name : "LogInPage",
        data (){
        return {
            id : '',
            password : '',

        }
    },
    methods : {
        login(){
            this.axios.post('http://localhost:3000/login',  { 
            headers : {
                "Content-Type" : "application/x-www-form-urlencoded"
            },
            id : this.id,
            password : this.password
                
            }).then((response) => { //이 부분이 아예 동작 안함
                    alert(response.data.message); 
                
            
                
            }).catch((error) => {  
                this.error=error    
            
                
            })

        }
    }

}
</script>


<style>

.loginpage {
    display: inline-block;
    width : 50%;
    background-color: #C8D4E0;
    padding: 5%;
    margin-bottom: 5%;
}

.loginpage input {
    margin: 3%;
    height: 30px;
    font-size: large;
}

.loginpage button {
    font-size: large;
    background-color: #7E868E;
    margin-top: 5%;
    width : 15%;
}

@media screen and (max-width:768px){
    .loginpage {
        width : 80%;
    }

    .loginpage input {
        height : 15px;
        font-size : small;
    }

    .loginpage button {
        font-size : small;
        width : 30%;
    }
}

</style>

 

 

frontend/src/components/LogOutPage.vue

<template>
  <div class="logoutpage">
    <form action="/logout" method="get">
        <button @click="logout()">LOG OUT</button>
    </form>
  </div>
</template>

<script>
export default {
    name : "LogOutPage",
        methods : {
        logout(){
            this.axios.get('http://localhost:3000/logout',  { 
                headers : {
                    "Content-Type" : "application/x-www-form-urlencoded"
                },
                    
                }).then((response) => { //이 부분이 아예 동작 안함
                        alert(response.data.message); 
                    
            
                }).catch((error) => {  
                    this.error=error    
                
                    
            })
        }
  }

}
</script>

<style>

</style>

 

페이지 연결은 frontend/router/index.js 에서 하면 된다.

 

 

 

닫는 글


 

내가 원하던 대로 구현하진 못했지만 이번 기회에 세션, 쿠키를 써볼 수 있는 좋은 경험이었다.

 

추후에는 요즘에 많이 쓰는 네이버/카카오 로그인 API를 구현해보고 싶다.

 

나는 자체적으로 회원가입을 구현해버려서 API를 쓰기엔 아까워서 쓰지 못했지만.

 

다음은 결제 기능이다.

 

결제 기능은 또 얼마나 헤매게 될 지 모르겠지만 워낙 구현하고 싶었던 기능이라서 기대된다.

 

 

 

++해결

 

[Node.js, Vue.js, Ajax] ::디버깅:: Vue.js alert 넣기

여는 글 먼저 내가 어떤 기능을 구현하고 싶었는 지 간단하게 설명하자면 회원가입 시 DB를 조회한 후 아이디가 이미 존재하면 이미 존재하는 아이디라고 alert를 통해 사용자에게 알리는 기능이

codelist.tistory.com

 

 

 

아래 글은 해결X 삽질 기록

 

여는 글


 

 

저번 포스팅에서 id 중복체크 기능을 구현했고 이를 사용자에게 알리기 위해 팝업창을 띄우는 기능이었다.

 

3일 동안 해결하려 했지만(사실 3일이면 오래 붙잡은 편도 아니지만 계속 붙잡으면 다른 기능 구현할 시간이 없기 때문에 눈물을 머금고 넘긴다..) 도저히 못 하겠어서, 그래도 추후 고치게 된다면 같은 삽질은 하지 말자라는 개념으로 삽질 기록을 남기려고 한다.

 

 

시도해 본 방법


 

Node.js에서

 

 

1. response.send 사용하기

 

backend/routes/index.js

router.post('/signup', (request, response) => {
  console.log('success served');
  var ck = true;
  User.find((err, users) => { //id 중복체크
      users.forEach((item) => {
        if(item.id == request.body.id){
          ck = false;
          response.send("<script>alert('사용할 수 없는 ID입니다');</script>"); //이 부분
        }
        
      })
    if(ck==true){
      User.create ({
        id:request.body.id,
        password:request.body.password,
        name:request.body.name,
        address:request.body.postcode + ' ' + request.body.roadAddress + ' ' + request.body.detailAddress + request.body.extraAddress,
        phone:request.body.phone,
      });
    }
});


});

 

저렇게 하면 팝업창은 잘 뜨는데 뜨고 나면 페이지가 빈 화면으로 바뀐다.

 

뒤로가기해도 안된다.

 

 

2. 그래서 response.send 밑에 response.redirect를 넣어봤다.

 

일단 alert 띄우고 다시 회원가입페이지로 넘어가길 바랬는데 오류가 떴고 오류를 구글링해보니 send(또는 write, json까지 다 해봤다)와 redirect 연속 사용은 안된다는 내용인 것 같다.

 

서버에 한 번에 요청을 두 번 보내게 되서 에러가 난다는 듯 하다.

 

+ redirect 단독으로만 사용했을 때는 redirect('http://localhost:3000');은 되지만 redirect('http://localhost:3000/signup');은 404에러가 났다.

 

 

 

3. response.send말고 response.render를 사용해봤다.

 

 

render는 해당 페이지에 텍스트를 보낼 수 있다.

 

예를 들어 backend/routes/index.js

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

 

위 코드는 기본으로 제공되는 코드이고 저렇게 작성해 놓으면 backend/views/index.js에 <%= title %>에 Express라는 텍스트가 출력된다.

 

근데 render를 사용하려면 페이지 파일이 nodejs쪽에 있어야 했다.

 

즉 내 경우에는 backend/views 아래에 페이지 파일이 있어야했지만 나는 frontend/src 밑에 있었기 때문에 경로 에러가 떴고 render 기본 경로는 backend/app.js에서 고칠 수 있다고 해서 고쳤는데도 똑같은 경로 에러가 났다.

 

backend/app.js

var views = [
  path.join(__dirname, 'views')
]

app.set('views', views);

 

여기를 보면 기본 경로가 views로 되어있고 이 views는 backend/views 하위 파일들을 사용할 수 있도록 등록한다는 것을 의미한다.

 

그래서 나는 views 경로를 배열로 만들어서 frontend/src 경로도 연결해주었고 연결된 것까지 에러 출력된 것을 보고 확인했는데 에러가 떠서 포기했다.

(에러 코드에 보면 frontend/src/components에서 못 찾겠다고 출력된다.)

 

이외에도 popup 패키지 설치, renderVue 패키지 설치 등등 해봤지만 만족할 만한 결과를 얻지 못했다.

 

 

 

Vue.js에서

 

그렇다면 다시 response.json으로 돌아가서, 여기까지는 그래도 실행이 된다.

 

빈 화면에 텍스트만 나와서 그렇지.

 

그렇다면 vue에서 response를 받아서 alert를 실행하도록 하면 되지 않을까? 라고 생각했다.

 

이게 제일 이상적인 방법이기도 했고 구글링했을 때도 다른 분들은 잘 되는 듯 했다.

 

먼저 backend/routes/index.js에서

response.json({message : '사용할 수 없는 ID입니다.'})

 

이렇게 작성하고 frontend/src/components/SignUpPage.vue에서

 methods : {
        signup() {
            this.axios.post('http://localhost:3000/signup',  { 
            headers : {
                "Content-Type" : "application/x-www-form-urlencoded"
            },
            id : this.id,
            password : this.password,
            name : this.name,
            address : this.postcode,
            phone : this.phone
                
            }).then((response) => { // 여기
                    alert(response.data.message); 
                
                
            }).catch((error) => {  
                this.error=error    
            
                
            })
        
        },

 

이렇게 해주면 되지 않을까라고 생각했다.

 

실제로 이렇게 하면 실행된다는 포스팅을 여러 번 보기도 했고.

 

근데 난 안된다.

 

그래서 response 함수의 문제인가 해서 여러 번 테스트해봤다.

 

먼저 response를 받긴 하는 건지 궁금해서 console.log를 써서 확인해보려고 했는데 console.log가 아예 동작하지 않는다.

 

response를 못 받아서? 그래서 무조건 실행되도록 함수 밖에 써봤는 데도 안 먹히는 걸보고 frontend에서는 console 명령어가 아예 안 먹는 것 같다.

 

backend에서는 잘 돌아가지만. 

 

 

 

결론


 

backend에서는 respose.send든 json이든 write든 메시지를 잘 보낸다. 

 

앞에서도 말했지만 빈 화면에 텍스트를 잘 출력한다.

 

그렇다면 두 가지로 원인을 추측 해볼 수 있다.

 

node.js에서 Vue로 아예 전달이 안된다.

 

전달은 됐지만 Vue에서 node.js 메시지 핸들링을 못하고 있다.

 

내 경험상 보통 vue가 안 될때가 잦아서 아마 이번에도 후자의 이유이지 않을까...

 

 

닫는 글


 

Node.js 공부의 필요성을 뼈저리게 느꼈다.

 

로그인 기능처럼 필수 기능이 아니라서 일단은 넘어가긴 하는데...

(id 중복 생성은 안되게 해 놓은 상태고 그걸 사용자한테 알리는 기능이 안되는 것이다)

 

 

 

 

여는 글


 

엄청 쉽다.

 

코드 몇 줄만 쓰면 된다.

 

 

 

DB 값 가져오기


 

참고한 블로그 https://kidong.tistory.com/entry/Mongoose-%EC%82%AC%EC%9A%A9%EB%B2%95-Node%EC%97%90%EC%84%9C-mongoDB-%EC%97%B0%EA%B2%B0-CRUD

 

Mongoose 사용법 -Node에서 mongoDB 연결 + CRUD

- MongoDB 구조 : Database > Collection > Document Node에서 MongoDB를 사용하는 방법은 여러가지가 있다. MongoDB에서 자체적으로 제공하는 Driver를 사용해도 되지만 Mongoose를 사용하는 것이 훨씬 간편하다...

kidong.tistory.com

 

 

 

backend/routes/index.js

var express = require('express');
var router = express.Router();
router.use(express.urlencoded({extended:true}));
router.use(express.json())
var User = require('./mongodbuser');


/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

router.post('/signup', (request, response) => {
  console.log('success served');
  console.log(request.body);
  var ck = true;
  User.find((err, users) => { //id 중복체크
      users.forEach((item) => {
        if(item.id == request.body.id){
          console.log('사용할 수 없는 id 입니다.');
          ck = false;
        }
      })
    if(ck==true){
      User.create ({
        id:request.body.id,
        password:request.body.password,
        name:request.body.name,
        address:request.body.address,
        phone:request.body.phone,
      });
    }
});


});

module.exports = router;

 

+ 저 id 중복 비교는 평문 비교라 보안에 취약해 배포할 때는 암호화를 해야 한다고 한다.

 

테스트용으로 아직은 콘솔에 에러를 뿌리지만 이제 웹 페이지에 에러를 넣어주면 끝이다. (추후 작성)

위의 기능은 구현하지 못했다. 삽질 기록은 아래 포스팅 

 

[Vue.js, Node.js] 해결X Node.js에서 보낸 response Vue에서 처리

여는 글 제목에서 보다시피 해결하지 못했다. 저번 포스팅에서 id 중복체크 기능을 구현했고 이를 사용자에게 알리기 위해 팝업창을 띄우는 기능이었다. 3일 동안 해결하려 했지만(사실 3일이면

codelist.tistory.com

 

 

닫는 글


 

하다보니까 자꾸 욕심이 난다.

 

원래는 회원가입만 하려고 했는데 id 중복체크랑 주소 api 사용도 해버리고....

 

이 부분은 지난 주에 끝냈어야 하는데 욕심이 자꾸 나서 일정이 밀린다....

 

지금 또 아이디찾기 만들고 싶은 욕심이 나는 데 이건 진짜 시간이 없어서 못 만들겠다...