본문 바로가기

Project/2024WebFinalProject

Back-end 개발(11/18~12/11)

일정

11/18 - 12/17까지 개발

11/18 - 12/6 (3주) 동안 백엔드 개발

12/9 - 12/13 (1주) 동안 테스트 및 수정

12/16 - 12/17 동안 발표준비 및 발표

 

전체 백엔드 개발 일정

 

개인 일정

로그인 -> 재고관리 -> 마이페이지-> 메인페이지 -> 알림모달

중요도 순으로 구현

 

GitHub 프로젝트 - 따로 브런치를 빼지 않고 각자 fork해간 후 pull request 하기로 함.

origin project pull -> fork project pull -> local project commit and fork peoject push -> origin pull request

 

프론트 깃

https://github.com/DDYDL/cafeconnect_front.git

백엔드 깃

https://github.com/DDYDL/cafeconnect_back.git

 

- spring boot(gradle), react.js, jotai, mariaDB 사용

 

백엔드 모두 개발 후(postman으로 테스트) 프론트와 백엔드를 연결

 

 

로그인, 소셜 로그인(11/20)

spring security로 로그인 처리 후 실행해보니

failed to lazily initialize a collection of role: cohttp://m.kong.cc.entity.Member.storeList, could not initialize proxy - no Session

에러가 남.

PrincipalDetailsService에서 Member 엔티티를 MeberRepository로 가져오는데, Member로 가져오면 영속성 컨텍스트 내에 존재하지 않아서 제대로 가져오지 못 해 발생하는 에러 같음

MemberRepository에서 Member를 Optional<Member>로 바꿔주고, 사용할 때는 .get()을 사용했더니 해결 됨.

-> @Data 사용해서 나는 오류였음

-> toString이 겹쳐서 생김, @Setter @Getter로 바꿔주니 해결됨

 

콘솔에 토큰 잘 가져옴

 

postman에서 태스트한 결과도 token이 헤더로 잘 들어옴

 

 

일반 로그인이 본사/가맹점으로 나눠져 있는데, 각자 페이지가 다름

-> /login으로 URL은 똑같이 들어올건데, 권한 처리를 해줘야 하나?

-> 프론트에서 같은 back-url /login으로 들어온 후 보내줄 때 각 프론트 페이지에서 navigate로 해당하는 페이지로 보내는 방식은?

-> 같은 back-url로 보내는게 가능한가? 가능하긴 함.

 

피드백

-> login요청이 들어오면 테이블을 2개 다 찾아보는 방법이 있는데 성능 면에서 좋지 않다.

-> token에 roles를 가지고 있게 하여 백엔드에서 판별하여 해당 테이블을 찾는 방법도 있다.

-> 테이블을 본사, 가맹점 따로 두지 말고 하나로 합치고, 프론트 화면도 하나로 사용하고, 테이블에서 roles로 판별하는 방법이 있다. -> 이걸 사용하기로 함.

 

 

소셜 로그인이 가맹점만 가능하므로 소셜로그인 시 shopMain으로 가도록 설정

shopMain으로 가면서 토큰을 URL에 달고 감

 

데이터베이스에도 kakao와 naver로 로그인한 계정이 잘 들어감

소셜 로그인할 때 비밀번호를 가져오는 건 안 된다고 한다.

 

 

프론트에서 로그인 시 토큰 가져오고, 가시 토큰으로 유저의 정보를 가져오면서 roles까지 가져오게 했다.

roles로 가맹점인지 본사인지 확인한 후 navigate를 바꿔줌

 

가맹점 로그인 후 가맹점 페이지로 잘 넘어가고, 토큰과 멤버 정보가 세션스토리지에 저장되어 있다.

 

 

null 처리(11.22)

store 테이블의 경우 member보다 먼저 생성되고, 가맹점이 가입할 때 store 조회와 member  삽입이 일어남.

store 테이블 조회 시 member PK가 없으므로 오류가 남.

-> store의 joinColumn 시 nullable=true 해도 조회 시 FK가 없어서 nullException 오류 생김.

