diff --git a/BEConfig b/BEConfig index e5c7ac1..785c081 160000 --- a/BEConfig +++ b/BEConfig @@ -1 +1 @@ -Subproject commit e5c7ac1d96a515288fd6d66dd27a0326411917a6 +Subproject commit 785c0814ce55a5e8458a53c994a5846985967540 diff --git a/src/main/java/com/example/umc/domain/review/controller/ReviewController.java b/src/main/java/com/example/umc/domain/review/controller/ReviewController.java index c96f210..3ab24b8 100644 --- a/src/main/java/com/example/umc/domain/review/controller/ReviewController.java +++ b/src/main/java/com/example/umc/domain/review/controller/ReviewController.java @@ -3,6 +3,7 @@ import com.example.umc.domain.review.dto.ReviewResponseDto; import com.example.umc.domain.review.service.ReviewService; import com.example.umc.global.apiPayload.ApiResponse; +import com.example.umc.global.apiPayload.code.status.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -32,6 +33,8 @@ public ApiResponse> getMyReviews( .stream() .map(ReviewResponseDto::from) .collect(Collectors.toList()); - return ApiResponse.onSuccess(reviews); + + SuccessStatus code = SuccessStatus._OK; + return ApiResponse.onSuccess(code, reviews); } } diff --git a/src/main/java/com/example/umc/domain/test/controller/TestController.java b/src/main/java/com/example/umc/domain/test/controller/TestController.java new file mode 100644 index 0000000..72d0b36 --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/controller/TestController.java @@ -0,0 +1,40 @@ +package com.example.umc.domain.test.controller; + +import com.example.umc.domain.test.converter.TestConverter; +import com.example.umc.domain.test.dto.TestResDTO; +import com.example.umc.domain.test.service.TestQueryService; +import com.example.umc.global.apiPayload.ApiResponse; +import com.example.umc.global.apiPayload.code.status.SuccessStatus; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/temp") +public class TestController { + + private final TestQueryService testQueryService; + + @GetMapping("/test") + public ApiResponse test() { + // 응답 코드 정의 + SuccessStatus code = SuccessStatus._OK; + + return ApiResponse.onSuccess( + code, + TestConverter.toTestingDTO("This is Test!")); + } + + // 예외 상황 + @GetMapping("/exception") + public ApiResponse exception(@RequestParam Long flag) { + testQueryService.checkFlag(flag); + + // 응답 코드 정의 + SuccessStatus code = SuccessStatus._OK; + return ApiResponse.onSuccess(code, TestConverter.toExceptionDTO("This is Test!")); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc/domain/test/converter/TestConverter.java b/src/main/java/com/example/umc/domain/test/converter/TestConverter.java new file mode 100644 index 0000000..a929c91 --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/converter/TestConverter.java @@ -0,0 +1,20 @@ +package com.example.umc.domain.test.converter; + +import com.example.umc.domain.test.dto.TestResDTO; + +public class TestConverter { + + // 객체 -> DTO + public static TestResDTO.Testing toTestingDTO(String testing) { + return TestResDTO.Testing.builder() + .testString(testing) + .build(); + } + + // 객체 -> DTO + public static TestResDTO.Exception toExceptionDTO(String testing) { + return TestResDTO.Exception.builder() + .testString(testing) + .build(); + } +} diff --git a/src/main/java/com/example/umc/domain/test/dto/TestResDTO.java b/src/main/java/com/example/umc/domain/test/dto/TestResDTO.java new file mode 100644 index 0000000..a8fdb74 --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/dto/TestResDTO.java @@ -0,0 +1,19 @@ +package com.example.umc.domain.test.dto; + +import lombok.Builder; +import lombok.Getter; + +public class TestResDTO { + + @Builder + @Getter + public static class Testing { + private String testString; + } + + @Builder + @Getter + public static class Exception { + private String testString; + } +} diff --git a/src/main/java/com/example/umc/domain/test/exception/TestException.java b/src/main/java/com/example/umc/domain/test/exception/TestException.java new file mode 100644 index 0000000..8a3d669 --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/exception/TestException.java @@ -0,0 +1,10 @@ +package com.example.umc.domain.test.exception; + +import com.example.umc.global.exception.GeneralException; + +public class TestException extends GeneralException { + + public TestException(com.example.umc.domain.test.exception.code.TestErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/example/umc/domain/test/exception/code/TestErrorCode.java b/src/main/java/com/example/umc/domain/test/exception/code/TestErrorCode.java new file mode 100644 index 0000000..9dd521c --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/exception/code/TestErrorCode.java @@ -0,0 +1,39 @@ +package com.example.umc.domain.test.exception.code; + +import com.example.umc.global.apiPayload.code.BaseErrorCode; +import com.example.umc.global.apiPayload.code.ErrorReasonDto; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum TestErrorCode implements BaseErrorCode { + + // For test + TEST_EXCEPTION(HttpStatus.BAD_REQUEST, "TEST400_1", "이거는 테스트"), + ; + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDto getReason() { + return ErrorReasonDto.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDto getReasonHttpStatus() { + return ErrorReasonDto.builder() + .httpStatus(httpStatus) + .message(message) + .code(code) + .isSuccess(false) + .build(); + } +} diff --git a/src/main/java/com/example/umc/domain/test/service/TestQueryService.java b/src/main/java/com/example/umc/domain/test/service/TestQueryService.java new file mode 100644 index 0000000..ac2175b --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/service/TestQueryService.java @@ -0,0 +1,5 @@ +package com.example.umc.domain.test.service; + +public interface TestQueryService { + void checkFlag(Long flag); +} diff --git a/src/main/java/com/example/umc/domain/test/service/TestQueryServiceImpl.java b/src/main/java/com/example/umc/domain/test/service/TestQueryServiceImpl.java new file mode 100644 index 0000000..f5f98ec --- /dev/null +++ b/src/main/java/com/example/umc/domain/test/service/TestQueryServiceImpl.java @@ -0,0 +1,18 @@ +package com.example.umc.domain.test.service; + +import com.example.umc.domain.test.exception.TestException; +import com.example.umc.domain.test.exception.code.TestErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TestQueryServiceImpl implements TestQueryService { + + @Override + public void checkFlag(Long flag) { + if (flag == 1) { + throw new TestException(TestErrorCode.TEST_EXCEPTION); + } + } +} diff --git a/src/main/java/com/example/umc/global/apiPayload/ApiResponse.java b/src/main/java/com/example/umc/global/apiPayload/ApiResponse.java index 8935d96..6456e10 100644 --- a/src/main/java/com/example/umc/global/apiPayload/ApiResponse.java +++ b/src/main/java/com/example/umc/global/apiPayload/ApiResponse.java @@ -10,7 +10,7 @@ @Getter @AllArgsConstructor -@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +@JsonPropertyOrder({ "isSuccess", "code", "message", "result" }) public class ApiResponse { @JsonProperty("isSuccess") private final boolean isSuccess; @@ -25,8 +25,15 @@ public static ApiResponse onSuccess(T result) { true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), - result - ); + result); + } + + public static ApiResponse onSuccess(BaseCode code, T result) { + return new ApiResponse<>( + true, + code.getReasonHttpStatus().getCode(), + code.getReasonHttpStatus().getMessage(), + result); } public static ApiResponse of(BaseCode code, String message, T result) { @@ -34,8 +41,7 @@ public static ApiResponse of(BaseCode code, String message, T result) { true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), - result - ); + result); } public static ApiResponse onFailure(String code, String message, T result) { diff --git a/src/main/java/com/example/umc/global/apiPayload/code/status/SuccessStatus.java b/src/main/java/com/example/umc/global/apiPayload/code/status/SuccessStatus.java index d82db55..da3da6c 100644 --- a/src/main/java/com/example/umc/global/apiPayload/code/status/SuccessStatus.java +++ b/src/main/java/com/example/umc/global/apiPayload/code/status/SuccessStatus.java @@ -9,8 +9,9 @@ @Getter @AllArgsConstructor public enum SuccessStatus implements BaseCode { - _OK(HttpStatus.OK, "COMMON2000", "성공입니다."), + _OK(HttpStatus.OK, "COMMON200", "성공적으로 요청을 처리했습니다."), ; + private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/src/main/java/com/example/umc/global/config/SecurityConfig.java b/src/main/java/com/example/umc/global/config/SecurityConfig.java index 18f7a6c..307085d 100644 --- a/src/main/java/com/example/umc/global/config/SecurityConfig.java +++ b/src/main/java/com/example/umc/global/config/SecurityConfig.java @@ -18,6 +18,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(authz -> authz // API 경로 허용 .requestMatchers("/api/v1/reviews/**").permitAll() + .requestMatchers("/temp/**").permitAll() .requestMatchers("/swagger-ui/**").permitAll() .requestMatchers("/v3/api-docs/**").permitAll() .requestMatchers("/swagger-ui.html").permitAll() diff --git a/src/main/java/com/example/umc/global/exception/GeneralException.java b/src/main/java/com/example/umc/global/exception/GeneralException.java new file mode 100644 index 0000000..e88bf73 --- /dev/null +++ b/src/main/java/com/example/umc/global/exception/GeneralException.java @@ -0,0 +1,15 @@ +package com.example.umc.global.exception; + +import com.example.umc.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; + +@Getter +public class GeneralException extends RuntimeException { + + private BaseErrorCode codeBase; + + public GeneralException(BaseErrorCode codeBase) { + super(codeBase.getReason().getMessage()); + this.codeBase = codeBase; + } +} diff --git a/src/main/java/com/example/umc/global/exception/GlobalExceptionHandler.java b/src/main/java/com/example/umc/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..b77edc9 --- /dev/null +++ b/src/main/java/com/example/umc/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,92 @@ +package com.example.umc.global.exception; + +import com.example.umc.global.apiPayload.ApiResponse; +import com.example.umc.global.apiPayload.code.ErrorReasonDto; +import com.example.umc.global.apiPayload.code.status.ErrorStatus; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +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.annotation.ExceptionHandler; +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 java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler + public ResponseEntity general(GeneralException e, WebRequest request) { + ErrorReasonDto reasonHttpStatus = e.getCodeBase().getReasonHttpStatus(); + return handleExceptionInternal(e, reasonHttpStatus, HttpHeaders.EMPTY, reasonHttpStatus.getHttpStatus(), request); + } + + @ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + return handleExceptionInternalFalse(e, + ApiResponse.onFailure( + ErrorStatus._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getCode(), + ErrorStatus._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getMessage(), + null), + HttpHeaders.EMPTY, HttpStatus.INTERNAL_SERVER_ERROR, request, e.getMessage()); + } + + @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 = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge(fieldName, errorMessage, (existingMessage, newMessage) -> existingMessage + ", " + newMessage); + }); + + return handleExceptionInternalArgs(e, HttpHeaders.EMPTY, errors, HttpStatus.BAD_REQUEST, request); + } + + @ExceptionHandler + public ResponseEntity handleConstraintViolationException(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException translating unexpected")); + + return handleExceptionInternalFalse(e, null, HttpHeaders.EMPTY, HttpStatus.BAD_REQUEST, request, errorMessage); + } + + protected ResponseEntity handleExceptionInternal(Exception e, Object body, + HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) { + + return handleExceptionInternalFalse(e, ApiResponse.onFailure( + ((ErrorReasonDto) body).getCode(), + ((ErrorReasonDto) body).getMessage(), + null), headers, statusCode, request, ((ErrorReasonDto) body).getMessage()); + } + + private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, + Map errors, + HttpStatusCode statusCode, WebRequest request) { + return handleExceptionInternalFalse(e, ApiResponse.onFailure("COMMON400", "잘못된 요청입니다.", errors), headers, + statusCode, request, errors.toString()); + } + + private ResponseEntity handleExceptionInternalFalse(Exception e, Object body, + HttpHeaders headers, HttpStatusCode statusCode, WebRequest request, String errorPoint) { + ServletWebRequest servletWebRequest = (ServletWebRequest) request; + String url = servletWebRequest.getRequest().getRequestURI(); + log.error("Rest API Exception: {}, url: {}, message: {}", statusCode, url, errorPoint, e); + + return super.handleExceptionInternal(e, body, headers, statusCode, request); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index be44d1a..c94aca1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,3 @@ spring.application.name=umc spring.profiles.active=dev -spring.config.import=optional:file:./BEConfig/application.properties,optional:file:./BEConfig/application-dev.properties,optional:file:./BEConfig/application-test.properties,optional:file:./BEConfig/application-prod.properties \ No newline at end of file +spring.config.import=optional:file:./BEConfig/application.properties,optional:file:./BEConfig/application-dev.properties \ No newline at end of file