diff --git a/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java b/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java new file mode 100644 index 00000000..0f6a0fe0 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java @@ -0,0 +1,4 @@ +package com.waither.weatherservice.controller; + +public class WeatherController { +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherTestController.java b/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherTestController.java index 6ba2d24c..0f808b5d 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherTestController.java +++ b/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherTestController.java @@ -9,10 +9,10 @@ import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.core.JsonProcessingException; -import com.waither.weatherservice.dto.AccuweatherTestRequest; -import com.waither.weatherservice.dto.AirTestRequest; -import com.waither.weatherservice.dto.ForeCastTestRequest; -import com.waither.weatherservice.dto.MsgTestRequest; +import com.waither.weatherservice.dto.request.AccuweatherTestRequest; +import com.waither.weatherservice.dto.request.AirTestRequest; +import com.waither.weatherservice.dto.request.ForeCastTestRequest; +import com.waither.weatherservice.dto.request.MsgTestRequest; import com.waither.weatherservice.service.WeatherService; import lombok.RequiredArgsConstructor; diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/AccuweatherTestRequest.java b/weather-service/src/main/java/com/waither/weatherservice/dto/request/AccuweatherTestRequest.java similarity index 62% rename from weather-service/src/main/java/com/waither/weatherservice/dto/AccuweatherTestRequest.java rename to weather-service/src/main/java/com/waither/weatherservice/dto/request/AccuweatherTestRequest.java index b61db344..79d68329 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/dto/AccuweatherTestRequest.java +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/request/AccuweatherTestRequest.java @@ -1,4 +1,4 @@ -package com.waither.weatherservice.dto; +package com.waither.weatherservice.dto.request; public record AccuweatherTestRequest( double latitude, diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/AirTestRequest.java b/weather-service/src/main/java/com/waither/weatherservice/dto/request/AirTestRequest.java similarity index 53% rename from weather-service/src/main/java/com/waither/weatherservice/dto/AirTestRequest.java rename to weather-service/src/main/java/com/waither/weatherservice/dto/request/AirTestRequest.java index 8373b2e2..0d6b5db3 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/dto/AirTestRequest.java +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/request/AirTestRequest.java @@ -1,4 +1,4 @@ -package com.waither.weatherservice.dto; +package com.waither.weatherservice.dto.request; public record AirTestRequest( String searchDate diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/ForeCastTestRequest.java b/weather-service/src/main/java/com/waither/weatherservice/dto/request/ForeCastTestRequest.java similarity index 66% rename from weather-service/src/main/java/com/waither/weatherservice/dto/ForeCastTestRequest.java rename to weather-service/src/main/java/com/waither/weatherservice/dto/request/ForeCastTestRequest.java index 44acf314..be2dbc30 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/dto/ForeCastTestRequest.java +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/request/ForeCastTestRequest.java @@ -1,4 +1,4 @@ -package com.waither.weatherservice.dto; +package com.waither.weatherservice.dto.request; public record ForeCastTestRequest( int nx, diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/MsgTestRequest.java b/weather-service/src/main/java/com/waither/weatherservice/dto/request/MsgTestRequest.java similarity index 52% rename from weather-service/src/main/java/com/waither/weatherservice/dto/MsgTestRequest.java rename to weather-service/src/main/java/com/waither/weatherservice/dto/request/MsgTestRequest.java index 4f113864..60ebdf39 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/dto/MsgTestRequest.java +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/request/MsgTestRequest.java @@ -1,4 +1,4 @@ -package com.waither.weatherservice.dto; +package com.waither.weatherservice.dto.request; public record MsgTestRequest( String location diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/request/WeatherRequest.java b/weather-service/src/main/java/com/waither/weatherservice/dto/request/WeatherRequest.java new file mode 100644 index 00000000..d43122dc --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/request/WeatherRequest.java @@ -0,0 +1,7 @@ +package com.waither.weatherservice.dto.request; + +import lombok.Builder; + +@Builder +public record WeatherRequest() { +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/response/WeatherResponse.java b/weather-service/src/main/java/com/waither/weatherservice/dto/response/WeatherResponse.java new file mode 100644 index 00000000..f6d3198b --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/response/WeatherResponse.java @@ -0,0 +1,7 @@ +package com.waither.weatherservice.dto.response; + +import lombok.Builder; + +@Builder +public record WeatherResponse() { +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/exception/CustomException.java b/weather-service/src/main/java/com/waither/weatherservice/exception/CustomException.java new file mode 100644 index 00000000..842cfcd8 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/exception/CustomException.java @@ -0,0 +1,15 @@ +package com.waither.weatherservice.exception; + +import com.waither.weatherservice.response.status.BaseErrorCode; + +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException { + + private final BaseErrorCode errorCode; + + public CustomException(BaseErrorCode errorCode) { + this.errorCode = errorCode; + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/exception/GlobalExceptionHandler.java b/weather-service/src/main/java/com/waither/weatherservice/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..789828da --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/exception/GlobalExceptionHandler.java @@ -0,0 +1,64 @@ +package com.waither.weatherservice.exception; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.waither.weatherservice.response.ApiResponse; +import com.waither.weatherservice.response.ErrorCode; +import com.waither.weatherservice.response.status.BaseErrorCode; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + // @Valid 유효성 검사를 실패했을 시 + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity>> handleMethodArgumentNotValidException( + MethodArgumentNotValidException ex + ) { + // 실패한 validation 을 담을 Map + Map failedValidations = new HashMap<>(); + List fieldErrors = ex.getBindingResult().getFieldErrors(); + // fieldErrors 를 순회하며 failedValidations 에 담는다. + fieldErrors.forEach(error -> failedValidations.put(error.getField(), error.getDefaultMessage())); + ApiResponse> errorResponse = ApiResponse.onFailure( + ErrorCode.VALIDATION_FAILED.getCode(), + ErrorCode.VALIDATION_FAILED.getMessage(), + failedValidations); + return ResponseEntity.status(ex.getStatusCode()).body(errorResponse); + } + + // Custom 예외에 대한 처리 + @ExceptionHandler({CustomException.class}) + public ResponseEntity> handleCustomException(CustomException e) { + log.warn("[WARNING] Custom Exception : {}", e.getErrorCode()); + BaseErrorCode errorCode = e.getErrorCode(); + return ResponseEntity. + status(errorCode.getHttpStatus()) + .body(errorCode.getErrorResponse()); + } + + @ExceptionHandler({Exception.class}) + public ResponseEntity> handleAllException(Exception e) { + log.error("[WARNING] Internal Server Error : {} ", e.getMessage()); + BaseErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR_500; + ApiResponse errorResponse = ApiResponse.onFailure( + errorCode.getCode(), + errorCode.getMessage(), + e.getMessage() + ); + return ResponseEntity + .status(errorCode.getHttpStatus()) + .body(errorResponse); + } + +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/exception/WeatherExceptionHandler.java b/weather-service/src/main/java/com/waither/weatherservice/exception/WeatherExceptionHandler.java new file mode 100644 index 00000000..5f26b3ca --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/exception/WeatherExceptionHandler.java @@ -0,0 +1,9 @@ +package com.waither.weatherservice.exception; + +import com.waither.weatherservice.response.status.BaseErrorCode; + +public class WeatherExceptionHandler extends CustomException { + public WeatherExceptionHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiException.java b/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiException.java deleted file mode 100644 index 6a6b8df2..00000000 --- a/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.waither.weatherservice.openapi; - -public class OpenApiException extends RuntimeException { - public OpenApiException(String message) { - super(message); - } -} diff --git a/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiUtil.java b/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiUtil.java index bf756fd8..991c7ed9 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiUtil.java +++ b/weather-service/src/main/java/com/waither/weatherservice/openapi/OpenApiUtil.java @@ -17,6 +17,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.waither.weatherservice.exception.WeatherExceptionHandler; +import com.waither.weatherservice.response.WeatherErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -69,14 +71,14 @@ public List callForeCastApi( .accept(MediaType.APPLICATION_JSON) .retrieve().bodyToMono(ForeCastOpenApiResponse.class) .onErrorResume(throwable -> { - throw new OpenApiException(RESPONSE_EXCEPTION_MSG); + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR); }) .block().getResponse(); if (response.getHeader().getResultCode().equals("00")) { return response.getBody().getItems().getItem(); } else { - throw new OpenApiException(response.getHeader().getResultMsg()); + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR); } } @@ -124,7 +126,7 @@ public List callDisasterMsgApi(String location) thro }) .retrieve().bodyToMono(String.class) .onErrorResume(throwable -> { - throw new OpenApiException(RESPONSE_EXCEPTION_MSG); + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR); }) .block(); @@ -135,7 +137,8 @@ public List callDisasterMsgApi(String location) thro return response.getDisasterMsg().get(1).getRow(); } else { String resultMsg = response.getDisasterMsg().get(0).getHead().get(2).getResultData().getResultMsg(); - throw new OpenApiException(resultMsg); + log.info("[*] OpenApi Error : {}", resultMsg); + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR); } } @@ -161,8 +164,11 @@ public List callAirKorea(String searchDate) throw AirKoreaOpenApiResponse.Response response = webClient.get() .uri(uri) .accept(MediaType.APPLICATION_JSON) - .retrieve().bodyToMono(AirKoreaOpenApiResponse.class) - .blockOptional().orElseThrow(() -> new OpenApiException(RESPONSE_EXCEPTION_MSG)).getResponse(); + .retrieve() + .bodyToMono(AirKoreaOpenApiResponse.class) + .blockOptional() + .orElseThrow(() -> new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR)) + .getResponse(); if (response.getHeader().getResultCode().equals("00")) { @@ -178,7 +184,8 @@ public List callAirKorea(String searchDate) throw return items; } else { - throw new OpenApiException(response.getHeader().getResultMsg()); + log.info("[*] OpenApi Error : {}", response.getHeader().getResultMsg()); + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR); } } @@ -215,7 +222,7 @@ public String callAccuweatherLocationApi(double latitude, double longitude) thro .accept(MediaType.APPLICATION_JSON) .retrieve().bodyToMono(String.class) .onErrorResume(throwable -> { - throw new OpenApiException(RESPONSE_EXCEPTION_MSG); + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR); }) .block(); diff --git a/weather-service/src/main/java/com/waither/weatherservice/response/ApiResponse.java b/weather-service/src/main/java/com/waither/weatherservice/response/ApiResponse.java new file mode 100644 index 00000000..a98e1812 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/response/ApiResponse.java @@ -0,0 +1,46 @@ +package com.waither.weatherservice.response; + +import org.springframework.http.HttpStatus; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@JsonPropertyOrder({"code", "message", "result"}) +public class ApiResponse { + + // HTTP 상태 코드나 사용자 정의 코드 + private final String code; + // API 요청에 대한 설명이나 상태 메시지 + private final String message; + // result 값은 null이 아닐 때만 응답에 포함시킨다. + @JsonInclude(JsonInclude.Include.NON_NULL) + // 결과 데이터. (보통 dto -> json 파싱 될 예정) + private T result; + + // 성공한 경우 응답 생성 + public static ApiResponse onSuccess(T result) { + return new ApiResponse<>(String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.getReasonPhrase(), result); + } + + // 성공한 경우 응답 생성 (상태 코드 지정 가능) + public static ApiResponse onSuccess(HttpStatus status, T result) { + return new ApiResponse<>(String.valueOf(status.value()), status.getReasonPhrase(), result); + } + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(String code, String message, T result) { + return new ApiResponse<>(code, message, result); + } + + // 실패한 경우 응답 생성 (데이터 없음) + public static ApiResponse onFailure(String statusCode, String message) { + return new ApiResponse<>(statusCode, message, null); + } + +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/response/ErrorCode.java b/weather-service/src/main/java/com/waither/weatherservice/response/ErrorCode.java new file mode 100644 index 00000000..6be16245 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/response/ErrorCode.java @@ -0,0 +1,43 @@ +package com.waither.weatherservice.response; + +import org.springframework.http.HttpStatus; + +import com.waither.weatherservice.response.status.BaseErrorCode; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ErrorCode implements BaseErrorCode { + + // 일반적인 ERROR 응답 + BAD_REQUEST_400(HttpStatus.BAD_REQUEST, + "COMMON400", + HttpStatus.BAD_REQUEST.getReasonPhrase()), + UNAUTHORIZED_401(HttpStatus.UNAUTHORIZED, + "COMMON401", + HttpStatus.UNAUTHORIZED.getReasonPhrase()), + FORBIDDEN_403(HttpStatus.FORBIDDEN, + "COMMON403", + HttpStatus.FORBIDDEN.getReasonPhrase()), + NOT_FOUND_404(HttpStatus.NOT_FOUND, + "COMMON404", + HttpStatus.NOT_FOUND.getReasonPhrase()), + INTERNAL_SERVER_ERROR_500( + HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500", + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()), + + // 유효성 검사 + VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "VALID400_0", "입력값에 대한 검증에 실패했습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ApiResponse getErrorResponse() { + return ApiResponse.onFailure(code, message); + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/response/WeatherErrorCode.java b/weather-service/src/main/java/com/waither/weatherservice/response/WeatherErrorCode.java new file mode 100644 index 00000000..1c542b3e --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/response/WeatherErrorCode.java @@ -0,0 +1,25 @@ +package com.waither.weatherservice.response; + +import org.springframework.http.HttpStatus; + +import com.waither.weatherservice.response.status.BaseErrorCode; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum WeatherErrorCode implements BaseErrorCode { + + WEATHER_EXAMPLE_ERROR(HttpStatus.BAD_REQUEST, "WEAT4000", "날씨 에러입니다."), + WEATHER_OPENAPI_ERROR(HttpStatus.BAD_REQUEST, "WEAT4001", "OpenApi 관련 오류입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ApiResponse getErrorResponse() { + return ApiResponse.onFailure(code, message); + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/response/status/BaseErrorCode.java b/weather-service/src/main/java/com/waither/weatherservice/response/status/BaseErrorCode.java new file mode 100644 index 00000000..a89beee3 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/response/status/BaseErrorCode.java @@ -0,0 +1,16 @@ +package com.waither.weatherservice.response.status; + +import org.springframework.http.HttpStatus; + +import com.waither.weatherservice.response.ApiResponse; + +public interface BaseErrorCode { + + HttpStatus getHttpStatus(); + + String getCode(); + + String getMessage(); + + ApiResponse getErrorResponse(); +}