-> 백엔드에서 삼항 연산자로 null 처리 해주기

 

같은 아이디 가진 행 합치기 - 재고 합해야 함

 

 

QueryDsl 조건 and 연산(11.25)

유통기한과 대/중/소분류를 and 연산해서 Stock List를 가져와야 함.

대분류, 중분류, 소분류 각각 선택할 때마다 가져옴 -> 카테고리 구분하기 위해 String category 받음

 

Controller에서 Map으로 받아서 처리

 

QueryDsl을 작성하는데 유통기한이 오는 경우, 안 오는 경우/ 카테고리 대/중/소가 오는 경우, 안 오는 경우 해서 각각 2가지, 4가지 경우로 쿼리문을 8가지나 작성해야 했다.

-> BooleanExpression을 사용해서 조건이 오는 경우와 안 오는 경우를 처리해줌.

-> 여기에서는 유통기한만 BooleanExpression로 처리해주었다.

 

카테고리는 if문으로 처리(String으로 3가지 경우가 와서 if문이 더 편리할 것 같았다.)

 

유통기한은 함수를 따로 만들어주어 expirationDate에 한 글자라도 넘어오면 stock의 유통기한과 현재 날짜를 빼서 3일 이하이면 쿼리문에 걸리도록 설정했고

빈 문자열이면 null이 return되도록 설정했다.

QueryDsl은 null이 넘어오면 무시하므로 해당 where절이 실행되지 않는다.

 

 

kakao지도로 여러개의 마커 띄우기(12.04)

처음에 useEffect로 active한 가맹점 리스트를 모두 가져옴

가맹점 리스트를 가져온 후 가맹점 주소를 위도/경도로 변환하여 다른 리스트에 저장

해당 리스트를 가지고 위도/경도에 마커를 띄우고자 했으나 뜨지 않음

-> 콘솔 확인 결과 위도/경도 변환 잘 됨

-> useState로 선언한 배열을 forEach 안에서 바로 변경하려고 하여 반영되지 않았음

-> 따로 배열을 두어 배열에 저장하고 마지막에 useState 배열에 저장하고자 함.

 

-> 배열이 모두 저장되기 전에 useState가 먼저 세팅되는 문제 발생

-> 순서 문제라고 생각되어 async/await를 주어 가맹점 먼저 가져오기 -> 위도/경도 변환 -> useState 변수에 세팅

( async/ await 함수는 Promise 객체를 반환해야 하며, useEffect에는 리턴값이 있으면 안 됨)

-> 위 순서로 돌아가게 작성해도 위도/경도 변환하는 함수가 가장 나중에 실행되었음

 

-> 위도/경도로 변환하는 geocoder.addressSearch() 함수가 돌아올 때 순서가 보장되지 않는다는걸 알게됨.(그래서 결과순서가 랜덤으로 가져오게 됨)

-> 위도/경도로 변환 함수 안에서 처음 가져온 가맹점 리스트와 변환한 배열 리스트의 길이가 같으면(모두 변환 완료되면) useState함수를 세팅하도록 설정하니 잘 나옴

 

 

useEffect로 가맹점 리스트를 가져오고

 

가맹점 리스트를 세팅하고 isStore를 true로 만들면 getLatLng() 함수가 실행된다.

 

가져온 가맹점 리스트에 forEach를 사용해서 위도/경도로 바꿔준다. 현재 사용자의 위치와 가맹점 위치 사이의 거리를 계산하여 2Km 이내의 가맹점만 보이도록 설정했다.

 

여기에서 모든 가맹점의 변환이 끝나면 useState 배열을 set 해준다.

 

지도에 표시할 marker 컨테이너를 선언한다.

 

지도 안에서 map함수를 이용해 여러 마커를 지도 위에 그려준다.

 

커스텀 오버레이를 생성할 컨테이너

 

지도에 커스텀 오버레이 적용

 

count를 모든 가맹점 리스트로 주고, 새로운 배열에 추가되지 않으면 감소하게 만들어 가맹점의 개수를 체크하였음

 

