From 93259ff0881a213dd53cf8601f9c6fbb820282b9 Mon Sep 17 00:00:00 2001 From: msk226 Date: Wed, 6 Aug 2025 15:18:50 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[SPOT-303][FIX]=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EC=8B=9C=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20IntegrityConstraintViolationException=EB=A5=BC=20?= =?UTF-8?q?=EB=AC=BC=EB=A6=AC=20=EC=82=AD=EC=A0=9C=EB=A1=9C=20=EC=9D=BC?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/spot/service/auth/AuthServiceImpl.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java b/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java index d82c7617..7f31c20f 100644 --- a/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java +++ b/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java @@ -6,9 +6,16 @@ import com.example.spot.api.exception.handler.MemberHandler; import com.example.spot.domain.Member; import com.example.spot.domain.auth.RsaKey; +import com.example.spot.repository.MemberAttendanceRepository; +import com.example.spot.repository.MemberReportRepository; +import com.example.spot.repository.MemberScrapRepository; import com.example.spot.repository.MemberStudyRepository; import com.example.spot.repository.MemberThemeRepository; +import com.example.spot.repository.MemberVoteRepository; import com.example.spot.repository.PreferredRegionRepository; +import com.example.spot.repository.PreferredStudyRepository; +import com.example.spot.repository.StudyLikedCommentRepository; +import com.example.spot.repository.StudyLikedPostRepository; import com.example.spot.repository.StudyReasonRepository; import com.example.spot.web.dto.member.MemberRequestDTO.SignUpDetailDTO; import com.example.spot.web.dto.member.MemberResponseDTO.CheckMemberDTO; @@ -77,6 +84,7 @@ public class AuthServiceImpl implements AuthService{ private final PreferredRegionRepository preferredRegionRepository; private final StudyReasonRepository studyReasonRepository; + private final MailService mailService; private final NaverOAuthService naverOAuthService; @@ -166,8 +174,7 @@ public MemberResponseDTO.InactiveMemberDTO withdraw() { throw new MemberHandler(ErrorStatus._OWNED_STUDY_EXISTS); } - // inactive 필드 활성화 - member.setInactive(LocalDateTime.now()); + memberRepository.delete(member); // SecurityContextHolder 정리 SecurityUtils.deleteCurrentUser(); From acd1cb457566af27d801cb93c1274cc587e220b4 Mon Sep 17 00:00:00 2001 From: msk226 Date: Wed, 6 Aug 2025 15:34:16 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[SPOT-303][REFACTOR]=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/service/auth/AuthServiceImpl.java | 150 +++++++++--------- 1 file changed, 72 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java b/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java index 7f31c20f..ea283520 100644 --- a/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java +++ b/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java @@ -5,46 +5,42 @@ import com.example.spot.api.exception.GeneralException; import com.example.spot.api.exception.handler.MemberHandler; import com.example.spot.domain.Member; -import com.example.spot.domain.auth.RsaKey; -import com.example.spot.repository.MemberAttendanceRepository; -import com.example.spot.repository.MemberReportRepository; -import com.example.spot.repository.MemberScrapRepository; -import com.example.spot.repository.MemberStudyRepository; -import com.example.spot.repository.MemberThemeRepository; -import com.example.spot.repository.MemberVoteRepository; -import com.example.spot.repository.PreferredRegionRepository; -import com.example.spot.repository.PreferredStudyRepository; -import com.example.spot.repository.StudyLikedCommentRepository; -import com.example.spot.repository.StudyLikedPostRepository; -import com.example.spot.repository.StudyReasonRepository; -import com.example.spot.web.dto.member.MemberRequestDTO.SignUpDetailDTO; -import com.example.spot.web.dto.member.MemberResponseDTO.CheckMemberDTO; -import com.example.spot.web.dto.member.MemberResponseDTO.NicknameDuplicateDTO; -import com.example.spot.web.dto.rsa.Rsa; import com.example.spot.domain.auth.RefreshToken; +import com.example.spot.domain.auth.RsaKey; import com.example.spot.domain.auth.VerificationCode; import com.example.spot.domain.enums.Carrier; import com.example.spot.domain.enums.Gender; import com.example.spot.domain.enums.LoginType; import com.example.spot.domain.enums.Status; import com.example.spot.repository.MemberRepository; +import com.example.spot.repository.MemberStudyRepository; +import com.example.spot.repository.MemberThemeRepository; +import com.example.spot.repository.PreferredRegionRepository; import com.example.spot.repository.RefreshTokenRepository; +import com.example.spot.repository.StudyReasonRepository; import com.example.spot.repository.rsa.RSAKeyRepository; import com.example.spot.repository.verification.VerificationCodeRepository; import com.example.spot.security.utils.JwtTokenProvider; import com.example.spot.security.utils.MemberUtils; import com.example.spot.security.utils.RSAUtils; -import com.example.spot.web.dto.member.MemberRequestDTO; -import com.example.spot.web.dto.member.MemberResponseDTO; import com.example.spot.security.utils.SecurityUtils; import com.example.spot.service.message.MailService; +import com.example.spot.web.dto.member.MemberRequestDTO; +import com.example.spot.web.dto.member.MemberRequestDTO.SignUpDetailDTO; +import com.example.spot.web.dto.member.MemberResponseDTO; +import com.example.spot.web.dto.member.MemberResponseDTO.CheckMemberDTO; +import com.example.spot.web.dto.member.MemberResponseDTO.NicknameDuplicateDTO; import com.example.spot.web.dto.member.MemberResponseDTO.SocialLoginSignInDTO; import com.example.spot.web.dto.member.naver.NaverCallback; import com.example.spot.web.dto.member.naver.NaverMember; import com.example.spot.web.dto.member.naver.NaverOAuthToken; +import com.example.spot.web.dto.rsa.Rsa; import com.example.spot.web.dto.token.TokenResponseDTO; import com.example.spot.web.dto.token.TokenResponseDTO.TokenDTO; - +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.security.PrivateKey; import java.security.SecureRandom; import java.time.LocalDate; @@ -56,11 +52,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -69,7 +60,7 @@ @Service @RequiredArgsConstructor @Transactional -public class AuthServiceImpl implements AuthService{ +public class AuthServiceImpl implements AuthService { @PersistenceContext private EntityManager entityManager; @@ -96,6 +87,7 @@ public class AuthServiceImpl implements AuthService{ /** * 토큰을 재발급 합니다. + * * @param refreshToken 리프레시 토큰 * @return 새로운 토큰을 생성하여 반환합니다. * @throws GeneralException 토큰이 만료되었거나, 잘못된 토큰일 경우 발생합니다. @@ -105,7 +97,7 @@ public TokenDTO reissueToken(String refreshToken) { // 리프레시 토큰 조회 및 검증 RefreshToken tokenInDB = refreshTokenRepository.findByToken(refreshToken) - .orElseThrow(() -> new GeneralException(ErrorStatus._INVALID_REFRESH_TOKEN)); + .orElseThrow(() -> new GeneralException(ErrorStatus._INVALID_REFRESH_TOKEN)); // 리프레시 토큰 만료 확인 if (jwtTokenProvider.isTokenExpired(tokenInDB.getToken())) { @@ -118,24 +110,26 @@ public TokenDTO reissueToken(String refreshToken) { // memberId로 회원 조회 Member member = memberRepository.findById(memberIdByToken) - .orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND)); + .orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND)); // 회원의 리프레시 토큰과 요청된 리프레시 토큰 비교 - if (!Objects.equals(member.getId(), memberIdByToken)) + if (!Objects.equals(member.getId(), memberIdByToken)) { throw new GeneralException(ErrorStatus._INVALID_JWT); + } // 토큰 재발급 TokenDTO tokenDTO = jwtTokenProvider.reissueToken(refreshToken); // 리프레시 토큰 저장 RefreshToken token = RefreshToken.builder() - .memberId(member.getId()) - .token(tokenDTO.getRefreshToken()) - .build(); + .memberId(member.getId()) + .token(tokenDTO.getRefreshToken()) + .build(); // 기존 리프레시 토큰 삭제 - if (refreshTokenRepository.existsByMemberId(member.getId())) + if (refreshTokenRepository.existsByMemberId(member.getId())) { refreshTokenRepository.deleteByMemberId(member.getId()); + } // 새로운 리프레시 토큰 저장 refreshTokenRepository.save(token); @@ -144,7 +138,7 @@ public TokenDTO reissueToken(String refreshToken) { return tokenDTO; } -/* ----------------------------- 공통 회원 관리 API ------------------------------------- */ + /* ----------------------------- 공통 회원 관리 API ------------------------------------- */ @Override public MemberResponseDTO.MemberInfoCreationDTO signUpAndPartialUpdate(SignUpDetailDTO request) { @@ -185,11 +179,12 @@ public MemberResponseDTO.InactiveMemberDTO withdraw() { return MemberResponseDTO.InactiveMemberDTO.toDTO(member); } -/* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */ + /* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */ /** * 네이버 로그인 인증 요청 URL로 실제 요청을 전송하고 로그인 페이지로 리디렉션하는 메서드입니다. - * @param request : HTTPServletRequest + * + * @param request : HTTPServletRequest * @param response : HttpServletResponse */ @Override @@ -203,38 +198,39 @@ public void authorizeWithNaver(HttpServletRequest request, HttpServletResponse r } /** - * SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. - * 로그인 Callback 성공시 반환되는 naverCallback을 바탕으로 액세스 토큰을 발급받고 프로필에 접근합니다. - * 현재 SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 기반으로 회원 정보를 생성하여 DB에 저장합니다. - * 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다. - * @param request : HttpServletRequest - * @param response : HttpServletResponse + * SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. 로그인 Callback 성공시 반환되는 naverCallback을 바탕으로 액세스 토큰을 발급받고 프로필에 접근합니다. 현재 + * SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 기반으로 회원 정보를 생성하여 DB에 저장합니다. 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다. + * + * @param request : HttpServletRequest + * @param response : HttpServletResponse * @param naverCallback : Callback 함수 성공시 반환되는 요소(code, state, error, error_description) - * @return SocialLoginSignInDTO(isSpotMember, signInDTO-토큰정보) + * @return SocialLoginSignInDTO(isSpotMember, signInDTO - 토큰정보) */ @Override - public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception { + public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, + NaverCallback naverCallback) throws Exception { NaverMember.ResponseDTO responseDTO = naverOAuthService.getNaverMember(request, response, naverCallback); return getSocialLoginSignInDTO(responseDTO); } /** - * SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. - * 클라이언트로부터 전달받은 액세스 토큰을 통해 프로필에 접근합니다. - * 현재 SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 기반으로 회원 정보를 생성하여 DB에 저장합니다. - * 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다. - * @param request : HttpServletRequest + * SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. 클라이언트로부터 전달받은 액세스 토큰을 통해 프로필에 접근합니다. 현재 SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 + * 기반으로 회원 정보를 생성하여 DB에 저장합니다. 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다. + * + * @param request : HttpServletRequest * @param response : HttpServletResponse - * @return SocialLoginSignInDTO(isSpotMember, signInDTO-토큰정보) + * @return SocialLoginSignInDTO(isSpotMember, signInDTO - 토큰정보) */ @Override - public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception { + public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, + NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception { NaverMember.ResponseDTO responseDTO = naverOAuthService.getNaverMember(request, response, naverTokenDTO); return getSocialLoginSignInDTO(responseDTO); } /** * 네이버 회원 프로필을 통해 SocialLoginSignInDTO를 생성하는 함수입니다. + * * @param responseDTO : 네이버 회원 프로필 DTO * @return SocialLoginSignInDTO (SPOT 회원 정보 및 토큰 정보) */ @@ -251,8 +247,7 @@ private SocialLoginSignInDTO getSocialLoginSignInDTO(NaverMember.ResponseDTO res refreshTokenRepository.deleteByMemberId(member.getId()); memberRepository.deleteById(member.getId()); entityManager.flush(); - } - else { + } else { throw new MemberHandler(ErrorStatus._MEMBER_EMAIL_ALREADY_EXISTS); } } @@ -271,8 +266,7 @@ private SocialLoginSignInDTO getSocialLoginSignInDTO(NaverMember.ResponseDTO res isSpotMember = Boolean.FALSE; signUpWithNaver(responseDTO); } - } - else { + } else { isSpotMember = Boolean.FALSE; signUpWithNaver(responseDTO); } @@ -307,6 +301,7 @@ public boolean isMemberExistsByCheckList(Member member) { /** * 현재 SPOT에 가입되어 있지 않은 회원에 한해 회원 정보를 생성하여 DB에 저장합니다. + * * @param memberDTO : naverCallback을 바탕으로 생성된 프로필 객체 */ private void signUpWithNaver(NaverMember.ResponseDTO memberDTO) { @@ -349,11 +344,12 @@ private void signUpWithNaver(NaverMember.ResponseDTO memberDTO) { memberRepository.save(member); } -/* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */ + /* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */ /** * 일반 로그인을 위한 메서드입니다. 아이디와 비밀번호를 확인한 후 토큰을 발급하는 로직을 수행합니다. - * @param signInDTO 로그인할 회원의 아이디와 비밀번호를 입력 받습니다. + * + * @param signInDTO 로그인할 회원의 아이디와 비밀번호를 입력 받습니다. * @return 로그인한 회원의 토큰 정보(액세스 & 리프레시 토큰 & 만료기간), 이메일과 회원 아이디(정수)가 반환됩니다. */ @Override @@ -414,11 +410,11 @@ public Rsa.RSAPublicKey getRSAPublicKey() throws Exception { } /** - * 인증 코드를 전송하는 메서드입니다. - * 일반 회원가입, 아이디 찾기, 비밀번호 찾기에 공통으로 적용되는 인증 메일 전송 로직입니다. - * @param request 클라이언트의 요청 정보 객체를 입력 받습니다. + * 인증 코드를 전송하는 메서드입니다. 일반 회원가입, 아이디 찾기, 비밀번호 찾기에 공통으로 적용되는 인증 메일 전송 로직입니다. + * + * @param request 클라이언트의 요청 정보 객체를 입력 받습니다. * @param response 서버의 응답 정보 객체를 입력 받습니다. - * @param email 인증 코드를 전송할 이메일을 입력 받습니다. + * @param email 인증 코드를 전송할 이메일을 입력 받습니다. */ @Override public void sendVerificationCode(HttpServletRequest request, HttpServletResponse response, String email) { @@ -435,6 +431,7 @@ public void sendVerificationCode(HttpServletRequest request, HttpServletResponse /** * 인증 코드를 생성하는 메서드입니다. + * * @return 1 ~ 9999 사이의 랜덤한 정수를 반환합니다. */ private String createCode() { @@ -444,9 +441,9 @@ private String createCode() { } /** - * 이메일로 전송된 인증 코드를 메모리에 저장된 인증 코드 객체 정보와 비교 검증하는 메서드입니다. - * 인증이 완료되면 일반 회원가입, 아이디 찾기, 비밀번호 찾기에 필요한 임시 토큰을 발급합니다. - * @param code 이메일로 전달된 인증 코드를 입력 받습니다. + * 이메일로 전송된 인증 코드를 메모리에 저장된 인증 코드 객체 정보와 비교 검증하는 메서드입니다. 인증이 완료되면 일반 회원가입, 아이디 찾기, 비밀번호 찾기에 필요한 임시 토큰을 발급합니다. + * + * @param code 이메일로 전달된 인증 코드를 입력 받습니다. * @param email 인증 코드가 전송된 이메일을 입력 받습니다. * @return 발급한 임시 토큰 정보(토큰 & 만료기간)를 반환합니다. */ @@ -474,14 +471,8 @@ public TokenResponseDTO.TempTokenDTO verifyEmail(String code, String email) { * 일반 회원가입에 사용되는 메서드입니다. * * @param rsaId - * @param signUpDTO 회원의 기본 정보를 입력 받습니다. - * name : 이름 - * frontRID : 주민번호 앞자리 - * backRID : 주민번호 뒷자리 첫 글자 - * email : 이메일 - * loginId : 아이디 - * password : 비밀번호 (RSA Key로 암호화한 값) - * pwCheck : 비밀번호 확인 + * @param signUpDTO 회원의 기본 정보를 입력 받습니다. name : 이름 frontRID : 주민번호 앞자리 backRID : 주민번호 뒷자리 첫 글자 email : 이메일 loginId : + * 아이디 password : 비밀번호 (RSA Key로 암호화한 값) pwCheck : 비밀번호 확인 * @return 가입한 회원은 자동으로 로그인되며, 회원의 토큰 정보(액세스 & 리프레시 토큰 & 만료기간), 이메일과 회원 아이디(정수)가 반환됩니다. */ @Override @@ -498,8 +489,7 @@ public MemberResponseDTO.MemberSignInDTO signUp(Long rsaId, MemberRequestDTO.Sig refreshTokenRepository.deleteByMemberId(member.getId()); memberRepository.deleteById(member.getId()); entityManager.flush(); - } - else { + } else { throw new MemberHandler(ErrorStatus._MEMBER_EMAIL_ALREADY_EXISTS); } } @@ -559,13 +549,15 @@ public MemberResponseDTO.MemberSignInDTO signUp(Long rsaId, MemberRequestDTO.Sig /** * 생성된 리프레시 토큰을 DB에 저장하는 메서드입니다. + * * @param member 리프레시 토큰을 발급한 회원을 입력 받습니다. - * @param token 저장할 토큰 정보(액세스 & 리프레시 토큰, 만료기간)를 입력 받습니다. + * @param token 저장할 토큰 정보(액세스 & 리프레시 토큰, 만료기간)를 입력 받습니다. */ private void saveRefreshToken(Member member, TokenDTO token) { - if (refreshTokenRepository.existsByMemberId(member.getId())) + if (refreshTokenRepository.existsByMemberId(member.getId())) { refreshTokenRepository.deleteAllByMemberId(member.getId()); + } RefreshToken refreshToken = RefreshToken.builder() .memberId(member.getId()) @@ -577,6 +569,7 @@ private void saveRefreshToken(Member member, TokenDTO token) { /** * 아이디 찾기에 사용되는 메서드입니다. 임시 토큰을 검증한 후 이메일로 가입된 회원 정보를 확인합니다. + * * @return 아이디/이메일, 로그인 타입, 계정 생성일시가 반환합니다. */ @Override @@ -599,6 +592,7 @@ public MemberResponseDTO.FindIdDTO findId() { /** * 비밀번호 찾기에 사용되는 메서드입니다. 임시 토큰을 검증한 후 아이디 & 이메일로 가입된 회원 정보를 확인합니다. + * * @param loginId 비밀번호를 찾고자 하는 회원의 아이디를 입력 받습니다. * @return 닉네임, 아이디, 발급된 임시 비밀번호를 반환합니다. */ @@ -697,8 +691,8 @@ public NicknameDuplicateDTO checkNicknameAvailability(String nickname) { } /** - * 임시 비밀번호를 발급하는 메서드입니다. - * 알파벳 대소문자, 숫자, 특수기호를 혼합하여 13자리 비밀번호를 생성합니다. + * 임시 비밀번호를 발급하는 메서드입니다. 알파벳 대소문자, 숫자, 특수기호를 혼합하여 13자리 비밀번호를 생성합니다. + * * @return 생성된 임시 비밀번호를 반환합니다. */ private String generateTempPassword() { @@ -718,7 +712,7 @@ private String generateTempPassword() { .collect(Collectors.joining()); } -/* ----------------------------- 로그아웃 API ------------------------------------- */ + /* ----------------------------- 로그아웃 API ------------------------------------- */ }