diff --git a/application/main-application/build.gradle b/application/main-application/build.gradle index 2c4e550..7613e3a 100644 --- a/application/main-application/build.gradle +++ b/application/main-application/build.gradle @@ -36,18 +36,21 @@ dependencies { implementation 'com.fasterxml.uuid:java-uuid-generator:5.1.0' } + bootJar { - archiveFileName = 'yapp-main-application.jar' + archiveFileName = 'yapp-main-application.jar' + enabled = true // 실행 가능한 모듈에서만 활성화 +} + +jar { + enabled = false } tasks.named('test') { useJUnitPlatform() } -bootJar { - enabled = true +springBoot { + mainClass = "org.mainapplication.MainApplication" // MainApplication 클래스의 패키지 경로 } -jar { - enabled = false -} diff --git a/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java b/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java index 6b14c39..c1b8dec 100644 --- a/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java +++ b/application/main-application/src/main/java/org/mainapplication/domain/auth/controller/AuthController.java @@ -25,5 +25,4 @@ public ResponseEntity refreshAccessToken(@CookieValue("RefreshToken") St String newAccessToken = tokenService.reissueAccessToken(refreshToken, response); return ResponseEntity.ok(newAccessToken); } - } diff --git a/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java b/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java index d87f251..0a57b3b 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java +++ b/application/main-application/src/main/java/org/mainapplication/global/config/WebSecurityConfig.java @@ -7,6 +7,7 @@ import org.mainapplication.global.constants.WebSecurityURI; import org.mainapplication.global.filter.JwtAuthenticationFilter; import org.mainapplication.global.filter.TestAuthenticationFilter; +import org.mainapplication.global.oauth2.handler.CustomAuthenticationEntryPoint; import org.mainapplication.global.oauth2.handler.CustomOAuth2FailureHandler; import org.mainapplication.global.oauth2.handler.CustomOAuth2SuccessHandler; import org.mainapplication.global.oauth2.service.CustomOauth2UserService; @@ -37,6 +38,7 @@ public class WebSecurityConfig { private final CustomOauth2UserService customOauth2UserService; private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; private final CustomOAuth2FailureHandler customOAuth2FailureHandler; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final TestAuthenticationFilter testAuthenticationFilter; private void defaultBasicFilterChain(HttpSecurity http) throws Exception { @@ -73,7 +75,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(customAuthenticationEntryPoint()) + .authenticationEntryPoint(customAuthenticationEntryPoint.oAuth2EntryPoint()) ) .addFilterBefore(testAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // .addFilterBefore(jwtAuthenticaltionFilter, UsernamePasswordAuthenticationFilter.class); @@ -81,15 +83,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return http.build(); } - @Bean - public AuthenticationEntryPoint customAuthenticationEntryPoint() { - return (request, response, authException) -> { - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"로그인이 필요합니다.\"}"); - }; - } - @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); diff --git a/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java b/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java index 6812b3c..0057280 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java +++ b/application/main-application/src/main/java/org/mainapplication/global/constants/WebSecurityURI.java @@ -12,7 +12,8 @@ public final class WebSecurityURI { "/auth/login/oauth2/code/google", "/swagger-ui/**", "/v3/api-docs/**", - "/swagger-resources/**" + "/swagger-resources/**", + "/common/health/**" ); public static final List CORS_ALLOW_URIS = diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/CustomException.java b/application/main-application/src/main/java/org/mainapplication/global/error/CustomException.java new file mode 100644 index 0000000..ea978a0 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/CustomException.java @@ -0,0 +1,16 @@ +package org.mainapplication.global.error; + +import java.io.Serializable; + +import lombok.Getter; + +@Getter +public abstract class CustomException extends RuntimeException implements Serializable { + + private final transient ErrorCodeStatus errorCodeStatus; + + protected CustomException(ErrorCodeStatus errorCodeStatus) { + super(errorCodeStatus.getMessage()); + this.errorCodeStatus = errorCodeStatus; + } +} \ No newline at end of file diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/ErrorCodeStatus.java b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorCodeStatus.java new file mode 100644 index 0000000..e32fccc --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorCodeStatus.java @@ -0,0 +1,11 @@ +package org.mainapplication.global.error; + +import org.springframework.http.HttpStatus; + +public interface ErrorCodeStatus { + String name(); + + HttpStatus getHttpStatus(); + + String getMessage(); +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java new file mode 100644 index 0000000..37e15a6 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/ErrorResponse.java @@ -0,0 +1,8 @@ +package org.mainapplication.global.error; + +public record ErrorResponse(String errorClassName, String message) { + + public static ErrorResponse of(String errorClassName, String message) { + return new ErrorResponse(errorClassName, message); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java b/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..a71dd6e --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/GlobalExceptionHandler.java @@ -0,0 +1,238 @@ +package org.mainapplication.global.error; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mainapplication.global.error.code.CommonErrorCode; +import org.mainapplication.global.response.GlobalResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +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.context.request.WebRequest; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @Override + protected ResponseEntity handleExceptionInternal( + Exception ex, + Object body, + HttpHeaders headers, + HttpStatusCode statusCode, + WebRequest request) { + + ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), ex.getMessage()); + return super.handleExceptionInternal(ex, errorResponse, headers, statusCode, request); + } + + /** + * enum 타입 String의 값을 잘못입력 햇을 때 + */ + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("HttpMessageNotReadableException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * 메소드 인자가 유효하지 않을 때 클라이언트에게 HTTP 400 상태 코드(Bad Request)를 반환. + * 주로 Valid, @valiated 에러시 발생 + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("MethodArgumentNotValidException : {}", ex.getMessage(), ex); + String errorMessage = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), errorMessage); + final GlobalResponse response = GlobalResponse.fail(status.value(), errorResponse); + return ResponseEntity.status(status).body(response); + } + + /** + * 지원되지 않는 HTTP 요청 메서드를 사용했을 때 클라이언트에게 HTTP 405 상태 코드(Method Not Allowed)를 반환 + */ + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("HttpRequestMethodNotSupportedException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.METHOD_NOT_ALLOWED; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * 요청 주소가 없는 주소일 경우 404 상태코드(Not Found) + */ + @Override + protected ResponseEntity handleNoHandlerFoundException( + NoHandlerFoundException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("NoHandlerFoundException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.NOT_FOUND_REQUEST_ADDRESS; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * 요청 리소스가 없을 경우 (없는 주소로 요청) + */ + @Override + protected ResponseEntity handleNoResourceFoundException( + NoResourceFoundException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("NoResourceFoundException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.NOT_FOUND_REQUEST_RESOURCE; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * @RequestParam의 값이 없을 때 + */ + @Override + protected ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("MissingServletRequestParameterException : {}", ex.getMessage(), ex); + + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.REQUESTED_PARAM_NOT_VALIDATE; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** + * @Valid나 @Validated 애노테이션을 사용하여 요청 본문 또는 메서드의 인자에 대한 유효성을 검사할 때 발생 + * List의 request를 받을 때 발생 + */ + @Override + protected ResponseEntity handleHandlerMethodValidationException( + HandlerMethodValidationException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + + log.error("HandlerMethodValidationException : {}", ex.getMessage(), ex); + + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.REQUESTED_VALUE_NOT_VALIDATE; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** enum type 일치하지 않아 binding 못할 경우 발생 주로 @RequestParam enum으로 binding 못했을 경우 발생 */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + protected ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException ex) { + + log.error("MethodArgumentTypeMismatchException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** CustomException 예외 처리 */ + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException ex) { + + log.error("CustomException : {}", ex.getMessage(), ex); + final ErrorCodeStatus errorCodeStatus = ex.getErrorCodeStatus(); + final ErrorResponse errorResponse = ErrorResponse.of(errorCodeStatus.name(), errorCodeStatus.getMessage()); + final GlobalResponse response = GlobalResponse.fail(errorCodeStatus.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(errorCodeStatus.getHttpStatus()).body(response); + } + + /** 500번대 에러 처리 */ + @ExceptionHandler(Exception.class) + protected ResponseEntity handleException(Exception ex) { + + log.error("Internal Server Error : {}", ex.getMessage(), ex); + final ErrorCodeStatus internalServerError = CommonErrorCode.INTERNAL_SERVER_ERROR; + final ErrorResponse errorResponse = ErrorResponse.of(ex.getClass().getSimpleName(), + internalServerError.getMessage()); + final GlobalResponse response = GlobalResponse.fail(internalServerError.getHttpStatus().value(), errorResponse); + return ResponseEntity.status(internalServerError.getHttpStatus()).body(response); + } + + /** Request Param Validation 예외 처리 */ + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException( + ConstraintViolationException e) { + log.error("ConstraintViolationException : {}", e.getMessage(), e); + + Map bindingErrors = new HashMap<>(); + e.getConstraintViolations() + .forEach( + constraintViolation -> { + List propertyPath = + List.of( + constraintViolation + .getPropertyPath() + .toString() + .split("\\.")); + String path = + propertyPath.stream() + .skip(propertyPath.size() - 1L) + .findFirst() + .orElse(null); + bindingErrors.put(path, constraintViolation.getMessage()); + }); + + final ErrorResponse errorResponse = ErrorResponse.of(e.getClass().getSimpleName(), bindingErrors.toString()); + final GlobalResponse response = GlobalResponse.fail(HttpStatus.BAD_REQUEST.value(), errorResponse); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/code/CommonErrorCode.java b/application/main-application/src/main/java/org/mainapplication/global/error/code/CommonErrorCode.java new file mode 100644 index 0000000..4141b36 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/code/CommonErrorCode.java @@ -0,0 +1,23 @@ +package org.mainapplication.global.error.code; + +import org.mainapplication.global.error.ErrorCodeStatus; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum CommonErrorCode implements ErrorCodeStatus { + // 서버 오류 + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 HTTP method 입니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류, 관리자에게 문의하세요"), + METHOD_ARGUMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "Enum Type이 일치하지 않거나 값이 없습니다."), + REQUESTED_PARAM_NOT_VALIDATE(HttpStatus.BAD_REQUEST, "Reqeust Param 값이 유효하지 않습니다"), + REQUESTED_VALUE_NOT_VALIDATE(HttpStatus.BAD_REQUEST, "메서드의 인자가 유효하지 않습니다."), + NOT_FOUND_REQUEST_ADDRESS(HttpStatus.NOT_FOUND, "잘못된 요청 주소입니다."), + NOT_FOUND_REQUEST_RESOURCE(HttpStatus.NOT_FOUND, "존재하지 않은 요청 주소입니다."); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java b/application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java new file mode 100644 index 0000000..35a3fb1 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/error/exception/CommonException.java @@ -0,0 +1,10 @@ +package org.mainapplication.global.error.exception; + +import org.mainapplication.global.error.CustomException; +import org.mainapplication.global.error.ErrorCodeStatus; + +public class CommonException extends CustomException { + public CommonException(ErrorCodeStatus errorCodeStatus) { + super(errorCodeStatus); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java b/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java index 5c4ce08..0c6ad54 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java +++ b/application/main-application/src/main/java/org/mainapplication/global/filter/JwtAuthenticationFilter.java @@ -7,7 +7,6 @@ import org.mainapplication.global.util.JwtUtil; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import jakarta.servlet.FilterChain; @@ -16,7 +15,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -@Component +// @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { diff --git a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..5647491 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomAuthenticationEntryPoint.java @@ -0,0 +1,40 @@ +package org.mainapplication.global.oauth2.handler; + +import org.mainapplication.global.error.ErrorResponse; +import org.mainapplication.global.response.GlobalResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + public AuthenticationEntryPoint oAuth2EntryPoint() { + return (request, response, authException) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ErrorResponse errorResponse = ErrorResponse.of( + authException.getClass().getSimpleName(), + "로그인이 필요합니다." + ); + + GlobalResponse globalResponse = GlobalResponse.fail( + HttpStatus.UNAUTHORIZED.value(), + errorResponse + ); + + // JSON 변환 후 응답 + String jsonResponse = objectMapper.writeValueAsString(globalResponse); + response.getWriter().write(jsonResponse); + }; + } +} \ No newline at end of file diff --git a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java index 5819a4f..745aa77 100644 --- a/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java +++ b/application/main-application/src/main/java/org/mainapplication/global/oauth2/handler/CustomOAuth2FailureHandler.java @@ -2,10 +2,12 @@ import java.io.IOException; +import org.mainapplication.global.constants.UrlConstants; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -14,11 +16,9 @@ public class CustomOAuth2FailureHandler implements AuthenticationFailureHandler @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws - IOException { + IOException, ServletException { - // 실패 응답을 JSON으로 반환 - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"error\": \"Authentication failed\", \"message\": \"" + exception.getMessage() + "\"}"); + //TODO 서비스 페이지로 리다이렉트할지, Error Response를 리턴할지 추후에 결정 + response.sendRedirect(UrlConstants.PROD_DOMAIN_URL + "?error=" + exception.getMessage()); } } \ No newline at end of file diff --git a/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java new file mode 100644 index 0000000..bc260b5 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponse.java @@ -0,0 +1,15 @@ +package org.mainapplication.global.response; + +import java.time.LocalDateTime; + +import org.mainapplication.global.error.ErrorResponse; + +public record GlobalResponse(int status, Object data, LocalDateTime timestamp) { + public static GlobalResponse success(int status, Object data) { + return new GlobalResponse(status, data, LocalDateTime.now()); + } + + public static GlobalResponse fail(int status, ErrorResponse errorResponse) { + return new GlobalResponse(status, errorResponse, LocalDateTime.now()); + } +} diff --git a/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java new file mode 100644 index 0000000..800a122 --- /dev/null +++ b/application/main-application/src/main/java/org/mainapplication/global/response/GlobalResponseAdvice.java @@ -0,0 +1,48 @@ +package org.mainapplication.global.response; + +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import jakarta.servlet.http.HttpServletResponse; + +@RestControllerAdvice(basePackages = "org.application") +public class GlobalResponseAdvice implements ResponseBodyAdvice { + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return true; + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response) { + + HttpServletResponse servletResponse = + ((ServletServerHttpResponse)response).getServletResponse(); + + // http 상태 코드 + int status = servletResponse.getStatus(); + HttpStatus resolve = HttpStatus.resolve(status); + + // 상태 코드가 null이거나 응답 바디가 문자열인 경우 원본 응답을 반환 + if (resolve == null || body instanceof String) { + return body; + } + + // 2xx 범위인 경우 응답 처리 + if (resolve.series() == HttpStatus.Series.SUCCESSFUL) { + return GlobalResponse.success(status, body); + } + return body; + } +} \ No newline at end of file diff --git a/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java b/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java index bc2bc71..e9583dc 100644 --- a/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java +++ b/application/main-application/src/main/java/org/mainapplication/health/HealCheckController.java @@ -1,5 +1,6 @@ package org.mainapplication.health; +import org.mainapplication.global.response.GlobalResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,7 +14,7 @@ public class HealCheckController { @Operation(summary = "서버 상태 체크", security = {}) @GetMapping("/health") - public ResponseEntity healthCheck() { - return ResponseEntity.ok("OK"); + public ResponseEntity healthCheck() { + return ResponseEntity.ok(GlobalResponse.success(200, "OK")); } } diff --git a/application/main-application/src/main/resources/application.yml b/application/main-application/src/main/resources/application.yml index 18767a5..f98582a 100644 --- a/application/main-application/src/main/resources/application.yml +++ b/application/main-application/src/main/resources/application.yml @@ -31,10 +31,15 @@ spring: url-prefix: ${AWS_S3_URL_PREFIX} logging: - level: - org: - springframework: - security=DEBUG: + level: + org: + springframework: + security: trace + web: DEBUG + hibernate: + orm: + jdbc: + bind: trace server: servlet: diff --git a/settings.gradle b/settings.gradle index 451b765..7311591 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,9 +4,6 @@ rootProject.name = 'yapp' include 'application:main-application' include 'application:upload-schedule-application' -// common 모듈 -include 'common:common-module' - // system domain 모듈 include 'domain:domain-module'