Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.tranner.external_api_proxy.api.service;

import com.tranner.external_api_proxy.api.dto.response.V1KeywordSearchResponse;
import com.tranner.external_api_proxy.api.dto.response.V1KeywordSearchResult;
import com.tranner.external_api_proxy.common.type.PhotoSize;
import com.tranner.external_api_proxy.common.type.PlaceType;
import com.tranner.external_api_proxy.api.exception.ApiErrorCode;
import com.tranner.external_api_proxy.common.exception.custom.InternalServerException;
import com.tranner.external_api_proxy.common.util.PlaceTypeMappingUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.*;

@Service
@RequiredArgsConstructor
public class NearbySearchService {

private final WebClient webClient;
private final PhotoService photoService;

@Value("${google.maps.api.key}")
private String GOOGLE_PLACES_KEY;

public Mono<V1KeywordSearchResponse> searchNearbyLegacy(Double latitude, Double longitude, @Nullable String pageToken) {
Integer radius = 500;

return webClient.get()
.uri(uriBuilder -> {
var builder = uriBuilder
.scheme("https")
.host("maps.googleapis.com")
.path("/maps/api/place/nearbysearch/json")
.queryParam("location", latitude + "," + longitude)
.queryParam("radius", radius)
.queryParam("key", GOOGLE_PLACES_KEY)
.queryParam("language", "ko");

if (pageToken != null && !pageToken.isBlank()) {
builder.queryParam("pagetoken", pageToken);
}

return builder.build();
})
.retrieve()
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), response ->
response.bodyToMono(String.class)
.flatMap(body -> {
String message = String.format("Google V1 Text API Error: %s\nResponse body: %s",
response.statusCode(), body);
return Mono.error(new com.tranner.external_api_proxy.common.exception.custom.InternalServerException(ApiErrorCode.GOOGLE_HTTP_ERROR, message));
})
)
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
.map(responseMap -> {
List<Map<String, Object>> places = (List<Map<String, Object>>) responseMap.getOrDefault("results", List.of());

List<V1KeywordSearchResult> results = places.stream().map(place -> {
V1KeywordSearchResult result = new V1KeywordSearchResult();

result.setId((String) place.get("place_id"));
result.setPlaceName((String) place.get("name"));
result.setAddress((String) place.get("vicinity"));

Map<String, Object> location = (Map<String, Object>) ((Map<String, Object>) place.get("geometry")).get("location");
if (location != null) {
result.setLatitude(((Number) location.get("lat")).doubleValue());
result.setLongitude(((Number) location.get("lng")).doubleValue());
}

List<String> types = (List<String>) place.get("types");
result.setPlaceType(PlaceTypeMappingUtil.classify(types));

List<Map<String, Object>> photos = (List<Map<String, Object>>) place.get("photos");
if (photos != null && !photos.isEmpty()) {
String photoReference = (String) photos.get(0).get("photo_reference");
if (photoReference != null) {
String photoUrl = photoService.getPhotoUrl(photoReference, PhotoSize.MIDDLE);
result.setPhotoUrl(photoUrl);
}
}

return result;
}).toList();

String nextPageToken = (String) responseMap.get("next_page_token");
return new V1KeywordSearchResponse(results, nextPageToken);
})
.onErrorResume(ex -> Mono.error(new InternalServerException(ApiErrorCode.GOOGLE_API_REQUEST_ERROR, ex.getMessage())));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.tranner.external_api_proxy.search.controller;

import com.tranner.external_api_proxy.discovery.dto.response.DetailResponseDTO;
import com.tranner.external_api_proxy.discovery.dto.response.PlaceListResponseDTO;
import com.tranner.external_api_proxy.search.dto.response.SearchResponseDTO;
import com.tranner.external_api_proxy.search.service.SearchService;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,10 +18,20 @@ public class SearchController {
private final SearchService searchService;

/*
1. 주변 검색(전체보기에서 사용)
1. 검색어로 검색 text search, location 설정
2. 타입 별 검색(명소, 맛집, 숙소)
*/

//1. 주변 검색
@GetMapping("/nearbySearch")
public ResponseEntity<SearchResponseDTO> nearbySearch(@RequestParam("latitude") Double latitude,
@RequestParam("longitude") Double longitude,
@RequestParam("pageToken") @Nullable String pageToken){
SearchResponseDTO searchResponseDTO = searchService.nearbySearch(latitude, longitude, pageToken).block();
return ResponseEntity.ok().body(searchResponseDTO);
}

/**
* @param text
* @param latitude
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import com.tranner.external_api_proxy.api.dto.response.DetailSearchResult;
import com.tranner.external_api_proxy.api.dto.response.V1KeywordSearchResponse;
import com.tranner.external_api_proxy.api.service.NearbySearchService;
import com.tranner.external_api_proxy.api.service.PlaceDetailService;
import com.tranner.external_api_proxy.api.service.V1TextSearchService;
import com.tranner.external_api_proxy.common.type.RegionCode;
import com.tranner.external_api_proxy.discovery.dto.response.DetailResponseDTO;
import com.tranner.external_api_proxy.discovery.dto.response.PlaceListResponseDTO;
import com.tranner.external_api_proxy.discovery.dto.response.PlacesDTO;
import com.tranner.external_api_proxy.search.dto.response.PlaceDTO;
import com.tranner.external_api_proxy.search.dto.response.SearchResponseDTO;
import lombok.RequiredArgsConstructor;
Expand All @@ -23,8 +25,44 @@
public class SearchService {

private final V1TextSearchService v1TextSearchService;
private final NearbySearchService nearbySearchService;
private final PlaceDetailService placeDetailService;

public Mono<SearchResponseDTO> nearbySearch(Double latitude, Double longitude, @Nullable String pageToken) {


return nearbySearchService.searchNearbyLegacy(latitude, longitude, pageToken)
.map(response -> {
if (response == null) {
throw new RuntimeException("Google Places API 응답이 null입니다.");
}

// 4. 응답 매핑
List<PlaceDTO> dtoList = response.getPlaces().stream()
.map(place -> PlaceDTO.builder()
.placeId(place.getId())
.placeName(place.getPlaceName())
.placeType(place.getPlaceType().toString())
.photoUrl(place.getPhotoUrl())
.address(place.getAddress())
.latitude(place.getLatitude())
.longitude(place.getLongitude())
.build())
.toList();
System.out.println("4. DTO 변환 완료, 총 개수: " + dtoList.size());

// 5. 최종 응답 객체 생성
SearchResponseDTO result = SearchResponseDTO.builder()
.places(dtoList)
.pageToken(response.getNextPageToken())
.build();
System.out.println("5. 응답 객체 생성 완료, pageToken: " + response.getNextPageToken());

return result;

});
}

public SearchResponseDTO searchByText(String text, Double latitude, Double longitude, @Nullable String pageToken) {

// 3. Google Places Text Search 호출 (동기 처리)
Expand Down