결과로 페이지에 들어가자마자 해당하는 가맹점의 마커가 여러개 뜨게 된다.

 

선택한 상품의 재고가 있는 가맹점만 가져온 후 현재 위치를 기준으로 2Km 반경의 가맹점만 가져오는 지도

커스텀 오버레이를 사용한 마커도 현재 위치의 마커와 커스텀 오버레이가 잘 뜬다.

 

2Km가 넘어가는 가맹점은 뜨지 않고, 2Km 안의 가맹점만 배열에 추가되는 것을 알 수 있다.

 

 

react Map 자료구조와 boolean 배열 useState로 다루기(12.05)

storeCode가 같은 행은 stockCount를 합쳐서 보내주고 싶음

-> Union을 사용하면 되지 않을까 생각했는데 QueryDsl에는 Union All이 없다고 함

-> for문을 이용해 가져온 stock 리스트를 Map으로 합쳐 사용했다.

-> 프론트에서 Map을 받아 Object.entries(stockList).map((stock, index)=>());을 사용해 해당하는 행을 출력하고

-> 행을 클릭하면 각각 다른 행들이 나오도록 했다.

 

해당하는 모든 stock을 가져와서 Map<String, List<StockDto>> 형식으로 바꿔준다.

 

아이템코드를 기준으로 stock들을 묶어주고 프론트로 전송한다.

프론트에서는 한 아이템에 한 행이 나온다.

 

행을 클릭하면 해당 아이템코드의 행들이 모두 나오게 된다.

 

가져온 형식은 아래와 같다.

 

한 행을 클릭했을 때 펼쳐지기 위해 useState boolean을 이용했는데, 여러개의 행이 각각 다른 상태를 가져야했다.

-> useState boolean 배열을 이용했다.

 

재고관리 시 등록된 모든 item 개수를 넘지 않으므로 item의 개수만큼 배열을 선언한 후

 

false로 배열을 채워주었다.

 

행을 클릭하면 해당하는 인덱스를 받아온 후 해당 index를 true로 바꿔주었다.

info배열이 바뀌었으므로 리랜더링이 일어나 화면을 다시 그리게 된다.

 

index를 이용해 boolean 배열의 상태관리를 하였다.

 

 

소셜로그인 토큰 처리(12.06)

소셜로그인 시 Jotai에 Login Token, Member 정보, alarm 리스트, fcm Token 이 들어가야 하고,

데이터베이스에는 fcmToken이 들어가야 한다.

 

일반 로그인은 LoginStore.js 에서 바로 사용자 정보를 받아오지만 소셜로그인은 인증 후 다음 url(ShopMain.js)로 이동한 후 url에 token 정보를 받아오므로 컴포넌트가 변경된다.

-> LoginStore.js에서 사용자 정보를 받아올 수 없다.

-> 다음으로 넘어가기 전 컴포넌트를 하나 더 두고 정보를 Jotai에 넣은 후 ShopMain.js 컴포넌트로 넘어가게 했다.

 

SocialLogin.js 에서 소셜 로그인 후 url의 token을 가져와서 사용자 정보를 다시 요청했다.

 

소셜로그인은 가맹점만 가능하므로 따로 roles 확인이 필요하지 않으나 만일을 위해 넣어놓았다.

 

SocialLogin.js 에서 fcmToken까지 가져오려 했으나 소셜 로그인을 하면 새로고침이 되어 새로 fcmToken을 가져오는데, 이 가져오는 시간보다 fcmToken을 가지고 요청하는 axios가 더 빨라 제대로 설정되지 않는 문제가 발생한다.

-> fcmToken을 SocialLogin.js에서 가져온 후 다음 컴포넌트인 ShopMain.js에서 설정하도록 하여 해결했다.

 

-> 소셜 로그인 시에도 Jotai와 DB에 해당하는 데이터가 잘 요청되어 들어간다.

 

 

Java Date 형식을 react로 받아서 업데이트(12.06)

