From db965ebf814e87a1019c22688f493e81c90dce70 Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Fri, 9 Jan 2026 14:23:19 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat=20:=20=EA=B1=B0=EB=A6=AC=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EC=BF=BC=EB=A6=AC=20PartySearchRepository=EB=A1=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20(=EA=B1=B0=EB=A6=AC=EC=88=9C,=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=95,=20=EA=B2=80=EC=83=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/repository/PartyRepository.java | 8 +--- .../repository/PartySearchRepository.java | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java index c8d9470..fc92205 100644 --- a/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java +++ b/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java @@ -1,16 +1,14 @@ package ita.tinybite.domain.party.repository; -import io.lettuce.core.dynamic.annotation.Param; import ita.tinybite.domain.party.entity.Party; import ita.tinybite.domain.party.enums.PartyCategory; import java.util.List; import java.util.Optional; import ita.tinybite.domain.party.enums.PartyStatus; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -24,8 +22,4 @@ public interface PartyRepository extends JpaRepository { List findByPickupLocation_PlaceAndCategory(String place, PartyCategory category); List findByHostUserIdAndStatus(Long userId, PartyStatus partyStatus); - - Page findByTitleContaining(String title, Pageable pageable); - - Page findByTitleContainingAndCategory(String title, PartyCategory category, Pageable pageable); } diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java new file mode 100644 index 0000000..76c462d --- /dev/null +++ b/src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java @@ -0,0 +1,48 @@ +package ita.tinybite.domain.party.repository; + +import ita.tinybite.domain.party.entity.Party; +import ita.tinybite.domain.party.enums.PartyCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface PartySearchRepository extends JpaRepository { + + Page findByTitleContaining(String title, Pageable pageable); + + Page findByTitleContainingAndCategory(String title, PartyCategory category, Pageable pageable); + + @Query(value = """ + SELECT p.* + FROM party p + WHERE p.title LIKE CONCAT('%', :title, '%') + ORDER BY (6371000 * acos( + cos(radians(:lat)) * cos(radians(p.pickup_latitude)) + * cos(radians(p.pickup_longitude) - radians(:lon)) + + sin(radians(:lat)) * sin(radians(p.pickup_latitude)))) + """, countQuery = """ + SELECT COUNT(*) + FROM party p + WHERE p.title LIKE CONCAT('%', :title, '%') + """, nativeQuery = true) + Page findByTitleContainingWithDistance(String title, @Param("lat") Double lat, @Param("lon") Double lon, Pageable pageable); + + @Query(value = """ + SELECT p.* + FROM party p + WHERE p. + ORDER BY (6371000 * acos( + cos(radians(:lat)) * cos(radians(p.pickup_latitude)) + * cos(radians(p.pickup_longitude) - radians(:lon)) + + sin(radians(:lat)) * sin(radians(p.pickup_latitude)))) + """, countQuery = """ + SELECT COUNT(*) + FROM party p + WHERE p.title LIKE CONCAT('%', :title, '%') + """, nativeQuery = true) + Page findByTitleContainingAndCategoryWithDistance(String q, Double lat, Double lon, PartyCategory category, Pageable pageable); +} From 25059b2936bc94904a4040bd3a1fd57fc859ca3b Mon Sep 17 00:00:00 2001 From: Wonjae Lim Date: Fri, 9 Jan 2026 14:23:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat=20:=20=EA=B1=B0=EB=A6=AC=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/controller/PartyController.java | 7 +- .../party/service/PartySearchService.java | 68 +++++++++++++------ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/main/java/ita/tinybite/domain/party/controller/PartyController.java b/src/main/java/ita/tinybite/domain/party/controller/PartyController.java index fde9243..7968ab8 100644 --- a/src/main/java/ita/tinybite/domain/party/controller/PartyController.java +++ b/src/main/java/ita/tinybite/domain/party/controller/PartyController.java @@ -491,10 +491,13 @@ public APIResponse getParty( schema = @Schema(allowableValues = {"ALL", "DELIVERY", "GROCERY", "HOUSEHOLD"}) ) @RequestParam(defaultValue = "ALL") PartyCategory category, + @RequestParam(required = false, name = "lat") Double userLat, + @RequestParam(required = false, name = "lon") Double userLon, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size ) { - return APIResponse.success(partySearchService.searchParty(q, category, page, size)); + + return APIResponse.success(partySearchService.searchParty(q, category, page, size, userLat, userLon)); } @Operation( @@ -512,7 +515,7 @@ public APIResponse> getRecentLog() { @Operation( summary = "특정 최근 검색어 삭제", description = """ - 최근 검색어에서 특정 검색어를 삭제합니다.
+ 최근 검색어에서 특정 검색어를 삭제합니다.
이때 검색어에 대한 Id값은 없고, 최근 검색어 자체를 keyword에 넣어주시면 됩니다. """ ) diff --git a/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java b/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java index 0cf87c2..ce76ecb 100644 --- a/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java +++ b/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java @@ -7,7 +7,8 @@ import ita.tinybite.domain.party.enums.ParticipantStatus; import ita.tinybite.domain.party.enums.PartyCategory; import ita.tinybite.domain.party.repository.PartyParticipantRepository; -import ita.tinybite.domain.party.repository.PartyRepository; +import ita.tinybite.domain.party.repository.PartySearchRepository; +import ita.tinybite.global.util.DistanceCalculator; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -23,7 +24,7 @@ @RequiredArgsConstructor public class PartySearchService { - private final PartyRepository partyRepository; + private final PartySearchRepository partySearchRepository; private final PartyParticipantRepository participantRepository; private final StringRedisTemplate redisTemplate; private final SecurityProvider securityProvider; @@ -35,7 +36,7 @@ private String key(Long userId) { } // 파티 검색 조회 - public PartyQueryListResponse searchParty(String q, PartyCategory category, int page, int size) { + public PartyQueryListResponse searchParty(String q, PartyCategory category, int page, int size, Double lat, Double lon) { Long userId = securityProvider.getCurrentUser().getUserId(); // recent_search:{userId} @@ -45,24 +46,49 @@ public PartyQueryListResponse searchParty(String q, PartyCategory category, int redisTemplate.opsForZSet().add(key, q, System.currentTimeMillis()); Pageable pageable = PageRequest.of(page, size); - - // category가 없을 시에는 ALL로 처리 - Page result = (category == null || category == PartyCategory.ALL) - ? partyRepository.findByTitleContaining(q, pageable) - : partyRepository.findByTitleContainingAndCategory(q, category, pageable); - - List partyCardResponseList = result.stream() - .map(party -> { - int currentParticipants = participantRepository - .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); - return PartyCardResponse.from(party, currentParticipants); - }) - .toList(); - - return PartyQueryListResponse.builder() - .parties(partyCardResponseList) - .hasNext(result.hasNext()) - .build(); + List partyCardResponseList; + + // 거리 정보 X + if(lat == null || lon == null) { + // category가 없을 시에는 ALL로 처리 + Page queryResults = (category == null || category == PartyCategory.ALL) + ? partySearchRepository.findByTitleContaining(q, pageable) + : partySearchRepository.findByTitleContainingAndCategory(q, category, pageable); + + partyCardResponseList = queryResults.stream() + .map(party -> { + int currentParticipants = participantRepository + .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); + return PartyCardResponse.from(party, currentParticipants); + }) + .toList(); + + return PartyQueryListResponse.builder() + .parties(partyCardResponseList) + .hasNext(queryResults.hasNext()) + .build(); + } else { + // 거리 정보 O (lat, lon) + Page queryResults = (category == null || category == PartyCategory.ALL) + ? partySearchRepository.findByTitleContainingWithDistance(q, lat, lon, pageable) + : partySearchRepository.findByTitleContainingAndCategoryWithDistance(q, lat, lon, category, pageable); + + partyCardResponseList = queryResults.stream() + .map(party -> { + int currentParticipants = participantRepository + .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); + PartyCardResponse res = PartyCardResponse.from(party, currentParticipants); + Double distance = DistanceCalculator.calculateDistance(lat, lon, party.getPickupLocation().getPickupLatitude(), party.getPickupLocation().getPickupLongitude()); + res.addDistanceKm(distance); + return res; + }) + .toList(); + + return PartyQueryListResponse.builder() + .parties(partyCardResponseList) + .hasNext(queryResults.hasNext()) + .build(); + } }