diff --git a/src/main/java/com/hyundai/app/event/enumType/EventType.java b/src/main/java/com/hyundai/app/event/enumType/EventType.java index cb0726f..6f74be5 100644 --- a/src/main/java/com/hyundai/app/event/enumType/EventType.java +++ b/src/main/java/com/hyundai/app/event/enumType/EventType.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Random; -import static com.hyundai.app.exception.ErrorCode.EVENT_TYPE_NOT_EXIST; +import static com.hyundai.app.exception.ErrorCode.EVENT_TYPE_INVALID; /** * @author 엄상은 @@ -50,7 +50,7 @@ public static EventType of(String eventType) { .filter(e -> e.equals(eventEnum)) .findFirst() .map(EventType::getRandomEventType) - .orElseThrow(() -> new AdventureOfHeendyException(EVENT_TYPE_NOT_EXIST)); + .orElseThrow(() -> new AdventureOfHeendyException(EVENT_TYPE_INVALID)); } } \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/exception/ErrorCode.java b/src/main/java/com/hyundai/app/exception/ErrorCode.java index 5653869..1603240 100644 --- a/src/main/java/com/hyundai/app/exception/ErrorCode.java +++ b/src/main/java/com/hyundai/app/exception/ErrorCode.java @@ -26,23 +26,17 @@ public enum ErrorCode { // 인증 및 인가 UNAUTHORIZED_ACCESS(UNAUTHORIZED, "인증되지 않은 사용자입니다."), FORBIDDEN_ACCESS(FORBIDDEN, "인가되지 않은 접근입니다."), - - // 리뷰 - REVIEW_SCORE_INVALID(BAD_REQUEST, "별점은 1~5점까지의 정수이어야 합니다."), - REVIEW_CONTENT_INVALID(BAD_REQUEST, "리뷰 내용은 최소 5자 이상이어야합니다."), - - // 아이디값 - STORE_ID_INVALID(BAD_REQUEST, "해당하는 매장 id가 없습니다."), - HASHTAG_ID_INVALID(BAD_REQUEST, "해당하는 해시태그 id가 없습니다."), - MEMBER_ID_INVALID(BAD_REQUEST, "해당하는 회원 id가 없습니다."), - MEMBER_NOT_EXIST(BAD_REQUEST, "해당하는 회원 oauth id가 존재하지 않습니다."), + MEMBER_NOT_EXIST(NOT_FOUND, "해당하는 회원 oauth id가 존재하지 않습니다."), // 이벤트 - EVENT_NOT_EXIST(BAD_REQUEST, "해당하는 이벤트가 존재하지 않습니다."), - EVENT_TYPE_NOT_EXIST(BAD_REQUEST, "이벤트 타입은 RESTAURANT, CAFE, SHOPPING, RANDOM 중 하나이어야 합니다."), + EVENT_NOT_EXIST(NOT_FOUND, "해당하는 이벤트가 존재하지 않습니다."), + EVENT_TYPE_INVALID(BAD_REQUEST, "이벤트 타입은 RESTAURANT, CAFE, SHOPPING, RANDOM 중 하나이어야 합니다."), // 매장 - STORE_NOT_EXIST(BAD_REQUEST, "해당하는 매장 id가 존재하지 않습니다."), + STORE_NOT_EXIST(NOT_FOUND, "해당하는 매장 id가 존재하지 않습니다."), + REVIEW_SCORE_INVALID(BAD_REQUEST, "별점은 1~5점까지의 정수이어야 합니다."), + REVIEW_CONTENT_INVALID(BAD_REQUEST, "리뷰 내용은 최소 5자 이상이어야합니다."), + HASHTAG_NOT_EXIST(NOT_FOUND, "해당하는 해시태그 id가 없습니다."), // 500 에러 SERVER_UNAVAILABLE(SERVICE_UNAVAILABLE, "서버에 오류가 발생하였습니다."), diff --git a/src/main/java/com/hyundai/app/exception/ErrorResponse.java b/src/main/java/com/hyundai/app/exception/ErrorResponse.java new file mode 100644 index 0000000..a751696 --- /dev/null +++ b/src/main/java/com/hyundai/app/exception/ErrorResponse.java @@ -0,0 +1,20 @@ +package com.hyundai.app.exception; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +/** + * @author 황수영 + * @since 2024/02/28 + * 예외 응답 형식 + */ +@Getter +@Builder +@RequiredArgsConstructor +public class ErrorResponse { + private final String errorMessage; + private final String errorType; + private final HttpStatus httpStatus; +} \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/exception/ExceptionResponseHandler.java b/src/main/java/com/hyundai/app/exception/ExceptionResponseHandler.java new file mode 100644 index 0000000..0444e23 --- /dev/null +++ b/src/main/java/com/hyundai/app/exception/ExceptionResponseHandler.java @@ -0,0 +1,69 @@ +package com.hyundai.app.exception; + +import lombok.extern.log4j.Log4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** + * @author 황수영 + * @since 2024/02/28 + * 예외 응답 반환하는 핸들러 + */ +@Log4j +@RestControllerAdvice +public class ExceptionResponseHandler extends ResponseEntityExceptionHandler { + + /** + * @author 황수영 + * @since 2024/02/28 + * 자체 예외 응답 처리 + */ + @ExceptionHandler(AdventureOfHeendyException.class) + public ResponseEntity handleCustomException(AdventureOfHeendyException e) { + return handleExceptionInternal(e); + } + + /** + * @author 황수영 + * @since 2024/02/28 + * 자체 예외 이외의 모든 예외 처리 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllException(Exception e) { + return handleExceptionAll(e); + } + + /** + * @author 황수영 + * @since 2024/02/28 + * 자체 예외 ResponseEntity 생성 + */ + private ResponseEntity handleExceptionInternal(AdventureOfHeendyException e) { + log.error("자체 예외 발생 : " + e); + + return ResponseEntity.status(e.getErrorCode().getHttpStatus()) + .body(ErrorResponse.builder() + .errorMessage(e.getErrorCode().getMessage()) + .errorType(String.valueOf(e.getErrorCode())) + .httpStatus(e.getErrorCode().getHttpStatus()) + .build()); + } + + /** + * @author 황수영 + * @since 2024/02/28 + * 자체 예외 이외의 모든 ResponseEntity 생성 + */ + private ResponseEntity handleExceptionAll(Exception e) { + log.error("예외 발생 : " + e); + + return ResponseEntity.status(ErrorCode.SERVER_UNAVAILABLE.getHttpStatus()) + .body(ErrorResponse.builder() + .errorMessage(e.getMessage()) + .errorType(e.toString()) + .httpStatus(ErrorCode.SERVER_UNAVAILABLE.getHttpStatus()) + .build()); + } +} \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/guide/GuideController.java b/src/main/java/com/hyundai/app/guide/GuideController.java index eb8de67..fc165e2 100644 --- a/src/main/java/com/hyundai/app/guide/GuideController.java +++ b/src/main/java/com/hyundai/app/guide/GuideController.java @@ -1,5 +1,7 @@ package com.hyundai.app.guide; +import com.hyundai.app.exception.AdventureOfHeendyException; +import com.hyundai.app.exception.ErrorCode; import com.hyundai.app.guide.dto.GuideTypeResDto; import com.hyundai.app.guide.dto.HashtagListResDto; import com.hyundai.app.store.dto.StoreResDto; @@ -51,6 +53,10 @@ public ResponseEntity> getGuideByCategory(@PathVariable( public ResponseEntity> findStoresByHashtags(@RequestParam("hashtagId")int hashtagId) { log.debug("해시 태그 선택 시, 관련 식당들 조회 => 해시 태그 : " + hashtagId); List stores = hashtagService.findStoresByMostSavedHashtags(hashtagId); + if (stores.isEmpty()) { + log.error("해당 해시 태그의 식당들이 존재하지 않습니다. => 해시 태그 : " + hashtagId); + throw new AdventureOfHeendyException(ErrorCode.STORE_NOT_EXIST); + } return new ResponseEntity<>(stores, HttpStatus.ACCEPTED); } } \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/member/controller/AuthController.java b/src/main/java/com/hyundai/app/member/controller/AuthController.java index 74a5170..4c0d2b0 100644 --- a/src/main/java/com/hyundai/app/member/controller/AuthController.java +++ b/src/main/java/com/hyundai/app/member/controller/AuthController.java @@ -5,7 +5,6 @@ import com.hyundai.app.member.service.MemberService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -28,6 +27,11 @@ public class AuthController { @Qualifier("memberServiceImpl") private MemberService memberService; + /** + * @author 황수영 + * @since 2024/02/12 + * 회원가입/로그인(OAuth 로그인) API + */ @PostMapping("/login") @ApiOperation("회원가입/로그인 API") public ResponseEntity login(@RequestBody LoginReqDto loginReqDto) { diff --git a/src/main/java/com/hyundai/app/member/controller/MemberController.java b/src/main/java/com/hyundai/app/member/controller/MemberController.java index 998e0bc..e00c7e0 100644 --- a/src/main/java/com/hyundai/app/member/controller/MemberController.java +++ b/src/main/java/com/hyundai/app/member/controller/MemberController.java @@ -31,6 +31,11 @@ public class MemberController { @Qualifier("memberServiceImpl") private MemberService memberService; + /** + * @author 황수영 + * @since 2024/02/14 + * 회원 조회 API + */ @GetMapping @ApiOperation("회원 정보 조회 API") public ResponseEntity login(@ApiIgnore @MemberId String memberId) { diff --git a/src/main/java/com/hyundai/app/member/oauth/kakao/KakaoOauthClient.java b/src/main/java/com/hyundai/app/member/oauth/kakao/KakaoOauthClient.java index 7de4d35..303c2f9 100644 --- a/src/main/java/com/hyundai/app/member/oauth/kakao/KakaoOauthClient.java +++ b/src/main/java/com/hyundai/app/member/oauth/kakao/KakaoOauthClient.java @@ -54,7 +54,12 @@ private KakaoResDto getMemberInfoByLoginToken(String accessToken) { HttpEntity> request = new HttpEntity<>(headers); return getMemberInfoFromOAuth(request); } - + + /** + * @author 황수영 + * @since 2024/02/12 + * Kakao에서 받아온 사용자 정보에서 email 추출 + */ private KakaoResDto getMemberInfoFromOAuth(HttpEntity> request) { KakaoResDto kakaoResDto = null; try { diff --git a/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java b/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java index 3a54755..d05128d 100644 --- a/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java +++ b/src/main/java/com/hyundai/app/member/service/MemberServiceImpl.java @@ -1,5 +1,7 @@ package com.hyundai.app.member.service; +import com.hyundai.app.exception.AdventureOfHeendyException; +import com.hyundai.app.exception.ErrorCode; import com.hyundai.app.member.domain.Member; import com.hyundai.app.member.dto.LoginReqDto; import com.hyundai.app.member.dto.LoginResDto; @@ -22,7 +24,7 @@ /** * @author 황수영 * @since 2024/02/13 - * (설명) + * 회원 관련 서비스단 */ @Log4j @Service @@ -31,18 +33,17 @@ public class MemberServiceImpl implements MemberService { @Value("${jwt.access-validity}") private long accessValidity; - private final MemberMapper memberMapper; private final KakaoOauthClient oAuthClient; private final JwtTokenGenerator authTokenGenerator; private final AwsS3Config awsS3Config; private final MemberQrService memberQrService; - public MemberResDto getMemberInfo(String id) { - Member member = memberMapper.findById(id); - return MemberResDto.of(member); - } - + /** + * @author 황수영 + * @since 2024/02/13 + * 회원가입/로그인 기능 + */ public LoginResDto login(LoginReqDto loginReqDto) { String email = oAuthClient.getEmail(loginReqDto); OauthType oauthType = OauthType.valueOf(loginReqDto.getOauthType().toUpperCase()); @@ -56,11 +57,21 @@ public LoginResDto login(LoginReqDto loginReqDto) { return joinByOauthId(email, oauthType); } + /** + * @author 황수영 + * @since 2024/02/13 + * access token 재발급 + */ private String updateAccessToken(Member member) { String memberId = String.valueOf(member.getId()); return authTokenGenerator.createJwtToken(memberId, accessValidity); } + /** + * @author 황수영 + * @since 2024/02/13 + * token 갱신 + */ private LoginResDto getUpdatedToken(Member member) { String newAccessToken = updateAccessToken(member); String refreshToken = member.getRefreshToken(); @@ -71,10 +82,14 @@ private LoginResDto getUpdatedToken(Member member) { .build(); } + /** + * @author 황수영 + * @since 2024/02/13 + * oauth id값으로 회원가입 + */ public LoginResDto joinByOauthId(String email, OauthType oauthType) { - String oauthId = oauthType.createOauthIdWithEmail(email); - LoginResDto loginResDto = authTokenGenerator.createLoginResDto(oauthId); String memberId = UUID.randomUUID().toString(); + LoginResDto loginResDto = authTokenGenerator.createLoginResDto(memberId); String qrUrl = generateQrCodeAndUploadToS3(memberId); Member member = Member.builder() @@ -82,19 +97,30 @@ public LoginResDto joinByOauthId(String email, OauthType oauthType) { .email(email) .nickname(Nickname.getRandomNickname()) .role(Role.ROLE_MEMBER) - .oauthId(oauthId) + .oauthId(oauthType.createOauthIdWithEmail(email)) .refreshToken(loginResDto.getRefreshToken()) .qrUrl(qrUrl) .build(); log.debug("joinByEmail member" + member.toString()); memberMapper.saveMember(member); - - Member savedMember = memberMapper.findByOauthId(oauthId); - log.debug("joinByEmail savedMember" + savedMember); - return loginResDto; } - + + /** + * @author 황수영 + * @since 2024/02/13 + * 회원 정보 조회 + */ + public MemberResDto getMemberInfo(String id) { + log.debug("회원 정보 조회 : " + id); + Member member = memberMapper.findById(id); + if (member == null) { + log.error("회원 id가 존재하지 않습니다. : " + id); + throw new AdventureOfHeendyException(ErrorCode.MEMBER_NOT_EXIST); + } + return MemberResDto.of(member); + } + /** * @author 엄상은 * @since 2024/02/26 diff --git a/src/main/java/com/hyundai/app/security/AuthDetailsService.java b/src/main/java/com/hyundai/app/security/AuthDetailsService.java index 1308fad..1cd76de 100644 --- a/src/main/java/com/hyundai/app/security/AuthDetailsService.java +++ b/src/main/java/com/hyundai/app/security/AuthDetailsService.java @@ -1,5 +1,7 @@ package com.hyundai.app.security; +import com.hyundai.app.exception.AdventureOfHeendyException; +import com.hyundai.app.exception.ErrorCode; import com.hyundai.app.member.mapper.MemberMapper; import com.hyundai.app.member.domain.Member; import lombok.RequiredArgsConstructor; @@ -22,8 +24,11 @@ public class AuthDetailsService implements UserDetailsService { @Override public AuthUserDetails loadUserByUsername(String id) throws UsernameNotFoundException { - Member member = memberMapper.findById(id); - log.debug("loadUserByUsername() => member : " + member); - return new AuthUserDetails(member); + Member findMember = memberMapper.findById(id); + log.debug("loadUserByUsername() => member : " + id); + if (findMember == null) { + throw new AdventureOfHeendyException(ErrorCode.MEMBER_NOT_EXIST); + } + return new AuthUserDetails(findMember); } } \ No newline at end of file diff --git a/src/main/java/com/hyundai/app/security/jwt/JwtTokenGenerator.java b/src/main/java/com/hyundai/app/security/jwt/JwtTokenGenerator.java index 05f5fac..a4da35c 100644 --- a/src/main/java/com/hyundai/app/security/jwt/JwtTokenGenerator.java +++ b/src/main/java/com/hyundai/app/security/jwt/JwtTokenGenerator.java @@ -41,16 +41,12 @@ public class JwtTokenGenerator implements InitializingBean { public void afterPropertiesSet() { jwtSecret = Base64.getEncoder().encodeToString(jwtSecret.getBytes()); } - - public Authentication getAuthentication(String accessToken) { - Claims claims = getClaims(accessToken); - AuthUserDetails userDetails = authDetailsService.loadUserByUsername(claims.getSubject()); - log.debug("AuthTokenGenerator getAuthentication() userDetails : " + userDetails); - - return new UsernamePasswordAuthenticationToken( - userDetails, accessToken, userDetails.getAuthorities()); - } - + + /** + * @author 황수영 + * @since 2024/02/14 + * 로그인 Res Dto 생성 + */ public LoginResDto createLoginResDto(String id) { String accessToken = createJwtToken(id, accessValidity); String refreshToken = createJwtToken(id, refreshValidity); @@ -61,6 +57,11 @@ public LoginResDto createLoginResDto(String id) { .build(); } + /** + * @author 황수영 + * @since 2024/02/14 + * JWT 생성 + */ public String createJwtToken(String id, long validity) { Date now = new Date(); Claims claims = Jwts.claims() @@ -74,6 +75,11 @@ public String createJwtToken(String id, long validity) { .compact(); } + /** + * @author 황수영 + * @since 2024/02/14 + * JWT 파싱 + */ public Claims getClaims(String accessToken) { try { return Jwts.parser() @@ -84,7 +90,12 @@ public Claims getClaims(String accessToken) { return e.getClaims(); } } - + + /** + * @author 황수영 + * @since 2024/02/14 + * 토큰 유효성 검증 + */ public void isTokenValidate(String token) { try { Jwts.parser() diff --git a/src/main/java/com/hyundai/app/store/service/StoreServiceImpl.java b/src/main/java/com/hyundai/app/store/service/StoreServiceImpl.java index 1e7e50f..446162d 100644 --- a/src/main/java/com/hyundai/app/store/service/StoreServiceImpl.java +++ b/src/main/java/com/hyundai/app/store/service/StoreServiceImpl.java @@ -43,6 +43,7 @@ public class StoreServiceImpl implements StoreService { public StoreResDto getStoreDetail(int storeId) { Store store = storeMapper.getStoreDetail(storeId); if (store == null) { + log.error("해당하는 매장 번호가 존재하지 않습니다. storeId : " + storeId); throw new AdventureOfHeendyException(STORE_NOT_EXIST); } log.debug("매장 번호 :" + storeId + " 정보 조회 : " + store); @@ -84,6 +85,9 @@ public void createReview(int storeId, String memberId, ReviewReqDto reviewReqDto */ private double calcAvgScore(int storeId, int newScore) { Store store = storeMapper.getStoreDetail(storeId); + if (store == null) { + throw new AdventureOfHeendyException(STORE_NOT_EXIST); + } int reviewCount = store.getReviewCount(); float avgScore = store.getAvgScore(); log.debug("별점 업데이트 => reviewCount : " + reviewCount + ", avgScore : " + avgScore); @@ -149,7 +153,7 @@ private void handleStoreHashtag(int storeId, int hashtagId) { private void validateIfHashtagIdExist(int hashtagId) { if (hashtagMapper.getHashtag(hashtagId) == null) { log.error("해시태그 id: " + hashtagId + "가 존재하지 않습니다."); - throw new AdventureOfHeendyException(HASHTAG_ID_INVALID); + throw new AdventureOfHeendyException(HASHTAG_NOT_EXIST); } } }