Wecode - Project 2 (부트캠프)/Project 2 회고

Project 2 마지막날 실제 구현 기능

JBS 12 2023. 10. 6. 12:41

기획과 다르게 어땠는지

 

<회원가입/로그인/비밀번호 찾기>

다들 1차 프로젝트, fondation 때부터 계속 해왔기에, 간단하다고 생각하는데 

basic is the best입니다. 

 

저희 닥터 마틴 페이지가 기능이 많이 구현되어있더라고요, 

본인인증, 통한 기입 제외 

 

-회원가입
본인인증 통한 phone number/ birthdate 자동기입 생략
2)  아이디 생략 -> 이메일로 대체 (편의성)
3)  보다 간단한 비밀번호 설정 조건 (편의성)
4)  이메일 기입 한 칸으로 통합 (편의성)
5)  필수값인 ‘성별’ -> ‘선택안함’ 값 추가 (다양성)
6)  추천인 ID 생략
7)  마케팅 수신 동의 디폴트로 필수 아닌, ’선택값’으로 설정


-로그인 
비밀번호 찾기 (‘아이디 찾기’ 생략) 
비회원 주문조회 생략



-소셜 로그인 구현

 

<메인페이지(상품 전체페이지) >

 - 정렬  /  페이지네이션(Offset Pagination) 완료 

 - 필터 미구현

 - 주로 query문 작성

 - 정렬 및 페이지네이션 상태에 따른 상품 데이터 응답

 

   * 응답값 예시

 {

            "id": 6,

            "category_id": 1,

            "sub_category_id": 1,

            "product_name": "2976 첼시 스무스",

            "price": "260000",

            "original_price": "260000",

            "created_at": "2023-09-29T07:21:47.000Z",

            "updated_at": null,

            "products_description": "추가 정보\n제품 소재 : ~~~~~~",

            "total_sales": "2",

            "productThumbnail": "https://www.drmartens.co.kr/data/goods/1/2022/11/2647_temp_16687383771404list1.jpg",

            "optionId": 1,

            "color": "black",

            "optionCheck": [

                {

                    "size": 220,

                    "quantity": 100

                },

                {

                    "size": 290,

                    "quantity": 100

                },

                {

                    "size": 280,

                    "quantity": 100

                },

                {

                    "size": 270,

                    "quantity": 100

                },

                {

                    "size": 250,

                    "quantity": 100

                },

                {

                    "size": 240,

                    "quantity": 100

                },

                {

                    "size": 230,

                    "quantity": 100

                },

                {

                    "size": 260,

                    "quantity": 100

                }

            ]

        },



코드 흐름

1. models

 - QueryBuilder.js  :  쿼리문의 WHERE절, ORDER BY절 등을 정렬 상황에 맞춰 함수로 자동 변경

   * 코드

     > WHERE절, GROUP BY절 변경 query문

const sortQueryBuilder = async (sub_category_id, category_id, sort_by) => {

  try {

    const orderingSortWhereBuilder = {

      ranking: `WHERE sub_category_id = ${sub_category_id} GROUP BY products.id, options.color_id, op.optionCheck`,

      regist: `WHERE sub_category_id = ${sub_category_id} GROUP BY products.id, options.color_id, op.optionCheck`,

      low_price: `WHERE sub_category_id = ${sub_category_id} GROUP BY products.id, options.color_id, op.optionCheck`,

      high_price: `WHERE sub_category_id = ${sub_category_id} GROUP BY products.id, options.color_id, op.optionCheck`,

      review: `WHERE sub_category_id = ${sub_category_id} GROUP BY products.id, options.color_id, op.optionCheck`,

      sale: `WHERE sub_category_id = ${sub_category_id} GROUP BY products.id, options.color_id, op.optionCheck`,

    };

    if (sub_category_id == 'null') {

      orderingSortWhereBuilder.ranking = `WHERE category_id = ${category_id} GROUP BY products.id, options.color_id, op.optionCheck`;

      orderingSortWhereBuilder.regist = `WHERE category_id = ${category_id} GROUP BY products.id, options.color_id, op.optionCheck`;

      orderingSortWhereBuilder.low_price = `WHERE category_id = ${category_id} GROUP BY products.id, options.color_id, op.optionCheck`;

      orderingSortWhereBuilder.high_price = `WHERE category_id = ${category_id} GROUP BY products.id, options.color_id, op.optionCheck`;

      orderingSortWhereBuilder.review = `WHERE category_id = ${category_id} GROUP BY products.id, options.color_id, op.optionCheck`;

      orderingSortWhereBuilder.sale = `WHERE category_id = ${category_id} GROUP BY products.id, options.color_id, op.optionCheck`;

    }

   * 페이지네이션 query문

const pageQueryBuilder = async (page) => {

  try {

    const pagination = `LIMIT 12 OFFSET ${(parseInt(page, 10) - 1) * 12}`;

    return pagination;

  } catch (err) {

    console.log(err);

  }

};

 - allProductDao.js  :  기본적인 query문(SELECT products.*)에 QueryBuilder에서 나온 결과를 붙여 최종 query문 생성

   * 코드

const productSortDao = async (sortQueryBuilder, pageQueryBuilder) => {

  try {

    const result = await dataSource.query(

      `SELECT products.* ${sortQueryBuilder} ${pageQueryBuilder}`,

    );

    return result;

  } catch (err) {

    console.log(err);

  }

};

 

2. services

 - allProductService.js  :  ★★★ QueryBuilder의 결과를 Dao에 인자로 넣는다. ★★★

   * 코드

const productSortService = async (sub_category_id, category_id, sort_by, page) => {

  try {

    const sortResult = await sortQueryBuilder(sub_category_id, category_id, sort_by);

    const pageResult = await pageQueryBuilder(page);

    const result = await productSortDao(sortResult, pageResult);

    return result;

  } catch (err) {

    console.log(err);

  }

};

 

3. controllers

 - allProductController.js  :  service를 불러와 정렬, 페이지네이션 실행

   * 코드

    const productSortServiceResult = await productSortService(

      sub_category_id,

      category_id,

      sort_by,

      page,

    );

    const productSortController = productSortServiceResult;

 

4. route

 - /products 접속시 allProductRouter(allProductController) 실행

 

메인페이지 기능
1) Category 이동 - 시작/ 남성 / 키즈 / 여성
2) 정렬
3)  Pagination : 시작/2/1
4)  Sub category - 시작/ 부츠 / 슈즈 / 샌들 / 전체보기

