diff --git a/src/main/java/cmf/commitField/global/error/ErrorCode.java b/src/main/java/cmf/commitField/global/error/ErrorCode.java new file mode 100644 index 0000000..5bf8517 --- /dev/null +++ b/src/main/java/cmf/commitField/global/error/ErrorCode.java @@ -0,0 +1,39 @@ +package cmf.commitField.global.error; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ErrorCode { + + // Common + INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "제공된 입력 값이 유효하지 않습니다."), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 요청 방식입니다."), + ENTITY_NOT_FOUND(HttpStatus.BAD_REQUEST, "요청한 엔티티를 찾을 수 없습니다."), + INVALID_TYPE_VALUE(HttpStatus.BAD_REQUEST, "제공된 값의 타입이 유효하지 않습니다."), + ERROR_PARSING_JSON_RESPONSE(HttpStatus.BAD_REQUEST, "JSON 응답을 파싱하는 중 오류가 발생했습니다."), + MISSING_INPUT_VALUE(HttpStatus.BAD_REQUEST, "필수 입력 값이 누락되었습니다."), + DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스 오류가 발생했습니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."), + + // User + NOT_FOUND_USER(HttpStatus.BAD_REQUEST, "해당 유저가 존재하지 않습니다"), + PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."), + + // Auth + ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "인증되지 않은 유저입니다."), + SC_FORBIDDEN(HttpStatus.UNAUTHORIZED, "권한이 없는 유저입니다."), + INVALID_JWT_SIGNATURE(HttpStatus.UNAUTHORIZED, "서명 검증에 실패했습니다." ), + ILLEGAL_REGISTRATION_ID(HttpStatus.BAD_REQUEST,"해당 사항이 없는 로그인 경로입니다."), + + TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, "토큰이 만료되었습니다."), + + // member + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다"); + + + private final HttpStatus httpStatus; + private final String message; + } diff --git a/src/main/java/cmf/commitField/global/error/ErrorResponse.java b/src/main/java/cmf/commitField/global/error/ErrorResponse.java new file mode 100644 index 0000000..ebe15ef --- /dev/null +++ b/src/main/java/cmf/commitField/global/error/ErrorResponse.java @@ -0,0 +1,32 @@ +package cmf.commitField.global.error; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDateTime; + +@Getter +public class ErrorResponse { + private final String timestamp; + private final String error; + private final String message; + + @Builder + public ErrorResponse(String timestamp, String error, String message){ + this.timestamp = timestamp; + this.error = error; + this.message = message; + } + public static ResponseEntity toResponseEntity(ErrorCode errorCode){ + return ResponseEntity + .status(errorCode.getHttpStatus()) + .body(ErrorResponse.builder() + .timestamp(LocalDateTime.now().toString()) + .error(errorCode.getHttpStatus().name()) + .message(errorCode.getMessage()) + .build() + ); + + } +} \ No newline at end of file diff --git a/src/main/java/cmf/commitField/global/exception/CustomException.java b/src/main/java/cmf/commitField/global/exception/CustomException.java new file mode 100644 index 0000000..635eecd --- /dev/null +++ b/src/main/java/cmf/commitField/global/exception/CustomException.java @@ -0,0 +1,11 @@ +package cmf.commitField.global.exception; + +import cmf.commitField.global.error.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class CustomException extends RuntimeException { + private final ErrorCode errorCode; +} diff --git a/src/main/java/cmf/commitField/global/exception/ExceptionControllerAdvice.java b/src/main/java/cmf/commitField/global/exception/ExceptionControllerAdvice.java new file mode 100644 index 0000000..2238706 --- /dev/null +++ b/src/main/java/cmf/commitField/global/exception/ExceptionControllerAdvice.java @@ -0,0 +1,69 @@ +package cmf.commitField.global.exception; + +import cmf.commitField.global.error.ErrorCode; +import cmf.commitField.global.error.ErrorResponse; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import java.sql.SQLIntegrityConstraintViolationException; + +@Slf4j +@RestControllerAdvice +public class ExceptionControllerAdvice { + + @ExceptionHandler(value = {ConstraintViolationException.class, MethodArgumentNotValidException.class, MethodArgumentTypeMismatchException.class}) + public ResponseEntity handleValidationExceptions(Exception e) { + log.error("Validation Exception: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.INVALID_INPUT_VALUE); + } + + @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) + public ResponseEntity handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { + log.error("HTTP Method Not Supported: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.METHOD_NOT_ALLOWED); + } + + @ExceptionHandler(value = CustomException.class) + protected ResponseEntity handleCustomException(CustomException e) { + log.error("Custom Exception: {}", e.getErrorCode(), e); + return ErrorResponse.toResponseEntity(e.getErrorCode()); + } + + @ExceptionHandler(value = NullPointerException.class) + public ResponseEntity handleNullPointerException(NullPointerException e) { + log.error("Null Pointer Exception: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.MISSING_INPUT_VALUE); + } + + @ExceptionHandler(value = MissingServletRequestParameterException.class) + public ResponseEntity handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { + log.error("Missing Request Parameter: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.MISSING_INPUT_VALUE); + } + + @ExceptionHandler(value = HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.error("Message Not Readable: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.INVALID_INPUT_VALUE); + } + + @ExceptionHandler(value = SQLIntegrityConstraintViolationException.class) + public ResponseEntity handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) { + log.error("SQL Integrity Constraint Violation: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.DATABASE_ERROR); + } + + @ExceptionHandler(value = Exception.class) + public ResponseEntity handleGeneralException(Exception e) { + log.error("Unhandled Exception: {}", e.getMessage(), e); + return ErrorResponse.toResponseEntity(ErrorCode.INTERNAL_SERVER_ERROR); + } +} \ No newline at end of file diff --git a/src/main/java/cmf/commitField/global/globalDto/GlobalResponse.java b/src/main/java/cmf/commitField/global/globalDto/GlobalResponse.java new file mode 100644 index 0000000..0a2a2ec --- /dev/null +++ b/src/main/java/cmf/commitField/global/globalDto/GlobalResponse.java @@ -0,0 +1,51 @@ +package cmf.commitField.global.globalDto; + +import cmf.commitField.global.error.ErrorCode; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class GlobalResponse { + + private final LocalDateTime timestamp; // 응답 생성 시간 + private final int statusCode; // HTTP 상태 코드 + private final String message; // 응답 메시지 + private final T data; // 응답 데이터 (성공 시 데이터, 실패 시 추가 정보) + + // 성공 응답 생성자 + private GlobalResponse(GlobalResponseCode responseCode, T data) { + this.timestamp = LocalDateTime.now(); + this.statusCode = responseCode.getCode(); + this.message = responseCode.getMessage(); + this.data = data; + } + + // 에러 응답 생성자 + private GlobalResponse(ErrorCode errorCode, T data) { + this.timestamp = LocalDateTime.now(); + this.statusCode = errorCode.getHttpStatus().value(); + this.message = errorCode.getMessage(); + this.data = data; + } + + // 성공 응답 (데이터 포함) + public static GlobalResponse success(T data) { + return new GlobalResponse<>(GlobalResponseCode.OK, data); + } + + // 성공 응답 (데이터 없음) + public static GlobalResponse success() { + return success(null); + } + + // 에러 응답 (데이터 포함) + public static GlobalResponse error(ErrorCode errorCode, T data) { + return new GlobalResponse<>(errorCode, data); + } + + // 에러 응답 (데이터 없음) + public static GlobalResponse error(ErrorCode errorCode) { + return error(errorCode, null); + } +} diff --git a/src/main/java/cmf/commitField/global/globalDto/GlobalResponseCode.java b/src/main/java/cmf/commitField/global/globalDto/GlobalResponseCode.java new file mode 100644 index 0000000..ba2c3e4 --- /dev/null +++ b/src/main/java/cmf/commitField/global/globalDto/GlobalResponseCode.java @@ -0,0 +1,17 @@ +package cmf.commitField.global.globalDto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum GlobalResponseCode { + + OK(200, "요청이 성공하였습니다."), + BAD_REQUEST(400, "잘못된 요청입니다."), + NOT_FOUND(404, "찾을 수 없습니다."), + INTERNAL_SERVER_ERROR(500, "서버 내부 오류가 발생하였습니다."); + + private final int code; // HTTP 상태 코드 + private final String message; // 응답 메시지 +} diff --git a/src/main/java/cmf/commitField/global/jpa/BaseEntity.java b/src/main/java/cmf/commitField/global/jpa/BaseEntity.java index 0d12eb9..49c050f 100644 --- a/src/main/java/cmf/commitField/global/jpa/BaseEntity.java +++ b/src/main/java/cmf/commitField/global/jpa/BaseEntity.java @@ -1,4 +1,26 @@ package cmf.commitField.global.jpa; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDateTime; + +import static jakarta.persistence.GenerationType.IDENTITY; + public class BaseEntity { -} + @Id + @GeneratedValue(strategy = IDENTITY) + @EqualsAndHashCode.Include + private Long id; + + @CreatedDate + @Getter + private LocalDateTime createdAt; + + @CreatedDate + @Getter + private LocalDateTime modifiedAt; +} \ No newline at end of file