83일차 -예외 처리

이해찬·2023년 11월 2일
0

항해일지

목록 보기
35/35
post-thumbnail

2023.11.02



📟 기본 길찾기 서비스 - 예외처리 수정

public class KakaoRouteSearchService {

    private final RestTemplate restTemplate;
    private final KakaoUriBuilderService kakaoUriBuilderService;
    private final KakaoAddressSearchService kakaoAddressSearchService;
    private final UserRepository userRepository;

    @Value("${kakao.rest.api.key}")
    private String kakaoRestApiKey;


    public KakaoRouteAllResponseDto requestRouteSearch(String originAddress, String destinationAddress, Long userId) {

        if (ObjectUtils.isEmpty(originAddress) || ObjectUtils.isEmpty(destinationAddress)) {
            throw new IllegalArgumentException("주소는 비워둘 수 없습니다.");
        }

        // 사용자 인증 정보 확인
        if(!userId.equals(userRepository.getById(userId))){
            throw new IllegalArgumentException("사용자가 정보가 일치하지 않습니다.");
        }

        // 출발지와 도착지 주소를 각각 좌표로 변환
        DocumentDto origin = kakaoAddressSearchService.requestAddressSearch(originAddress).getDocumentDtoList().get(0);
        DocumentDto destination = kakaoAddressSearchService.requestAddressSearch(destinationAddress).getDocumentDtoList().get(0);

        // "위도,경도" 형식의 문자열 생성
        String originCoord = origin.getLongitude() + "," + origin.getLatitude();
        String destinationCoord = destination.getLongitude() + "," + destination.getLatitude();

        URI uri = kakaoUriBuilderService.buildUriByRouteSearch(originCoord, destinationCoord);

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.AUTHORIZATION, "KakaoAK " + kakaoRestApiKey);
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity httpEntity = new HttpEntity(headers);

        try {
            return restTemplate.exchange(uri, HttpMethod.GET, httpEntity, KakaoRouteAllResponseDto.class).getBody();
        } catch (RestClientException e) {
            log.error("API 호출 중 오류 발생", e);
            throw new RuntimeException("API 호출 실패", e);
        }
    }


//    //경로 재생성
    public KakaoRouteAllResponseDto requestRouteReSearch(double lat, double lng) {

        if (ObjectUtils.isEmpty(lat) || ObjectUtils.isEmpty(lng)){
            throw new IllegalArgumentException("좌표가 올바르지 않습니다.");
        }
        log.info("기본 경로 이탈시 준비");

        double startCoord = Double.parseDouble(lat + "," + lng);

        URI uri = kakaoUriBuilderService.buildUriByReRouteSearch(startCoord);

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.AUTHORIZATION, "KakaoAK " + kakaoRestApiKey);
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity httpEntity = new HttpEntity(headers);

        try {
            return restTemplate.exchange(uri, HttpMethod.GET, httpEntity, KakaoRouteAllResponseDto.class).getBody();
        } catch (RestClientException e) {
            log.error("API 호출 중 오류 발생", e);
            throw new RuntimeException("API 호출 실패", e);
        }
    }
}


✅ 수정할 부분

  • 자세한 주소 메시지 처리 -> 사용자가 인지할 수 있는 메시지
  • 사용자 인증 정보 코드 수정 -> long 타입과 객체 비교 중
  • 출발지, 도착지의 get(0) -> 인덱스 오류 처리




📟 카카오 api호출 - 예외 처리


@Slf4j
@Service
@RequiredArgsConstructor
public class KakaoKeywordSearchService {

    private final KakaoUriBuilderService kakaoUriBuilderService;
    private final RestTemplate restTemplate;

    private static final String PARK_CATEGORY = "AT4";

    @Value("${kakao.rest.api.key}")
    private String kakaoRestApiKey;


    
    // 관광명소 기반
    public KakaoApiResponseDto requestAttractionKeywordSearch(String query) {

        URI uri = kakaoUriBuilderService.buildUriByKeywordSearch(query);

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.AUTHORIZATION, "KakaoAK " + kakaoRestApiKey);
        HttpEntity httpEntity = new HttpEntity(headers);
        log.info("*** 키워드 주소 api");

        // kakao api 호출
        return restTemplate.exchange(uri, HttpMethod.GET, httpEntity, KakaoApiResponseDto.class).getBody();
    }

}