한 행 수정 시 바로 저장할 수 있는데, map 형식을 바로 수정하고 수정한 행만 따로 upStock으로 받은 후 저장시 사용]

upStock을 사용하지만 처음 띄워줄 때는 기존 stockList의 데이터를 보여줘야 했다.

-> 사용자가 어떤 행을 클릭할지 모르기 때문에 미리 upStock에 데이터를 넣어놓을 수 없었다.

-> Map을 map()함수로 돌리면서 행을 보여주는데, map안에서 setState함수를 사용할 수 없으므로 선택한 행만 upStock에 넣을 방법이 없었다.

-> 해당하는 인덱스와 변경한 값을 받아 해당 인덱스에 들어가 있는 리스트의 값을 수정하고, 해당 리스트 전체를 바꿔 리랜더링이 일어나도록 설계하였다.

 

사용하는 DatePicker가 Date형식이 들어가야 프론트에 제대로 보여줬으므로 format으로 날짜를 변경하는건 update 직전에 하기로 했다.

upStock은 반드시 초기화 해주어야 수정 후 다른 행을 수정시 데이터가 겹치지 않는다.

 

 

새로고침 시 Jotai의 member가 초기화되는 문제(12.09)

-> 특정 페이지에서만 새로고침 시 member가 초기화 되었다.

-> 특정 페이지의 useEffect에서 member를 설정하는데 member를 제대로 가져오지 않아 생기는 문제였다.

-> update등 member를 고쳐야 할 때만 setMember를 하게하여 해결했다.

 

로그인 후 refresh Token이 만료되었을 때 로그아웃 처리(12.11)

accessToken 만료시 요청마다 재발급 받게 설정

refrechToken 만료시 401 error가 뜨고(권한 에러), 해당 에러를 전역으로 잡아서 처리하려고 함.

-> axios interceptors를 사용해 받으려 했으나 에러가 잡히지 않음

-> 직접 만든 axios에 header를 가져가는 axiosInToken을 사용해 로그인이 필요한 모든 axios 통신에 사용한 것이 문제였음.

-> axiosInToken으로는 에러 interceptors 안 잡힘

 

다른 방법 사용

-> Error 컴포넌트에 에러 처리 코드를 넣은 후 forwardRef, useImperativeHandle를 사용해 다른 컴포넌트에서 해당 함수를 호출할 수 있도록 만들었다.

-> Refresh Token이 만료된 후 요청을 하면 자동으로 로그아웃이 되고 로그인 창으로 넘어가게 된다.

 

 원래 전역으로 에러를 가로채서 한 곳에서 처리하려고 했으나 axios는 가로채오는데, 직접 만든 헤더가 달린 axiosInToken은 에러를 가로채오지 못 했다.

 

 

따로 Error 컴포넌트를 만들어 에러가 났을 때 처리해준다.

 

다른 컴포넌트에서 로그인이 필요한 요청이 있을 때 axiosInToken으로 헤더에 token을 달고 가고, access token이 만료되었다면 res.header.authorization에 refresh token을 가지고 다시 토큰을 생성해 가져온다.

가져온 토큰을 Jotai에 다시 넣어 사용자가 모르게 토큰을 재발급해온다.

 

에러에 Error 컴포넌트에서 작성한 에러처리 함수인 logoutError() 함수를 호출한다.

 

Error 컴포넌트도 추가해준다.

 

 

CorsConfig에서 테스트를 위해 모든 접속을 허용해 놓은걸 3000(react 포트) 포트만 허용으로 바꾼다.

 

작성한 uri만 인증없이 통과하도록 permitAll()을 해주고, 다른 uri는 인증 절차를 거치려고 했으나 permitAll()이 작동하지 않고 인증에 걸리는 것 같았다.

 

따라서 인증 직전에 인증이 필요하지 않은(로그인 하지 않아도 처리 가능해야할) uri들을 모두 걸러서 return 해주었다.

 

 

-> 방법 변경

axiosInToken안에 interceptors를 넣어 세션을 지우고 로그인 창으로 넘어가도록 함.

로그인에서는 초기값으로 세팅함.