diff --git a/spring-runtime/spring-runtime-core/src/main/java/com/runtime/core/exception/GlobalExceptionHandler.java b/spring-runtime/spring-runtime-core/src/main/java/com/runtime/core/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0fb26af --- /dev/null +++ b/spring-runtime/spring-runtime-core/src/main/java/com/runtime/core/exception/GlobalExceptionHandler.java @@ -0,0 +1,95 @@ +package com.runtime.core.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 전역 예외 처리 핸들러 + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex) { + ErrorResponse error = ErrorResponse.builder() + .timestamp(LocalDateTime.now()) + .status(ex.getStatus().value()) + .error(ex.getErrorCode()) + .message(ex.getMessage()) + .build(); + return ResponseEntity.status(ex.getStatus()).body(error); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach(error -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + ErrorResponse error = ErrorResponse.builder() + .timestamp(LocalDateTime.now()) + .status(HttpStatus.BAD_REQUEST.value()) + .error("VALIDATION_ERROR") + .message("입력 검증에 실패했습니다") + .validationErrors(errors) + .build(); + return ResponseEntity.badRequest().body(error); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception ex) { + ErrorResponse error = ErrorResponse.builder() + .timestamp(LocalDateTime.now()) + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .error("INTERNAL_ERROR") + .message("예상치 못한 오류가 발생했습니다") + .build(); + return ResponseEntity.internalServerError().body(error); + } +} + +/** + * 비즈니스 예외 기본 클래스 + */ +class BusinessException extends RuntimeException { + private final HttpStatus status; + private final String errorCode; + + public BusinessException(String message, HttpStatus status, String errorCode) { + super(message); + this.status = status; + this.errorCode = errorCode; + } + + public BusinessException(String message, HttpStatus status) { + this(message, status, "BUSINESS_ERROR"); + } + + public HttpStatus getStatus() { return status; } + public String getErrorCode() { return errorCode; } +} + +/** + * 표준 에러 응답 DTO + */ +@lombok.Data +@lombok.Builder +@lombok.AllArgsConstructor +@lombok.NoArgsConstructor +class ErrorResponse { + private LocalDateTime timestamp; + private int status; + private String error; + private String message; + private Map validationErrors; +}