<상세페이지>

프론트- 상세페이지 :

 이미지 캐러셀이 왼쪽과 오른쪽 구역 2곳이 있어 동기화를 하는 부분이 어려웠습니다. 이미지 캐러셀 관련 라이브러리 사용 없이 받아온 데이터를 map으로 돌려 이미지를 꺼내고, 좌우로 넘기는 버튼을 함수로 만들었습니다. 왼쪽, 오른쪽 이미지의 부모 컴포넌트에서 count를 관리하는 state를 만들고 자식 컴포넌트에 props로 넘겨 사용했습니다. 수량, 할인율/할인가/원가 등의 정보는 백엔드와의 통신으로 받아온 데이터값을 state에 담아 넘겨주고 불러와서 사용했습니다.

 

백엔드 - 상세페이지 : 레이어드 패턴을 이용해 구현 하였습니다.

라우터와 컨트롤러와 서비스와 DAO를 분리하여, 코드를 구성하였습니다.

DAO를 단순하게 구성하고, 응답값을 서비스 레이어에서 처리하여, 추 후 요청에따라 유지보수가 용이하도록 작성하였습니다.

비동기함수를 사용하여, 혼돈이 없도록 구성하였습니다.

미들웨어에 선언된 공통함수인 error 함수를 호출하여, 에러 발생 시 에러핸들링을 효율적이고 가독성이 높게 구성했습니다.

<주문 페이지>

  • 프론트: 현재 UI만 완성된 상태입니다. 

=> 프론트 부분 거의 useState로 관리해서 사용하는 부분이 많았던 것 

 

백엔드 - 주문:

주문에 필요한 고객의 데이터(예시: 주소)를 입력하여 저장, 구매하기 액션을 취하면

주문하려는 상품은 결제 대기 상태로 상품의 상태를 변경함 

실제 주문서가 될 데이터는 결제가 끝나면 저장이 됨

 

 

통신은 안돼서, ui만 

<결제 페이지>

백엔드 - 결제:

토스페이먼츠 결제 모듈을 사용하여 

고객의 정보 및 가격의 총량으로 결제를 진행, 

카드사 - PG 서버 간 요청 후 

모든 요청이 성공시 결제 상품의 결제 상태가 성공으로 바뀌며 

재고 수량은 구매 총량만큼 차감됨

 

프론트 결제 : 주문/결제 페이지에서 토스 페이먼츠 결제 모듈로 넘어가는 거로 

 

<장바구니> 

백엔드 - 장바구니: 

  • 단일 상품 삭제 
  • 여러 상품을 지정하여 삭제하는 것이 가능

단, 상품은 실제로 삭제되는 것이 아니며 

삭제 된 날짜와 삭제 플래그로 

유저의 행동 데이터를 

수집 옵션에 대한 변경 및 수량 변경이 가능

 

프론트 - 장바구니 

  • 장바구니 팝업창 : const [isPopUp, setIsPopUp] = useState(false);와 같이 state를 통해 기본값을 false로 두고 버튼을 눌렀을 때 true값으로 변경해서 팝업창을 띄울 수 있습니다. 백엔드와의 통신을 통해 받아온 데이터를 state에 담고, 이를 자식 컴포넌트에 props로 전달해서 꺼내서 사용했습니다.
  • 메인페이지 연결 팝업창 장바구니에서 설명
  • 수량, 사이즈 : 현재는 상세페이지에서 재고를 확인할 수 있는 데이터를 받을 수 

장바구니 페이지에서 옵션/수량 변경 버튼을 누르면 state로 관리하고 있는 해당 제품의 정보를 props로 넘기고, 팝업창을 띄워 그 안에서 사용할 수 있습니다. props로 선택된 사이즈와 수량을 알 수 있고, 이를 state로 관리하여 다른 사이즈, 수량으로 변경할 수 있습니다.

 

 

<AWS> 

AWS 백엔드 서버 http://3.34.190.81:8000/