diff --git a/src/main/java/com/klaroworks/runtime/rolematrix/exception/GlobalExceptionHandler.java b/src/main/java/com/klaroworks/runtime/rolematrix/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..48a1290 --- /dev/null +++ b/src/main/java/com/klaroworks/runtime/rolematrix/exception/GlobalExceptionHandler.java @@ -0,0 +1,74 @@ +package com.klaroworks.runtime.rolematrix.exception; + +import com.klaroworks.runtime.rolematrix.dto.ErrorResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +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.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** 전역 예외 처리 핸들러 */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(DomainException.class) + public ResponseEntity handleDomainException(DomainException ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + MDC.put("traceId", traceId); + log.warn("Domain exception: {} [traceId={}]", ex.getMessage(), traceId, ex); + return ResponseEntity.status(ex.getDefaultStatus()) + .body(ErrorResponse.of(ex.getDefaultStatus().value(), ex.getErrorCode(), ex.getMessage(), request.getRequestURI(), traceId)); + } + + @ExceptionHandler(ValidationException.class) + public ResponseEntity handleValidationException(ValidationException ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + log.warn("Validation exception: {} [traceId={}]", ex.getMessage(), traceId); + return ResponseEntity.badRequest() + .body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), ex.getErrorCode(), ex.getMessage(), request.getRequestURI(), traceId)); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + Map fieldErrors = new HashMap<>(); + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + fieldErrors.put(error.getField(), error.getDefaultMessage()); + } + log.warn("Bean validation failed: {} [traceId={}]", fieldErrors, traceId); + return ResponseEntity.badRequest() + .body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), "VAL_BEAN_VALIDATION", "입력 검증에 실패했습니다: " + fieldErrors, request.getRequestURI(), traceId)); + } + + @ExceptionHandler(InfrastructureException.class) + public ResponseEntity handleInfrastructureException(InfrastructureException ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + log.error("Infrastructure exception: {} [traceId={}]", ex.getMessage(), traceId, ex); + return ResponseEntity.status(ex.getDefaultStatus()) + .body(ErrorResponse.of(ex.getDefaultStatus().value(), ex.getErrorCode(), ex.getMessage(), request.getRequestURI(), traceId)); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleUnknownException(Exception ex, HttpServletRequest request) { + String traceId = getOrGenerateTraceId(); + log.error("Unexpected exception: {} [traceId={}]", ex.getMessage(), traceId, ex); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ErrorResponse.of(500, "SYS_INTERNAL_ERROR", "예상치 못한 오류가 발생했습니다. 관리자에게 문의하세요.", request.getRequestURI(), traceId)); + } + + private String getOrGenerateTraceId() { + String existing = MDC.get("traceId"); + return existing != null ? existing : UUID.randomUUID().toString().substring(0, 8); + } +}