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) -> 인덱스 오류 처리
@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);
}
});
}