-
Notifications
You must be signed in to change notification settings - Fork 1
[FIX] kric api의 안정성 강화, 환승 횟수 로직 정교화 및 횟수 제한 강화 #151
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
[release] develop -> main 병합 진행
[release] 진행상황 Main 브랜치에 반영
[release]진행상황 main branch에 반영
[release] 진행상황 Main 브랜치에 반영
[release] 진행상황 main 브랜치에 반영
erd 이미지 수정
WalkthroughKRIC 연동 로직과 WebClient 설정을 수정하고, 환승 카운트/필터링 로직을 변경했습니다. KRIC DTO 스키마를 갱신하고 응답 파싱 경로를 교체했습니다. 시설/노드 조회 흐름을 재구성해 routeId 기반 휠체어 위치를 포함했습니다. 엔티티/리포지토리를 추가·변경했으며 README(KO)를 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant TS as TransportationService
participant FS as FacilityService
participant NR as NodeRepository
participant FR as FacilityRepository
participant WR as WheelchairInfoRepository
participant KRIC as KRIC API
TS->>FS: getNodeInfo(nodeId, routeId)
FS->>NR: findById(nodeId)
NR-->>FS: Node?
alt routeId provided
FS->>WR: findByRouteId(routeId)
WR-->>FS: List<Wheelchair>
end
FS->>FR: findByNodeId(nodeId) (fetch lifts)
FR-->>FS: Facility?
alt Facility with codes
FS->>KRIC: GET /openapi/vulnerableUserInfo/stationDisabledToilet
KRIC-->>FS: List<KricToiletRawItem> or null
FS-->>TS: NodeInfo(List<String> wheelchair/elevator, restroom map)
else no facility/codes
FS-->>TS: NodeInfo with available data
end
sequenceDiagram
participant TS as TransportationService
participant Geo as Haversine
participant FS as FacilityService
TS->>TS: 필터(환승 ≤ 3)
TS->>TS: 환승 카운트(모드/노선명 변화 기준, 웨이포인트 제외)
TS->>Geo: WALK 구간 시작/끝 좌표
Geo-->>TS: 거리(m)
TS->>FS: getNodeInfo(startNodeId, routeId)
FS-->>TS: NodeInfo
TS-->>TS: Step 생성(SUBWAY/BUS/WALK 등)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
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 (1)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
51-54: LocationInfo 정의는 코드베이스 내 참조 없음 — 제거 권장rg 검색 결과 해당 심볼은 정의된 파일 외에 참조가 없습니다. 사용되지 않는다면 제거하세요.
- 파일: src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java — 삭제 후보(라인 대략 51–54)
제안 삭제(diff):
- public record LocationInfo( - Double latitude, - Double longitude - ) {}외부(다른 저장소/클라이언트)에서 사용될 가능성이 있으면 삭제 전 확인 바랍니다.
🧹 Nitpick comments (25)
README.md (11)
1-3: 문서 추가 자체는 좋습니다프로젝트 전반(개요, 팀, 스택, 아키텍처, 폴더 구조 등)을 한 곳에 모아둔 점이 유용합니다. 아래는 마크다운 렌더링 품질과 링크 신뢰성을 높이기 위한 가벼운 손보기 제안입니다.
12-13: 헤딩 레벨 일관성(HD001) 정리 제안H1 다음 바로 H3를 사용하고(라인 12–13), H2 다음 H4를 사용(라인 188)해 markdownlint가 경고합니다. 각 위치에서 한 단계씩만 증가하도록 조정하면 좋습니다.
아래처럼 변경해 주세요:
-### [🛠️Wayble 서비스 링크 바로가기](https://wayble.site) -### [🎬Wayble 노션 링크 바로가기](https://www.notion.so/wayble-20475cf0b87b806d9473feb579ab23e0) +## [🛠️Wayble 서비스 링크 바로가기](https://wayble.site) +## [🎬Wayble 노션 링크 바로가기](https://www.notion.so/wayble-20475cf0b87b806d9473feb579ab23e0)-#### [🛠️Wayble 기능명세 링크](https://www.notion.so/API-21d75cf0b87b80248a0ec55c6134ad20) +### [🛠️Wayble 기능명세 링크](https://www.notion.so/API-21d75cf0b87b80248a0ec55c6134ad20)Also applies to: 188-188
15-25: 목차 앵커 불일치(MD051) 수정 제안목차의 앵커가 실제 헤딩과 다릅니다. 특히 “BE 폴더 구조”, “BE 시스템 구성도”, “상세 기능” 등이 링크 깨짐 원인입니다. 아래처럼 실제 헤딩에 맞춰 정리해주세요. “상세 기능” 섹션은 본문에 부재하므로 임시 제거 또는 섹션 추가 중 선택이 필요합니다.
-### 📂 Content -- [🔎 팀 소개](#팀-소개) -- [🔎 기술 스택](#기술-스택) -- [🔎 서비스 고안 배경](#서비스-고안-배경) -- [🔎 주요 기능](#주요-기능) -- [🔎 상세 기능](#상세-기능) -- [🔎 BE 폴더 구조](#BE-폴더-구조) -- [🔎 BE 시스템 구성도](#BE-시스템-구성) -- [🔎 데이터베이스 구조](#데이터베이스-구조) -- [🔎 api 명세](#api-명세) +### 📂 Content +- [🔎 팀 소개](#-팀-소개) +- [🔎 기술 스택](#-기술-스택) +- [🔎 서비스 고안 배경](#-서비스-고안-배경) +- [🔎 주요 기능](#-주요-기능) +<!-- 상세 기능 섹션이 본문에 없으므로 일단 비활성화하거나 본문 섹션을 추가하세요 --> +<!-- - [🔎 상세 기능](#-상세-기능) --> +- [🔎 폴더 구조](#-폴더-구조) +- [🔎 시스템 구성도](#-시스템-구성도) +- [🔎 데이터베이스 구조](#-데이터베이스-구조) +- [🔎 API 명세](#-api-명세)원하시면 실제 GitHub 앵커 규칙에 맞춰 전체 헤딩→앵커 매핑 표를 정리해드릴게요.
4-4: 굵은 글씨 대신 헤딩 사용(MD036)으로 가독성 개선현재 굵은 글씨로 사실상 헤딩 역할을 하는 라인이 다수 있습니다. 헤딩으로 변경하면 목차/접근성/렌더링 품질이 좋아집니다.
-**사용자를 위한 맞춤형 배리어프리 서비스 Wayble** +## 사용자를 위한 맞춤형 배리어프리 서비스 Wayble-**WAYBLE은** +### WAYBLE은-**장애 유형·이동 수단별 최적화된 경로 탐색** +### 장애 유형·이동 수단별 최적화된 경로 탐색-**접근성 필터 기반 장소 추천** +### 접근성 필터 기반 장소 추천-**장애인 참여형 접근성 정보 수집·공유** +### 장애인 참여형 접근성 정보 수집·공유-**1. 지도 기반 접근 가능 장소 검색** +### 1) 지도 기반 접근 가능 장소 검색-**2. 맞춤형 경로 안내** +### 2) 맞춤형 경로 안내-**3. 접근성 리뷰 작성·열람** +### 3) 접근성 리뷰 작성·열람-**4. 마이 플레이스** +### 4) 마이 플레이스Also applies to: 81-81, 90-90, 92-92, 94-94, 101-101, 106-106, 114-114, 118-118
36-41: 팀 표 마크다운 파이프 스타일(MD055) 정리표 행 끝의 트레일링 파이프가 없어 lint가 경고합니다. 아래처럼 모두 행 끝에 파이프를 추가해 주세요.
-| 기승민 (Lead) | 양효인 | 유승인 | 이원준 | 주정빈 | -| ---------------------------------------- | ------------------------------------ | -------------------------------------- | ------------------------------------------------ | -------------------------------------- | -|  |  || |  -| BE | BE | BE | BE | BE | -| [@KiSeungMin](https://github.com/KiSeungMin) |[@hyoinYang](https://github.com/hyoinYang)|[@seung-in-Yoo](https://github.com/seung-in-Yoo) | [@wonjun-lee-fcwj245](https://github.com/wonjun-lee-fcwj245) |[@zyovn](https://github.com/zyovn) | +| 기승민 (Lead) | 양효인 | 유승인 | 이원준 | 주정빈 | +| ---------------------------------------------- | ------------------------------------ | -------------------------------------- | ------------------------------------------------ | ------------------------------------ | +|  |  |  |  |  | +| BE | BE | BE | BE | BE | +| [@KiSeungMin](https://github.com/KiSeungMin) | [@hyoinYang](https://github.com/hyoinYang) | [@seung-in-Yoo](https://github.com/seung-in-Yoo) | [@wonjun-lee-fcwj245](https://github.com/wonjun-lee-fcwj245) | [@zyovn](https://github.com/zyovn) |추가로 표 위/아래에 빈 줄 하나씩을 두면 가독성이 더 좋아집니다(MD058).
44-56: “기술 스택” 표 주변에 빈 줄 추가(MD058)표 위/아래에 빈 줄이 없어 lint 경고가 납니다. 한 줄씩 추가해주세요.
-## 🔎 기술 스택 - +## 🔎 기술 스택 + | Category | Stack | ... -| Version Control |   | -<br> +| Version Control |   | + +<br>
136-136: 코드 블록에 언어 지정(MD040)폴더 트리 코드 블록에 언어를 지정하면 렌더링/복사성이 좋아집니다. 일반 텍스트라면 text를 권장합니다.
-``` +```text
96-96: 플레이스홀더 문구 제거 또는 보강“(채우기)” 플레이스홀더는 독자에게 혼란을 줄 수 있습니다. 작성 예정이라면 “준비 중” 등으로 명시하거나 항목을 일단 제거하는 것이 깔끔합니다.
-(채우기) +준비 중Also applies to: 124-124
155-161: 민감 파일 안내 문구 관련 보안 점검README에 application_secret.yml, keystore.p12 등 민감 파일이 명시되어 있습니다. 실제 레포에 커밋되지 않도록 .gitignore/배포 파이프라인 시크릿 관리가 되어있는지 재확인 부탁드립니다.
필요 시:
- .gitignore에 해당 파일 추가
- 운영/배포는 GitHub Actions Secrets, Parameter Store, Secrets Manager 등으로 대체
- README에는 “개발 환경에서만 필요, 저장소에 커밋하지 말 것” 주석 추가
186-191: API 명세 링크 표현 통일섹션 제목이 “API 명세”이므로 링크 텍스트도 대소문자/표현을 통일하면 좋습니다. 위 헤딩 레벨 조정과 함께 반영하면 깔끔합니다.
-#### [🛠️Wayble 기능명세 링크](https://www.notion.so/API-21d75cf0b87b80248a0ec55c6134ad20) +### [🛠️Wayble API/기능 명세](https://www.notion.so/API-21d75cf0b87b80248a0ec55c6134ad20)
62-87: README에 PR 핵심 변경 요약 단락 추가 고려이번 PR의 핵심(예: KRIC WebClient 타임아웃/리트라이, 환승 횟수 제한/카운트 로직 보강)이 실제 운영 관점에서 중요하므로, 간단한 “최근 변경 사항” 혹은 CHANGELOG 링크를 README 상단에 추가하면 온보딩이 수월합니다.
원하시면 “최근 변경 사항” 박스(한두 문단)와 관련 이슈/PR 링크 템플릿을 바로 만들어 드리겠습니다.
src/main/java/com/wayble/server/common/config/WebClientConfig.java (2)
34-34: in-memory 제한(2MB) 설정은 적절합니다. 다만 프로퍼티로 외부화하는 것을 고려해주세요.KRIC 응답 페이로드가 커질 수 있으므로 운영 중 튜닝 가능성을 위해
kric.api.max-in-memory-size같은 프로퍼티로 노출해 두면 유연성이 올라갑니다.
35-38: 재시도 범위를 5xx/Timeout까지 확장하고, 지터/최대 backoff를 추가하는 것이 안정성 측면에서 유리합니다.현재는
WebClientRequestException에만 재시도하며 5xx는 재시도되지 않습니다. 또한 Timeout도 재시도 대상이 아닙니다. 아래와 같이 5xx를 예외로 승격하고, Timeout/ResponseException도 재시도하며, 지터/최대 backoff를 추가하는 것을 권장합니다. 또한 비멱등 요청 오발 재시도를 방지하기 위해 GET 메서드에만 재시도하도록 제한합니다.- .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) -> next.exchange(request) + .flatMap(response -> { + if (response.statusCode().is5xxServerError()) { + return reactor.core.publisher.Mono.error( + new org.springframework.web.reactive.function.client.WebClientResponseException( + "5xx from KRIC: " + response.statusCode(), + response.rawStatusCode(), + response.statusCode().getReasonPhrase(), + null, null, null + ) + ); + } + return reactor.core.publisher.Mono.just(response); + }) + .timeout(java.time.Duration.ofSeconds(15)) + .retryWhen( + reactor.util.retry.Retry.backoff(3, java.time.Duration.ofSeconds(1)) + .maxBackoff(java.time.Duration.ofSeconds(5)) + .jitter(0.5) + .filter(throwable -> + request.method() == org.springframework.http.HttpMethod.GET && ( + throwable instanceof org.springframework.web.reactive.function.client.WebClientRequestException + || throwable instanceof java.util.concurrent.TimeoutException + || throwable instanceof org.springframework.web.reactive.function.client.WebClientResponseException + ) + ) + ))추가로, 연결/응답 타임아웃은 Netty 커넥터 레벨에서도 설정해 두면(예: connect timeout, response timeout) 보다 일관되게 동작합니다. 필요하시면 적용 코드도 드리겠습니다.
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java (2)
6-16: 필드 확장 반영 좋습니다. 다만 JSON 바인딩 키 스펙을 한 번 더 확인해주세요.KRIC 응답의 필드명이 정확히 현재 레코드 파라미터명과 일치하지 않으면 매핑이 실패할 수 있습니다(대소문자/철자 포함). 확실히 하려면
@JsonProperty로 명시 바인딩을 권장합니다.예시(필요한 모든 필드에 동일 패턴 적용):
- String railOprIsttCd, - String lnCd, + @com.fasterxml.jackson.annotation.JsonProperty("railOprIsttCd") String railOprIsttCd, + @com.fasterxml.jackson.annotation.JsonProperty("lnCd") String lnCd, String stinCd, - String grndDvNm, - String stinFlor, - String gateInotDvNm, - String exitNo, - String dtlLoc, - String mlFmlDvNm, - String toltNum, - String diapExchNum + @com.fasterxml.jackson.annotation.JsonProperty("grndDvNm") String grndDvNm, + @com.fasterxml.jackson.annotation.JsonProperty("stinFlor") String stinFlor, + @com.fasterxml.jackson.annotation.JsonProperty("gateInotDvNm") String gateInotDvNm, + @com.fasterxml.jackson.annotation.JsonProperty("exitNo") String exitNo, + @com.fasterxml.jackson.annotation.JsonProperty("dtlLoc") String dtlLoc, + @com.fasterxml.jackson.annotation.JsonProperty("mlFmlDvNm") String mlFmlDvNm, + @com.fasterxml.jackson.annotation.JsonProperty("toltNum") String toltNum, + @com.fasterxml.jackson.annotation.JsonProperty("diapExchNum") String diapExchNum실제 샘플 응답(JSON)을 기반으로 키 일치 여부를 확인해 주세요. 필요 시 샘플 기반으로 주석/테스트도 추가 가능했습니다.
3-3: 불필요한 import 제거
record는 접근자(getter)를 자동 생성하므로lombok.Getter는 불필요합니다. 제거하여 정리하는 것을 권장합니다.-import lombok.Getter;src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (2)
46-47:List<String>로의 변경은 단순하지만, 포맷 표준화 또는 VO 도입을 고려해 주세요.문자열 리스트는 포맷이 팀/클라 간 쉽게 일탈될 수 있습니다. 최소한 예시/포맷 규칙(예: “상행·하행/호선/출구 번호/상세 위치” 등)을 스키마/문서에 명시하거나, 간단한 값 객체(예:
FacilityLocation { String description; })를 두는 것을 권장합니다.원한다면 Swagger 스키마 예시를 추가해 드릴 수 있습니다.
58-59: NodeInfo도 동일하게 포맷 표준화를 권장합니다.
List<String>로의 통일은 일관성을 주지만, 소비자 입장에서 위치 문자열의 문법이 통제되지 않으면 파싱/표시 이슈가 발생합니다. 간단한 VO 또는 명확한 문서화를 함께 고려해 주세요.src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
40-41: 연관관계 추가 방향 좋습니다. Lazy 로딩으로 기본 성능도 안전합니다.Route 기준으로 Wheelchair를 조회해야 하는 요구 사항과 일치합니다. 다만 컬렉션 NPE 방지를 위해 기본 초기화를 고려할 수 있습니다.
- private List<Wheelchair> wheelchairs; + private List<Wheelchair> wheelchairs = new java.util.ArrayList<>();또한 영속성 전파/고아 객체 제거가 필요할지(작성/삭제를 Route를 통해 관리한다면
cascade = ALL,orphanRemoval = true) 도메인 요구사항에 맞게 판단해 주세요.src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (2)
11-15: 파생 쿼리 메서드로 간소화 가능 (@query 불필요)Spring Data JPA의 파생 쿼리를 활용하면 @query 없이 동일 기능을 제공할 수 있습니다. 또한 타입 안정성과 유지보수성이 개선됩니다.
-import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; +// @Query, @Param 불필요 ... - @Query("SELECT w FROM Wheelchair w WHERE w.route.routeId = :routeId") - List<Wheelchair> findByRouteId(@Param("routeId") Long routeId); + List<Wheelchair> findAllByRoute_RouteId(Long routeId);
8-10: 불필요한 import 정리 제안
Optional을 사용하지 않으므로 제거해도 됩니다.import java.util.List; -import java.util.Optional;src/main/java/com/wayble/server/direction/service/TransportationService.java (1)
592-604: WALK 구간 실제 거리 반영 좋습니다연속 WALK 구간을 실제 하버사인 거리로 계산해 Step에 반영한 점 좋습니다. 이후 정렬 로직(calculateWalkDistance)이 현재 상수(500m)를 사용하고 있어 실제 거리 기반으로 정렬되지 않는 점은 개선 여지가 있습니다. 필요 시 제가 반영 코드 제안드릴 수 있습니다.
추가 제안(참고용, 별도 변경 필요 영역):
// 제안: 실제 walkDistance를 합산하도록 변경 private int calculateWalkDistance(List<TransportationResponseDto.Step> route) { return route.stream() .filter(step -> step.mode() == DirectionType.WALK) .mapToInt(TransportationResponseDto.Step::moveNumber) // WALK에서는 moveNumber를 거리(m)로 사용 중 .sum(); }src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java (1)
11-24: 쿼리 성능 향상용 인덱스 권장 (route_id)
findByRouteId조회가 빈번하다면 route_id에 인덱스를 추가하는 것이 좋습니다. JPA로는 @table(indexes=…)로 지정 가능합니다.-@Table(name = "wheelchair") +@Table( + name = "wheelchair", + indexes = { + @Index(name = "idx_wheelchair_route", columnList = "route_id") + } +)src/main/java/com/wayble/server/direction/service/FacilityService.java (3)
37-55: 노드 존재 검증 및 경로별 휠체어 정보 병합: 방향성 OK
- Node 존재 시에만 후속 처리를 수행하는 흐름 적절합니다.
- routeId가 있을 때 노선별 휠체어 위치를 문자열로 트리밍 후 수집하는 로직도 깔끔합니다.
향후 elevator도 동일한 방식으로 채울 계획인지 확인 부탁드립니다. 현재는 항상 빈 리스트로 반환됩니다.
엘리베이터 정보도
Facility의elevators를 이용해List<String>로 맵핑하는 보일러플레이트 코드를 제가 준비해 드릴까요?
59-71: Facility 조회 실패/정보 누락 로깅은 좋음 — 단, Repository 쿼리 의도 확인 필요
facilityRepository.findByNodeId(nodeId)는 내부적으로f.id = :nodeId를 사용합니다. @mapsid로 동일하긴 하나, 의도를 명확히 하려면f.node.id로 조건을 거는 편이 가독성상 안전합니다. Repository 쪽 코멘트 참고해 주세요.
97-103: WebClient 타임아웃 추가 권장PR 목표에 ‘타임아웃 추가’가 포함되어 있습니다. 현재
.block()만 사용하고 있어 호출이 지연될 수 있습니다. Mono 레벨에서 타임아웃을 추가하세요.KricToiletRawResponse response = kricWebClient .get() .uri(uri) .retrieve() - .bodyToMono(KricToiletRawResponse.class) + .bodyToMono(KricToiletRawResponse.class) + .timeout(Duration.ofSeconds(3)) .block();추가로, WebClient Bean 구성에서 커넥션/읽기 타임아웃을 설정해 두면 더욱 안전합니다.
추가 import (파일 상단에):
import java.time.Duration;
📜 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 selected for processing (13)
README.md(1 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/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)
💤 Files with no reviewable changes (2)
- src/main/java/com/wayble/server/direction/entity/transportation/Facility.java
- src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java
🧰 Additional context used
🧬 Code Graph Analysis (7)
src/main/java/com/wayble/server/common/config/WebClientConfig.java (1)
src/main/java/com/wayble/server/direction/external/kric/KricProperties.java (1)
ConfigurationProperties(5-10)
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/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/external/kric/dto/KricToiletRawResponse.java (2)
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java (1)
KricToiletRawBody(5-7)src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1)
MsgBody(28-30)
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/TransportationService.java (2)
src/main/java/com/wayble/server/direction/service/WalkingService.java (1)
Slf4j(20-64)src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (1)
Service(16-170)
src/main/java/com/wayble/server/direction/service/FacilityService.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/Node.java (1)
Entity(13-79)
🪛 markdownlint-cli2 (0.17.2)
README.md
4-4: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
12-12: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
16-16: Link fragments should be valid
(MD051, link-fragments)
17-17: Link fragments should be valid
(MD051, link-fragments)
18-18: Link fragments should be valid
(MD051, link-fragments)
19-19: Link fragments should be valid
(MD051, link-fragments)
20-20: Link fragments should be valid
(MD051, link-fragments)
21-21: Link fragments should be valid
(MD051, link-fragments)
22-22: Link fragments should be valid
(MD051, link-fragments)
23-23: Link fragments should be valid
(MD051, link-fragments)
24-24: Link fragments should be valid
(MD051, link-fragments)
38-38: Table pipe style
Expected: leading_and_trailing; Actual: leading_only; Missing trailing pipe
(MD055, table-pipe-style)
55-55: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
81-81: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
90-90: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
92-92: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
94-94: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
101-101: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
106-106: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
114-114: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
118-118: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
136-136: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
188-188: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4
(MD001, heading-increment)
🔇 Additional comments (6)
src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java (1)
6-6: KRIC 응답 스키마(body vs body.item) 변경 여부를 재확인해 주세요.현재
body를List<KricToiletRawItem>로 평탄화했습니다. 실제 KRIC 응답이{ "body": { "item": [...] } }구조라면 역직렬화가 실패합니다. 반대로{ "body": [...] }구조라면 지금 구현이 맞습니다.만약 기존 구조(중첩)라면 아래처럼 유지하는 편이 안전합니다(참고 코드):
// KricToiletRawResponse.java public record KricToiletRawResponse( KricToiletRawBody body ) {} // KricToiletRawBody.java public record KricToiletRawBody( @com.fasterxml.jackson.annotation.JsonProperty("item") java.util.List<KricToiletRawItem> item ) {}실제 샘플 응답으로 검증이 필요하면 말씀 주세요. 테스트/매퍼 보완까지 함께 드릴 수 있습니다.
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
23-23: 주석 업데이트 좋습니다.
WALK단계에서moveNumber가 “거리(미터)”일 수 있음을 명시해 혼동을 줄였습니다.src/main/java/com/wayble/server/direction/service/TransportationService.java (2)
290-293: 환승 제한(3회 미만) 필터링 적용, 방향성 적절PR 목표(환승 제한 강화)에 부합합니다. 이후 정렬 지표에 실제 도보 거리(아래 WALK step 계산) 반영도 고려해 보세요.
658-659: 에러 로그 레벨 상향 적절실패 시 기본값으로 대체하고 error로 로깅 처리한 점, 장애 분석에 유리합니다.
src/main/java/com/wayble/server/direction/service/FacilityService.java (2)
113-115: 예외 로깅 형식 👍식별자(stinCd, railOprIsttCd, lnCd)와 예외 메시지/스택을 함께 남겨 원인 분석에 유리합니다.
86-94: KRIC 파라미터명(railOprIsttCd) 확인 — 변경 불필요KRIC "vulnerableUserInfo/stationDisabledToilet" API의 파라미터명은 railOprIsttCd가 맞습니다. 현재 코드에서는 queryParam 이름으로 "railOprIsttCd"를 사용하고 값으로 facility.getRailOprLsttCd()를 전달하고 있으므로 호출은 올바릅니다. (엔티티 필드명 railOprLsttCd는 DB 컬럼 rail_opr_lstt_cd와 매핑되어 네이밍이 다름)
수정이 필요한 위치(참고):
- src/main/java/com/wayble/server/direction/service/FacilityService.java
- getToiletInfo(): .queryParam("railOprIsttCd", facility.getRailOprLsttCd())
- src/main/java/com/wayble/server/direction/entity/transportation/Facility.java
- @column(name="rail_opr_lstt_cd") private String railOprLsttCd;
권장사항(선택): 네이밍 혼동 방지를 위해 필드 주석 추가나 이름 통일을 고려하세요.
| @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로 컬렉션을 조인하면서 단건(Optional<Facility>)을 반환하고 있는데, DISTINCT가 없어 동일 Facility가 중복 로우로 반환될 수 있습니다. JPA 구현에 따라 NonUniqueResultException이 발생할 수 있으므로 DISTINCT를 추가해 중복 제거가 필요합니다. 또한 메서드명은 nodeId로 찾는 의미인데 WHERE 절은 f.id를 사용하고 있어, 의도를 명확히 하려면 f.node.id를 사용하는 편이 좋습니다(@mapsid로 동등하긴 하나 가독성 관점에서 이점).
아래와 같이 수정 제안드립니다.
- @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.node.id = :nodeId")
+ Optional<Facility> findByNodeId(@Param("nodeId") Long nodeId);추가로, 현재 호출부(FacilityService)에서는 lifts를 실제로 사용하지 않으므로 성능이 민감하다면 fetch join 자체를 제거하거나 필요 시 elevators까지 함께 fetch join하는 방향을 검토해 주세요.
📝 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.node.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 query uses a collection fetch join (LEFT JOIN FETCH
f.lifts) while returning a single Optional<Facility>, which can yield duplicate
rows and cause NonUniqueResultException; change the JPQL to SELECT DISTINCT f
and update the WHERE clause to reference f.node.id = :nodeId for clarity; if
lifts are not needed by the caller consider removing the fetch join (or fetch
additional collections like elevators only when necessary) to avoid unnecessary
performance cost.
| TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); | ||
|
|
||
| subwayInfo = new TransportationResponseDto.SubwayInfo( | ||
| nodeInfo.wheelchair(), | ||
| nodeInfo.elevator(), | ||
| nodeInfo.accessibleRestroom() | ||
| ); | ||
| } else { | ||
| subwayInfo = new TransportationResponseDto.SubwayInfo( | ||
| new ArrayList<>(), | ||
| new ArrayList<>(), | ||
| false | ||
| ); | ||
| } |
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.
NPE 위험: currentEdge.getRoute() null 체크 필요
facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()) 호출 전 currentEdge.getRoute()가 null일 수 있습니다. 방어 코드가 없으므로 NPE 위험이 있습니다.
- TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId());
+ Long routeId = (currentEdge.getRoute() != null) ? currentEdge.getRoute().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()); | |
| subwayInfo = new TransportationResponseDto.SubwayInfo( | |
| nodeInfo.wheelchair(), | |
| nodeInfo.elevator(), | |
| nodeInfo.accessibleRestroom() | |
| ); | |
| } else { | |
| subwayInfo = new TransportationResponseDto.SubwayInfo( | |
| new ArrayList<>(), | |
| new ArrayList<>(), | |
| false | |
| ); | |
| } | |
| Long routeId = (currentEdge.getRoute() != null) ? currentEdge.getRoute().getRouteId() : null; | |
| TransportationResponseDto.NodeInfo nodeInfo = | |
| facilityService.getNodeInfo(currentEdge.getStartNode().getId(), routeId); | |
| subwayInfo = new TransportationResponseDto.SubwayInfo( | |
| nodeInfo.wheelchair(), | |
| nodeInfo.elevator(), | |
| nodeInfo.accessibleRestroom() | |
| ); | |
| } else { | |
| subwayInfo = new TransportationResponseDto.SubwayInfo( | |
| new ArrayList<>(), | |
| new ArrayList<>(), | |
| false | |
| ); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 643 to 656, currentEdge.getRoute() may be null before calling
currentEdge.getRoute().getRouteId(), so add a null check for
currentEdge.getRoute() (or use Optional/null-safe access) and only call
facilityService.getNodeInfo(...) when the route is non-null; if null, fall back
to the existing else behavior (create SubwayInfo with empty lists and false) and
optionally log a warning or debug message to aid troubleshooting.
| 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(); | ||
| } | ||
| } |
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.
환승 카운트 로직이 사실상 매 스텝마다 +1; 의도와 불일치
현재 분기 구조상 동일 모드+동일 노선에서도 환승이 증가합니다. 일반적으로 첫 대중교통 탑승은 환승으로 보지 않고, 이후 모드 변경 또는 노선명이 달라질 때만 +1 해야 합니다.
아래와 같이 간결하고 의도에 맞게 수정 제안드립니다.
- 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();
- }
- }
+ for (TransportationResponseDto.Step step : steps) {
+ if (step.mode() == DirectionType.WALK
+ || step.mode() == DirectionType.FROM_WAYPOINT
+ || step.mode() == DirectionType.TO_WAYPOINT) {
+ continue;
+ }
+
+ if (previousMode == null) {
+ // 첫 탑승은 환승 아님
+ previousMode = step.mode();
+ previousRouteName = step.routeName();
+ continue;
+ }
+
+ if (step.mode() != previousMode) {
+ transferCount++;
+ } else {
+ if (previousRouteName != null && step.routeName() != null
+ && !previousRouteName.equals(step.routeName())) {
+ transferCount++;
+ }
+ }
+
+ previousMode = step.mode();
+ previousRouteName = step.routeName();
+ }📝 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.
| 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(); | |
| } | |
| } | |
| 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) { | |
| continue; | |
| } | |
| if (previousMode == null) { | |
| // 첫 탑승은 환승 아님 | |
| previousMode = step.mode(); | |
| previousRouteName = step.routeName(); | |
| continue; | |
| } | |
| if (step.mode() != previousMode) { | |
| transferCount++; | |
| } else { | |
| if (previousRouteName != null && step.routeName() != null | |
| && !previousRouteName.equals(step.routeName())) { | |
| transferCount++; | |
| } | |
| } | |
| previousMode = step.mode(); | |
| previousRouteName = step.routeName(); | |
| } |
zyovn
left a comment
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.
수고하셨습니다! 👍
✔️ 연관 이슈
📝 작업 내용
스크린샷 (선택)
Summary by CodeRabbit
신기능
개선
버그 수정
문서