ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 백오피스 프로젝트 2. 고객님 주문, 주문조회, 회원가입, 이메일인증, 트랜젝션
    Projects/Qwerty - 배달의민족 (team) 2023. 7. 18. 15:37

     

     

    나랑 재용님이 맡은 역할: 

     

    • “고객님” - 리뷰 및 평점 관련 CRUD 기능
      • 사용자는 음식점에 대한 리뷰를 작성하고, 평점을 남길 수 있어야 합니다.
    • “고객님” - 메뉴 주문 기능
      • “고객님”은 메뉴를 주문할 수 있어야 합니다.
      • 단, 잔여 포인트가 메뉴 가격보다 비싸면 주문을 할 수 없어야 합니다.
      • 주문 시 포인트 차감을 할 때는 반드시 트랜잭션을 이용해주세요.
    • 로그인 및 회원가입
      • 사용자는 “고객님” 혹은 “사장님”으로 계정을 생성하고 로그인 할 수 있어야 합니다.
      • 회원가입 시 이메일 인증 기능을 넣어주세요.
      • 이 때, “고객님”으로 가입 시 100만 포인트를 지급해주세요.
        • 포인트 → 메뉴 주문시 사용되는 사이버 화폐입니다.

    원래 조원 5명이 각각 역할분담을 했으나, 크게 고객님 파트 / 사장님 파트 , 두가지 파트로 나뉘어져서 나랑 재용님이 고객님 파트를 맡게 되었다. 

     

    특이사항으로는 

    1. 이메일인증

    2. 트렌잭션 사용으로 포인트차감

    3. 모델 관계구현

     

    이었다.

     

     

     

    여기서 주문 - 주문메뉴 - 메뉴의 관계에서 우리는 고객이 menuId기준으로 주문을 시키고, 그 menuId를 여러개 모아 orderId를 따로 생성하는것 때문에 어떻게 로직을 짜야하는지 몰랐다. 튜터님께 여쭤보니

     

    순서: Order(주문)먼저만들고, OrderTable(주문메뉴) 나중에 만듦
    
    1. 고객이 주문 생성. MenuId와 quantity를 설정.
    
    2. 서버가 고객 주문정보를 토대로 Order 테이블에 orderId가 나옴
    (orderId에는 TotalPrice, MenuId 몇개했는지, 등 다 생성됨)
    유저에게 return해줄 값은 orderId랑 price.
    
    3. orderMenu에서는 order에 어떤 메뉴들이 있나 확인.
    프론트엔드에서 menuId가 넘어오고, 서버에서 price를 찾아서 계산해야함
    (컬럼에서 price랑 createAt은 빼도 됨)

     

    라고 하심.

     

     

    트랜젝션같은 경우는 아래처럼 만듦.  https://sangwoorhie.tistory.com/176 참고

    이 과정에서 우리가 썬더클라이언트에서 무한루프 에러가 날 떄 에러핸들링방법에 대해 2가지 배웠다.

    1. try-catch를 써 볼것.

    2. response가 있다면 꼭 return을 쓸 것. 

    const express = require('express');
    const router = express.Router();
    const authMiddleware = require('../middlewares/cusAuthMiddleware.js');
    const ceoAuthMiddleware = require('../middlewares/ceoAuthMiddleware.js');
    
    const { Orders, OrderMenus, Users, sequelize, Stores, Menus } = require('../models/index.js');
    const { Op, Sequelize, Transaction, QueryTypes } = require('sequelize');
    
    // 0. 고객 주문 메뉴 생성 (POST) (성공)
    router.post('/user/store/:storeId/order/menu/:menuId', authMiddleware, async (req, res) => {
      const { userId } = res.locals.user;
      const { quantity } = req.body;
      const { menuId } = req.params;
    
      const menu = await Menus.findOne({ where: { menuId } });
      const totalPrice = quantity * menu.price;
      console.log(userId, parseInt(menuId), quantity, totalPrice);
      try {
        await OrderMenus.create({ userId, menuId: parseInt(menuId), quantity, totalPrice});
        return res.status(200).json({ message: '성공' });
      } catch(error){
        console.log(error)
        return res.status(400).json({ message: '실패' });
      }
    });
    
    // 1. 고객 주문 생성 (POST) (성공)
    router.post('/user/store/:storeId/order', authMiddleware, async (req, res) => {
      try {
        const { userId } = res.locals.user;
        const { address } = req.body;
        const { storeId } = req.params
        // storeId = Number(storeId)
    
        const orderMenus = await OrderMenus.findAll({
          attributes: ['totalPrice'],
          where: { userId },
        });
        let payAmount = 0;
        orderMenus.forEach((orderMenu) => {
          payAmount += orderMenu.totalPrice;
        });
        console.log('payAmount', payAmount)
        const user = await Users.findOne({ where: { userId } });
        if (user.point > payAmount) {
          const afterpoint = user.point - payAmount;
          console.log('afterpoint', afterpoint)
          await Users.update({ point: afterpoint }, { where: { userId }});
          console.log(userId, storeId, payAmount, address)
          await Orders.create({ userId, storeId, payAmount, address });
        } else {
          return res.status(500).json({message: '잔액이 부족합니다. '})
        }
        const order = await Orders.findOne({
          where: { [Op.and]: [{ userId }, { storeId }, { status: false }] }
        })
        await OrderMenus.update({ orderId: order.orderId, status: true }, { where: { status: false } })
        await Orders.update({ status: true }, { where: { [Op.and]: [{ userId }, { storeId }] }})
        return res.status(200).json({ message: '주문 완료!'})
      } catch (error) {
        console.log(error);
        return res.status(400).json({ message: '요청이 정상적으로 처리되지 않았습니다.' });
      }
    });
    
    // const transaction = await sequelize.transaction({
    //   isolationLevel: Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED,
    // });
    // try{
    //   const orderMenus = await OrderMenus.findAll({
    //       attributes:['totalPrice'],
    //       where: { userId },
    //   });
    //   let payAmount = 0
    //   orderMenus.forEach((orderMenu) => {
    //       payAmount += orderMenu.totalPrice
    //   })
    //   const user = await Users.findOne({ where: { userId } });
    //   if (user.point > payAmount) {
    //     const afterpoint = user.point - payAmount;
    //     await Users.update({ point: afterpoint }, { transaction });
    //     await Orders.create({ userId, storeId, payAmount, address }, { transaction });
    //     await transaction.commit();
    //   }
    // } catch (error) {
    //   await transaction.rollback();
    //   return res.status(500).json({message: 'Transaction Error!'})
    // }
    
    // 2. 고객이 배달을 받았는지 여부 (POST) (성공)
    router.post('/user/order/:orderId/delivery', authMiddleware, async (req, res) => {
      const { userId } = res.locals.user;
      const { orderId } = req.params
      try {
        await Orders.update({ delivery: true }, { where: { [Op.and]: [ { userId }, {orderId} ] }});
        return res.status(200).json({ message: '배달 완료 처리되었습니다!'})
      } catch {
        return res.status(400).json({ message: '요청이 정상적으로 처리되지 않았습니다.' });
      }
    
    });
    
    // 3. 고객 주문조회 (성공)
    router.get('/user/order/:orderId', authMiddleware, async (req, res) => {
      try {
        const { userId } = res.locals.user
        const { orderId } = req.params;
    
        if (!orderId) {
          res.status(404).json({ message: '주문정보가 조회되지 않습니다.' });
        }
        const order = await Orders.findOne({
          attributes: ['payAmount', 'address', 'delivery', 'createdAt'],
          where: { [Op.and]: [ { userId }, {orderId} ] },
        });
        if (!order) {
          res.status(404).json({ message: '주문 정보가 존재하지 않습니다.' });
        }
        return res.status(200).json({ data: order})
      } catch (error) {
        console.log(error);
        return res.status(400).json({ message: '요청이 정상적으로 처리되지 않았습니다.' });
      }
    });
    
    // // 4. 고객 주문취소
    // router.delete('/user/order/:orderId', authMiddleware, async (req, res) => {
    //   try {
    //     const { orderId } = req.params;
    //     const { userId } = res.locals.user;
    
    //     if (!orderId) {
    //       res.status(404).json({ message: '주문정보가 조회되지 않습니다.' });
    //     }
    
    //     const transaction = await Sequelize.transaction({
    //       isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED, //격리수준 설정
    //     });
    //     try {
    //       const user = await OrderMenus.destroy({ where: userId }, { transaction });
    //       const afterpoint = user.point + totalPrice;
    //       await Users.update({ point: afterpoint }, { transaction });
    //       await transaction.commit();
    //     } catch (transactionError) {
    //       await transaction.rollback();
    //       throw new Error(transactionError);
    //     }
    
    //     const order = await Orders.findOne({ where: { orderId } });
    //     if (!order) {
    //       res.status(404).json({ message: '주문정보가 존재하지 않습니다.' });
    //     }
    
    //     await Orders.destroy({
    //       where: { [Op.and]: [{ orderId }, { userId }] },
    //     });
    //     return res.status(200).json({ message: '주문 정보가 정상적으로 취소처리 되었습니다.' });
    //   } catch (error) {
    //     console.log(error);
    //     return res.status(400).json({ message: '요청이 정상적으로 처리되지 않았습니다.' });
    //   }
    // });
    
    // 5. 사장님 주문 조회 API 
    router.get('/ceo/:storeId/order', ceoAuthMiddleware, async (req, res) => {
      const { ceoId } = res.locals.user;
      const { storeId } = req.params;
    
      try {
        const orders = await Orders.findAll({
          attributes: ['payAmount', 'address'],
          order: [['createdAt', 'DESC']],
          where: { storeId }
        });
        return res.status(200).json({ data: orders });
      } catch (error) {
        console.log(error);
        return res.status(400).json({ errorMessage: '사장님 주문 전체 조회 과정에 오류가 발생하였습니다.' });
      }
    });
    
    // 6. 사장님 주문 상세 조회 (완성)
    router.get('/ceo/:storeId/order/:orderId', ceoAuthMiddleware, async (req, res) => {
      const { ceoId } = res.locals.user;
      const { orderId } = req.params;
    
      try {
        const order = await Orders.findOne({ where: { orderId } })
        console.log('order =>', order)
        const userId = order.userId
        console.log('userId =>', userId)
        const orderMenus = await OrderMenus.findAll({
          attributes: ['totalPrice', 'quantity'],
          where: { [Op.and]: [ { userId }, { orderId } ] }
        })
        return res.status(200).json({ data: orderMenus });
      } catch (error) {
        console.log(error);
        return res.status(400).json({ errorMessage: '사장님 주문 상세 조회 과정에 오류가 발생하였습니다.' });
      }
    });
    
    // 7. 사장님 주문 취소 API (완성)
    router.delete('/ceo/:storeId/order/:orderId', ceoAuthMiddleware, async (req, res) => {
      const { ceoId } = res.locals.user;
      const { storeId, orderId } = req.params;
      try {
        await Orders.destroy({
          where: { [Op.and]: [{ storeId: storeId }, { orderId: orderId }] },
        });
        return res.status(200).json({ message: '사장님 주문 취소 완료' });
      } catch (error) {
        console.log(error);
        return res.status(400).json({ errorMessage: '사장님 주문 취소 과정에 오류가 발생하였습니다.' });
      }
    });
    
    module.exports = router;

     


    이메일 인증같은경우는 routes에서는 req.body에서 이메일을 받게 하고, 미들웨어와 config를 지정했다.

     

    미들웨어
    
    const validator = require('express-validator')
    const {body, vailidationResult} = validator
    
    // 단일 요청에 유효성 검사
    const validate = function (req, res, next) {
        const error = vailidationResult(req);
          //만약 오류가 없다면 다음 미들웨어로 전달
        if(error.isEmpty()) {
            next();
        }
          //오류가 있다면 400 코드를 설정하고 json(메시지를 생성)
        else{
            res.status(400).json({
                  //에러 메세지는 errors배열을 순회하며 오류 메시지를 생성.
                message: error.array().map((v, idk) => `${v.msg}`)
            });
        }
    };
    
    // 이메일 유효성 검사
    const emailValidate = {
        createUser: [
            body('email')
            .isEmail()
            .normalizeEmail()
            .withMessage('이메일 형식이 아닙니다. 확인해주세요.')
        ],
        loginUser: [
            body('email')
            .isEmail()
            .withMessage('이메일 형식이 아닙니다. 확인해주세요.')
        ],
        verifyEmail: [
            body('email', '유효한 E-mail 주소를 입력해주세요.')
              .isEmail()
              .normalizeEmail(),
            validate,
        ]
    }
    module.exports = emailValidate;

     

     

    config/ email.json

    /email/email.js
    
    const nodemailer = require('nodemailer');
    const confir = require('./email.json')
    
    const mailsender = {
        sendGmail: function(receiver) {
        const transporter = nodemailer.transporter({
            service:'Naver',
            host: "smtp.naver.net",
            port: 465,
            secure: true,
            auth: {
                email: config.email,
                password: config.password
                },
          tls: {
            rejectUnauthorized: false
        },
        });
        const authNum = Math.random().toString().substr(2,6);
        transporter.Emailsend({
            from: config.email,
            to: receiver, //콜백함수
            subject: "축스피드 회원가입 인증번호",
            text: `축스피드 회원가입 인증번호는 ${authNum}입니다.
                    인증번호 입력 란에 입력해주세요.`
        });
        return authNum;
    },
    
        sendNaver: function (receiver) {
    
        const transporter  = nodemailer.transporter({
            service:'Naver',
            host: "smtp.naver.net",
            port: 465,
            secure: true,
            auth: {
                email: config.email,
                password: config.password
            },
            tls: {
                rejectUnauthorized: false
            },
        })
    
        const authNum = Math.random().toString().substr(2,6);
        transporter.Emailsend({
            from: config.email,
            to: receiver, //콜백함수
            subject: "축스피드 회원가입 인증번호",
            text: `축스피드 회원가입 인증번호는 ${authNum}입니다.
                    인증번호 입력 란에 입력해주세요.`
        });
        return authNum;
        }
    
    };

     

    /email/email.json
    
    {
        "email": "",
        "password": ""
      }

    댓글

Designed by Tistory.