Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: ExceptionHandler 예외 메시지 구체화 및 번쩍 관련 로직 수정 #537

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,112 +17,150 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;

@RestControllerAdvice
@Slf4j
public class ControllerExceptionAdvice {

/**
* 400 Bad Request
* 클라이언트에서 잘못된 요청을 보냈을 때 처리하는 예외들
*/
@ExceptionHandler(BaseException.class)
public ResponseEntity<ExceptionResponse> handleGlobalException(BaseException e) {
log.warn("{}", e.getMessage());
public ResponseEntity<ExceptionResponse> handleBaseException(BaseException e) {
log.warn("기본 예외 발생: {}", e.getMessage());
return ResponseEntity.status(e.getStatusCode())
.body(ExceptionResponse.fail(e.getErrorCode()));
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ExceptionResponse> handleMissingParameter(
MissingServletRequestParameterException e) {
log.warn("{}", e.getMessage());
public ResponseEntity<ExceptionResponse> handleMissingParameter(MissingServletRequestParameterException e) {
log.warn("누락된 요청 파라미터: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode()));
ErrorStatus.MISSING_REQUEST_PARAMETER.getErrorCode(),
String.format("누락된 요청 파라미터: %s", e.getParameterName())));
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ExceptionResponse> handleIllegalArgument(IllegalArgumentException e) {
log.warn("{}", e.getMessage());
log.warn("잘못된 입력 값: {}", e.getMessage());
String errorDetails = String.format("예외 발생 위치: %s.%s - 입력 값: %s",
e.getStackTrace()[0].getClassName(),
e.getStackTrace()[0].getMethodName(),
e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.INVALID_ARGUMENT.getErrorCode(),
errorDetails));
}

@ExceptionHandler(ConstraintViolationException.class) // @Notnull 오류
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ExceptionResponse> handleConstraintViolationException(ConstraintViolationException e) {
log.warn("{}", e.getMessage());
log.warn("제약 조건 위반 발생: {}", e.getMessage());

StringBuilder violationMessages = new StringBuilder();
for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
violationMessages.append(String.format("필드: %s, 메시지: %s; ",
violation.getPropertyPath(), violation.getMessage()));
}

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.CONSTRAINT_VIOLATION.getErrorCode(),
violationMessages.toString()));
}

@ExceptionHandler(DataIntegrityViolationException.class) // null value 오류
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ExceptionResponse> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
log.warn("{}", e.getMessage());
log.warn("데이터 무결성 위반: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.DATA_INTEGRITY_VIOLATION.getErrorCode(),
e.getMostSpecificCause().getMessage()));
}

@ExceptionHandler(HttpMessageNotReadableException.class) // null value 오류
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ExceptionResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.warn("{}", e.getMessage());
log.warn("읽을 수 없는 요청 메시지: {}", e.getMessage());
String errorDetails = String.format("잘못된 JSON 요청: %s", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.MESSAGE_NOT_READABLE.getErrorCode(),
errorDetails));
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ExceptionResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.warn("{}", e.getMessage());
log.warn("메서드 인자 검증 실패: {}", e.getMessage());
StringBuilder errorDetails = new StringBuilder("유효성 검증 실패: ");
e.getBindingResult().getFieldErrors().forEach(fieldError ->
errorDetails.append(
String.format("필드: '%s', 오류: '%s' ", fieldError.getField(), fieldError.getDefaultMessage())
)
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.METHOD_ARGUMENT_NOT_VALID.getErrorCode(),
errorDetails.toString()));
}

