-
Notifications
You must be signed in to change notification settings - Fork 1
[release] 진행상황 main 브랜치에 반영 #152
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
Conversation
[feat] 공덕 지역 웨이블존 데이터 추가
[FEAT] 공덕 지역 웨이블 마커 데이터 추가
[FIX] kric api의 안정성 강화, 환승 횟수 로직 정교화 및 횟수 제한 강화
Walkthrough관리자 WaybleZone 상세에 이전/다음 내비게이션을 추가하고, 이에 필요한 DTO/서비스/레포지토리를 도입했습니다. 교통 도메인에서는 DTO 시그니처 변경, 휠체어/엘리베이터 정보 처리 방식 개편, KRIC 화장실 API 연계 수정, 경로 필터·이동거리·환승 계산 로직을 조정했습니다. 그래프/마커 리소스와 일부 설정·테스트도 갱신했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Admin as Admin UI
participant Ctrl as AdminWaybleZoneViewController
participant Svc as AdminWaybleZoneService
participant Repo as AdminWaybleZoneRepository
Admin->>Ctrl: GET /admin/wayble-zones/{id}
Ctrl->>Svc: getNavigationInfo(id)
Svc->>Repo: getNavigationInfo(id)
Repo-->>Svc: AdminWaybleZoneNavigationDto(prevId,nextId)
Svc-->>Ctrl: Navigation DTO
Ctrl-->>Admin: View(Model: navigation, details)
sequenceDiagram
participant TS as TransportationService
participant FS as FacilityService
participant NR as NodeRepository
participant FR as FacilityRepository
participant WC as WheelchairInfoRepository
participant KRIC as KRIC API
TS->>FS: getNodeInfo(nodeId, routeId)
FS->>NR: findById(nodeId)
NR-->>FS: Node
FS->>WC: findByRouteId(routeId)
WC-->>FS: List<Wheelchair>
FS->>FR: findByNodeId(nodeId) (fetch lifts)
FR-->>FS: Facility
FS->>KRIC: stationDisabledToilet(... identifiers ...)
KRIC-->>FS: List<KricToiletRawItem>
FS-->>TS: NodeInfo(wheelchair locations, elevator=[], restroom flag)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 3
🔭 Outside diff range comments (3)
src/main/java/com/wayble/server/direction/init/GraphInit.java (3)
60-62: RAMP 패널티가 절대 적용되지 않는 버그(rampMarkers 초기화 누락)rampMarkers가 항상 emptySet이어서(초기값만 존재) RAMP_PENALTY가 절대 적용되지 않습니다. markerMap 계산 직후, RAMP 타입 노드를 rampMarkers에 채워 넣어야 합니다.
- markerMap = findWaybleMarkers(); - adjacencyList = buildAdjacencyList(); + markerMap = findWaybleMarkers(); + // RAMP 타입 노드를 별도로 모아 패널티 적용 + rampMarkers = markerMap.entrySet().stream() + .filter(e -> e.getValue() == Type.RAMP) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + adjacencyList = buildAdjacencyList();
71-79: NONE 타입에도 할인 적용되는 로직 오류(타입별 가중치 적용 필요)현 로직은 “마커가 있다”는 이유만으로 MARKER_DISCOUNT를 적용합니다. Type.NONE까지 할인되는 것은 비즈니스 의도와 어긋날 가능성이 큽니다. 타입별로 패널티/할인을 분기하세요.
- for (Edge edge : edges) { - boolean isWaybleMarker = markerMap.containsKey(edge.from()) || markerMap.containsKey(edge.to()); - boolean isRamp = rampMarkers.contains(edge.from()) || rampMarkers.contains(edge.to()); - - double distance = edge.length(); - - if (isRamp) distance *= RAMP_PENALTY; - else if (isWaybleMarker) distance *= MARKER_DISCOUNT; + for (Edge edge : edges) { + final Type fromType = markerMap.get(edge.from()); + final Type toType = markerMap.get(edge.to()); + + double distance = edge.length(); + + // RAMP는 패널티, 엘리베이터/리프트/충전기는 할인, NONE은 조정 없음 + if (fromType == Type.RAMP || toType == Type.RAMP) { + distance *= RAMP_PENALTY; + } else if (isPositiveMarker(fromType) || isPositiveMarker(toType)) { + distance *= MARKER_DISCOUNT; + }추가 헬퍼 메서드(클래스 내부 어떤 위치든 무방):
private boolean isPositiveMarker(Type t) { if (t == null) return false; return t == Type.WHEELCHAIR_LIFT || t == Type.WHEELCHAIR_CHARGER // 필요 시 다른 “편의” 타입도 추가 || t == Type.ELEVATOR; }
92-103: 가까운 노드 매핑 조건 오류: marker.id()와 node.id() 비교는 의미가 없습니다현재 로직은 “가까운 노드 ID(nearNode)가 marker.id()와 동일하면 매핑을 생략”합니다. 노드 ID와 마커 ID는 서로 다른 도메인이라 일반적으로 일치하지 않으며, 해당 조건은 사실상 임의의 생략을 유발합니다. 최근접 노드에 항상 매핑하도록 수정하세요.
- if (nearNode != marker.id()) { - waybleMarkers.put(nearNode, marker.type()); - } + waybleMarkers.put(nearNode, marker.type());추가 개선(선택): 임계 거리(예: 100m)를 두고, 초과 시 매핑을 생략하면 그래프 외곽/원거리 마커의 오매핑을 방지할 수 있습니다. 원하시면 해당 로직까지 포함해 패치 드리겠습니다.
🧹 Nitpick comments (13)
src/main/resources/wayble_markers.json (1)
1-7874: 데이터 볼륨 급증에 따른 초기화 성능/정확도 리스크
- 성능: 현재 GraphInit.findWaybleMarkers는 O(N_nodes × N_markers)입니다. 수천~수만 단위에서 초기화 시간이 커질 수 있습니다.
- 정확도: 그래프 적용 범위를 넘어서는 원거리 마커도 “가장 가까운 노드”로 맵핑됩니다(거리 임계치 없이). 이 경우 특정 노드/엣지에 부적절한 할인/패널티가 적용될 수 있습니다.
완화 제안:
- 공간 인덱스 도입(KD-Tree, R*-tree, 혹은 geohash 그리드 버킷)으로 최근접 탐색을 가속.
- 맵핑 거리 임계치(e.g. 100m)를 두고, 기준 초과 시 매핑 생략.
원하시면 HaversineUtil 기반의 그리드 버킷 샘플 구현을 드리겠습니다.
src/main/java/com/wayble/server/direction/init/GraphInit.java (2)
56-58: ObjectMapper TypeReference 제네릭 명시(가독성/타입 안전성 개선)다이아몬드 연산자 + 익명 클래스 조합은 컴파일러 버전/옵션에 따라 가독성과 경고 면에서 불리할 수 있습니다. 제네릭을 명시해 주세요.
- markers = markerStream != null - ? objectMapper.readValue(markerStream, new TypeReference<>() {}) - : new ArrayList<>(); + markers = markerStream != null + ? objectMapper.readValue(markerStream, new TypeReference<List<WaybleMarker>>() {}) + : new ArrayList<>();
92-99: 초기화 성능 최적화 제안: 최근접 탐색 가속화모든 마커마다 모든 노드를 순회하는 O(N×M)은 데이터가 커질수록 초기화 지연을 유발합니다. KD-Tree/Grid bucketing 등으로 근방 노드 후보군만 비교하도록 개선하세요.
원하시면 geohash 버킷 기반 근접 후보 탐색 샘플을 제공하겠습니다(외부 라이브러리 없이도 구현 가능).
src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java (1)
18-19: 이전/다음 조회 API 추가 적절 — null 의미를 인터페이스 수준에서 명시하면 더 좋습니다반환 DTO의 previousId/nextId가 없을 때 null임을 Javadoc으로 명시하면, 서비스/컨트롤러/템플릿에서의 처리 기준이 더 명확해집니다. 예: “가장 작은/가장 큰 ID 경계에서는 해당 값이 null”.
src/main/java/com/wayble/server/common/config/WebClientConfig.java (2)
35-38: timeout은 재시도 대상이 아닙니다 + 비멱등 메서드까지 재시도됩니다현재
.timeout(...).retryWhen(... .filter(WebClientRequestException))구성이라, TimeoutException은 재시도되지 않습니다. 또한 HTTP 메서드에 상관없이 재시도되어 POST/PUT 같은 비멱등 요청에도 적용될 수 있습니다.다음과 같이 개선을 제안합니다: (1) GET/HEAD 등 멱등 메서드에 한해 재시도, (2) TimeoutException도 재시도 대상으로 포함.
- .filter((request, next) -> next.exchange(request) - .timeout(java.time.Duration.ofSeconds(15)) - .retryWhen(reactor.util.retry.Retry.backoff(3, java.time.Duration.ofSeconds(1)) - .filter(throwable -> throwable instanceof org.springframework.web.reactive.function.client.WebClientRequestException))) + .filter((request, next) -> { + boolean retryableMethod = request.method() == org.springframework.http.HttpMethod.GET + || request.method() == org.springframework.http.HttpMethod.HEAD; + var exchange = next.exchange(request) + .timeout(java.time.Duration.ofSeconds(15)); + if (!retryableMethod) { + return exchange; + } + return exchange.retryWhen( + reactor.util.retry.Retry.backoff(3, java.time.Duration.ofSeconds(1)) + .filter(t -> t instanceof org.springframework.web.reactive.function.client.WebClientRequestException + || t instanceof java.util.concurrent.TimeoutException) + ); + })추가로, 재시도 로그를 남기려면
Retry.backoff(...).doBeforeRetry(rs -> log.warn(...))를 덧붙이는 것도 권장합니다.
34-34: maxInMemorySize 2MB 하드코딩 → 프로퍼티화 제안KRIC 응답 크기가 변동 가능하다면, 2MB는 환경에 따라 부족할 수 있습니다.
application.yml에 예:wayble.kric.max-in-memory-size프로퍼티를 두고 주입받아 설정하도록 하면 운영/스테이지별 튜닝이 용이합니다.src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (1)
13-16: 메서드명(findByNodeId)과 JPQL 조건(f.id) 불일치
Facility가@MapsId로node.id와 동일하다면 기능상 문제는 없겠지만, 가독성 측면에서 혼동을 유발합니다. 실제 의도가 Node의 id 기준 조회라면 JPQL도f.node.id로 맞추거나, 메서드명을findByIdWithLifts로 바꾸는 것을 권장합니다.아래와 같이 대안도 고려 가능합니다.
- @Query("SELECT DISTINCT f FROM Facility f " + - "LEFT JOIN FETCH f.lifts " + - "WHERE f.id = :nodeId") + @Query("SELECT DISTINCT f FROM Facility f " + + "LEFT JOIN FETCH f.lifts " + + "WHERE f.node.id = :nodeId") Optional<Facility> findByNodeId(@Param("nodeId") Long nodeId);src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (2)
23-23: moveNumber의 이중 의미(횟수 vs 거리)로 인한 클라이언트 혼동 가능성WALK에서는 거리(m), 그 외에는 이동 횟수로 쓰이면, 소비 측에서 단위/의미 분기가 필요합니다. 필드를 분리(
moveCount,distanceMeters)하거나, 최소한 스키마 설명에 단위를 명시하고 예시를 추가해 주세요.
58-61: NodeInfo의 필드 타입 변경도 동일한 호환성 이슈
NodeInfo.wheelchair/elevator역시 List로 변경. 동일한 백워드 컴패티빌리티 전략과 문서 갱신이 필요합니다.src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java (1)
6-17: KRIC 응답 필드명 매핑 검증 필요 + Lombok import 불필요
- 현재 Record 필드명이 KRIC JSON/XML 필드명과 1:1 매칭되어야 역직렬화가 성공합니다. 외부 API가 snake_case, 대소문자/축약이 다르면
@JsonProperty명시를 권장합니다.- 이 파일에서는
lombok.Getter가 사용되지 않습니다(Record는 자동 접근자 생성). 불필요한 import는 제거 바랍니다.예시:
public record KricToiletRawItem( @com.fasterxml.jackson.annotation.JsonProperty("railOprIsttCd") String railOprIsttCd, @com.fasterxml.jackson.annotation.JsonProperty("lnCd") String lnCd, // ... ) {}src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
38-41: 양방향 관계의 일관성 관리 필요
Route와Wheelchair간의 양방향 관계가 설정되었지만, 관계의 일관성을 보장하는 헬퍼 메서드가 없습니다. 휠체어 정보를 추가/제거할 때 양방향 관계가 올바르게 유지되도록 메서드를 추가하는 것을 고려해보세요.@OneToMany(mappedBy = "route", fetch = FetchType.LAZY) private List<Wheelchair> wheelchairs; + +// 양방향 관계 관리를 위한 헬퍼 메서드 +public void addWheelchair(Wheelchair wheelchair) { + if (wheelchairs == null) { + wheelchairs = new ArrayList<>(); + } + wheelchairs.add(wheelchair); + wheelchair.setRoute(this); +} + +public void removeWheelchair(Wheelchair wheelchair) { + if (wheelchairs != null) { + wheelchairs.remove(wheelchair); + wheelchair.setRoute(null); + } +}src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (1)
9-10: 사용되지 않는 import 제거
Optionalimport가 선언되어 있지만 실제로 사용되지 않습니다.import java.util.List; -import java.util.Optional;src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java (1)
8-8: Builder 접근 레벨 제한 필요다른 엔티티들과 일관성을 유지하기 위해
@Builder에AccessLevel.PRIVATE을 추가하는 것이 좋습니다.-@Builder +@Builder(access = AccessLevel.PRIVATE)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
src/main/resources/data/gongduck_wayblezone.csvis excluded by!**/*.csv
📒 Files selected for processing (22)
src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java(2 hunks)src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneNavigationDto.java(1 hunks)src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java(2 hunks)src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java(2 hunks)src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java(2 hunks)src/main/java/com/wayble/server/common/config/WebClientConfig.java(1 hunks)src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java(3 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Facility.java(0 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Route.java(2 hunks)src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java(1 hunks)src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java(0 hunks)src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java(1 hunks)src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java(1 hunks)src/main/java/com/wayble/server/direction/init/GraphInit.java(1 hunks)src/main/java/com/wayble/server/direction/repository/FacilityRepository.java(1 hunks)src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java(1 hunks)src/main/java/com/wayble/server/direction/service/FacilityService.java(4 hunks)src/main/java/com/wayble/server/direction/service/TransportationService.java(5 hunks)src/main/java/com/wayble/server/wayblezone/importer/CsvSupport.java(1 hunks)src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html(1 hunks)src/main/resources/wayble_markers.json(4 hunks)src/test/java/com/wayble/server/direction/service/WalkingServiceTest.java(0 hunks)
💤 Files with no reviewable changes (3)
- src/test/java/com/wayble/server/direction/service/WalkingServiceTest.java
- src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java
- src/main/java/com/wayble/server/direction/entity/transportation/Facility.java
🧰 Additional context used
🧬 Code Graph Analysis (14)
src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneNavigationDto.java (2)
src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java (2)
AdminWaybleZoneDetailDto(10-59)FacilityInfo(40-43)src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java (1)
AdminWaybleZonePageDto(5-34)
src/main/java/com/wayble/server/direction/init/GraphInit.java (2)
src/test/java/com/wayble/server/direction/service/WalkingServiceTest.java (1)
BeforeEach(35-77)src/main/java/com/wayble/server/direction/dto/TransportationGraphDto.java (1)
TransportationGraphDto(9-12)
src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java (1)
src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java (1)
AdminWaybleZoneRepository(6-7)
src/main/java/com/wayble/server/wayblezone/importer/CsvSupport.java (3)
src/main/java/com/wayble/server/wayblezone/importer/SeochoCsvImporter.java (1)
Slf4j(31-172)src/main/java/com/wayble/server/wayblezone/sync/WaybleZoneSyncScheduler.java (1)
Slf4j(8-35)src/main/java/com/wayble/server/direction/service/DirectionService.java (1)
Slf4j(25-68)
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java (1)
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java (1)
KricToiletRawBody(5-7)
src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (2)
src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1)
RouteRepository(7-9)src/main/java/com/wayble/server/direction/repository/EdgeRepository.java (1)
Repository(10-17)
src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
Entity(11-42)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (1)
Entity(8-47)
src/main/resources/wayble_markers.json (1)
src/main/java/com/wayble/server/direction/entity/WaybleMarker.java (1)
WaybleMarker(5-11)
src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (2)
src/main/java/com/wayble/server/direction/entity/transportation/Facility.java (1)
Entity(11-46)src/main/java/com/wayble/server/direction/entity/transportation/Lift.java (1)
Entity(6-29)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1)
Schema(5-17)
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java (1)
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java (1)
KricToiletRawBody(5-7)
src/main/java/com/wayble/server/direction/service/FacilityService.java (3)
src/main/java/com/wayble/server/direction/entity/transportation/Facility.java (1)
Entity(11-46)src/main/java/com/wayble/server/direction/entity/transportation/Lift.java (1)
Entity(6-29)src/main/java/com/wayble/server/direction/entity/transportation/Node.java (1)
Entity(13-79)
src/main/java/com/wayble/server/direction/service/TransportationService.java (1)
src/main/java/com/wayble/server/direction/service/WalkingService.java (1)
Slf4j(20-64)
🔇 Additional comments (22)
src/main/resources/wayble_markers.json (2)
1995-2005: 마커 타입 재분류(예: RAMP → WHEELCHAIR_LIFT/NONE 등) 영향 검증 필요다음 라인 근처에서 타입 재분류가 보입니다. 경로 가중치 로직(특히 GraphInit의 마커 할인/패널티)에 직접적인 영향이 있습니다. 의도된 변경인지, 데이터 소스 기준과 합치하는지 확인 부탁드립니다.
- Line 1998: id 1740825183 → type WHEELCHAIR_LIFT
- Line 2004: id 1012654209 → type WHEELCHAIR_CHARGER
- Line 2034: id 6875654572 → type NONE
- Line 2154: id 2747865798 → type NONE
변경 이력/출처 기준 문서(또는 원천 데이터 변경 로그)가 있다면 레퍼런스와 함께 코멘트로 공유해 주세요. 필요 시 cross-check를 자동화하는 스크립트도 제공하겠습니다.
Also applies to: 2031-2035, 2152-2155
2-7: 검증 완료 — Type enum과 wayble_markers.json의 타입/ID/좌표가 일치합니다스크립트 실행 결과 아래 항목들 모두 정상 확인되었습니다.
- Type enum 파일: src/main/java/com/wayble/server/direction/entity/type/Type.java
- 포함된 상수: WHEELCHAIR_CHARGER, NO_THRESHOLD, RAMP, WHEELCHAIR_LIFT, NONE
- JSON 파일: src/main/resources/wayble_markers.json
- 고유 타입 목록: NONE, RAMP, WHEELCHAIR_CHARGER, WHEELCHAIR_LIFT (모두 enum에 존재)
- ID 중복: 없음
- 위/경도 범위: 정상
- 전체 마커 수: 1312
따라서 원래 지적한 역직렬화/스키마 불일치 우려는 해소되었습니다.
src/main/java/com/wayble/server/direction/init/GraphInit.java (2)
55-61: 검증 완료 — Type enum에 ELEVATOR와 WHEELCHAIR_LIFT가 모두 존재합니다제공하신 스크립트 출력에서 src/main/java/com/wayble/server/direction/entity/type/Type.java에 ELEVATOR와 WHEELCHAIR_LIFT 상수가 모두 포함됨을 확인했습니다. 따라서 해당 변경으로 인한 역직렬화 실패나 가중치 분기 누락 우려는 없습니다.
43-52: 그래프 리소스 파일 존재 확인 — 문제 없음
- 코드: src/main/java/com/wayble/server/direction/init/GraphInit.java — getResourceAsStream("/seocho_gongdeok_pedestrian.json")
- 리소스: src/main/resources/seocho_gongdeok_pedestrian.json — 존재 확인됨
검증 완료.
src/main/java/com/wayble/server/wayblezone/importer/CsvSupport.java (1)
5-5: Slf4j import 추가는 일관성 측면에서 문제 없습니다해당 클래스에서 로깅을 아직 사용하지 않더라도, 프로젝트 전반의 패턴(다른 Importer/Scheduler 등에서 @slf4j 사용)과의 일관성 관점에서는 무해합니다. 추후 CSV 파싱 실패나 카테고리 매핑 미스(Log at debug/info) 등에 활용하실 계획이라면 유지해도 좋습니다.
src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneNavigationDto.java (1)
3-6: 간결하고 목적에 맞는 DTO 도입 👍이전/다음 경계를 null로 표현하는 설계가 사용처(뷰에서의 조건부 렌더링)와 잘 맞습니다. 추후 필드가 늘어나지 않는 한 record 유지가 적절해 보입니다.
src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java (1)
63-65: 단순 위임 메서드로 충분합니다클래스 레벨 @transactional(readOnly = true) 컨텍스트와도 일치하고, 서비스 레이어 책임(Repository 캡슐화)을 충실히 수행합니다.
src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java (1)
107-124: 확인 — WaybleZone 엔티티에 soft-delete 설정이 적용되어 있습니다간단히 확인했습니다: src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java에
@SQLDelete(sql = "UPDATE wayble_zone SET deleted_at = now() WHERE id = ?")
및 @SQLRestriction("deleted_at IS NULL")가 선언되어 있어 soft-deleted 레코드는 기본적으로 제외됩니다. 따라서 현재 getNavigationInfo의 max/min 쿼리는 soft-deleted 레코드를 포함하지 않습니다.수정이 필요한 위치
- src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java — @SQLDelete, @SQLRestriction (클래스 선언부, 약 23–24행)
선택적 리팩터(성능): 집계(max/min) 대신 정렬+limit 방식 고려 가능(필수 아님):
변경 전
Long previousId = queryFactory
.select(waybleZone.id.max())
.from(waybleZone)
.where(waybleZone.id.lt(currentId))
.fetchOne();Long nextId = queryFactory
.select(waybleZone.id.min())
.from(waybleZone)
.where(waybleZone.id.gt(currentId))
.fetchOne();변경 후 (선택)
Long previousId = queryFactory
.select(waybleZone.id)
.from(waybleZone)
.where(waybleZone.id.lt(currentId))
.orderBy(waybleZone.id.desc())
.limit(1)
.fetchOne();Long nextId = queryFactory
.select(waybleZone.id)
.from(waybleZone)
.where(waybleZone.id.gt(currentId))
.orderBy(waybleZone.id.asc())
.limit(1)
.fetchOne();src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html (1)
75-111: 네비게이션 UI 구현이 적절하게 구성되었습니다.이전/다음 버튼의 조건부 렌더링과 비활성화 상태 처리가 올바르게 구현되어 있고, Tailwind CSS 스타일링과 접근성 고려도 적절합니다.
src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java (4)
5-5: 새로운 DTO import가 추가되었습니다.네비게이션 기능을 위한
AdminWaybleZoneNavigationDtoimport가 적절하게 추가되었습니다.
62-67: 네비게이션 정보를 모델에 추가하는 로직이 적절합니다.서비스에서 네비게이션 정보를 조회하여 모델에 추가하는 구현이 명확하고 올바릅니다.
69-69: 로깅 메시지에 네비게이션 정보가 포함되었습니다.디버깅을 위한 로깅에 이전/다음 ID 정보가 포함되어 적절합니다.
62-69: 검증 완료 — 네비게이션 서비스 연동 정상 확인됨요약: AdminWaybleZoneNavigationDto, Service의 getNavigationInfo 호출, Repository 인터페이스 선언 및 RepositoryImpl의 getNavigationInfo 구현이 모두 존재하고 일치합니다. 추가 수정은 필요하지 않습니다.
검증된 파일:
- src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneNavigationDto.java
- src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java (getNavigationInfo)
- src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java (getNavigationInfo 구현)
- src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java (메서드 선언)
- src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java (컨트롤러 바인딩/로그)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
46-49: 타입 변경: List → List는 API 호환성에 영향
SubwayInfo.wheelchair/elevator가 문자열 리스트로 변경되었습니다. 기존 소비자(앱/프론트)가 좌표(lat/lng)를 기대했다면 즉시 깨집니다. 문서/스키마와 통합 테스트, 클라이언트 업데이트가 동반되어야 합니다.
- OpenAPI 스키마 업데이트 여부 확인
- 기존 필드를 한동안 유지(deprecate)하는 점진 이행 전략 고려
- 서버 내부 변환 계층에서 과거 스키마로도 응답 가능한 백워드 컴패티빌리티 옵션 검토
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java (1)
6-6: 검증 요약: DTO 변경은 코드베이스에 반영되었으나 외부 KRIC 응답 스키마 확인 필요KricToiletRawResponse의
body가List<KricToiletRawItem>로 정의되어 있고, 서비스 호출부(FacilityService)도 해당 형태로 사용 중입니다.getBody().getItem()같은 구식 접근이나KricToiletRawBody클래스 잔존은 확인되지 않았습니다. 다만 실제 KRIC API 응답이 여전히 body→item 같은 중첩 래퍼 구조라면 역직렬화가 실패하므로 API 응답 스키마(또는 통합 호출 테스트)를 반드시 검증해 주세요.점검된 위치:
- src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java — body: List
- src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java — item 레코드 정의
- src/main/java/com/wayble/server/direction/service/FacilityService.java — bodyToMono(KricToiletRawResponse.class)로 역직렬화 후 response.body()를 List로 처리
변경된 스니펫:
List<KricToiletRawItem> bodysrc/main/java/com/wayble/server/direction/service/TransportationService.java (2)
290-292: 환승 횟수 제한 변경 확인 필요환승 횟수 제한이 4회에서 3회로 줄었습니다. 이는 사용자 경험에 영향을 줄 수 있는 중요한 변경사항입니다. 의도된 변경인지 확인이 필요합니다.
비즈니스 요구사항과 이 변경이 일치하는지, 그리고 사용자 경험에 미치는 영향을 고려했는지 확인해주세요.
593-603: 도보 거리 계산 로직 개선됨고정값 0 대신 실제 하버사인 거리를 계산하여 도보 거리를 설정하도록 개선되었습니다. 더 정확한 거리 정보를 제공할 수 있게 되었습니다.
src/main/java/com/wayble/server/direction/service/FacilityService.java (5)
47-55: 휠체어 정보 처리 로직 개선됨routeId 기반으로 휠체어 정보를 조회하고 위치 정보를 수집하는 로직이 추가되었습니다. null 체크와 빈 문자열 처리가 적절하게 구현되었습니다.
90-90: 파라미터명 변경 확인 필요
railOprLsttCd가railOprIsttCd로 변경되었습니다. 이것이 의도된 변경인지, 오타인지 확인이 필요합니다.KRIC API 문서를 확인하여 올바른 파라미터명이
railOprIsttCd인지railOprLsttCd인지 검증해주세요.
104-111: 개선된 null 체크 로직API 응답과 body에 대한 체계적인 null 체크가 추가되어 NullPointerException을 방지할 수 있게 되었습니다.
87-87: 확인 완료 — KRIC API 엔드포인트가 /openapi/vulnerableUserInfo/stationDisabledToilet로 맞습니다KRIC 공개 문서에서 역사별 장애인 화장실 조회 엔드포인트가 해당 경로로 표기되어 있으며(serviceKey, format, railOprIsttCd, lnCd, stinCd 등 파라미터도 명시됨). (출처: https://data.kric.go.kr/rips/M_01_02/detail.do?id=432&operation=stationDisabledToilet&service=vulnerableUserInfo)
- 수정 불필요: src/main/java/com/wayble/server/direction/service/FacilityService.java — 라인 87
37-37: getNodeInfo 시그니처 변경 검증 완료 — 호출부가 모두 업데이트되어 있음저장소 전체 검색 결과, 선언부와 1곳의 호출만 존재하며 호출부는 routeId 인자를 함께 전달하고 있습니다.
- 선언: src/main/java/com/wayble/server/direction/service/FacilityService.java:37 — public TransportationResponseDto.NodeInfo getNodeInfo(Long nodeId, Long routeId)
- 호출: src/main/java/com/wayble/server/direction/service/TransportationService.java:643 — facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId());
따라서 추가 조치 불필요합니다.
| @Query("SELECT f FROM Facility f " + | ||
| "LEFT JOIN FETCH f.lifts " + | ||
| "WHERE f.id = :nodeId") | ||
| Optional<Facility> findByNodeId(@Param("nodeId") Long nodeId); |
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.
FETCH JOIN + 단건 조회에서 DISTINCT 누락 시 NonUniqueResultException 위험
LEFT JOIN FETCH f.lifts로 다대일 확대 후 단건 반환 시 중복 로우가 생길 수 있습니다. SELECT DISTINCT를 붙여 중복을 제거하지 않으면 JPA가 단건 결과를 집계하지 못해 NonUniqueResultException이 발생할 수 있습니다.
다음과 같이 DISTINCT를 추가해주세요.
- @Query("SELECT f FROM Facility f " +
+ @Query("SELECT DISTINCT f FROM Facility f " +
"LEFT JOIN FETCH f.lifts " +
"WHERE f.id = :nodeId")
Optional<Facility> findByNodeId(@Param("nodeId") Long nodeId);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Query("SELECT f FROM Facility f " + | |
| "LEFT JOIN FETCH f.lifts " + | |
| "WHERE f.id = :nodeId") | |
| Optional<Facility> findByNodeId(@Param("nodeId") Long nodeId); | |
| @Query("SELECT DISTINCT f FROM Facility f " + | |
| "LEFT JOIN FETCH f.lifts " + | |
| "WHERE f.id = :nodeId") | |
| Optional<Facility> findByNodeId(@Param("nodeId") Long nodeId); |
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/repository/FacilityRepository.java
around lines 13 to 16, the JPQL uses LEFT JOIN FETCH f.lifts which can produce
duplicate rows for a single Facility and cause NonUniqueResultException on a
single-result query; update the query to use SELECT DISTINCT f (i.e., prefix the
select with DISTINCT) so duplicates are eliminated and the Optional<Facility>
single-result retrieval is safe.
| try { | ||
| if (currentEdge.getStartNode() != null) { | ||
| TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId()); | ||
| TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); |
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.
API 서명 변경 확인 필요
facilityService.getNodeInfo()가 이제 두 개의 파라미터 (nodeId, routeId)를 받도록 변경되었습니다. currentEdge.getRoute()가 null일 경우 NullPointerException이 발생할 수 있습니다.
-TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId());
+Route route = currentEdge.getRoute();
+Long routeId = route != null ? route.getRouteId() : null;
+TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), routeId);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); | |
| Route route = currentEdge.getRoute(); | |
| Long routeId = route != null ? route.getRouteId() : null; | |
| TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), routeId); |
| int transferCount = 0; | ||
| for (int i = 0; i < steps.size() - 1; i++) { | ||
| TransportationResponseDto.Step currentStep = steps.get(i); | ||
| TransportationResponseDto.Step nextStep = steps.get(i + 1); | ||
|
|
||
| if (currentStep.mode() != DirectionType.WALK && nextStep.mode() != DirectionType.WALK) { | ||
| if (currentStep.mode() == nextStep.mode() && | ||
| currentStep.routeName() != null && nextStep.routeName() != null && | ||
| !currentStep.routeName().equals(nextStep.routeName())) { | ||
| transferCount++; | ||
| } else if (currentStep.mode() != nextStep.mode()) { | ||
| transferCount++; | ||
| DirectionType previousMode = null; | ||
| String previousRouteName = null; | ||
|
|
||
| for (TransportationResponseDto.Step step : steps) { | ||
| if (step.mode() != DirectionType.WALK && step.mode() != DirectionType.FROM_WAYPOINT && step.mode() != DirectionType.TO_WAYPOINT) { | ||
| if (previousMode != null) { | ||
| if (previousMode == step.mode() && | ||
| previousRouteName != null && step.routeName() != null && | ||
| !previousRouteName.equals(step.routeName())) { | ||
| transferCount++; | ||
| } else if (previousMode == step.mode() && | ||
| previousRouteName != null && step.routeName() != null && | ||
| previousRouteName.equals(step.routeName())) { | ||
| transferCount++; | ||
| } else if (previousMode != step.mode()) { | ||
| transferCount++; | ||
| } | ||
| } | ||
| previousMode = step.mode(); | ||
| previousRouteName = step.routeName(); | ||
| } | ||
| } | ||
| return transferCount; |
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.
환승 횟수 계산 로직의 복잡성
새로운 환승 횟수 계산 로직이 복잡하고 이해하기 어렵습니다. 특히 Lines 744-754에서 같은 모드와 같은 노선명을 가진 경우에도 환승으로 카운트하는 로직이 의도된 것인지 확인이 필요합니다.
Lines 744-751의 조건문이 논리적으로 올바르지 않습니다:
- 같은 모드, 같은 노선명일 때도 환승으로 카운트 (Lines 749-751)
- 같은 모드, 다른 노선명일 때도 환승으로 카운트 (Lines 745-747)
이는 항상 환승으로 카운트되는 문제가 있습니다. 다음과 같이 수정을 제안합니다:
-if (previousMode == step.mode() &&
- previousRouteName != null && step.routeName() != null &&
- !previousRouteName.equals(step.routeName())) {
- transferCount++;
-} else if (previousMode == step.mode() &&
- previousRouteName != null && step.routeName() != null &&
- previousRouteName.equals(step.routeName())) {
- transferCount++;
-} else if (previousMode != step.mode()) {
+// 모드가 다르거나 같은 모드에서 노선이 바뀔 때만 환승
+if (previousMode != step.mode() ||
+ (previousMode == step.mode() &&
+ previousRouteName != null && step.routeName() != null &&
+ !previousRouteName.equals(step.routeName()))) {
transferCount++;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| int transferCount = 0; | |
| for (int i = 0; i < steps.size() - 1; i++) { | |
| TransportationResponseDto.Step currentStep = steps.get(i); | |
| TransportationResponseDto.Step nextStep = steps.get(i + 1); | |
| if (currentStep.mode() != DirectionType.WALK && nextStep.mode() != DirectionType.WALK) { | |
| if (currentStep.mode() == nextStep.mode() && | |
| currentStep.routeName() != null && nextStep.routeName() != null && | |
| !currentStep.routeName().equals(nextStep.routeName())) { | |
| transferCount++; | |
| } else if (currentStep.mode() != nextStep.mode()) { | |
| transferCount++; | |
| DirectionType previousMode = null; | |
| String previousRouteName = null; | |
| for (TransportationResponseDto.Step step : steps) { | |
| if (step.mode() != DirectionType.WALK && step.mode() != DirectionType.FROM_WAYPOINT && step.mode() != DirectionType.TO_WAYPOINT) { | |
| if (previousMode != null) { | |
| if (previousMode == step.mode() && | |
| previousRouteName != null && step.routeName() != null && | |
| !previousRouteName.equals(step.routeName())) { | |
| transferCount++; | |
| } else if (previousMode == step.mode() && | |
| previousRouteName != null && step.routeName() != null && | |
| previousRouteName.equals(step.routeName())) { | |
| transferCount++; | |
| } else if (previousMode != step.mode()) { | |
| transferCount++; | |
| } | |
| } | |
| previousMode = step.mode(); | |
| previousRouteName = step.routeName(); | |
| } | |
| } | |
| return transferCount; | |
| int transferCount = 0; | |
| DirectionType previousMode = null; | |
| String previousRouteName = null; | |
| for (TransportationResponseDto.Step step : steps) { | |
| if (step.mode() != DirectionType.WALK && step.mode() != DirectionType.FROM_WAYPOINT && step.mode() != DirectionType.TO_WAYPOINT) { | |
| if (previousMode != null) { | |
| // 모드가 다르거나 같은 모드에서 노선이 바뀔 때만 환승 | |
| if (previousMode != step.mode() || | |
| (previousMode == step.mode() && | |
| previousRouteName != null && step.routeName() != null && | |
| !previousRouteName.equals(step.routeName()))) { | |
| transferCount++; | |
| } | |
| } | |
| previousMode = step.mode(); | |
| previousRouteName = step.routeName(); | |
| } | |
| } | |
| return transferCount; |
✔️ 연관 이슈
📝 작업 내용
스크린샷 (선택)
Summary by CodeRabbit