여는 글


 

Codeigniter로 개발해야 할 일이 생겨 노트북에 개발 환경을 세팅해야 합니다.

 

다른 컴에도 깔 게 될 일이 생길 지 모르니 정리해두는 차원에서 작성해보겠습니다.

(환경 세팅은 처음에 해놓고 안 하니까 나중에는 기억이 안나서...)

 

 

 

 

환경세팅


 

 

1. https://ssimplay.tistory.com/557

 

[vs code] 비주얼 스튜디오 코드 php 개발환경 설정하기

vs code에서 php 작업을 하려면 약간의 준비가 필요한데요. 차근차근 같이 해봅쉬다~! 1. php 설치 [coding/etc] - [PHP] php 설치하기 (윈도우) [PHP] php 설치하기 (윈도우) 오늘은 php를 설치해보겠습니다. 1...

ssimplay.tistory.com

 

이 포스팅에서 PHP 설치 방법 링크도 타고 가서 그대로 따라하면 됩니다.

 

 

2. 환경변수 설정

 

환경변수에서 C:\Bitnami\wampstack-8.1.5-0\php (버전 별로 상이) 추가해주기

 

참고 : https://velog.io/@kangpungyun/%EC%BD%94%EB%93%9C%EC%9D%B4%EA%B7%B8%EB%82%98%EC%9D%B4%ED%84%B04-%EC%84%A4%EC%B9%98-%EC%A4%91-%EC%97%90%EB%9F%AC

 

 

3. Codeigniter4 설치하기

 

구글에 많이 나와있으므로 생략하지만 공식 홈페이지에서 그냥 다운로드 받으면 됩니다.

 

그러면 C:\Users\유저명\Downloads\codeigniter4-CodeIgniter4-v4.1.9-0-g202f41a

 

이렇게 codeigniter4 파일이 생깁니다.

 

 

4. VSCODE에 디렉터리 가져오기

 

보통 프레임워크쓰면 기본 파일 구조가 생성되어 있는 디렉터리에서 작업하니까 디렉터리를 옮겨야 합니다.

 

3.에서 다운 받은 codeigniter4을 들어가보면 codeigniter4-CodeIgniter4-v4.1.9-0-g202f41a/codeigniter4-CodeIgniter4-v4.1.9-0-g202f41a 이런 구조인데 이 파일을 vscode에 넣으면 됩니다.

 

하위에 app/controllers ... 등 익숙한 mvc 구조의 파일이 보이게 됩니다.

 

 

5. vscode terminal에서 php spark serve 치면 localhost:8080으로 접속하라고 뜨면 성공!

(php 인식 안된다는 식의 에러뜨면 환경변수 잘못 설정한 겁니다.)

 

참고 : http://ci4doc.cikorea.net/installation/running.html

 

 

닫는 글


 

저는 Django 경험이 있어서 그런지 디렉터리 구조는 생각보다 이해가 쉽네요.

 

PHP는 개발 해봐야 알겠지만 생각보다 코드가 직관적이라서 쉬운 듯? 어려운 듯? 하고... 

 

해당 카테고리는 배포까지 보고 깊게 개발할 목적이 아닌 localhost환경에서 기본 CRUD를 익히기 위한 아주 기본적인 개발 기록만 남길 예정입니다.

(간단한 페이지 2~3개, db 연동+사용 해보기 정도)

 

코드이그나이터 구조 이해를 위한 공부 카테고리입니다. 

 

 

 

'Codeigniter4' 카테고리의 다른 글

[PHP8, Codeigniter4] MariaDB 연결 + DB 출력  (0) 2022.05.08

 

 

 

개발 전 과정

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

 

 

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

 

 

세션 구현 시도

 

https://codelist.tistory.com/62

 

[Vue.js, Node.js] 쿠키로 로그인/로그아웃 구현

여는 글 쿠키는 보안상 좋지 않아서 쿠키만으로는 로그인/로그아웃을 구현하기엔 좋지 않다. 대부분은 세션+쿠키를 사용하는 것 같다. 그래서 나도 처음엔 세션부터 구현하려고 했다. 세션에는

codelist.tistory.com

 

 

이제 DB 삭제가 되기 때문에 내가 세션을 자체적으로 구현하려고 했다.

 

로그인 시 DB 생성 -> DB 생성 시 TTL 사용해서 지정 시간 뒤에 자동으로 세션 값 삭제

 

근데 이틀 동안 TTL에서 막혀서 그냥 라이브러리 써야겠다 싶었는데 1번 글에서 만났던 문제를 또 마주하게 됐다.

 

 

        req.session.userinfo = users.phone
                         ^

TypeError: Cannot read property 'userinfo' of undefined

이런 에러가 나는데 다른 사람들은 잘만 사용하던데 난 왜 안될까...

 

일단 req.session 자체가 undefined라 키도 당연히 오류나는 것 같아서 req.session undefined 구글링부터 해보고 있다...

 

 

 

 

+22.1.4 해결

 

 

req.session undefined를 구글링하고 있는데 아래 포스팅을 보게 됐다.

 

