From 8c3d493923cabaa4c850aff744710ba253c1cf70 Mon Sep 17 00:00:00 2001 From: answl Date: Wed, 25 Feb 2026 21:06:18 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EA=B8=B0=EB=B0=98=20top10=20=EA=B0=80?= =?UTF-8?q?=EB=A7=B9=EC=A0=90=2010=EA=B0=9C=20=EC=B6=94=EC=B6=9C=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/map/controller/MapController.java | 32 ++++++++ .../server/map/dto/NearbySearchRequest.java | 10 +++ .../server/map/dto/NearbySearchResponse.java | 13 +++ .../map/service/NearbyMerchantService.java | 82 +++++++++++++++++++ .../merchant/entity/MerchantLocation.java | 42 ++++++++++ .../MerchantLocationRepository.java | 20 +++++ .../server/security/SecurityConfig.java | 3 +- 7 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/payper/server/map/controller/MapController.java create mode 100644 src/main/java/com/payper/server/map/dto/NearbySearchRequest.java create mode 100644 src/main/java/com/payper/server/map/dto/NearbySearchResponse.java create mode 100644 src/main/java/com/payper/server/map/service/NearbyMerchantService.java create mode 100644 src/main/java/com/payper/server/merchant/entity/MerchantLocation.java create mode 100644 src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java diff --git a/src/main/java/com/payper/server/map/controller/MapController.java b/src/main/java/com/payper/server/map/controller/MapController.java new file mode 100644 index 0000000..f306e9b --- /dev/null +++ b/src/main/java/com/payper/server/map/controller/MapController.java @@ -0,0 +1,32 @@ +package com.payper.server.map.controller; + +import com.payper.server.global.response.ApiResponse; +import com.payper.server.map.dto.NearbySearchRequest; +import com.payper.server.map.dto.NearbySearchResponse; +import com.payper.server.map.service.NearbyMerchantService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/map") +@RequiredArgsConstructor +public class MapController { + + private final NearbyMerchantService nearbyMerchantService; + + @GetMapping("/nearby-top10") + public ApiResponse> getNearbyTop10( + @Valid @ModelAttribute NearbySearchRequest request) { + + List result = nearbyMerchantService.searchNearbyTop10( + request.latitude(), request.longitude(), request.radiusKm() + ); + return ApiResponse.ok(result); + } +} diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java new file mode 100644 index 0000000..19eb6a4 --- /dev/null +++ b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java @@ -0,0 +1,10 @@ +package com.payper.server.map.dto; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; + +public record NearbySearchRequest( + @NotNull Double latitude, + @NotNull Double longitude, + @NotNull @DecimalMin(value = "0.0", inclusive = false) Double radiusKm +) {} diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java new file mode 100644 index 0000000..c440050 --- /dev/null +++ b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java @@ -0,0 +1,13 @@ +package com.payper.server.map.dto; + +public class NearbySearchResponse { + + public record NearbyPlaceItem( + int merchantRank, + String merchantName, + String placeName, + double latitude, + double longitude, + double distanceKm + ) {} +} diff --git a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java new file mode 100644 index 0000000..899f7f6 --- /dev/null +++ b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java @@ -0,0 +1,82 @@ +package com.payper.server.map.service; + +import com.payper.server.map.dto.NearbySearchResponse; +import com.payper.server.merchant.entity.MerchantLocation; +import com.payper.server.merchant.repository.MerchantLocationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class NearbyMerchantService { + + private static final int MAX_RESULTS = 10; + private static final double EARTH_RADIUS_KM = 6371.0; + + private static final List TOP10_MERCHANTS = List.of( + "스타벅스", "맥도날드", "CGV", "올리브영", "다이소", + "GS25", "CU", "이디야커피", "파리바게뜨", "교촌치킨" + ); + + private final MerchantLocationRepository merchantLocationRepository; + + public List searchNearbyTop10( + double userLat, double userLng, double radiusKm) { + + Map> byMerchant = + merchantLocationRepository.findByMerchantNames(TOP10_MERCHANTS) + .stream() + .collect(Collectors.groupingBy(ml -> ml.getMerchant().getName())); + + List results = new ArrayList<>(); + + for (int i = 0; i < TOP10_MERCHANTS.size() && results.size() < MAX_RESULTS; i++) { + String merchantName = TOP10_MERCHANTS.get(i); + List locations = byMerchant.get(merchantName); + if (locations == null) continue; + + int rank = i + 1; + int remaining = MAX_RESULTS - results.size(); + + locations.stream() + .map(ml -> { + double dist = haversine(userLat, userLng, ml.getLatitude(), ml.getLongitude()); + return new NearbySearchResponse.NearbyPlaceItem( + rank, + merchantName, + ml.getPlaceName(), + ml.getLatitude(), + ml.getLongitude(), + dist + ); + }) + .filter(item -> item.distanceKm() <= radiusKm) + .sorted(Comparator.comparingDouble(NearbySearchResponse.NearbyPlaceItem::distanceKm)) + .limit(remaining) + .forEach(results::add); + } + + return results; + } + + /** + * Haversine 공식으로 두 좌표 간 거리(km)를 계산한다. + */ + private double haversine(double lat1, double lon1, double lat2, double lon2) { + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return EARTH_RADIUS_KM * c; + } +} diff --git a/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java b/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java new file mode 100644 index 0000000..ee26f41 --- /dev/null +++ b/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java @@ -0,0 +1,42 @@ +package com.payper.server.merchant.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MerchantLocation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "merchant_id", nullable = false) + private Merchant merchant; + + /** + * 지점명 (예: 스타벅스 강남점) + */ + @Column(nullable = false) + private String placeName; + + @Column(nullable = false) + private Double latitude; + + @Column(nullable = false) + private Double longitude; + + public static MerchantLocation create(Merchant merchant, String placeName, + Double latitude, Double longitude) { + return MerchantLocation.builder() + .merchant(merchant) + .placeName(placeName) + .latitude(latitude) + .longitude(longitude) + .build(); + } +} diff --git a/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java b/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java new file mode 100644 index 0000000..a2b2a00 --- /dev/null +++ b/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java @@ -0,0 +1,20 @@ +package com.payper.server.merchant.repository; + +import com.payper.server.merchant.entity.MerchantLocation; +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.util.List; + +@Repository +public interface MerchantLocationRepository extends JpaRepository { + + @Query(""" + select ml from MerchantLocation ml + join fetch ml.merchant m + where m.name in :merchantNames + """) + List findByMerchantNames(@Param("merchantNames") List merchantNames); +} diff --git a/src/main/java/com/payper/server/security/SecurityConfig.java b/src/main/java/com/payper/server/security/SecurityConfig.java index 0e44954..f2707a2 100644 --- a/src/main/java/com/payper/server/security/SecurityConfig.java +++ b/src/main/java/com/payper/server/security/SecurityConfig.java @@ -53,7 +53,8 @@ void init() { requestMatcher.matcher(HttpMethod.GET, "/api/v1/posts/**"), requestMatcher.matcher(HttpMethod.GET, "/api/v1/comments/*/replies"), requestMatcher.matcher(HttpMethod.GET, "/api/v1/merchants/**"), - requestMatcher.matcher(HttpMethod.GET, "/api/v1/categories/**") + requestMatcher.matcher(HttpMethod.GET, "/api/v1/categories/**"), + requestMatcher.matcher(HttpMethod.GET, "/api/v1/map/**") ); // 인증이 필요한 요청 authenticatedRequestMatcher = new OrRequestMatcher( From 76033d13003dc847f907b22e9e5a2bcc90648543 Mon Sep 17 00:00:00 2001 From: answl Date: Wed, 25 Feb 2026 21:33:28 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20gemini=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../map/service/NearbyMerchantService.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java index 899f7f6..2387cba 100644 --- a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java +++ b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java @@ -47,20 +47,21 @@ public List searchNearbyTop10( int remaining = MAX_RESULTS - results.size(); locations.stream() - .map(ml -> { - double dist = haversine(userLat, userLng, ml.getLatitude(), ml.getLongitude()); - return new NearbySearchResponse.NearbyPlaceItem( - rank, - merchantName, - ml.getPlaceName(), - ml.getLatitude(), - ml.getLongitude(), - dist - ); + .map(ml -> new Object() { + final MerchantLocation location = ml; + final double distance = haversine(userLat, userLng, ml.getLatitude(), ml.getLongitude()); }) - .filter(item -> item.distanceKm() <= radiusKm) - .sorted(Comparator.comparingDouble(NearbySearchResponse.NearbyPlaceItem::distanceKm)) + .filter(item -> item.distance <= radiusKm) + .sorted(Comparator.comparingDouble(item -> item.distance)) .limit(remaining) + .map(item -> new NearbySearchResponse.NearbyPlaceItem( + rank, + merchantName, + item.location.getPlaceName(), + item.location.getLatitude(), + item.location.getLongitude(), + item.distance + )) .forEach(results::add); } From 98096d7eabc7f79dfa2fcdc55e9f785085e9b1e6 Mon Sep 17 00:00:00 2001 From: answl Date: Wed, 25 Feb 2026 21:42:40 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20gemini=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 데이터베이스 수준에서 1차적인 필터링 진행 (Bounding Box) --- .../map/service/NearbyMerchantService.java | 12 +++++++++++- .../repository/MerchantLocationRepository.java | 16 +++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java index 2387cba..c4ce005 100644 --- a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java +++ b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java @@ -31,8 +31,18 @@ public class NearbyMerchantService { public List searchNearbyTop10( double userLat, double userLng, double radiusKm) { + // 위도 1도는 약 111km, 경도 1도는 cos(위도) * 111km + double latChange = radiusKm / 111.0; + double lonChange = radiusKm / (111.0 * Math.cos(Math.toRadians(userLat))); + + double minLat = userLat - latChange; + double maxLat = userLat + latChange; + double minLon = userLng - lonChange; + double maxLon = userLng + lonChange; + + Map> byMerchant = - merchantLocationRepository.findByMerchantNames(TOP10_MERCHANTS) + merchantLocationRepository.findByMerchantNamesInBoundingBox(TOP10_MERCHANTS, minLat, maxLat, minLon, maxLon) .stream() .collect(Collectors.groupingBy(ml -> ml.getMerchant().getName())); diff --git a/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java b/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java index a2b2a00..1a740b7 100644 --- a/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java +++ b/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java @@ -12,9 +12,15 @@ public interface MerchantLocationRepository extends JpaRepository { @Query(""" - select ml from MerchantLocation ml - join fetch ml.merchant m - where m.name in :merchantNames - """) - List findByMerchantNames(@Param("merchantNames") List merchantNames); + select ml from MerchantLocation ml + join fetch ml.merchant m + where m.name in :merchantNames + and ml.latitude between :minLat and :maxLat + and ml.longitude between :minLon and :maxLon +""") + List findByMerchantNamesInBoundingBox( + @Param("merchantNames") List merchantNames, + @Param("minLat") double minLat, @Param("maxLat") double maxLat, + @Param("minLon") double minLon, @Param("maxLon") double maxLon + ); } From 2031c1c89a91e91f679d9c1cdcd9d2a2a6de2fa8 Mon Sep 17 00:00:00 2001 From: answl Date: Thu, 26 Feb 2026 18:11:48 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refact:=20spotlessApply=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=ED=9B=84=20=EB=B3=80=EB=8F=99=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/map/controller/MapController.java | 8 ++--- .../server/map/dto/NearbySearchRequest.java | 5 ++-- .../server/map/dto/NearbySearchResponse.java | 3 +- .../map/service/NearbyMerchantService.java | 30 ++++++++----------- .../merchant/entity/MerchantLocation.java | 7 ++--- .../MerchantLocationRepository.java | 10 +++---- .../server/security/SecurityConfig.java | 5 ++-- 7 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/payper/server/map/controller/MapController.java b/src/main/java/com/payper/server/map/controller/MapController.java index f306e9b..95be5e1 100644 --- a/src/main/java/com/payper/server/map/controller/MapController.java +++ b/src/main/java/com/payper/server/map/controller/MapController.java @@ -5,14 +5,13 @@ import com.payper.server.map.dto.NearbySearchResponse; import com.payper.server.map.service.NearbyMerchantService; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @RequestMapping("/api/v1/map") @RequiredArgsConstructor @@ -24,9 +23,8 @@ public class MapController { public ApiResponse> getNearbyTop10( @Valid @ModelAttribute NearbySearchRequest request) { - List result = nearbyMerchantService.searchNearbyTop10( - request.latitude(), request.longitude(), request.radiusKm() - ); + List result = + nearbyMerchantService.searchNearbyTop10(request.latitude(), request.longitude(), request.radiusKm()); return ApiResponse.ok(result); } } diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java index 19eb6a4..76c985a 100644 --- a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java +++ b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java @@ -6,5 +6,6 @@ public record NearbySearchRequest( @NotNull Double latitude, @NotNull Double longitude, - @NotNull @DecimalMin(value = "0.0", inclusive = false) Double radiusKm -) {} + + @NotNull @DecimalMin(value = "0.0", inclusive = false) + Double radiusKm) {} diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java index c440050..30ce80d 100644 --- a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java +++ b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java @@ -8,6 +8,5 @@ public record NearbyPlaceItem( String placeName, double latitude, double longitude, - double distanceKm - ) {} + double distanceKm) {} } diff --git a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java index c4ce005..b730465 100644 --- a/src/main/java/com/payper/server/map/service/NearbyMerchantService.java +++ b/src/main/java/com/payper/server/map/service/NearbyMerchantService.java @@ -3,15 +3,14 @@ import com.payper.server.map.dto.NearbySearchResponse; import com.payper.server.merchant.entity.MerchantLocation; import com.payper.server.merchant.repository.MerchantLocationRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @Transactional(readOnly = true) @@ -21,10 +20,8 @@ public class NearbyMerchantService { private static final int MAX_RESULTS = 10; private static final double EARTH_RADIUS_KM = 6371.0; - private static final List TOP10_MERCHANTS = List.of( - "스타벅스", "맥도날드", "CGV", "올리브영", "다이소", - "GS25", "CU", "이디야커피", "파리바게뜨", "교촌치킨" - ); + private static final List TOP10_MERCHANTS = + List.of("스타벅스", "맥도날드", "CGV", "올리브영", "다이소", "GS25", "CU", "이디야커피", "파리바게뜨", "교촌치킨"); private final MerchantLocationRepository merchantLocationRepository; @@ -40,9 +37,9 @@ public List searchNearbyTop10( double minLon = userLng - lonChange; double maxLon = userLng + lonChange; - Map> byMerchant = - merchantLocationRepository.findByMerchantNamesInBoundingBox(TOP10_MERCHANTS, minLat, maxLat, minLon, maxLon) + merchantLocationRepository + .findByMerchantNamesInBoundingBox(TOP10_MERCHANTS, minLat, maxLat, minLon, maxLon) .stream() .collect(Collectors.groupingBy(ml -> ml.getMerchant().getName())); @@ -70,23 +67,22 @@ public List searchNearbyTop10( item.location.getPlaceName(), item.location.getLatitude(), item.location.getLongitude(), - item.distance - )) + item.distance)) .forEach(results::add); } return results; } - /** - * Haversine 공식으로 두 좌표 간 거리(km)를 계산한다. - */ + /** Haversine 공식으로 두 좌표 간 거리(km)를 계산한다. */ private double haversine(double lat1, double lon1, double lat2, double lon2) { double dLat = Math.toRadians(lat2 - lat1); double dLon = Math.toRadians(lon2 - lon1); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) - + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) - * Math.sin(dLon / 2) * Math.sin(dLon / 2); + + Math.cos(Math.toRadians(lat1)) + * Math.cos(Math.toRadians(lat2)) + * Math.sin(dLon / 2) + * Math.sin(dLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_KM * c; } diff --git a/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java b/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java index ee26f41..8d0884c 100644 --- a/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java +++ b/src/main/java/com/payper/server/merchant/entity/MerchantLocation.java @@ -18,9 +18,7 @@ public class MerchantLocation { @JoinColumn(name = "merchant_id", nullable = false) private Merchant merchant; - /** - * 지점명 (예: 스타벅스 강남점) - */ + /** 지점명 (예: 스타벅스 강남점) */ @Column(nullable = false) private String placeName; @@ -30,8 +28,7 @@ public class MerchantLocation { @Column(nullable = false) private Double longitude; - public static MerchantLocation create(Merchant merchant, String placeName, - Double latitude, Double longitude) { + public static MerchantLocation create(Merchant merchant, String placeName, Double latitude, Double longitude) { return MerchantLocation.builder() .merchant(merchant) .placeName(placeName) diff --git a/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java b/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java index 1a740b7..c19cd5b 100644 --- a/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java +++ b/src/main/java/com/payper/server/merchant/repository/MerchantLocationRepository.java @@ -1,13 +1,12 @@ package com.payper.server.merchant.repository; import com.payper.server.merchant.entity.MerchantLocation; +import java.util.List; 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.util.List; - @Repository public interface MerchantLocationRepository extends JpaRepository { @@ -20,7 +19,8 @@ public interface MerchantLocationRepository extends JpaRepository findByMerchantNamesInBoundingBox( @Param("merchantNames") List merchantNames, - @Param("minLat") double minLat, @Param("maxLat") double maxLat, - @Param("minLon") double minLon, @Param("maxLon") double maxLon - ); + @Param("minLat") double minLat, + @Param("maxLat") double maxLat, + @Param("minLon") double minLon, + @Param("maxLon") double maxLon); } diff --git a/src/main/java/com/payper/server/security/SecurityConfig.java b/src/main/java/com/payper/server/security/SecurityConfig.java index 221d5df..950a36b 100644 --- a/src/main/java/com/payper/server/security/SecurityConfig.java +++ b/src/main/java/com/payper/server/security/SecurityConfig.java @@ -53,9 +53,8 @@ void init() { requestMatcher.matcher(HttpMethod.GET, "/api/v1/comments/*/replies"), requestMatcher.matcher(HttpMethod.GET, "/api/v1/merchants/**"), requestMatcher.matcher(HttpMethod.GET, "/api/v1/categories/**"), - requestMatcher.matcher(HttpMethod.GET, "/api/v1/map/**") - ); - + requestMatcher.matcher(HttpMethod.GET, "/api/v1/map/**")); + // 인증이 필요한 요청 authenticatedRequestMatcher = new OrRequestMatcher( // requestMatcher.matcher("/**"), From 2633ebe4c5a1a9c511b35023699c8ce27829ac8a Mon Sep 17 00:00:00 2001 From: answl Date: Thu, 26 Feb 2026 18:19:36 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feature:=20swagger=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payper/server/map/controller/MapApi.java | 27 +++++++++++++++++++ .../server/map/controller/MapController.java | 7 ++--- .../server/map/dto/NearbySearchRequest.java | 6 +++++ .../server/map/dto/NearbySearchResponse.java | 14 ++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/payper/server/map/controller/MapApi.java diff --git a/src/main/java/com/payper/server/map/controller/MapApi.java b/src/main/java/com/payper/server/map/controller/MapApi.java new file mode 100644 index 0000000..7ae74b6 --- /dev/null +++ b/src/main/java/com/payper/server/map/controller/MapApi.java @@ -0,0 +1,27 @@ +package com.payper.server.map.controller; + +import com.payper.server.global.response.ApiResponse; +import com.payper.server.map.dto.NearbySearchRequest; +import com.payper.server.map.dto.NearbySearchResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; + +@Tag(name = "지도", description = "사용자 위치 기반 가맹점 검색 API") +public interface MapApi { + + @Operation( + summary = "반경 내 Top10 가맹점 검색", + description = """ + 사용자 위치(위도, 경도)와 반경(km)을 기준으로 DB에 저장된 가맹점 지점 중 + Top10 가맹점에 해당하는 지점을 최대 10개 반환. + 반환 순서: Top10 우선순위 → 같은 가맹점 내 거리 오름차순. + Top10 가맹점이 DB에 없으면 결과에서 제외됨. + """, + security = {}) + ResponseEntity>> getNearbyTop10( + @Parameter(description = "검색 조건 (위도, 경도, 반경(km))", required = true) + NearbySearchRequest request); +} diff --git a/src/main/java/com/payper/server/map/controller/MapController.java b/src/main/java/com/payper/server/map/controller/MapController.java index 95be5e1..0653608 100644 --- a/src/main/java/com/payper/server/map/controller/MapController.java +++ b/src/main/java/com/payper/server/map/controller/MapController.java @@ -7,6 +7,7 @@ import jakarta.validation.Valid; 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.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @@ -15,16 +16,16 @@ @RestController @RequestMapping("/api/v1/map") @RequiredArgsConstructor -public class MapController { +public class MapController implements MapApi { private final NearbyMerchantService nearbyMerchantService; @GetMapping("/nearby-top10") - public ApiResponse> getNearbyTop10( + public ResponseEntity>> getNearbyTop10( @Valid @ModelAttribute NearbySearchRequest request) { List result = nearbyMerchantService.searchNearbyTop10(request.latitude(), request.longitude(), request.radiusKm()); - return ApiResponse.ok(result); + return ResponseEntity.ok(ApiResponse.ok(result)); } } diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java index 76c985a..2bbe199 100644 --- a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java +++ b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java @@ -1,11 +1,17 @@ package com.payper.server.map.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.NotNull; +@Schema(description = "반경 내 Top10 가맹점 검색 요청") public record NearbySearchRequest( + @Schema(description = "사용자 위도", example = "37.5665") @NotNull Double latitude, + + @Schema(description = "사용자 경도", example = "126.9780") @NotNull Double longitude, + @Schema(description = "검색 반경 (km, 0 초과)", example = "1.0") @NotNull @DecimalMin(value = "0.0", inclusive = false) Double radiusKm) {} diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java index 30ce80d..208f141 100644 --- a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java +++ b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java @@ -1,12 +1,26 @@ package com.payper.server.map.dto; +import io.swagger.v3.oas.annotations.media.Schema; + public class NearbySearchResponse { + @Schema(description = "반경 내 Top10 가맹점 지점 정보") public record NearbyPlaceItem( + @Schema(description = "Top10 가맹점 우선순위 (1 = 최우선)", example = "1") int merchantRank, + + @Schema(description = "가맹점명", example = "스타벅스") String merchantName, + + @Schema(description = "지점명", example = "스타벅스 강남점") String placeName, + + @Schema(description = "지점 위도", example = "37.4979") double latitude, + + @Schema(description = "지점 경도", example = "127.0276") double longitude, + + @Schema(description = "사용자로부터의 거리 (km)", example = "0.35") double distanceKm) {} } From b1e6dbce7ebc1cd84ade5822d9584bca4b7e9ebe Mon Sep 17 00:00:00 2001 From: answl Date: Thu, 26 Feb 2026 18:20:17 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refact:=20spotlessApply=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=ED=9B=84=20=EB=B3=80=EB=8F=99=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/payper/server/map/controller/MapApi.java | 3 +-- .../payper/server/map/dto/NearbySearchRequest.java | 11 ++++++----- .../payper/server/map/dto/NearbySearchResponse.java | 3 +-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/payper/server/map/controller/MapApi.java b/src/main/java/com/payper/server/map/controller/MapApi.java index 7ae74b6..38acd1a 100644 --- a/src/main/java/com/payper/server/map/controller/MapApi.java +++ b/src/main/java/com/payper/server/map/controller/MapApi.java @@ -22,6 +22,5 @@ public interface MapApi { """, security = {}) ResponseEntity>> getNearbyTop10( - @Parameter(description = "검색 조건 (위도, 경도, 반경(km))", required = true) - NearbySearchRequest request); + @Parameter(description = "검색 조건 (위도, 경도, 반경(km))", required = true) NearbySearchRequest request); } diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java index 2bbe199..ab42238 100644 --- a/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java +++ b/src/main/java/com/payper/server/map/dto/NearbySearchRequest.java @@ -6,12 +6,13 @@ @Schema(description = "반경 내 Top10 가맹점 검색 요청") public record NearbySearchRequest( - @Schema(description = "사용자 위도", example = "37.5665") - @NotNull Double latitude, + @Schema(description = "사용자 위도", example = "37.5665") @NotNull + Double latitude, - @Schema(description = "사용자 경도", example = "126.9780") - @NotNull Double longitude, + @Schema(description = "사용자 경도", example = "126.9780") @NotNull + Double longitude, @Schema(description = "검색 반경 (km, 0 초과)", example = "1.0") - @NotNull @DecimalMin(value = "0.0", inclusive = false) + @NotNull + @DecimalMin(value = "0.0", inclusive = false) Double radiusKm) {} diff --git a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java index 208f141..25ae3ad 100644 --- a/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java +++ b/src/main/java/com/payper/server/map/dto/NearbySearchResponse.java @@ -9,8 +9,7 @@ public record NearbyPlaceItem( @Schema(description = "Top10 가맹점 우선순위 (1 = 최우선)", example = "1") int merchantRank, - @Schema(description = "가맹점명", example = "스타벅스") - String merchantName, + @Schema(description = "가맹점명", example = "스타벅스") String merchantName, @Schema(description = "지점명", example = "스타벅스 강남점") String placeName,