/**
* path variable errors
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ExceptionResponse> handleMethodArgumentTypeMismatchException(
MethodArgumentTypeMismatchException e) {
log.warn("{}", e.getMessage());
log.warn("인자 타입 불일치: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.ARGUMENT_TYPE_MISMATCH.getErrorCode(),
String.format("인자 타입 불일치: '%s', 잘못된 값: '%s'", e.getName(), e.getValue())));
}

@ExceptionHandler(MissingPathVariableException.class)
public ResponseEntity<ExceptionResponse> handleMissingPathVariableException(
MissingPathVariableException e) {
log.warn("{}", e.getMessage());
public ResponseEntity<ExceptionResponse> handleMissingPathVariableException(MissingPathVariableException e) {
log.warn("누락된 경로 변수: {}", e.getVariableName());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.MISSING_PATH_VARIABLE.getErrorCode(),
String.format("누락된 경로 변수: %s", e.getVariableName())));
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ExceptionResponse> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
log.warn("{}", e.getMessage());
@ExceptionHandler(IOException.class)
public ResponseEntity<ExceptionResponse> handleIOException(IOException e) {
log.warn("입출력 오류: {}", e.getMessage());
String errorMessage = (e.getCause() != null) ? e.getCause().getMessage() : e.getMessage();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ExceptionResponse.fail(
ErrorStatus.INVALID_INPUT_VALUE.getErrorCode()));
ErrorStatus.IO_EXCEPTION.getErrorCode(),
String.format("입출력 오류 발생: %s", errorMessage)));
}

@ExceptionHandler(IOException.class)
public ResponseEntity<ExceptionResponse> handleIOException(IOException e) {
log.warn("{}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
/**
* 405 Method Not Allowed
* 지원되지 않는 HTTP 메서드 처리
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ExceptionResponse> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException e) {
log.warn("지원되지 않는 요청 메서드: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
.body(ExceptionResponse.fail(
ErrorStatus.IO_EXCEPTION.getErrorCode()));
ErrorStatus.METHOD_NOT_SUPPORTED.getErrorCode(),
String.format("지원되지 않는 메서드: %s", e.getMethod())));
}

/**
* 500 Internal Server Error
* 예상치 못한 예외 처리
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionResponse> handleException(Exception e) {
log.error("", e);
log.error("예기치 않은 예외 발생: ", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ExceptionResponse.fail(
ErrorStatus.INTERNAL_SERVER_ERROR.getErrorCode()));
ErrorStatus.INTERNAL_SERVER_ERROR.getErrorCode(),
"예기치 않은 오류가 발생했습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
package org.sopt.makers.crew.main.global.exception;

import org.springframework.http.HttpStatus;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BaseException extends RuntimeException {

HttpStatus httpStatus;
String errorCode;
private HttpStatus httpStatus;
private String errorCode;

public BaseException(HttpStatus httpStatus) {
super();
this.httpStatus = httpStatus;
}
public BaseException(HttpStatus httpStatus) {
super();
this.httpStatus = httpStatus;
}

public BaseException(HttpStatus httpStatus, String errorCode) {
super(errorCode);
this.httpStatus = httpStatus;
this.errorCode = errorCode;
}
public BaseException(HttpStatus httpStatus, String errorCode) {
super(errorCode);
this.httpStatus = httpStatus;
this.errorCode = errorCode;
}

public int getStatusCode() {
return this.httpStatus.value();
}
public int getStatusCode() {
return this.httpStatus.value();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ public enum ErrorStatus {
NO_CONTENT_EXCEPTION("참여한 모임이 없습니다."),

/**
* 400 BAD_REQUEST
* 400 BAD_REQUEST - 비즈니스 로직 관련 에러
*/
VALIDATION_EXCEPTION("CF-001"),
VALIDATION_REQUEST_MISSING_EXCEPTION("요청값이 입력되지 않았습니다."),
INVALID_INPUT_VALUE("요청값이 올바르지 않습니다. : "),
INVALID_INPUT_VALUE_FILTER("요청값 또는 토큰이 올바르지 않습니다."),
NOT_FOUND_MEETING("모임이 없습니다."),
NOT_FOUND_POST("존재하지 않는 게시글입니다."),
Expand All @@ -40,6 +39,19 @@ public enum ErrorStatus {
NOT_ALLOW_MEETING_APPLY("허용되지 않는 모임 신청입니다."),
IO_EXCEPTION("파일 입출력 오류가 발생했습니다."),

/**
* 400 BAD_REQUEST - 유효성 검사 관련 에러
*/
MISSING_REQUEST_PARAMETER("필수 요청 파라미터가 누락되었습니다."),
METHOD_ARGUMENT_NOT_VALID("메서드 인자 유효성 검사가 실패했습니다."),
ARGUMENT_TYPE_MISMATCH("인자 타입이 일치하지 않습니다."),
MISSING_PATH_VARIABLE("필수 경로 변수가 누락되었습니다."),
INVALID_ARGUMENT("잘못된 인자입니다."),
CONSTRAINT_VIOLATION("제약 조건을 위반했습니다."),
DATA_INTEGRITY_VIOLATION("데이터 무결성이 위반되었습니다."),
MESSAGE_NOT_READABLE("읽을 수 없는 메시지 형식입니다."),
INVALID_INPUT_VALUE("요청값이 올바르지 않습니다. : "),

/**
* 401 UNAUTHORIZED
*/
Expand All @@ -51,6 +63,11 @@ public enum ErrorStatus {
*/
FORBIDDEN_EXCEPTION("권한이 없습니다."),

/**
* 405 METHOD_NOT_ALLOWED
*/
METHOD_NOT_SUPPORTED("지원되지 않는 HTTP 메서드입니다."),

/**
* 500 SERVER_ERROR
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.sopt.makers.crew.main.global.exception;

import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.Builder;
import lombok.Getter;

Expand All @@ -9,11 +10,19 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ExceptionResponse {

private final String errorCode;
private final String errorCode;
private final String message;

public static ExceptionResponse fail(String errorCode) {
return ExceptionResponse.builder()
.errorCode(errorCode)
.build();
}

public static ExceptionResponse fail(String errorCode) {
return ExceptionResponse.builder()
.errorCode(errorCode)
.build();
}
public static ExceptionResponse fail(String errorCode, String message) {
return ExceptionResponse.builder()
.errorCode(errorCode)
.message(message)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

@Schema(name = "LightningV2CreateLightningBodyDto", description = "번쩍 모임 생성 및 수정 request body dto")
public record LightningV2CreateLightningBodyDto(
@Schema(description = "번쩍 모임 생성 및 수정 request body")
@NotNull
@Valid
LightningV2CreateLightningBodyWithoutWelcomeMessageDto lightningBody,

@Schema(example = "[\"YB 환영\", \"OB 환영\"]", description = "환영 메시지 타입 리스트")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

Expand Down Expand Up @@ -37,12 +39,13 @@ public record LightningV2CreateLightningBodyWithoutWelcomeMessageDto(
String lightningPlace,

@Schema(example = "1", description = "최소 모집 인원")
@Size(min = 1)
@Min(1)
@NotNull
Integer minimumCapacity,

@Schema(example = "5", description = "최대 모집 인원")
@Size(max = 999)
@Min(1)
@Max(999)
@NotNull
Integer maximumCapacity,

Expand Down
Loading