From f0e8c8499b60db16ad64069cd7ca607a209e2d74 Mon Sep 17 00:00:00 2001 From: oroi2009 Date: Thu, 14 Aug 2025 16:23:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20=20feat=20:=20=EA=B7=BC?= =?UTF-8?q?=EC=B2=98=20=EA=B0=80=EA=B2=8C=20=EC=9C=84=EC=B9=98=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/repository/StoreRepository.java | 21 +++++++++++ .../domain/store/service/StoreService.java | 10 ++++++ .../store/service/StoreServiceImpl.java | 36 +++++++++++++++++++ .../store/web/controller/StoreController.java | 31 ++++++++++++++++ .../domain/store/web/dto/NearbyStoresRes.java | 17 +++++++++ 5 files changed, 115 insertions(+) create mode 100644 src/main/java/com/example/Centralthon/domain/store/service/StoreService.java create mode 100644 src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java create mode 100644 src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java create mode 100644 src/main/java/com/example/Centralthon/domain/store/web/dto/NearbyStoresRes.java diff --git a/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java b/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java index d7ea91b..b26546c 100644 --- a/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java +++ b/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java @@ -1,9 +1,30 @@ package com.example.Centralthon.domain.store.repository; +import com.example.Centralthon.domain.menu.entity.Menu; import com.example.Centralthon.domain.store.entity.Store; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; +import java.util.List; + @Repository public interface StoreRepository extends JpaRepository { + @Query(value = "SELECT s.* FROM stores s " + + "WHERE s.latitude BETWEEN :minLat AND :maxLat AND s.longitude BETWEEN :minLng AND :maxLng " + + "AND ST_Distance_Sphere(POINT(s.longitude, s.latitude), POINT(:lng, :lat)) <= 2000 " + + "AND EXISTS ( " + + " SELECT 1 FROM menus m " + + " WHERE m.store_id = s.store_id AND m.quantity > 0 AND m.deadline > NOW())" + , nativeQuery = true) + List findNearbyStores( + double lat, + double lng, + LocalDateTime now, + double minLat, + double maxLat, + double minLng, + double maxLng + ); } diff --git a/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java b/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java new file mode 100644 index 0000000..b8f59cb --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java @@ -0,0 +1,10 @@ +package com.example.Centralthon.domain.store.service; + +import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; + +import java.util.List; + +public interface StoreService { + List nearbyStores(double lat, double lng); + +} diff --git a/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java b/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java new file mode 100644 index 0000000..7ffd7ad --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java @@ -0,0 +1,36 @@ +package com.example.Centralthon.domain.store.service; + +import com.example.Centralthon.domain.store.entity.Store; +import com.example.Centralthon.domain.store.repository.StoreRepository; +import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; +import com.example.Centralthon.global.util.geo.BoundingBox; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.example.Centralthon.global.util.geo.GeoUtils.calculateBoundingBox; + +@Service +@RequiredArgsConstructor +public class StoreServiceImpl implements StoreService { + private final StoreRepository storeRepository; + + @Override + @Transactional(readOnly = true) + public List nearbyStores(double lat, double lng) { + LocalDateTime now = LocalDateTime.now(); + + // 사용자 위치를 기반으로 2km 반경의 Bounding Box 계산 + BoundingBox bbox = calculateBoundingBox(lat, lng, 2.0); + + List stores = storeRepository.findNearbyStores(lat, lng, now, + bbox.minLat(), bbox.maxLat(), bbox.minLng(), bbox.maxLng()); + + return stores.stream() + .map(store -> NearbyStoresRes.from(store)) + .toList(); + } +} diff --git a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java new file mode 100644 index 0000000..e0031d7 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java @@ -0,0 +1,31 @@ +package com.example.Centralthon.domain.store.web.controller; + +import com.example.Centralthon.domain.store.service.StoreService; +import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; +import com.example.Centralthon.global.response.SuccessResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +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 java.util.List; + +@RestController +@RequestMapping("/api/stores") +@RequiredArgsConstructor +public class StoreController { + private final StoreService storeService; + + // 근처 가게 위치 목록 조회 + @GetMapping("") + public ResponseEntity>> nearbyStores( + @RequestParam("lat") Double lat, + @RequestParam("lng") Double lng){ + + List stores = storeService.nearbyStores(lat, lng); + + return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(stores)); + } +} diff --git a/src/main/java/com/example/Centralthon/domain/store/web/dto/NearbyStoresRes.java b/src/main/java/com/example/Centralthon/domain/store/web/dto/NearbyStoresRes.java new file mode 100644 index 0000000..7d2124d --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/web/dto/NearbyStoresRes.java @@ -0,0 +1,17 @@ +package com.example.Centralthon.domain.store.web.dto; + +import com.example.Centralthon.domain.store.entity.Store; + +public record NearbyStoresRes( + Long storeId, + Double lat, + Double lng +) { + public static NearbyStoresRes from(Store store){ + return new NearbyStoresRes( + store.getId(), + store.getLatitude(), + store.getLongitude() + ); + } +} From f07d7d9933dbffdf118ab3fecd0b27bce8be2677 Mon Sep 17 00:00:00 2001 From: oroi2009 Date: Fri, 15 Aug 2025 09:56:58 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20refactor=20:=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=EB=AC=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/repository/StoreRepository.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java b/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java index b26546c..2b52cba 100644 --- a/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java +++ b/src/main/java/com/example/Centralthon/domain/store/repository/StoreRepository.java @@ -4,6 +4,7 @@ import com.example.Centralthon.domain.store.entity.Store; 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; import java.time.LocalDateTime; @@ -11,20 +12,20 @@ @Repository public interface StoreRepository extends JpaRepository { - @Query(value = "SELECT s.* FROM stores s " + - "WHERE s.latitude BETWEEN :minLat AND :maxLat AND s.longitude BETWEEN :minLng AND :maxLng " + - "AND ST_Distance_Sphere(POINT(s.longitude, s.latitude), POINT(:lng, :lat)) <= 2000 " + - "AND EXISTS ( " + - " SELECT 1 FROM menus m " + - " WHERE m.store_id = s.store_id AND m.quantity > 0 AND m.deadline > NOW())" - , nativeQuery = true) + @Query(value = """ + SELECT DISTINCT s.* FROM stores s + JOIN menus m ON m.store_id = s.store_id + WHERE s.latitude BETWEEN :minLat AND :maxLat AND s.longitude BETWEEN :minLng AND :maxLng + AND ST_Distance_Sphere(POINT(s.longitude, s.latitude), POINT(:lng, :lat)) <= 2000 + AND m.quantity > 0 AND m.deadline > :now + """, nativeQuery = true) List findNearbyStores( - double lat, - double lng, - LocalDateTime now, - double minLat, - double maxLat, - double minLng, - double maxLng + @Param("lat") double lat, + @Param("lng") double lng, + @Param("now") LocalDateTime now, + @Param("minLat") double minLat, + @Param("maxLat") double maxLat, + @Param("minLng") double minLng, + @Param("maxLng") double maxLng ); }