diff --git a/goorm/build.gradle b/goorm/build.gradle index 71c8e62..18566a9 100644 --- a/goorm/build.gradle +++ b/goorm/build.gradle @@ -9,7 +9,7 @@ version = '0.0.1-SNAPSHOT' java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } diff --git a/goorm/src/main/java/study/goorm/GoormApplication.java b/goorm/src/main/java/study/goorm/GoormApplication.java deleted file mode 100644 index 22d95f9..0000000 --- a/goorm/src/main/java/study/goorm/GoormApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package study.goorm; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; - -@SpringBootApplication -@EnableJpaAuditing -public class GoormApplication { - - public static void main(String[] args) { - SpringApplication.run(GoormApplication.class, args); - } - -} diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/entity/MemberLike.java b/goorm/src/main/java/study/goorm/domain/history/domain/entity/MemberLike.java index a8131e5..d244cf0 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/entity/MemberLike.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/entity/MemberLike.java @@ -11,6 +11,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"member_id", "history_id"})) + public class MemberLike extends BaseEntity { @Id diff --git a/goorm/src/main/java/study/goorm/domain/model/TestException.java b/goorm/src/main/java/study/goorm/domain/model/TestException.java new file mode 100644 index 0000000..6091937 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/model/TestException.java @@ -0,0 +1,11 @@ +package study.goorm.domain.model; + +import study.goorm.global.error.code.BaseErrorCode; +import study.goorm.global.exception.GeneralException; + +public class TestException extends GeneralException { + + public TestException(BaseErrorCode code) { + super(code); + } +} diff --git a/goorm/src/main/java/study/goorm/domain/model/api/TestController.java b/goorm/src/main/java/study/goorm/domain/model/api/TestController.java new file mode 100644 index 0000000..4a06960 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/model/api/TestController.java @@ -0,0 +1,27 @@ +package study.goorm.domain.model.api; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import study.goorm.domain.model.TestException; +import study.goorm.global.common.response.BaseResponse; +import study.goorm.global.error.code.status.ErrorStatus; +import study.goorm.global.error.code.status.SuccessStatus; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/test") +public class TestController { + + @GetMapping("/execute") + public BaseResponse test(@RequestParam String error){ + + if(error.equals("yes")){ + throw new TestException(ErrorStatus._BAD_REQUEST); + } + + return BaseResponse.onSuccess(SuccessStatus.OK,null); + } +} \ No newline at end of file diff --git a/goorm/src/main/java/study/goorm/global/common/response/BaseResponse.java b/goorm/src/main/java/study/goorm/global/common/response/BaseResponse.java new file mode 100644 index 0000000..0305bd4 --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/common/response/BaseResponse.java @@ -0,0 +1,41 @@ +package study.goorm.global.common.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import study.goorm.global.error.code.BaseCode; +import study.goorm.global.error.code.BaseErrorCode; + + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class BaseResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + private final String code; + private final String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private T result; + + + public static BaseResponse onSuccess(BaseCode code, T result) { + return new BaseResponse<>( + true, + code.getCode(), + code.getMessage(), + result); + } + + public static BaseResponse onFailure(BaseErrorCode code, T result) { + return new BaseResponse<>( + false, + code.getCode(), + code.getMessage(), + result); + } + +} \ No newline at end of file diff --git a/goorm/src/main/java/study/goorm/global/error/code/BaseCode.java b/goorm/src/main/java/study/goorm/global/error/code/BaseCode.java new file mode 100644 index 0000000..85e8b0e --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/error/code/BaseCode.java @@ -0,0 +1,10 @@ +package study.goorm.global.error.code; + +public interface BaseCode { + + String getCode(); + + String getMessage(); + + ReasonDTO getReasonHttpStatus(); +} \ No newline at end of file diff --git a/goorm/src/main/java/study/goorm/global/error/code/BaseErrorCode.java b/goorm/src/main/java/study/goorm/global/error/code/BaseErrorCode.java new file mode 100644 index 0000000..56fb63f --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/error/code/BaseErrorCode.java @@ -0,0 +1,10 @@ +package study.goorm.global.error.code; + +public interface BaseErrorCode { + + String getCode(); + + String getMessage(); + + ErrorReasonDTO getReasonHttpStatus(); +} diff --git a/goorm/src/main/java/study/goorm/global/error/code/ErrorReasonDTO.java b/goorm/src/main/java/study/goorm/global/error/code/ErrorReasonDTO.java new file mode 100644 index 0000000..2c34b1b --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/error/code/ErrorReasonDTO.java @@ -0,0 +1,14 @@ +package study.goorm.global.error.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ErrorReasonDTO { + private final HttpStatus httpStatus; + private final String code; + private final String message; + private final boolean isSuccess; +} diff --git a/goorm/src/main/java/study/goorm/global/error/code/ReasonDTO.java b/goorm/src/main/java/study/goorm/global/error/code/ReasonDTO.java new file mode 100644 index 0000000..181737f --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/error/code/ReasonDTO.java @@ -0,0 +1,15 @@ +package study.goorm.global.error.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ReasonDTO { + + private final HttpStatus httpStatus; + private final boolean isSuccess; + private final String code; + private final String message; +} diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java new file mode 100644 index 0000000..f976e91 --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -0,0 +1,42 @@ +package study.goorm.global.error.code.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import study.goorm.global.error.code.BaseErrorCode; +import study.goorm.global.error.code.ErrorReasonDTO; + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + + // 기본 에러 + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build(); + } +} \ No newline at end of file diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java new file mode 100644 index 0000000..71d54d6 --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -0,0 +1,41 @@ +package study.goorm.global.error.code.status; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import study.goorm.global.error.code.BaseCode; +import study.goorm.global.error.code.ReasonDTO; + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseCode { + + //Common + OK(HttpStatus.OK, "COMMON_200", "성공입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public ReasonDTO getReasonHttpStatus() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .httpStatus(httpStatus) + .build(); + } +} + + diff --git a/goorm/src/main/java/study/goorm/global/exception/GeneralException.java b/goorm/src/main/java/study/goorm/global/exception/GeneralException.java new file mode 100644 index 0000000..5419820 --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/exception/GeneralException.java @@ -0,0 +1,17 @@ +package study.goorm.global.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import study.goorm.global.error.code.BaseErrorCode; +import study.goorm.global.error.code.ErrorReasonDTO; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException { + + private BaseErrorCode code; + + public ErrorReasonDTO getErrorReasonHttpStatus() { + return this.code.getReasonHttpStatus(); + } +} diff --git a/goorm/src/main/java/study/goorm/global/exception/GlobalExceptionAdvice.java b/goorm/src/main/java/study/goorm/global/exception/GlobalExceptionAdvice.java new file mode 100644 index 0000000..5f5fcf7 --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/exception/GlobalExceptionAdvice.java @@ -0,0 +1,168 @@ +package study.goorm.global.exception; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.TypeMismatchException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +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.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import study.goorm.global.common.response.BaseResponse; +import study.goorm.global.error.code.BaseErrorCode; +import study.goorm.global.error.code.status.ErrorStatus; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class GlobalExceptionAdvice extends ResponseEntityExceptionHandler { + + @Override + protected ResponseEntity handleTypeMismatch( + TypeMismatchException e, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String errorMessage = e.getPropertyName() + ": 올바른 값이 아닙니다."; + + return handleExceptionInternalMessage(e, headers, request, errorMessage); + } + + @Override + protected ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException e, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String errorMessage = e.getParameterName() + ": 올바른 값이 아닙니다."; + + return handleExceptionInternalMessage(e, headers, request, errorMessage); + } + + @ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = + e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow( + () -> + new RuntimeException( + "ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint( + e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY, request); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException e, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + Map errors = new LinkedHashMap<>(); + + e.getBindingResult().getFieldErrors().stream() + .forEach( + fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage; + try { + errorMessage = Optional.ofNullable(ErrorStatus.valueOf(fieldError.getDefaultMessage()).getMessage()).orElse(""); + } catch (IllegalArgumentException ex) { + errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + } + errors.merge( + fieldName, + errorMessage, + (existingErrorMessage, newErrorMessage) -> + existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs( + e, HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"), request, errors); + } + + @ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + e.printStackTrace(); + + return handleExceptionInternalFalse( + e, + ErrorStatus._INTERNAL_SERVER_ERROR, + HttpHeaders.EMPTY, + ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(), + request, + e.getMessage()); + } + + @ExceptionHandler(value = GeneralException.class) + public ResponseEntity onThrowException( + GeneralException generalException, HttpServletRequest request) { + return handleExceptionInternal(generalException, generalException.getCode(), null, request); + } + + private ResponseEntity handleExceptionInternal( + Exception e, BaseErrorCode code, HttpHeaders headers, HttpServletRequest request) { + + BaseResponse body = + BaseResponse.onFailure(code, null); + + WebRequest webRequest = new ServletWebRequest(request); + return super.handleExceptionInternal(e, body, headers, code.getReasonHttpStatus().getHttpStatus(), webRequest); + } + + private ResponseEntity handleExceptionInternalFalse( + Exception e, + ErrorStatus errorCommonStatus, + HttpHeaders headers, + HttpStatus status, + WebRequest request, + String errorPoint) { + BaseResponse body = + BaseResponse.onFailure(errorCommonStatus, errorPoint); + return super.handleExceptionInternal(e, body, headers, status, request); + } + + private ResponseEntity handleExceptionInternalArgs( + Exception e, + HttpHeaders headers, + ErrorStatus errorCommonStatus, + WebRequest request, + Map errorArgs) { + BaseResponse body = + BaseResponse.onFailure(errorCommonStatus, errorArgs); + return super.handleExceptionInternal( + e, body, headers, errorCommonStatus.getHttpStatus(), request); + } + + private ResponseEntity handleExceptionInternalConstraint( + Exception e, ErrorStatus errorCommonStatus, HttpHeaders headers, WebRequest request) { + BaseResponse body = + BaseResponse.onFailure(errorCommonStatus, null); + return super.handleExceptionInternal( + e, body, headers, errorCommonStatus.getHttpStatus(), request); + } + + private ResponseEntity handleExceptionInternalMessage( + Exception e, HttpHeaders headers, WebRequest request, String errorMessage) { + ErrorStatus errorStatus = ErrorStatus._BAD_REQUEST; + BaseResponse body = + BaseResponse.onFailure(errorStatus, errorMessage); + + return super.handleExceptionInternal( + e, body, headers, errorStatus.getHttpStatus(), request); + } +}