https://jaroinside.tistory.com/15

 

1. express-session

express 사용시 express-session 을 사용하는 경우가 많습니다. 이번에 한 블로그의 공부를 따라하던중, express-session 을 사용하였는데, 로그인 구현 관련이었습니다. 로그인을 하면서 session을 저장하기

jaroinside.tistory.com

 

그래서 순서를 바꿨더니 페이지 접속 시 에러가 생겼다.

 

TypeError: Secret string must be provided.

 

사실 이 에러는 처음 보는 에러가 아니었다.

 

세션 이것저것 해보면서 몇 번 만나봤던 에러인데 나는 분명히 secret을 명시해뒀기 때문에 그냥 이상하고 뜬금없는 에러라고 생각해서 무시했는데 이번에는 저 에러에 대해서 구글링을 해봐야겠다는 생각이 들었다.

 

그리고는 아래 글을 보고 해결할 수 있었다.

 

https://stackoverflow.com/questions/38326191/node-js-session-login

 

Node.js session login

Node.js does not seem to be remembering my session. I am using Express with cookie-parser and express-session as middleware. In my application, I use Mongo to keep usernames and passwords. The

stackoverflow.com

 

cookieParser 설정에 대한 내용인데 먼저 글을 읽어보고 나는 cookieParser에 딱히 설정한 게 없을텐데..? 하면서 내 코드를 봤더니...

 

app.use(cookieParser({sameSite : 'none', secure : true}));

 

secure가 true로 되어있었다.

 

배포하고나서 쿠키 안될 때 건드렸던 부분인데 여기서도 secure가 문제일 줄 몰랐다.

 

그래서 secure를 지웠으나 그래도 똑같은 에러가 생겼는데 에러를 다시보면 secret을 제공해야한다- 라는 내용이므로 아래처럼 작성하니 session이 작동했다.

 

app.use(cookieParser('secret'));

 

 

session이 안되는 건 당연히 session 문제일 줄 알았는데 cookieParser가 문제일 줄 몰랐다.

 

 

기존 코드도 다시 보고 없던 에러가 생기면 유심히 봐야겠다...

 

 

++session store 설정 참고 코드

https://kimjingo.tistory.com/43

 

 

마이페이지-개인정보수정 기능을 구현하고 있다.

 

이전에 DB 값 삭제(탈퇴) 구현에서 한참을 애먹었었다.

 

https://codelist.tistory.com/71

 

[Node.js, Vue.js, MongoDB] ::디버깅 실패:: 세션 구현하기

기록 파일에 세션을 저장하는 것도 여전히 안되고 그나마 DB에 세션을 저장하는 게 될 가능성이 있다고 판단해서 여기를 집중적으로 해봤는데 여전히 안된다. 파일에 세션 저장하는 것처럼 아예

codelist.tistory.com

 

https://codelist.tistory.com/81

 

[Node.js, Vue.js] 탈퇴 기능(mongodb)

나는 mongodb를 사용하기 때문에 remove(구버전이라 이제 사용 X), deleteOne(), deleteMany() 등의 명령어를 사용해서 document를 삭제해야하는 데 삭제가 안됐다. 해결하고보니 두 가지의 문제점이 있었다.

codelist.tistory.com

 

그래도 해결했으니 설령 UPDATE 기능이 안되더라도 대처할 수 있겠다 싶었는데 UPDATE는 웬만한 시도를 다 해봤으나 동작하지 않았다.

 

UPDATE에서 막힐 거라고는 생각도 못했었는데 DELETE보다 안 되니까 이쯤되면 사실 DELETE도 완벽하게 구현된 게 아닌가? 라는 생각이 들었다.

 

일단 update, updateOne, findAndModify, findOneAndUpdate 등등 시도해봤고 앞에 컬렉션이름.~, db이름.~ 등 다 갖다붙여봤고 DELETE 구현할 때 시도해봤던 FindOne/Find 사용해보고 아예 없애보기도 했는데 5일동안 구현하지 못했다.

 

 

해결

 

참고한 곳 : https://velog.io/@yejineee/Mongoose-Atomic-Update-%EB%B0%A9%EC%8B%9D%EC%9D%84-%EC%B0%BE%EC%95%84%EC%84%9C

 

Mongoose Atomic Update 방식을 찾아서

Account 컬렉션은 transaction 도큐먼트의 objectId를 자신의 필드인 transactions에 추가하여야 한다. 그 과정은 다음 두 가지 일을 해야 한다. Account Object Id로 Account 도큐먼트를 찾는다. 그 도큐먼트의 trasa

velog.io

 

 

 

backend/routes/index.js

router.post('/mypage', (req, res) => { //마이페이지 정보 수정
  User.findOne({name : req.cookies.session},(err, users) => { 
      if(!req.cookies.session || err) {  // 쿠키 없거나 에러 났을 때
        res.redirect('/');
      } else {
        users.updateOne({$set : {phone : req.body.phone}}).exec(); // 여기!!
        res.redirect('/mypage');
      }
  }) 
})

 

마지막에 .exec() 붙이니까 된다.