From 43af2eaaa50b38d51312755a72257be9e51514e7 Mon Sep 17 00:00:00 2001 From: Sin Ye Rin <91180366+nyeroni@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:47:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=95=99=EA=B5=90=20=EB=A7=9B=EC=A7=91?= =?UTF-8?q?=20kakao=20api=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/KakaoSearchApiController.java | 25 +++++ .../controller/RestaurantApiController.java | 44 -------- .../category/restaurant/domain/Hashtag.java | 23 ---- .../restaurant/domain/Restaurant.java | 44 -------- .../restaurant/dto/KakaoApiDocument.java | 13 +++ .../restaurant/dto/KakaoApiResponse.java | 9 ++ .../restaurant/dto/RestaurantResponse.java | 16 ++- .../restaurant/dto/TopRestaurantResponse.java | 19 ---- .../repository/RestaurantRepository.java | 11 -- .../service/KakaoSearchApiService.java | 62 +++++++++++ .../restaurant/service/RestaurantService.java | 101 ------------------ .../yerong/wedle/common/config/AppConfig.java | 13 +++ 12 files changed, 128 insertions(+), 252 deletions(-) create mode 100644 src/main/java/yerong/wedle/category/restaurant/controller/KakaoSearchApiController.java delete mode 100644 src/main/java/yerong/wedle/category/restaurant/controller/RestaurantApiController.java delete mode 100644 src/main/java/yerong/wedle/category/restaurant/domain/Hashtag.java delete mode 100644 src/main/java/yerong/wedle/category/restaurant/domain/Restaurant.java create mode 100644 src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiDocument.java create mode 100644 src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiResponse.java delete mode 100644 src/main/java/yerong/wedle/category/restaurant/dto/TopRestaurantResponse.java delete mode 100644 src/main/java/yerong/wedle/category/restaurant/repository/RestaurantRepository.java create mode 100644 src/main/java/yerong/wedle/category/restaurant/service/KakaoSearchApiService.java delete mode 100644 src/main/java/yerong/wedle/category/restaurant/service/RestaurantService.java create mode 100644 src/main/java/yerong/wedle/common/config/AppConfig.java diff --git a/src/main/java/yerong/wedle/category/restaurant/controller/KakaoSearchApiController.java b/src/main/java/yerong/wedle/category/restaurant/controller/KakaoSearchApiController.java new file mode 100644 index 0000000..96e0670 --- /dev/null +++ b/src/main/java/yerong/wedle/category/restaurant/controller/KakaoSearchApiController.java @@ -0,0 +1,25 @@ +package yerong.wedle.category.restaurant.controller; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import yerong.wedle.category.restaurant.dto.RestaurantResponse; +import yerong.wedle.category.restaurant.service.KakaoSearchApiService; + +@RequiredArgsConstructor +@RequestMapping("/api/kakao-search") +@RestController +public class KakaoSearchApiController { + + private final KakaoSearchApiService kakaoSearchApiService; + + @GetMapping("/kakao") + public ResponseEntity> kakaoSearchDynamic(@RequestParam String universityName) { + List restaurantResponses = kakaoSearchApiService.searchRestaurant(universityName + " 맛집"); + return ResponseEntity.ok(restaurantResponses); + } +} diff --git a/src/main/java/yerong/wedle/category/restaurant/controller/RestaurantApiController.java b/src/main/java/yerong/wedle/category/restaurant/controller/RestaurantApiController.java deleted file mode 100644 index d501112..0000000 --- a/src/main/java/yerong/wedle/category/restaurant/controller/RestaurantApiController.java +++ /dev/null @@ -1,44 +0,0 @@ -package yerong.wedle.category.restaurant.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import yerong.wedle.category.restaurant.dto.RestaurantResponse; -import yerong.wedle.category.restaurant.dto.TopRestaurantResponse; -import yerong.wedle.category.restaurant.service.RestaurantService; - -import java.util.List; - -@Tag(name = "Restaurant API", description = "대학교 주변 맛집 정보 관련 API") -@RequiredArgsConstructor -@RestController -@RequestMapping("/api/restaurants") -public class RestaurantApiController { - - private final RestaurantService restaurantService; - - @Operation( - summary = "대학교 주변 식당 조회", - description = "대학교 ID를 이용해 해당 대학교 주변의 식당 목록을 조회합니다." - ) - @GetMapping - public ResponseEntity> getRestaurantsByUniversityId(@RequestParam Long universityId) { - List restaurantResponses = restaurantService.getRestaurantsByUniversityId(universityId); - return ResponseEntity.ok(restaurantResponses); - } - - @Operation( - summary = "학교별 1위 맛집 조회", - description = "모든 학교별 1위 맛집을 조회합니다." - ) - @GetMapping("/top") - public ResponseEntity> getTopRestaurants() { - List topRestaurants = restaurantService.getTopRestaurants(); - return ResponseEntity.ok(topRestaurants); - } -} diff --git a/src/main/java/yerong/wedle/category/restaurant/domain/Hashtag.java b/src/main/java/yerong/wedle/category/restaurant/domain/Hashtag.java deleted file mode 100644 index a827c02..0000000 --- a/src/main/java/yerong/wedle/category/restaurant/domain/Hashtag.java +++ /dev/null @@ -1,23 +0,0 @@ -package yerong.wedle.category.restaurant.domain; - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@Getter -@Entity -public class Hashtag { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "hashtag_id") - private Long hashtagId; - - @Column(nullable = false) - private String name; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "restaurant_id") - private Restaurant restaurant; -} \ No newline at end of file diff --git a/src/main/java/yerong/wedle/category/restaurant/domain/Restaurant.java b/src/main/java/yerong/wedle/category/restaurant/domain/Restaurant.java deleted file mode 100644 index 2022f8f..0000000 --- a/src/main/java/yerong/wedle/category/restaurant/domain/Restaurant.java +++ /dev/null @@ -1,44 +0,0 @@ -package yerong.wedle.category.restaurant.domain; - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; -import yerong.wedle.university.domain.University; - -import java.util.ArrayList; -import java.util.List; - -import static jakarta.persistence.FetchType.LAZY; -import static jakarta.persistence.GenerationType.IDENTITY; -import static lombok.AccessLevel.PROTECTED; - -@NoArgsConstructor(access = PROTECTED) -@Getter -@Entity -public class Restaurant { - - @Id - @GeneratedValue(strategy = IDENTITY) - @Column(name = "restaurant_id") - private Long restaurantId; - - @Column(nullable = false) - private String name; - - @Column(nullable = false) - private String location; - - private String placeUrl; - - @Column(nullable = false) - private int ranking; - - @ManyToOne(fetch = LAZY) - @JoinColumn(name = "university_id") - private University university; - - @OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL, orphanRemoval = true) - private List hashtags = new ArrayList<>(); - - private String imageUrl; -} diff --git a/src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiDocument.java b/src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiDocument.java new file mode 100644 index 0000000..e56ce46 --- /dev/null +++ b/src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiDocument.java @@ -0,0 +1,13 @@ +package yerong.wedle.category.restaurant.dto; + +import lombok.Data; + +@Data +public class KakaoApiDocument { + private String place_name; + private String road_address_name; + private String address_name; + private String phone; + private String category_name; + private String place_url; +} diff --git a/src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiResponse.java b/src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiResponse.java new file mode 100644 index 0000000..2927237 --- /dev/null +++ b/src/main/java/yerong/wedle/category/restaurant/dto/KakaoApiResponse.java @@ -0,0 +1,9 @@ +package yerong.wedle.category.restaurant.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class KakaoApiResponse { + private List documents; +} \ No newline at end of file diff --git a/src/main/java/yerong/wedle/category/restaurant/dto/RestaurantResponse.java b/src/main/java/yerong/wedle/category/restaurant/dto/RestaurantResponse.java index 97a0e8c..c05e23f 100644 --- a/src/main/java/yerong/wedle/category/restaurant/dto/RestaurantResponse.java +++ b/src/main/java/yerong/wedle/category/restaurant/dto/RestaurantResponse.java @@ -1,24 +1,20 @@ package yerong.wedle.category.restaurant.dto; -import jakarta.persistence.*; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import yerong.wedle.category.restaurant.domain.Hashtag; -import yerong.wedle.university.domain.University; -import java.util.ArrayList; -import java.util.List; - -import static jakarta.persistence.FetchType.LAZY; @Getter @AllArgsConstructor @NoArgsConstructor +@Builder public class RestaurantResponse { private String name; - private String location; + private String roadAddressName; + private String addressName; + private String phone; + private String categoryName; private String placeUrl; - private List hashtags; - private String imageUrl; } diff --git a/src/main/java/yerong/wedle/category/restaurant/dto/TopRestaurantResponse.java b/src/main/java/yerong/wedle/category/restaurant/dto/TopRestaurantResponse.java deleted file mode 100644 index 28edf16..0000000 --- a/src/main/java/yerong/wedle/category/restaurant/dto/TopRestaurantResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package yerong.wedle.category.restaurant.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class TopRestaurantResponse { - private String name; - private String location; - private String placeUrl; - private List hashtags; - private String imageUrl; - private String topMessage; -} diff --git a/src/main/java/yerong/wedle/category/restaurant/repository/RestaurantRepository.java b/src/main/java/yerong/wedle/category/restaurant/repository/RestaurantRepository.java deleted file mode 100644 index e26f995..0000000 --- a/src/main/java/yerong/wedle/category/restaurant/repository/RestaurantRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package yerong.wedle.category.restaurant.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import yerong.wedle.category.restaurant.domain.Restaurant; -import yerong.wedle.university.domain.University; - -import java.util.List; - -public interface RestaurantRepository extends JpaRepository { - List findByUniversity(University university); -} diff --git a/src/main/java/yerong/wedle/category/restaurant/service/KakaoSearchApiService.java b/src/main/java/yerong/wedle/category/restaurant/service/KakaoSearchApiService.java new file mode 100644 index 0000000..277ed8b --- /dev/null +++ b/src/main/java/yerong/wedle/category/restaurant/service/KakaoSearchApiService.java @@ -0,0 +1,62 @@ +package yerong.wedle.category.restaurant.service; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import yerong.wedle.category.restaurant.dto.RestaurantResponse; +import yerong.wedle.category.restaurant.dto.KakaoApiDocument; +import yerong.wedle.category.restaurant.dto.KakaoApiResponse; + +@Service +@RequiredArgsConstructor +public class KakaoSearchApiService { + + @Value("${kakao.client-id}") + private String KAKAO_CLIENT_ID; + + private final RestTemplate restTemplate; + + public List searchRestaurant(String query) { + String url = "https://dapi.kakao.com/v2/local/search/keyword.json?query=" + query + "&size=15"; + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + KAKAO_CLIENT_ID); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, KakaoApiResponse.class); + + List restaurantResponses = new ArrayList<>(); + + if (response.getBody() != null && response.getBody().getDocuments() != null) { + for (KakaoApiDocument document : response.getBody().getDocuments()) { + String fullCategoryName = document.getCategory_name(); + String lastCategory = null; + + if (fullCategoryName != null && !fullCategoryName.isEmpty()) { + String[] categories = fullCategoryName.split(" > "); + if (categories.length > 0) { + lastCategory = categories[categories.length - 1]; // 마지막 부분 + } + } + + restaurantResponses.add(RestaurantResponse.builder() + .name(document.getPlace_name()) + .roadAddressName(document.getRoad_address_name()) + .addressName(document.getAddress_name()) + .categoryName(lastCategory) + .phone(document.getPhone()) + .placeUrl(document.getPlace_url()) + .build()); + } + } + + return restaurantResponses; // 맛집 정보 반환 + } +} diff --git a/src/main/java/yerong/wedle/category/restaurant/service/RestaurantService.java b/src/main/java/yerong/wedle/category/restaurant/service/RestaurantService.java deleted file mode 100644 index 6f5bcf8..0000000 --- a/src/main/java/yerong/wedle/category/restaurant/service/RestaurantService.java +++ /dev/null @@ -1,101 +0,0 @@ -package yerong.wedle.category.restaurant.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import yerong.wedle.category.restaurant.domain.Hashtag; -import yerong.wedle.category.restaurant.domain.Restaurant; -import yerong.wedle.category.restaurant.dto.RestaurantResponse; -import yerong.wedle.category.restaurant.dto.TopRestaurantResponse; -import yerong.wedle.category.restaurant.exception.RestaurantNotFoundException; -import yerong.wedle.category.restaurant.repository.RestaurantRepository; -import yerong.wedle.university.domain.University; -import yerong.wedle.university.exception.UniversityNotFoundException; -import yerong.wedle.university.repository.UniversityRepository; - -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -@RequiredArgsConstructor -@Service -public class RestaurantService { - - private final RestaurantRepository restaurantRepository; - private final UniversityRepository universityRepository; - - @Transactional - public List getRestaurantsByUniversityId(Long universityId) { - University university = universityRepository.findById(universityId) - .orElseThrow(UniversityNotFoundException::new); - List restaurants = restaurantRepository.findByUniversity(university) - .stream() - .sorted(Comparator.comparingInt(Restaurant::getRanking)) - .collect(Collectors.toList()); - - return restaurants.stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - private RestaurantResponse convertToDto(Restaurant restaurant) { - List hashtags = restaurant.getHashtags().stream() - .map(Hashtag::getName) - .collect(Collectors.toList()); - - return new RestaurantResponse( - restaurant.getName(), - restaurant.getLocation(), - restaurant.getPlaceUrl(), - hashtags, - restaurant.getImageUrl() - ); - } - - @Transactional - public List getTopRestaurants() { - List universities = universityRepository.findAll(); - - return universities.stream() - .map(this::findTopRestaurantForUniversity) - .filter(topRestaurant -> topRestaurant != null) // null 체크하여 결과에서 제외 - .collect(Collectors.toList()); - } - - private TopRestaurantResponse findTopRestaurantForUniversity(University university) { - List restaurants = restaurantRepository.findByUniversity(university); - - if (restaurants.isEmpty()) { - return null; - } - - // 순위가 1인 식당만 찾기 - Restaurant topRestaurant = restaurants.stream() - .filter(restaurant -> restaurant.getRanking() == 1) - .findFirst() - .orElse(null); // 없으면 null 반환 - - if (topRestaurant == null) { - return null; // 순위가 1인 식당이 없으면 null 반환 - } - - return convertToTopDto(topRestaurant, university.getName()); - } - - private TopRestaurantResponse convertToTopDto(Restaurant restaurant, String universityName) { - List hashtags = restaurant.getHashtags().stream() - .map(Hashtag::getName) - .collect(Collectors.toList()); - - String topMessage = universityName + " 1등 맛집"; - - return new TopRestaurantResponse( - restaurant.getName(), - restaurant.getLocation(), - restaurant.getPlaceUrl(), - hashtags, - restaurant.getImageUrl(), - topMessage - ); - } -} \ No newline at end of file diff --git a/src/main/java/yerong/wedle/common/config/AppConfig.java b/src/main/java/yerong/wedle/common/config/AppConfig.java new file mode 100644 index 0000000..519c8b0 --- /dev/null +++ b/src/main/java/yerong/wedle/common/config/AppConfig.java @@ -0,0 +1,13 @@ +package yerong.wedle.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class AppConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +}