From 6a81dae90bddcd31114690bbd79b58a2bb303f27 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 04:35:47 +0000 Subject: [PATCH] =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=A7=81=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=EA=B3=84=ED=9A=8D=20=EC=88=98=EB=A6=BD=20(iss-cb35?= =?UTF-8?q?afc33813)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 spring-runtime/spring-runtime-core/src/main/java/com/runtime/core/exception/GlobalExceptionHandler.java 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; +}