From 39415a824f5d952337d84a6703538e423c69d9ad Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sun, 20 Oct 2024 15:45:13 +0900 Subject: [PATCH 1/5] =?UTF-8?q?#23=20[feat]=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=EB=A5=BC=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/kusitms/backend/global/dto/ApiResponse.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/kusitms/backend/global/dto/ApiResponse.java b/src/main/java/kusitms/backend/global/dto/ApiResponse.java index e436924..f2001d2 100644 --- a/src/main/java/kusitms/backend/global/dto/ApiResponse.java +++ b/src/main/java/kusitms/backend/global/dto/ApiResponse.java @@ -1,7 +1,6 @@ package kusitms.backend.global.dto; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import kusitms.backend.global.code.BaseCode; import kusitms.backend.global.code.BaseErrorCode; import lombok.Getter; @@ -31,4 +30,9 @@ public static ResponseEntity> onFailure(BaseErrorCode code) { ApiResponse response = new ApiResponse<>(false, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), null); return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); } + + public static ResponseEntity onFailure(BaseErrorCode code, String message) { + ApiResponse response = new ApiResponse<>(false, code.getReasonHttpStatus().getCode(), message, null); + return ResponseEntity.status(code.getReasonHttpStatus().getHttpStatus()).body(response); + } } \ No newline at end of file From 2be759c55b98152ce65fce2a3eabdebb271c6352 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sun, 20 Oct 2024 15:45:38 +0900 Subject: [PATCH 2/5] =?UTF-8?q?#23=20[fix]=20:=20MacOS=20Silicon=EC=97=90?= =?UTF-8?q?=EC=84=9C=EC=9D=98=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EB=88=84=EB=9D=BD=20=EB=AC=B8=EC=A0=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index b27df96..125b67a 100644 --- a/build.gradle +++ b/build.gradle @@ -67,6 +67,8 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' // WebClient implementation 'org.springframework.boot:spring-boot-starter-webflux' + // MacOS Silicon 라이브러리 누락 문제 + runtimeOnly 'io.netty:netty-resolver-dns-native-macos:4.1.104.Final:osx-aarch_64' } // 스니펫이 생성되는 디렉터리 경로를 설정 From 67fc44f21fed65efa34012ff67674da80e4fe6ed Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sun, 20 Oct 2024 15:45:56 +0900 Subject: [PATCH 3/5] =?UTF-8?q?#23=20[feat]=20:=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatbot/presentation/ChatbotController.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/kusitms/backend/chatbot/presentation/ChatbotController.java b/src/main/java/kusitms/backend/chatbot/presentation/ChatbotController.java index 9bdfed4..f7884bb 100644 --- a/src/main/java/kusitms/backend/chatbot/presentation/ChatbotController.java +++ b/src/main/java/kusitms/backend/chatbot/presentation/ChatbotController.java @@ -1,7 +1,8 @@ package kusitms.backend.chatbot.presentation; import jakarta.validation.Valid; -import jakarta.websocket.server.PathParam; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import kusitms.backend.chatbot.application.ChatbotService; import kusitms.backend.chatbot.application.ClovaService; import kusitms.backend.chatbot.dto.request.GetClovaChatbotAnswerRequest; @@ -11,11 +12,13 @@ import kusitms.backend.global.dto.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/chatbot") +@Validated public class ChatbotController { private final ChatbotService chatbotService; private final ClovaService clovaService; @@ -23,9 +26,9 @@ public class ChatbotController { // 가이드 챗봇 답변 조회 API @GetMapping("/guide") public ResponseEntity> getGuideChatbotAnswer( - @PathParam("stadiumName") String stadiumName, - @PathParam("categoryName") String categoryName, - @PathParam("orderNumber") int orderNumber){ + @RequestParam("stadiumName") @NotBlank String stadiumName, + @RequestParam("categoryName") @NotBlank String categoryName, + @RequestParam("orderNumber") @Min(1) int orderNumber){ GetGuideChatbotAnswerResponse response = chatbotService.getGuideChatbotAnswer(stadiumName, categoryName, orderNumber); From 3b2a6bca9208443f8548192760514f5bfd743f9d Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sun, 20 Oct 2024 15:46:53 +0900 Subject: [PATCH 4/5] =?UTF-8?q?#23=20[feat]=20:=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=97=90=EB=9F=AC=EB=A5=BC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java b/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java index c4a3a7e..47c3d8e 100644 --- a/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java @@ -1,14 +1,25 @@ package kusitms.backend.global.exception; +import jakarta.validation.ConstraintViolationException; import kusitms.backend.global.dto.ApiResponse; import kusitms.backend.global.dto.ErrorReasonDto; import kusitms.backend.global.status.ErrorStatus; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +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.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import java.util.List; +import java.util.stream.Collectors; + @Slf4j @RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @@ -16,26 +27,66 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { // 커스텀 예외 처리 @ExceptionHandler(CustomException.class) public ResponseEntity> handleCustomException(CustomException e) { - log.error("CustomException occurred: {}", e.getMessage()); + logError(e.getMessage(), e); return ApiResponse.onFailure(e.getErrorCode()); } // Security 인증 관련 처리 @ExceptionHandler(SecurityException.class) public ResponseEntity> handleSecurityException(SecurityException e) { - log.error("SecurityException: {}", e.getMessage()); + logError(e.getMessage(), e); return ApiResponse.onFailure(ErrorStatus._UNAUTHORIZED); } + // ConstraintViolationException 처리 (쿼리 파라미터에 올바른 값이 들어오지 않은 경우) + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleValidationParameterError(ConstraintViolationException ex) { + String errorMessage = ex.getMessage(); + logError("ConstraintViolationException", errorMessage); + return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST, errorMessage); + } + + // MissingServletRequestParameterException 처리 (필수 쿼리 파라미터가 입력되지 않은 경우) + @Override + protected ResponseEntity handleMissingServletRequestParameter(MissingServletRequestParameterException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String errorMessage = "필수 파라미터 '" + ex.getParameterName() + "'가 없습니다."; + logError("MissingServletRequestParameterException", errorMessage); + return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST, errorMessage); + } + + // MethodArgumentNotValidException 처리 (RequestBody로 들어온 필드들의 유효성 검증에 실패한 경우) + @Override + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String combinedErrors = extractFieldErrors(ex.getBindingResult().getFieldErrors()); + logError("Validation error", combinedErrors); + return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST, combinedErrors); + } + // 기타 Exception 처리 @ExceptionHandler(Exception.class) public ResponseEntity> handleException(Exception e) { - log.error("Exception: {}", e.getMessage()); - + logError(e.getMessage(), e); if (e instanceof IllegalArgumentException) { return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST); } - // 그 외 내부 서버 오류로 처리 return ApiResponse.onFailure(ErrorStatus._INTERNAL_SERVER_ERROR); } + + // 유효성 검증 오류 메시지 추출 메서드 (FieldErrors) + private String extractFieldErrors(List fieldErrors) { + return fieldErrors.stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining(", ")); + } + + // 로그 기록 메서드 + private void logError(String message, Object errorDetails) { + log.error("{}: {}", message, errorDetails); + } } \ No newline at end of file From 4f7458459df2db42d27a231af5f182d5b4b749e3 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sun, 20 Oct 2024 15:59:19 +0900 Subject: [PATCH 5/5] =?UTF-8?q?#23=20[feat]=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EB=A9=B0,=20=EB=A9=94=EC=84=B8=EC=A7=80=EB=A5=BC=20=EC=A0=95?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EC=97=AC=20=EC=A0=84=EB=8B=AC=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 50 +++++++++++++++++-- .../backend/global/status/ErrorStatus.java | 6 ++- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java b/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java index 47c3d8e..6599639 100644 --- a/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/kusitms/backend/global/exception/GlobalExceptionHandler.java @@ -10,11 +10,14 @@ import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; +import org.springframework.web.HttpMediaTypeNotSupportedException; +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.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import java.util.List; @@ -38,6 +41,14 @@ public ResponseEntity> handleSecurityException(Secur return ApiResponse.onFailure(ErrorStatus._UNAUTHORIZED); } + // IllegalArgumentException 처리 (잘못된 인자가 전달된 경우) + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + String errorMessage = "잘못된 요청입니다: " + e.getMessage(); + logError("IllegalArgumentException", errorMessage); + return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST, errorMessage); + } + // ConstraintViolationException 처리 (쿼리 파라미터에 올바른 값이 들어오지 않은 경우) @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity handleValidationParameterError(ConstraintViolationException ex) { @@ -68,13 +79,44 @@ protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotV return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST, combinedErrors); } - // 기타 Exception 처리 + // NoHandlerFoundException 처리 (요청 경로에 매핑된 핸들러가 없는 경우) + @Override + protected ResponseEntity handleNoHandlerFoundException(NoHandlerFoundException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String errorMessage = "해당 경로에 대한 핸들러를 찾을 수 없습니다: " + ex.getRequestURL(); + logError("NoHandlerFoundException", errorMessage); + return ApiResponse.onFailure(ErrorStatus._NOT_FOUND_HANDLER, errorMessage); + } + + // HttpRequestMethodNotSupportedException 처리 (지원하지 않는 HTTP 메소드 요청이 들어온 경우) + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String errorMessage = "지원하지 않는 HTTP 메소드 요청입니다: " + ex.getMethod(); + logError("HttpRequestMethodNotSupportedException", errorMessage); + return ApiResponse.onFailure(ErrorStatus._METHOD_NOT_ALLOWED, errorMessage); + } + + // HttpMediaTypeNotSupportedException 처리 (지원하지 않는 미디어 타입 요청이 들어온 경우) + @Override + protected ResponseEntity handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request) { + String errorMessage = "지원하지 않는 미디어 타입입니다: " + ex.getContentType(); + logError("HttpMediaTypeNotSupportedException", errorMessage); + return ApiResponse.onFailure(ErrorStatus._UNSUPPORTED_MEDIA_TYPE, errorMessage); + } + + // 내부 서버 에러 처리 (500) @ExceptionHandler(Exception.class) public ResponseEntity> handleException(Exception e) { + // 서버 내부 에러 발생 시 로그에 예외 내용 기록 logError(e.getMessage(), e); - if (e instanceof IllegalArgumentException) { - return ApiResponse.onFailure(ErrorStatus._BAD_REQUEST); - } return ApiResponse.onFailure(ErrorStatus._INTERNAL_SERVER_ERROR); } diff --git a/src/main/java/kusitms/backend/global/status/ErrorStatus.java b/src/main/java/kusitms/backend/global/status/ErrorStatus.java index 155f50a..31cf2f7 100644 --- a/src/main/java/kusitms/backend/global/status/ErrorStatus.java +++ b/src/main/java/kusitms/backend/global/status/ErrorStatus.java @@ -10,11 +10,13 @@ @AllArgsConstructor public enum ErrorStatus implements BaseErrorCode { // Global Error - _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"500", "서버에서 요청을 처리 하는 동안 오류가 발생했습니다."), + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"500", "서버 내부 오류가 발생했습니다. 자세한 사항은 백엔드 팀에 문의하세요."), _BAD_REQUEST(HttpStatus.BAD_REQUEST,"400", "입력 값이 잘못된 요청 입니다."), _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"401", "인증이 필요 합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "403", "금지된 요청 입니다."), - _METHOD_NOT_ALLOWED(HttpStatus.FORBIDDEN, "403", "금지된 요청 입니다."), + _METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "405", "허용되지 않은 요청 메소드입니다."), + _UNSUPPORTED_MEDIA_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "415", "지원되지 않는 미디어 타입입니다."), + _NOT_FOUND_HANDLER(HttpStatus.NOT_FOUND, "404", "해당 경로에 대한 핸들러를 찾을 수 없습니다."), _FAILED_SAVE_REDIS(HttpStatus.INTERNAL_SERVER_ERROR, "500", "Redis 저장에 실패하였습니다."), ;