-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] 지도에서 경사로, 엘리베이터를 거리순으로 조회하는 로직 구현 #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
54dd3eb
fd99f0f
a471f5d
72e9bec
65f002a
e879b45
b8e3229
0484f6c
7585475
9d051e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.wayble.server.explore.controller; | ||
|
|
||
| import com.wayble.server.common.response.CommonResponse; | ||
| import com.wayble.server.explore.dto.facility.WaybleFacilityConditionDto; | ||
| import com.wayble.server.explore.dto.facility.WaybleFacilityResponseDto; | ||
| import com.wayble.server.explore.service.WaybleFacilityDocumentService; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.validation.annotation.Validated; | ||
| 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 | ||
| @RequiredArgsConstructor | ||
| @Validated | ||
| @RequestMapping("/api/v1/facilities/search") | ||
| public class WaybleFacilitySearchController { | ||
| private final WaybleFacilityDocumentService waybleFacilityDocumentService; | ||
|
|
||
| @GetMapping("") | ||
| public CommonResponse<List<WaybleFacilityResponseDto>> findNearbyFacilities( | ||
| @Valid @ModelAttribute WaybleFacilityConditionDto conditionDto | ||
| ) { | ||
| return CommonResponse.success(waybleFacilityDocumentService.findNearbyFacilityDocuments(conditionDto)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.wayble.server.explore.dto.facility; | ||
|
|
||
| import com.wayble.server.explore.entity.FacilityType; | ||
| import jakarta.validation.constraints.DecimalMax; | ||
| import jakarta.validation.constraints.DecimalMin; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record WaybleFacilityConditionDto( | ||
| @DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다.") | ||
| @DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다.") | ||
| @NotNull(message = "위도 입력은 필수입니다.") | ||
| Double latitude, | ||
|
|
||
| @DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다.") | ||
| @DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다.") | ||
| @NotNull(message = "경도 입력은 필수입니다.") | ||
| Double longitude, | ||
|
|
||
| FacilityType facilityType | ||
| ) { | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,23 @@ | ||||||||||||||||
| package com.wayble.server.explore.dto.facility; | ||||||||||||||||
|
|
||||||||||||||||
| import com.wayble.server.explore.entity.FacilityType; | ||||||||||||||||
| import jakarta.validation.constraints.DecimalMax; | ||||||||||||||||
| import jakarta.validation.constraints.DecimalMin; | ||||||||||||||||
| import jakarta.validation.constraints.NotNull; | ||||||||||||||||
| import lombok.Builder; | ||||||||||||||||
|
|
||||||||||||||||
| @Builder | ||||||||||||||||
| public record WaybleFacilityRegisterDto ( | ||||||||||||||||
| @DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다.") | ||||||||||||||||
| @DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다.") | ||||||||||||||||
| @NotNull(message = "위도 입력은 필수입니다.") | ||||||||||||||||
| Double latitude, | ||||||||||||||||
|
|
||||||||||||||||
| @DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다.") | ||||||||||||||||
| @DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다.") | ||||||||||||||||
| @NotNull(message = "경도 입력은 필수입니다.") | ||||||||||||||||
| Double longitude, | ||||||||||||||||
|
|
||||||||||||||||
| FacilityType facilityType | ||||||||||||||||
| ){ | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 등록 시 facilityType 필수값 보장 필요 등록 DTO에서도 facilityType 누락을 방지하기 위해 @NotNull을 추가하세요. - FacilityType facilityType
+ @NotNull(message = "시설 타입 입력은 필수입니다.")
+ FacilityType facilityType📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.wayble.server.explore.dto.facility; | ||
|
|
||
| import com.wayble.server.explore.entity.FacilityType; | ||
| import com.wayble.server.explore.entity.WaybleFacilityDocument; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
|
|
||
| @Builder(access = AccessLevel.PRIVATE) | ||
| public record WaybleFacilityResponseDto( | ||
| Double latitude, | ||
|
|
||
| Double longitude, | ||
|
|
||
| FacilityType facilityType | ||
| ) { | ||
| public static WaybleFacilityResponseDto from(WaybleFacilityDocument facilityDocument) { | ||
| return WaybleFacilityResponseDto.builder() | ||
| .latitude(facilityDocument.getLocation().getLat()) | ||
| .longitude(facilityDocument.getLocation().getLon()) | ||
| .facilityType(facilityDocument.getFacilityType()) | ||
| .build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.wayble.server.explore.entity; | ||
|
|
||
| public enum FacilityType { | ||
| ELEVATOR, | ||
| RAMP | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.wayble.server.explore.entity; | ||
|
|
||
| import com.wayble.server.direction.entity.transportation.Facility; | ||
| import com.wayble.server.explore.dto.facility.WaybleFacilityRegisterDto; | ||
| import lombok.*; | ||
| import org.springframework.data.annotation.Id; | ||
| import org.springframework.data.elasticsearch.annotations.Document; | ||
| import org.springframework.data.elasticsearch.annotations.Field; | ||
| import org.springframework.data.elasticsearch.annotations.GeoPointField; | ||
| import org.springframework.data.elasticsearch.core.geo.GeoPoint; | ||
|
|
||
| @ToString | ||
| @Builder | ||
| @Getter | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| @Document(indexName = "wayble_facility_document", createIndex = true) | ||
| public class WaybleFacilityDocument { | ||
| @Id | ||
| @Field(name = "id") | ||
| private String id; | ||
|
|
||
| @GeoPointField | ||
| private GeoPoint location; | ||
|
|
||
| private FacilityType facilityType; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.wayble.server.explore.repository.facility; | ||
|
|
||
| import com.wayble.server.explore.entity.WaybleFacilityDocument; | ||
| import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface WaybleFacilityDocumentRepository extends ElasticsearchRepository<WaybleFacilityDocument, String> { | ||
| List<WaybleFacilityDocument> findAll(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package com.wayble.server.explore.repository.facility; | ||
|
|
||
| import co.elastic.clients.elasticsearch._types.GeoLocation; | ||
| import co.elastic.clients.elasticsearch._types.SortOptions; | ||
| import co.elastic.clients.elasticsearch._types.SortOrder; | ||
| import co.elastic.clients.elasticsearch._types.query_dsl.Query; | ||
| import com.wayble.server.explore.dto.facility.WaybleFacilityConditionDto; | ||
| import com.wayble.server.explore.dto.facility.WaybleFacilityResponseDto; | ||
| import com.wayble.server.explore.entity.WaybleFacilityDocument; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.domain.PageRequest; | ||
| import org.springframework.data.elasticsearch.client.elc.NativeQuery; | ||
| import org.springframework.data.elasticsearch.core.ElasticsearchOperations; | ||
| import org.springframework.data.elasticsearch.core.SearchHits; | ||
| import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class WaybleFacilityQuerySearchRepository { | ||
|
|
||
| private final ElasticsearchOperations operations; | ||
| private static final IndexCoordinates INDEX = IndexCoordinates.of("wayble_facility_document"); | ||
| private static final int LIMIT = 50; | ||
|
|
||
| /** | ||
| * 위도, 경도, 시설 타입을 바탕으로 WaybleFacilityDocument를 거리순으로 N개 반환 | ||
| */ | ||
| public List<WaybleFacilityResponseDto> findNearbyFacilitiesByType( | ||
| WaybleFacilityConditionDto condition) { | ||
|
|
||
| double radius = 10.0; // 기본 반경 5km | ||
| String radiusWithUnit = radius + "km"; | ||
|
|
||
| // 시설 타입에 따른 쿼리 조건 생성 | ||
| Query query = Query.of(q -> q | ||
| .bool(b -> { | ||
| // 시설 타입 조건 추가 | ||
| if (condition.facilityType() != null) { | ||
| b.must(m -> m | ||
| .term(t -> t | ||
| .field("facilityType.keyword") | ||
| .value(condition.facilityType().name()) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| // 위치 기반 필터: 중심 좌표 기준 반경 필터링 | ||
| b.filter(f -> f | ||
| .geoDistance(gd -> gd | ||
| .field("location") | ||
| .location(loc -> loc | ||
| .latlon(ll -> ll | ||
| .lat(condition.latitude()) | ||
| .lon(condition.longitude()) | ||
| ) | ||
| ) | ||
| .distance(radiusWithUnit) | ||
| ) | ||
| ); | ||
|
|
||
| return b; | ||
| }) | ||
| ); | ||
|
|
||
| // 거리 기준 오름차순 정렬 | ||
| SortOptions geoSort = SortOptions.of(s -> s | ||
| .geoDistance(gds -> gds | ||
| .field("location") | ||
| .location(GeoLocation.of(gl -> gl | ||
| .latlon(ll -> ll | ||
| .lat(condition.latitude()) | ||
| .lon(condition.longitude()) | ||
| ) | ||
| )) | ||
| .order(SortOrder.Asc) | ||
| ) | ||
| ); | ||
|
|
||
| // Elasticsearch 쿼리 구성 | ||
| NativeQuery nativeQuery = NativeQuery.builder() | ||
| .withQuery(query) | ||
| .withSort(geoSort) | ||
| .withPageable(PageRequest.of(0, LIMIT)) | ||
| .build(); | ||
|
|
||
| // 검색 수행 | ||
| SearchHits<WaybleFacilityDocument> hits = | ||
| operations.search(nativeQuery, WaybleFacilityDocument.class, INDEX); | ||
|
|
||
| // 결과를 Document 리스트로 반환 | ||
| return hits.stream() | ||
| .map(hit -> { | ||
| WaybleFacilityDocument doc = hit.getContent(); | ||
| return WaybleFacilityResponseDto.from(doc); | ||
| }) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.wayble.server.explore.service; | ||
|
|
||
| import com.wayble.server.explore.dto.facility.WaybleFacilityConditionDto; | ||
| import com.wayble.server.explore.dto.facility.WaybleFacilityResponseDto; | ||
| import com.wayble.server.explore.repository.facility.WaybleFacilityQuerySearchRepository; | ||
| import jakarta.transaction.Transactional; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
|
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 트랜잭션 어노테이션 일관성/적용 범위 조정
다음 수정안을 제안합니다: -import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;
@@
-@Transactional
+@Transactional(readOnly = true)
public class WaybleFacilityDocumentService {Also applies to: 12-15 🤖 Prompt for AI Agents |
||
|
|
||
| import java.util.List; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional | ||
| public class WaybleFacilityDocumentService { | ||
|
|
||
| private final WaybleFacilityQuerySearchRepository waybleFacilityQuerySearchRepository; | ||
|
|
||
| public List<WaybleFacilityResponseDto> findNearbyFacilityDocuments(WaybleFacilityConditionDto dto) { | ||
| return waybleFacilityQuerySearchRepository.findNearbyFacilitiesByType(dto); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null 허용 시 NPE 위험: facilityType에 @NotNull 추가 필요
WaybleFacilityQuerySearchRepository에서 condition.facilityType().name()을 사용하는 것으로 보입니다. facilityType이 null이면 NPE가 발생합니다. NotNull 제약을 추가하세요.
다음 수정안을 제안합니다:
테스트에 facilityType 누락 케이스(Negative)도 추가해 주세요.
📝 Committable suggestion
🤖 Prompt for AI Agents