가입한 회원중에 실제로 구매의향이 있는 회원을 분리하기 위해서 였습니다. 10명이 가입해도 실제 장바구니에 물건을 담는 회원은 적기 때문에 장바구니쪽 데이터를 좀 줄이는게 좋다고 생각합니다. 반대로 한번이라도 장바구니에 물건을 담은 회원은 뭔가 구매의사가 있었던 회원이라고 판단하고 남겨두었습니다만 말씀하신대로 삭제를 해도 크게 문제가 되지는 않을듯 합니다. 장바구니에 물건을 담은 회원들은 나중에 DM등을 발송할 대상이 되기도 한다고 생각하고 봐 주시면 좋을듯 합니다.
Access Token(AT)과 Refresh Token(RT) APIRefreshController에서 Access Token의 갱신 작업을 하고 있는데요. AT가 입장권, RT가 교환권이므로 교환권이 만료되었다는 얘기는 결국 새로 로그인해야 한다는 의미가 됩니다. 질문하신 내용이 RT이 만료된 상황에 대한 것이라면 -- 새로 로그인을 유도하는게 맞습니다. 서버에서는 RT를 확인하는 부분입니다. Map claims = JWTUtil.validateToken(refreshToken); 여기서 JWTUtil에 있는 validateToken( )은 예외를 발생시킵니다. public static Map validateToken(String token) { Map claim = null; try{ SecretKey key = Keys.hmacShaKeyFor(JWTUtil.key.getBytes("UTF-8")); claim = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) // 파싱 및 검증, 실패 시 에러 .getBody(); }catch(MalformedJwtException malformedJwtException){ throw new CustomJWTException("MalFormed"); }catch(ExpiredJwtException expiredJwtException){ throw new CustomJWTException("Expired"); }catch(InvalidClaimException invalidClaimException){ throw new CustomJWTException("Invalid"); }catch(JwtException jwtException){ throw new CustomJWTException("JWTError"); }catch(Exception e){ throw new CustomJWTException("Error"); } return claim; } 만료되거나 잘못된 RT에 대해서 알 수 있고 이를 advice에서 처리합니다. @ExceptionHandler(CustomJWTException.class) protected ResponseEntity handleJWTException(CustomJWTException e) { String msg = e.getMessage(); return ResponseEntity.ok().body(Map.of("error", msg)); } 이렇게 되면 RT가 만료되었을때 사용자는 {error:에러메시지}를 받게 됩니다. RT를 새로 발행할 때는 RT의 남은 시간을 봐서 새로운 RT도 만들어 줍니다. String newRefreshToken = checkTime((Integer)claims.get("exp")) == true? JWTUtil.generateToken(claims, 60*24) : refreshToken; 이렇게 하면 사용자는 주기적으로 AT와 RT가 변경되게 됩니다. 최근에는 AT 갱신시에 RT를 무조건 새로 만들어주는 방식도 많이 사용하고 있습니다. React쪽(다음챕터) 커스텀 훅으로 에러가 발생하면 경고창을 보여주도록 구성합니다.
커스텀 훅으로 빼기 전에 { loginStatus === 'pending' && className = "bg-amber-300" > 로그인 중... } { loginStatus === 'fulfilled' && className = "bg-green-300" > 로그인 성공! } { loginStatus === 'rejected' && className = "bg-red-300" > 로그인 실패 } 부분 때문에 그렇게 생각하실 수 있습니다. 변경된 커스텀 훅을 보면 변수의 이름은 같지만 내용물은 조금 달라집니다. //로그인 상태 객체 const loginState = useSelector((state: RootState) => state.loginSlice) //로그인 여부 const loginStatus = loginState.status 처럼 loginStatus는 true/false가 되고 loginState는 로그인한 사용자의 정보입니다(그래서 loginState.nickname처럼 사용하게 됩니다. 변수명이 동일해서 그렇게 느끼실 수도 있었겠네요.. 혹시 몰라서 AI의 설명을 첨부합니다. loginStatus === 'fulfilled' 처럼 사용하실 필요가 없습니다. 지금 작성하신 코드처럼 {loginStatus && ...} 형태로 사용하시는 것이 맞습니다. 그 이유와 두 개념의 차이를 명확히 정리해 드릴게요. 1. fulfilled 는 컴포넌트가 아니라 Redux 내부에서 쓰는 개념입니다 loginPostAsync.fulfilled 는 Redux Toolkit의 createAsyncThunk 가 API 요청에 성공했을 때 발생하는 액션 상태(Lifecycle)입니다. 이는 컴포넌트가 아니라 loginSlice.ts 내부의 extraReducers 에서 "로그인 API 통신이 성공했으니 스토어의 상태를 이렇게 바꿔라"라고 지정할 때 사용합니다. 2. 컴포넌트는 이미 처리된 '결과'만 가져다 씁니다 컴포넌트는 비동기 통신의 진행 단계를 직접 판별하는 곳이 아닙니다. 이미 비동기 통신이 성공하여 Redux 스토어에 저장된 최종 결과값( loginStatus )을 가져와서 화면에 보여주는 역할만 합니다. 따라서 loginStatus 가 이미 true 또는 유효한 값으로 세팅되어 있기 때문에, 조건문에서는 단순히 존재 여부나 참/거짓만 판별하면 됩니다. 3. 만약 === 'fulfilled' 를 쓰면 어떻게 될까요? loginStatus 가 불리언( true / false ) 타입이나 객체라면 === 'fulfilled' 라는 비교문은 항상 거짓(false)이 되어 메뉴가 아예 나타나지 않는 버그가 발생합니다. loginStatus 는 오직 로그인 상태 여부만을 깔끔하게 대변하도록 설계되었기 때문에, 지금처럼 {loginStatus && ...} 또는 {loginStatus ? (...) : null} 형태로 쓰는 것이 가장 올바른 리액트/리덕스 패턴입니다. 현재 작성하신 커스텀 훅과 컴포넌트 구조는 아주 정상적이니 이대로 사용하셔도 무방합니다
헉.. 음성이 안 나오는 부분이 있었나요? 제가 그런 피드백을 받은 적은 없어서 모르고 있었습니다. 학습에 불편을 드려 죄송합니다. 혹시 어떤 강의 인지 알려주시면 최대한 빠르게 조치하겠습니다. 현재 질문에 있는 '쿠키를 이용한 로그인' 강의는 제가 다시 확인해 봤는데 이상이 없었습니다. 다른 강의일까요?
6:42초 부분을 말씀하시는거 같습니다 select last_insert_id( ) 의 결과는 가장 최근에 추가된 새로운 번호값을 숫자로 반환합니다. MyBatis에서는 select를 하면 resultType을 지정한다고 생각하시면 되는데.. 숫자의 경우는 int 보다 큰 long 타입으로 처리하는 것이 안전하기 때문에 시퀀스나 auto_increment의 결과는 long으로 처리합니다. 혹시 제가 질문 의도를 잘못 이해할수도 있으니 현재 학습중인 부분을 알려주시면 좀 더 자세히 설명할 수 있을 듯 합니다.