✅ 수정할 부분

  • RestClientException: RestTemplate의 exchange 메소드에서 외부 API 호출 중 문제가 발생
  • NullPointerException: API 응답이 예상대로 오지 않아 getBody()에서 null을 반환하게 될 경우
  • HttpStatusCodeException: 외부 API에서 4xx나 5xx 같은 비정상적인 HTTP 상태 코드로 응답할 경우 -> 이 예외는 RestClientException의 하위 클래스
  • restTemplate 호출 하는 쪽에서 다양한 반응에 대비하여 예외 처리 추가하기.

  • api 호출 -> restTemplate.exchange 이 부분이 동일하게 예외 처리가 들어감
  • 그래서 중복 코드를 처리하기 위해 AOP or Helper 클래스 사용
  • 적은 범위라 간단하게 Helper 이용해서 처리

@Slf4j
public class KakaoApiExceptionHandler { // REST API 호출을 위해 restTemplate.exchange 부분 예외처리

    public static KakaoApiResponseDto handleApiCall(Supplier<KakaoApiResponseDto> apiCall, URI requestUri) {

        // 요청 시작 시 로그 남기기
        log.info("카카오 API 호출 시작: {}", requestUri);
        
        try {
            KakaoApiResponseDto response = apiCall.get();

            if (response == null) {
                log.error("카카오 API로부터 null 응답 받음");
                throw new RuntimeException("카카오 API 응답 문제");
            }

            // 요청 성공 시 로그 남기기
            log.info("카카오 API 호출 성공: {}", requestUri);
            
            return response;

        } catch (HttpStatusCodeException e) {
            log.error("카카오 API 호출 중 HTTP 오류 발생, 상태 코드: {}, 오류 본문: {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("카카오 API HTTP 오류", e);

        } catch (RestClientException e) {
            log.error("카카오 API 호출 중 오류 발생", e);
            throw new RuntimeException("카카오 API 호출 실패", e);
        }
    }
}


📈 프론트에 예외 처리 메시지 표시하기

  • 기존에는 @Valid 에 관한 예외처리만 수행해서 해당 예외 메시지가 프론트에 출력되었다.
  • 그러나 위에 새로운 예외처리를 위해서 GlobalExceptionHandler 를 추가하니 제대로 된 메시지가 출력되지 않아서 일부 코드를 수정





  • GlobalExceptionHandler 의 Response~ 이부분이 ValidExceptionHandler 에서 다루는 MethodArgumentNotValidException 와 겹쳐서 @Override 해서 사용하려 했으나, 오류가 발생해서 따로 분리해서 사용
  • 그러나 예외처리 메시지가 GlobalException~ 꺼는 나오지 않아서 우선순위를 @Order를 통해 지정했고, 추가적으로 js코드를 수정.

        $.ajax({
            type: 'POST',
            url: '/api/user/signup',
            data: JSON.stringify(formData),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json', // 응답을 JSON 형식으로 파싱합니다.
            success: function (response) {
                alert(response.msg); // 성공 메시지를 표시합니다.
                window.location.href = '/view/user/login-page'; // 홈 페이지로 리다이렉션합니다.
            },
            error: function (response) {
                console.log('서버로부터 오류 응답 :', response); // 오류 응답 로깅
                console.log('서버로부터 오류 응답 :', response.responseText);

                //response에서 JSON 데이터 추출
                var errors = JSON.parse(response.responseText);
                var errorMessage = "";

                // 일반 오류 메시지 처리 -> Global 예외처리 클래스
                if(errors.message) {
                    errorMessage += errors.message + "\n";
                }

                // 오류 메시지를 보기 좋게 줄 바꿈하여 표시
                // @Valid 예외 처리 -> Valid 예외처리 클래스

                if(errors.password) {
                    errorMessage += "password: " + errors.password + "\n"; // 비밀번호 오류 메시지
                }
                if(errors.email) {
                    errorMessage += "email: " + errors.email + "\n"; // 이메일 오류 메시지
                }
                if(errors.username) {
                    errorMessage += "username: " + errors.username + "\n"; // 사용자 이름 오류 메시지
                }
                // 구성된 오류 메시지를 alert 창에 표시
                alert(errorMessage);

            }
        });
    }
profile
디자인에서 개발자로

0개의 댓글