diff --git a/build.gradle b/build.gradle index bba915e..7d998df 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.5.6' + id 'org.springframework.boot' version '3.3.0' id 'io.spring.dependency-management' version '1.1.7' } @@ -39,7 +39,7 @@ dependencies { annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa" annotationProcessor "jakarta.persistence:jakarta.persistence-api" annotationProcessor "jakarta.annotation:jakarta.annotation-api" - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' } tasks.named('test') { diff --git a/src/main/java/com/example/umc9th/config/AppConfig.java b/src/main/java/com/example/umc9th/config/AppConfig.java new file mode 100644 index 0000000..80e9239 --- /dev/null +++ b/src/main/java/com/example/umc9th/config/AppConfig.java @@ -0,0 +1,16 @@ +package com.example.umc9th.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.web.client.RestTemplate; + +@Configuration +@EnableAsync +public class AppConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/com/example/umc9th/config/discord/DevNotifierService.java b/src/main/java/com/example/umc9th/config/discord/DevNotifierService.java new file mode 100644 index 0000000..6bd8bc7 --- /dev/null +++ b/src/main/java/com/example/umc9th/config/discord/DevNotifierService.java @@ -0,0 +1,14 @@ +package com.example.umc9th.config.discord; + + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +@Service +@Profile("!prod & !staging") +public class DevNotifierService implements Notifier{ + @Override + public void sendNotification(Exception e, String requestUri) { + System.out.println("디스코드 알림 전송(로컬 확인용)"); + } +} diff --git a/src/main/java/com/example/umc9th/config/discord/DiscordNotifierService.java b/src/main/java/com/example/umc9th/config/discord/DiscordNotifierService.java new file mode 100644 index 0000000..3fae335 --- /dev/null +++ b/src/main/java/com/example/umc9th/config/discord/DiscordNotifierService.java @@ -0,0 +1,70 @@ +package com.example.umc9th.config.discord; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Profile({"prod","staging"}) +public class DiscordNotifierService implements Notifier{ + private final RestTemplate restTemplate; + + @Value( "${discord.webhook-url}") + private String discordWebhookUrl; + + @Async + @Override + public void sendNotification(Exception e, String requestUri) { + try { + /* + RestTemplate은 HttpMessageConverter를 이용해 직렬화 + -> MappingJackson2HttpMessageConverter + + spring-boot-starter-web : Jackson 포함 + */ + Map payload = createDiscordPayload(e, requestUri); + restTemplate.postForEntity(discordWebhookUrl, payload, String.class); + } catch (Exception ex) { + // 알림 전송 실패 시, 원본 예외 처리에 영향을 주지 않도록 내부에서 처리 + System.err.println("Discord 알림 전송 실패: " + ex.getMessage()); + } + } + + // Discord Embed 메시지 페이로드 생성 + private Map createDiscordPayload(Exception e, String requestUri) { + // Embed 객체 생성 + Map embed = new HashMap<>(); + embed.put("title", "🚨 서버 에러 발생!"); + embed.put("color", Color.RED.getRGB() & 0xFFFFFF); // 16진수 Red 컬러 + + // 스택 트레이스를 문자열로 변환 (간결하게) + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String stackTrace = sw.toString().substring(0, Math.min(1000, sw.toString().length())) + "..."; // 1000자로 제한 + + // Embed에 필드 추가 + embed.put("fields", List.of( + Map.of("name", "Request URI", "value", "`" + requestUri + "`", "inline", false), + Map.of("name", "Exception Type", "value", "`" + e.getClass().getSimpleName() + "`", "inline", false), + Map.of("name", "Message", "value", e.getMessage(), "inline", false), + Map.of("name", "Stack Trace (Short)", "value", "```\n" + stackTrace + "\n```", "inline", false) + )); + + // 최상위 페이로드 + Map payload = new HashMap<>(); + payload.put("username", "500에러 발생!"); // 웹훅 이름 + payload.put("embeds", List.of(embed)); + + return payload; + } +} diff --git a/src/main/java/com/example/umc9th/config/discord/Notifier.java b/src/main/java/com/example/umc9th/config/discord/Notifier.java new file mode 100644 index 0000000..e090961 --- /dev/null +++ b/src/main/java/com/example/umc9th/config/discord/Notifier.java @@ -0,0 +1,5 @@ +package com.example.umc9th.config.discord; + +public interface Notifier { + void sendNotification(Exception e, String requestUri); +} diff --git a/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java b/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java index 19186f7..d8245fd 100644 --- a/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java +++ b/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java @@ -2,11 +2,12 @@ import com.example.umc9th.domain.review.dto.ReviewResponseDto; import com.example.umc9th.domain.review.service.ReviewService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode; import com.example.umc9th.global.dto.CursorResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -24,18 +25,18 @@ public class ReviewController { summary = "리뷰 조회", description = "가게별, 별점별로 리뷰를 조회합니다.") @GetMapping - public CursorResponseDto getReviews( + public ApiResponse> getReviews( @RequestParam(required = false) String storeName, @RequestParam(required = false, defaultValue = "0.0") Float minStar, @RequestParam(required = false, defaultValue = "5.0") Float maxStar, @RequestParam(required = false) Long cursorId, @RequestParam(required = false, defaultValue = "10") Integer size) { - return reviewService.getReviews(storeName, + return ApiResponse.onSuccess(GeneralSuccessCode._OK,reviewService.getReviews(storeName, minStar, maxStar, cursorId, - size); + size)); } } diff --git a/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java b/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java index 6acf51c..e3b6a1c 100644 --- a/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java +++ b/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java @@ -2,6 +2,8 @@ import com.example.umc9th.domain.store.dto.StoreResponseDto; import com.example.umc9th.domain.store.service.StoreService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode; import com.example.umc9th.global.dto.CursorResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -33,11 +35,11 @@ public class StoreController { 1-4. 페이징: page + size (커서 기반 페이징 옵션 가능) """) @GetMapping - public CursorResponseDto searchStore(@RequestParam(required = false) String storeName, - @RequestParam(required = false) String region, - @RequestParam(required = false) Long cursorId, - @RequestParam(required = false, defaultValue = "10") Integer size, - @RequestParam String sortType){ - return storeService.searchStore(storeName, region, cursorId, size, sortType); + public ApiResponse> searchStore(@RequestParam(required = false) String storeName, + @RequestParam(required = false) String region, + @RequestParam(required = false) Long cursorId, + @RequestParam(required = false, defaultValue = "10") Integer size, + @RequestParam String sortType){ + return ApiResponse.onSuccess(GeneralSuccessCode._OK,storeService.searchStore(storeName, region, cursorId, size, sortType)); } } diff --git a/src/main/java/com/example/umc9th/domain/user/controller/UserController.java b/src/main/java/com/example/umc9th/domain/user/controller/UserController.java index e149d8b..abc66f5 100644 --- a/src/main/java/com/example/umc9th/domain/user/controller/UserController.java +++ b/src/main/java/com/example/umc9th/domain/user/controller/UserController.java @@ -1,6 +1,9 @@ package com.example.umc9th.domain.user.controller; import com.example.umc9th.domain.user.service.UserService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -13,8 +16,7 @@ public class UserController { private final UserService userService; @DeleteMapping("/{userId}") - public ResponseEntity deleteUser(@PathVariable("userId") Long userId) { - userService.deleteUser(userId); - return ResponseEntity.noContent().build(); + public ApiResponse deleteUser(@PathVariable("userId") Long userId) { + return ApiResponse.onSuccess(GeneralSuccessCode._DELETED, userService.deleteUser(userId)); } } diff --git a/src/main/java/com/example/umc9th/domain/user/service/UserService.java b/src/main/java/com/example/umc9th/domain/user/service/UserService.java index 53b1850..1c61a92 100644 --- a/src/main/java/com/example/umc9th/domain/user/service/UserService.java +++ b/src/main/java/com/example/umc9th/domain/user/service/UserService.java @@ -1,5 +1,5 @@ package com.example.umc9th.domain.user.service; public interface UserService { - void deleteUser(Long userId); + String deleteUser(Long userId); } diff --git a/src/main/java/com/example/umc9th/domain/user/service/UserServiceImpl.java b/src/main/java/com/example/umc9th/domain/user/service/UserServiceImpl.java index f9451bb..57b9a3d 100644 --- a/src/main/java/com/example/umc9th/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/example/umc9th/domain/user/service/UserServiceImpl.java @@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService { @Override @Transactional - public void deleteUser(Long userId) { + public String deleteUser(Long userId) { userMissionRepository.deleteByUserId(userId); inquiryRepository.deleteByUserId(userId); reviewRepository.deleteByUserId(userId); @@ -32,5 +32,7 @@ public void deleteUser(Long userId) { notificationAgreementRepository.deleteByUserId(userId); termAgreementRepository.deleteByUserId(userId); userRepository.softDeleteUser(userId); + + return "유저" + userId.toString() + "이 삭제되었습니다."; } } diff --git a/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java b/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java new file mode 100644 index 0000000..c08e87b --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java @@ -0,0 +1,44 @@ +package com.example.umc9th.global.apiPayload; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess","code","message","result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonProperty("result") + private T result; + + public static ApiResponse onSuccess(BaseSuccessCode code, T result) { + return new ApiResponse<>( + true, code.getCode(), code.getMessage(), result); + } + + public static ApiResponse of(BaseSuccessCode code, T result) { + return new ApiResponse<>( + true, + code.getCode(), + code.getMessage(), + result); + } + + public static ApiResponse onFailure(BaseErrorCode code, T result) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + } +} + diff --git a/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java b/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..56823f7 --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,9 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + HttpStatus getHttpStatus(); + String getCode(); + String getMessage(); +} diff --git a/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java b/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java new file mode 100644 index 0000000..c2aadd4 --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java @@ -0,0 +1,9 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + HttpStatus getHttpStatus(); + String getCode(); + String getMessage(); +} \ No newline at end of file diff --git a/src/main/java/com/example/umc9th/global/apiPayload/code/status/GeneralErrorCode.java b/src/main/java/com/example/umc9th/global/apiPayload/code/status/GeneralErrorCode.java new file mode 100644 index 0000000..c18cdf3 --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/code/status/GeneralErrorCode.java @@ -0,0 +1,39 @@ +package com.example.umc9th.global.apiPayload.code.status; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode { + + // 인증 + USER_NOT_EXISTED(HttpStatus.NOT_FOUND,"USER_400","존재하지 않는 계정입니다"), + UNEXPECTED_TOKEN(HttpStatus.NOT_FOUND,"TOKEN_400","존재하지 않는 토큰 입니다"), + + // 검색 + PAGE_OUT_OF_RANGE(HttpStatus.BAD_REQUEST,"SEARCH_4001","요청한 페이지가 전체 페이지 수를 초과합니다."), + NO_RESULT(HttpStatus.NOT_FOUND,"SEARCH_40002","검색 결과가 없습니다."), + + // 기본 에러 + _BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON_400", "잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON_401", "인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON_403", "금지된 요청입니다."), + _NO_RESULTS_FOUND(HttpStatus.NOT_FOUND, "COMMON_404", "검색 결과가 없습니다."), + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_500", "서버 에러. 관리자에게 문의 바랍니다."), + + MULTIPLE_FIELD_VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "COMMON_001", "입력된 정보에 오류가 있습니다. 필드별 오류 메시지를 참조하세요."), + NO_MATCHING_ERROR_STATUS(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_002", "서버 에러. 일치하는 errorStatus를 찾을 수 없습니다."), + REQUEST_BODY_INVALID(HttpStatus.BAD_REQUEST, "COMMON_003", "요청 본문을 읽을 수 없습니다. 빈 문자열 또는 null이 있는지 확인해주세요."), + INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST, "COMMON_004", "요청한 날짜/시간 형식이 올바르지 않습니다. 형식을 확인해주세요."), + DUPLICATE_UNIQUE_KEY(HttpStatus.CONFLICT, "COMMON_005", "이미 처리된 요청입니다."), + + // s3 사진 첨부 에러 + FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE_001", "파일 업로드에 실패했습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/src/main/java/com/example/umc9th/global/apiPayload/code/status/GeneralSuccessCode.java b/src/main/java/com/example/umc9th/global/apiPayload/code/status/GeneralSuccessCode.java new file mode 100644 index 0000000..91d706e --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/code/status/GeneralSuccessCode.java @@ -0,0 +1,20 @@ +package com.example.umc9th.global.apiPayload.code.status; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode { + _OK(HttpStatus.OK, "COMMON200", "성공입니다."), + _CREATED(HttpStatus.CREATED, "COMMON201", "요청 성공 및 리소스 생성됨"), + _DELETED(HttpStatus.NO_CONTENT, "COMMON204", "삭제가 완료되었습니다."); + + + private final HttpStatus httpStatus; + private final String code; + private final String message; + +} diff --git a/src/main/java/com/example/umc9th/global/apiPayload/exception/ExceptionAdvice.java b/src/main/java/com/example/umc9th/global/apiPayload/exception/ExceptionAdvice.java new file mode 100644 index 0000000..956a281 --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/exception/ExceptionAdvice.java @@ -0,0 +1,168 @@ +package com.example.umc9th.global.apiPayload.exception; + +import com.example.umc9th.config.discord.DiscordNotifierService; +import com.example.umc9th.config.discord.Notifier; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.status.GeneralErrorCode; +import jakarta.servlet.http.HttpServletRequest; +// jakarta 임포트 +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +@RequiredArgsConstructor +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + + private final Notifier notifier; + + @ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint(e, GeneralErrorCode.valueOf(errorMessage), HttpHeaders.EMPTY, request); + } + + // @validation 예외처리 + + /* + @param headers, status, request -> 'Not annotated parameter overrides @NonNullApi parameter' + -> ResponseEntityExceptionHandler를 포함한 스프링 6의 많은 코어 패키지들은 package-info.java 파일을 통해 @NonNullApi 어노테이션이 붙어있습니다. + 이는 "이 패키지의 모든 파라미터와 반환 값은 기본적으로 @NonNull(널 불허)이다"라고 선언하는 것입니다. + */ + @Override + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + Map errors = new LinkedHashMap<>(); + + e.getBindingResult().getFieldErrors() + .forEach(fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs(e, HttpHeaders.EMPTY, GeneralErrorCode.valueOf("_BAD_REQUEST"), request, errors); + } + + @ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + /* + todo : e.printStackTrace() vs. log.error() + +e.printStackTrace()는 운영 환경에서 절대 사용하면 안 됩니다. +이유는 '장애 추적'이 불가능하기 때문입니다. + +1. **기록 대상 (가장 치명적)** + * `e.printStackTrace()`: System.err (표준 에러)로 출력합니다. + -> 콘솔에만 찍힐 뿐, 설정된 로그 파일(/logs/error.log 등)에 기록이 남지 않습니다. + -> 서버 장애 발생 시, 원인을 찾을 방법이 사라집니다. + * `log.error()`: Logback 같은 로깅 프레임워크가 관리합니다. + -> 설정된 파일, DB, 외부 모니터링 툴 등으로 로그를 '반드시' 전송합니다. + +2. **성능 및 제어** + * `e.printStackTrace()`: 동기(Blocking) I/O입니다. 성능 저하를 유발하며 제어가 불가능합니다. + * `log.error()`: 비동기 로깅을 지원하며, yml/properties를 통해 로그 레벨(ERROR, WARN, INFO)을 완벽하게 제어할 수 있습니다. + +3. **문맥 정보** + * `e.printStackTrace()`: 스택 트레이스만 덩그러니 출력됩니다. + * `log.error("500 Error", e)`: "500 Error" 같은 '문맥 메시지'와 함께 스택 트레이스를 기록할 수 있어, 로그 분석이 훨씬 용이합니다. + +결론: 서버에 기록을 남기고 장애를 추적하려면 반드시 log.error()를 사용해야 합니다. +*/ + log.error("500 Error",e); + String requestUri = ((ServletWebRequest)request).getRequest().getRequestURI(); + notifier.sendNotification(e,requestUri); + + return handleExceptionInternalFalse(e, GeneralErrorCode._INTERNAL_SERVER_ERROR.getHttpStatus(), request, e.getMessage()); + } + + // 비즈니스 로직 커스텀 예외처리 + @ExceptionHandler(value = GeneralException.class) + public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) { + return handleExceptionInternal(generalException, + generalException.getCode(), + HttpHeaders.EMPTY, + request); + } + + /** + * + * @param e + * @param reason : 구현체(GeneralErrorCode)로 쓰면 GeneralException.getCode()에서 타입 불일치 -> 객체 지향... + * @param headers : 부모 클래스 메서드 시그니처 일관성 따르기 위함 -> 쓰이지 않음 -> HttpHeaders.EMPTY + * @param request + * @return + */ + private ResponseEntity handleExceptionInternal(Exception e, BaseErrorCode reason, + HttpHeaders headers, HttpServletRequest request) { + + ApiResponse body = ApiResponse.onFailure(reason, reason.getMessage()); +// e.printStackTrace(); + + WebRequest webRequest = new ServletWebRequest(request); + return super.handleExceptionInternal( + e, + body, + headers, + reason.getHttpStatus(), + webRequest + ); + } + + private ResponseEntity handleExceptionInternalFalse(Exception e, + HttpStatus status, WebRequest request, String errorPoint) { + ApiResponse body = ApiResponse.onFailure(GeneralErrorCode._INTERNAL_SERVER_ERROR,errorPoint); + return super.handleExceptionInternal( + e, + body, + HttpHeaders.EMPTY, + status, + request + ); + } + + private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, GeneralErrorCode errorCommonStatus, + WebRequest request, Map errorArgs) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus, errorArgs); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } + + private ResponseEntity handleExceptionInternalConstraint(Exception e, GeneralErrorCode errorCommonStatus, + HttpHeaders headers, WebRequest request) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus, null); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java b/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..64d2a1b --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java @@ -0,0 +1,11 @@ +package com.example.umc9th.global.apiPayload.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException{ + private final BaseErrorCode code; +} diff --git a/src/main/java/com/example/umc9th/global/apiPayload/exception/handler/GlobalHandler.java b/src/main/java/com/example/umc9th/global/apiPayload/exception/handler/GlobalHandler.java new file mode 100644 index 0000000..747321f --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/exception/handler/GlobalHandler.java @@ -0,0 +1,10 @@ +package com.example.umc9th.global.apiPayload.exception.handler; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class GlobalHandler extends GeneralException { + public GlobalHandler(BaseErrorCode code) { + super(code); + } +}