Skip to content

Conversation

@hyoinYang
Copy link
Contributor

@hyoinYang hyoinYang commented Aug 13, 2025

✔️ 연관 이슈

📝 작업 내용

  • 최종 dto 수정: 이동하는 역 이름/ 개수, 버스 관련 정보(저상버스 여부, 마을버스 여부, 배차 간격)를 반영
  • 버스 관련 정보는 HttpClient를 이용해 공공데이터포털 api를 호출 (공공데이터포털의 service key 때문에 WebClient, RestTemplate를 사용하면 이중 인코딩 문제가 발생해 임시적으로 HttpClient 이용. 추후 리팩토링 예정)

스크린샷 (선택)

Summary by CodeRabbit

  • New Features

    • 대중교통 길찾기 응답 확장: 구간별 moveInfo, busInfo, subwayInfo 및 페이지네이션 제공
    • 실시간 버스 정보 연동: 정류장 검색, 최단 정류장 선택, 도착/배차 간격 및 저상버스 여부 표시
    • HTTP 클라이언트와 OpenData API 설정 노출 (타임아웃, 사용자 에이전트 등)
  • Refactor

    • DTO 패키지 구조 재정비 및 응답 모델 재구성
    • 경로 병합 로직 개선으로 구간 표현 정확도 향상
  • Chores

    • 애플리케이션 설정에 공공데이터 속성 추가
    • 저장소 인터페이스 추가로 노선 조회 지원

@hyoinYang hyoinYang self-assigned this Aug 13, 2025
@hyoinYang hyoinYang added the 💡 feature 기능 구현 및 개발 label Aug 13, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

오픈데이터 연동을 위한 설정·DTO·HTTP 클라이언트와 BusInfoService가 추가되고, 대중교통 DTO 패키 구조가 재배치되며 TransportationService가 경로 병합 및 단계별(버스/지하철) 추가 정보를 첨부하도록 변경되었습니다.

Changes

Cohort / File(s) Summary
앱 설정 등록
src/main/java/com/wayble/server/ServerApplication.java
@EnableConfigurationPropertiesOpenDataProperties 등록.
HTTP 클라이언트 설정
src/main/java/com/wayble/server/common/config/HttpClientConfig.java
connect/request timeout 구성값을 읽어 HttpClientDuration 빈을 생성하는 Spring @configuration 추가.
DTO 패키 및 구조 변경
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java, src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java, src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java
Request DTO를 dto.request로 이동. 기존 dto/TransportationResponseDto 삭제 및 새 dto.response.TransportationResponseDto 추가(steps에 moveInfo, moveNumber, busInfo, subwayInfo 등 포함).
컨트롤러/서비스 임포트 정리
src/main/java/com/wayble/server/direction/controller/TransportationController.java, src/main/java/com/wayble/server/direction/service/FacilityService.java
DTO 패키 변경에 따른 import 경로 업데이트.
오픈데이터 연동: 설정 및 DTOs
src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java, .../opendata/dto/OpenDataResponse.java, .../opendata/dto/StationSearchResponse.java, .../opendata/dto/Arrival.java
OpenData API용 @ConfigurationProperties 레코드와 도착·정류장 검색 응답 매핑용 Jackson-annotated 레코드 추가.
실시간 버스 정보 서비스
src/main/java/com/wayble/server/direction/service/BusInfoService.java
정류장 검색, 최단 거리 정류장 선택, 도착정보 조회 후 BusInfo(저상 여부, 저상 리스트, 배차간격 등) 생성하는 서비스 추가.
운행/경로 처리 로직 변경
src/main/java/com/wayble/server/direction/service/TransportationService.java
연속 엣지 병합 로직 변경, MoveInfo 목록 생성, BUS일 때 BusInfoService 호출로 busInfo 첨부, SUBWAY일 때 subwayInfo 구성 등으로 Step 생성 로직 확장; BusInfoService 주입 추가.
저장소 추가
src/main/java/com/wayble/server/direction/repository/RouteRepository.java
JpaRepository<Route, Long> 인터페이스 추가.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Controller
  participant TransportationService as Service
  participant BusInfoService as BusInfo
  participant FacilityService as Facility
  participant OpenDataAPI as OpenData

  Client->>Controller: 요청(TransportationRequestDto)
  Controller->>Service: findTransportation(request)
  Service->>Service: 엣지 그룹화 및 Step 생성
  alt Step 타입 == BUS
    Service->>BusInfo: getBusInfo(stationName,busId,x,y)
    BusInfo->>OpenData: 정류장 검색 API 호출
    OpenData-->>BusInfo: StationSearchResponse
    BusInfo->>OpenData: 도착정보 조회 API 호출
    OpenData-->>BusInfo: OpenDataResponse
    BusInfo-->>Service: BusInfo (isShuttleBus/isLowFloor/dispatchInterval)
  else Step 타입 == SUBWAY
    Service->>Facility: getNodeInfo(...)
    Facility-->>Service: NodeInfo
    Service->>Service: SubwayInfo 매핑
  end
  Service-->>Controller: TransportationResponseDto
  Controller-->>Client: 200 OK (응답 JSON)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
저상버스 여부 실시간 확인 (#124)
DTO에 이동 정류장 수와 이동 역 목록 반영 (#124)
버스 정보에 시내버스/마을버스(마포 우선) 포함 (#124)

Possibly related PRs

Suggested reviewers

  • zyovn
  • seung-in-Yoo

Poem

폴짝, 폴짝, 정류장 지도를 훑으며
JSON 냄새 맡아 저상 버스 찾았어요 🐰
몇 정류장 갈래요, 어느 역을 지나요?
오픈데이터로 소식 모아 한 그릇 담아
토끼는 기쁘게 푸드득 리뷰를 맡습니다.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/hyoin

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🔭 Outside diff range comments (1)
src/main/java/com/wayble/server/direction/service/FacilityService.java (1)

80-92: NPE 가능성: 응답/body/item null 처리 누락 및 예외 로깅 개선 필요

  • response, response.body(), response.body().item()이 null일 때 이후 for-each에서 NPE가 발생할 수 있습니다.
  • 예외 로깅은 스택트레이스를 남길 수 있도록 e를 마지막 파라미터로 전달하는 것이 좋습니다. 또한 빈 맵 반환 시 Map.of()가 더 의도를 드러냅니다.

다음과 같이 보완을 제안합니다:

             KricToiletRawResponse response = kricWebClient
                     .get()
                     .uri(uri)
                     .retrieve()
                     .bodyToMono(KricToiletRawResponse.class)
                     .block();
-
-            items = response.body().item();
+            if (response == null || response.body() == null || response.body().item() == null) {
+                log.warn("역사 화장실 api 응답이 비어있음: {}", uri);
+                return Map.of();
+            }
+            items = response.body().item();
 
         } catch(Exception e){
-            log.info("역사 화장실 api 호출 중 에러 발생: {}: {}", uri, e.getCause());
-            return new HashMap<>();
+            log.warn("역사 화장실 api 호출 중 에러 발생. uri: {}", uri, e);
+            return Map.of();
         }
🧹 Nitpick comments (6)
src/main/java/com/wayble/server/direction/repository/RouteRepository.java (2)

3-9: N+1 방지를 위한 EntityGraph 메서드 추가를 고려해 주세요.

서비스 계층에서 Route 조회 시 startNode/endNode를 자주 직렬화한다면 LAZY 로딩으로 인한 N+1 가능성이 있습니다. ReviewRepository에서와 같이 @entitygraph를 활용한 조회 메서드를 추가해두면 안전합니다. 필요 시 아래와 같이 확장할 수 있습니다.

-import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.EntityGraph;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;

 public interface RouteRepository extends JpaRepository<Route, Long>{
-    
+
+    @EntityGraph(attributePaths = {"startNode", "endNode"})
+    Optional<Route> findByRouteId(Long routeId);
+
+    @EntityGraph(attributePaths = {"startNode", "endNode"})
+    List<Route> findAllByRouteIdIn(Collection<Long> routeIds);
 }

7-9: 향후 조회 패턴에 맞춰 파생 쿼리 메서드 정의를 준비해 두세요.

노선명/유형 기반 탐색(예: BUS vs SUBWAY 또는 특정 노선명)이 빈번하다면, 파생 메서드 추가(예: findAllByRouteType(...), findByRouteTypeAndRouteName(...))가 DTO 매핑 구간에서의 조건 분기를 줄여줍니다. 현재 PR 범위를 벗어나면 이후 단계에서 반영해도 무방합니다.

src/main/java/com/wayble/server/common/config/HttpClientConfig.java (1)

3-12: 불필요한 @requiredargsconstructor 제거 제안.

본 클래스는 생성자 주입 필드가 없고, @value 필드만 존재합니다. @requiredargsconstructor는 효과가 없으므로 제거해도 됩니다.

적용 diff:

-import lombok.RequiredArgsConstructor;
@@
-@RequiredArgsConstructor
 public class HttpClientConfig {
src/main/java/com/wayble/server/direction/service/BusInfoService.java (1)

70-85: busId가 null일 때 버스 정보 제한이 불필요함

busId가 null일 때 최대 1개 노선만 처리하는 로직이 있습니다(Line 71). 그러나 이미 Line 61에서 정류소 도착 정보를 조회할 때 busId로 필터링이 되므로, 이 제한은 불필요합니다.

루프 제한 로직을 제거하여 코드를 단순화할 수 있습니다:

-int count = 0;
 for (OpenDataResponse.Item item : openDataResponse.msgBody().itemList()) {
-    if (count >= 1) break; // busId가 null일 때는 최대 1개 노선만
     
     // busType1과 busType2 추가
     isLowFloor.add("1".equals(item.busType1()));
     isLowFloor.add("1".equals(item.busType2()));
     
     // term을 정수로 변환
     try {
         dispatchInterval = Integer.parseInt(item.term());
     } catch (NumberFormatException e) {
         dispatchInterval = 0;
     }
-    
-    count++;
+    break; // 첫 번째 아이템만 처리
 }
src/main/java/com/wayble/server/direction/service/TransportationService.java (1)

316-317: 마을버스 판단 로직 중복

마을버스 여부를 판단하는 로직이 BusInfoService와 중복되어 있습니다. 이미 BusInfoService에서 처리하므로 여기서는 불필요합니다.

중복된 마을버스 판단 로직을 제거하세요:

 if (currentType == DirectionType.BUS) {
-    boolean isShuttle = routeName != null && routeName.contains("마포"); // 마을버스 구분
-
     Long stationId = currentEdge.getStartNode() != null ? currentEdge.getStartNode().getId() : null;
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)

51-56: NodeInfo record가 SubwayInfo와 중복됨

NodeInfoSubwayInfo가 동일한 필드를 가지고 있어 중복됩니다. 코드 관리의 복잡성을 증가시킬 수 있습니다.

NodeInfo를 제거하고 SubwayInfo를 재사용하거나, 상속 관계를 명확히 하세요:

-// 지하철 시설 정보 묶음 (서비스 내부에서 사용)
-public record NodeInfo(
-        List<LocationInfo> wheelchair,
-        List<LocationInfo> elevator,
-        Boolean accessibleRestroom
-) {}
+// NodeInfo를 SubwayInfo의 별칭으로 사용
+public static class NodeInfo extends SubwayInfo {
+    public NodeInfo(List<LocationInfo> wheelchair, List<LocationInfo> elevator, Boolean accessibleRestroom) {
+        super(wheelchair, elevator, accessibleRestroom);
+    }
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7463c2 and f19b48f.

📒 Files selected for processing (15)
  • src/main/java/com/wayble/server/ServerApplication.java (2 hunks)
  • src/main/java/com/wayble/server/common/config/HttpClientConfig.java (1 hunks)
  • src/main/java/com/wayble/server/direction/controller/TransportationController.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (0 hunks)
  • src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/response/BusInfo.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/Arrival.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/BusInfoService.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/FacilityService.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/TransportationService.java (5 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java
🧰 Additional context used
🧬 Code Graph Analysis (12)
src/main/java/com/wayble/server/direction/repository/RouteRepository.java (9)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
  • Entity (9-36)
src/main/java/com/wayble/server/direction/repository/PlaceRepository.java (1)
  • PlaceRepository (6-7)
src/main/java/com/wayble/server/direction/repository/DirectionElasticsearchRepository.java (1)
  • DirectionElasticsearchRepository (6-7)
src/main/java/com/wayble/server/direction/repository/NodeRepository.java (1)
  • NodeRepository (6-7)
src/main/java/com/wayble/server/direction/repository/EdgeRepository.java (1)
  • EdgeRepository (6-7)
src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (1)
  • FacilityRepository (10-12)
src/main/java/com/wayble/server/review/repository/ReviewRepository.java (1)
  • ReviewRepository (9-16)
src/main/java/com/wayble/server/review/repository/ReviewImageRepository.java (1)
  • ReviewImageRepository (6-7)
src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneImageRepository.java (1)
  • WaybleZoneImageRepository (6-7)
src/main/java/com/wayble/server/direction/service/FacilityService.java (1)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (3)
  • Schema (9-37)
  • NodeInfo (27-31)
  • Step (14-20)
src/main/java/com/wayble/server/ServerApplication.java (5)
src/main/java/com/wayble/server/common/client/tmap/TMapProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/common/config/WebClientConfig.java (1)
  • Configuration (10-36)
src/main/java/com/wayble/server/common/config/SwaggerConfig.java (1)
  • Configuration (12-34)
src/main/java/com/wayble/server/direction/external/kric/KricProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/common/config/security/JwtProperties.java (1)
  • Component (6-34)
src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (2)
src/main/java/com/wayble/server/direction/external/kric/KricProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/common/client/tmap/TMapProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/common/config/HttpClientConfig.java (2)
src/main/java/com/wayble/server/common/config/WebClientConfig.java (4)
  • Configuration (10-36)
  • Bean (17-21)
  • Bean (30-35)
  • Bean (23-28)
src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java (2)
  • Configuration (8-22)
  • Override (14-21)
src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (7)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (2)
  • Schema (9-37)
  • Step (14-20)
src/main/java/com/wayble/server/common/client/tmap/dto/response/TMapParsingResponse.java (1)
  • Builder (8-74)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
src/main/java/com/wayble/server/review/dto/ReviewResponseDto.java (1)
  • ReviewResponseDto (6-14)
src/main/java/com/wayble/server/wayblezone/dto/WaybleZoneListResponseDto.java (1)
  • Builder (6-17)
src/main/java/com/wayble/server/common/client/tmap/dto/response/TMapResponse.java (1)
  • TMapResponse (5-43)
src/main/java/com/wayble/server/auth/dto/TokenResponseDto.java (1)
  • TokenResponseDto (3-3)
src/main/java/com/wayble/server/direction/dto/response/BusInfo.java (3)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (4)
  • Schema (9-37)
  • NodeInfo (27-31)
  • Step (14-20)
  • LocationInfo (33-36)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
  • Entity (9-36)
src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (2)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
src/main/java/com/wayble/server/common/client/tmap/dto/response/TMapParsingResponse.java (1)
  • Builder (8-74)
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/direction/controller/TransportationController.java (2)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (2)
  • Schema (9-37)
  • Step (14-20)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (2)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (5)
  • Schema (9-37)
  • Step (14-20)
  • NodeInfo (27-31)
  • PageInfo (22-25)
  • LocationInfo (33-36)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/direction/service/TransportationService.java (2)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (2)
  • Schema (9-37)
  • Step (14-20)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
🔇 Additional comments (7)
src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1)

1-9: LGTM — 표준 JpaRepository 선언이며 기존 레포지토리들과 일관적입니다.

  • Route 엔티티의 @id 타입(Long)과 제네릭 파라미터가 일치합니다.
  • 패키지 구성 및 네이밍도 Node/Edge/Place 등 기존 레포지토리들과 동일한 스타일을 따릅니다.
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1)

1-1: 패키지 경로 변경 검증 완료
com.wayble.server.direction.dto.TransportationRequestDto(구 FQN) 사용처가 더 이상 존재하지 않으며, 모든 import가 신규 패키지(.dto.request / .dto.response)로 올바르게 반영된 것을 확인했습니다.

src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (1)

7-10: 키 노출 위험 없음 확인
OpenDataProperties 인스턴스 또는 toString() 호출이 로그에 출력되는 코드 흔적이 없습니다. 추가 조치 없이도 현재 키가 노출되지 않음을 확인했습니다.

src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1)

34-38: 배차 간격(term) 타입 확인 필요

OpenData의 배차 간격(term)이 숫자형으로 제공되는 경우가 많습니다. 현재 String으로 두어도 문제는 없지만, 계산/정렬에 사용할 계획이면 Integer로 매핑하는 편이 안전합니다. 실제 응답 스키마와 샘플을 기준으로 타입을 재검토해 주세요.

확인 후 Integer 매핑이 맞다면 다음과 같이 변경할 수 있습니다:

-        @JsonProperty("term") String term,
+        @JsonProperty("term") Integer term,
src/main/java/com/wayble/server/direction/service/FacilityService.java (1)

3-3: DTO 패키지 분리 반영한 import 변경 LGTM

응답 DTO 패키지 분리에 따른 import 경로 변경이 일관성 있게 적용되었습니다.

src/main/java/com/wayble/server/direction/controller/TransportationController.java (1)

4-5: DTO 패키지 경로 변경 반영 LGTM

요청/응답 DTO의 패키지 리팩터링에 맞춰 import가 정확하게 반영되었습니다. 컨트롤러의 공개 API 표면은 유지됩니다.

src/main/java/com/wayble/server/ServerApplication.java (1)

20-20: OpenDataProperties 프로퍼티 바인딩 활성화 LGTM

신규 OpenData 연동을 위한 프로퍼티 바인딩이 애플리케이션 레벨에서 활성화되었습니다.

애플리케이션 설정(application.yml/properties)에 OpenDataProperties에서 요구하는 프로퍼티(prefix 포함)가 누락되지 않았는지 확인해 주세요. 예: 키, 베이스 URL, 엔드포인트 경로 등.

Comment on lines +3 to +7
public record Arrival (
Integer busType1, // 1이면 저상
Integer busType2, // 1이면 저상
Integer term // 배차 간격
) {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

불리언/의미 있는 필드명으로 리팩터하고 단위를 명시하세요.

  • busType1/busType2는 “1이면 저상”인 불리언 성격의 값입니다. Integer 대신 Boolean(또는 원 API 값 유지 시 enum/flag)로 노출하고, 필드명을 lowFloor1/lowFloor2 등 의미 전달이 되는 이름으로 변경하는 편이 DTO 소비 측 가독성과 오용 방지에 좋습니다.
  • term은 헤드웨이(배차 간격)로 보입니다. 단위를 명확히(분/초) 하고 필드명에 담아 주세요. 예: headwayMinutes.

적용 예시(diff):

-public record Arrival (    
-    Integer busType1, // 1이면 저상
-    Integer busType2, // 1이면 저상
-    Integer term     // 배차 간격
-) {}
+public record Arrival(
+    Boolean lowFloor1,   // true면 저상
+    Boolean lowFloor2,   // true면 저상
+    Integer headwayMinutes // 배차 간격(분)
+) {}

참고: OpenDataResponse.Item의 원천 값이 문자열("1"/"0")이라면 변환 과정에서 null/이상치(그 외 값) 처리 정책을 명확히 해 주세요. 필요 시 정적 팩토리 메서드에서 검증/매핑을 수행하는 것을 권장합니다.

📝 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.

Suggested change
public record Arrival (
Integer busType1, // 1이면 저상
Integer busType2, // 1이면 저상
Integer term // 배차 간격
) {}
public record Arrival(
Boolean lowFloor1, // true면 저상
Boolean lowFloor2, // true면 저상
Integer headwayMinutes // 배차 간격(분)
) {}

Comment on lines +14 to +30
public record ComMsgHeader(
@JsonProperty("errMsg") String errMsg,
@JsonProperty("responseTime") String responseTime,
@JsonProperty("requestMsgID") String requestMsgID,
@JsonProperty("responseMsgID") String responseMsgID,
@JsonProperty("successYN") String successYN,
@JsonProperty("returnCode") String returnCode
) {}
public record MsgHeader(
@JsonProperty("headerMsg") String headerMsg,
@JsonProperty("headerCd") String headerCd,
@JsonProperty("itemCount") Integer itemCount
) {}

public record MsgBody(
@JsonProperty("itemList") List<Item> itemList
) {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

중첩 레코드에도 @JsonIgnoreProperties(ignoreUnknown = true) 적용 권장

상위 레벨에만 ignoreUnknown이 적용되어 있어, 하위 레벨(ComMsgHeader, MsgHeader, MsgBody)에 신규 필드가 추가될 경우 역직렬화 실패 가능성이 있습니다. 중첩 레코드에도 동일 옵션을 부여해 방어적으로 대응하는 것이 안전합니다.

다음과 같이 보완을 제안합니다:

     ) {}
-    public record MsgHeader(
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public record MsgHeader(
         @JsonProperty("headerMsg") String headerMsg,
         @JsonProperty("headerCd") String headerCd,
         @JsonProperty("itemCount") Integer itemCount
     ) {}
     
-    public record MsgBody(
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public record MsgBody(
         @JsonProperty("itemList") List<Item> itemList
     ) {}
     
+    @JsonIgnoreProperties(ignoreUnknown = true)
-    public record ComMsgHeader(
+    public record ComMsgHeader(
         @JsonProperty("errMsg") String errMsg,
         @JsonProperty("responseTime") String responseTime,
         @JsonProperty("requestMsgID") String requestMsgID,
         @JsonProperty("responseMsgID") String responseMsgID,
         @JsonProperty("successYN") String successYN,
         @JsonProperty("returnCode") String returnCode
     ) {}
📝 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.

Suggested change
public record ComMsgHeader(
@JsonProperty("errMsg") String errMsg,
@JsonProperty("responseTime") String responseTime,
@JsonProperty("requestMsgID") String requestMsgID,
@JsonProperty("responseMsgID") String responseMsgID,
@JsonProperty("successYN") String successYN,
@JsonProperty("returnCode") String returnCode
) {}
public record MsgHeader(
@JsonProperty("headerMsg") String headerMsg,
@JsonProperty("headerCd") String headerCd,
@JsonProperty("itemCount") Integer itemCount
) {}
public record MsgBody(
@JsonProperty("itemList") List<Item> itemList
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record ComMsgHeader(
@JsonProperty("errMsg") String errMsg,
@JsonProperty("responseTime") String responseTime,
@JsonProperty("requestMsgID") String requestMsgID,
@JsonProperty("responseMsgID") String responseMsgID,
@JsonProperty("successYN") String successYN,
@JsonProperty("returnCode") String returnCode
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record MsgHeader(
@JsonProperty("headerMsg") String headerMsg,
@JsonProperty("headerCd") String headerCd,
@JsonProperty("itemCount") Integer itemCount
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record MsgBody(
@JsonProperty("itemList") List<Item> itemList
) {}
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java
around lines 14 to 30, the nested record types ComMsgHeader, MsgHeader and
MsgBody lack @JsonIgnoreProperties(ignoreUnknown = true) so unknown future
fields can break deserialization; add the annotation above each nested record
declaration and ensure the Jackson import
(com.fasterxml.jackson.annotation.JsonIgnoreProperties) is present at the top of
the file so unknown properties on those nested records are safely ignored during
deserialization.

double stationX = Double.parseDouble(tmXStr);
double stationY = Double.parseDouble(tmYStr);

double distance = Math.sqrt(Math.pow(stationX - x, 2) + Math.pow(stationY - y, 2));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

TM 좌표계와 위경도 좌표계 불일치 문제

거리 계산 시 TM 좌표(tmX, tmY)와 위경도 좌표(x, y)를 직접 비교하고 있습니다. 좌표계가 다르면 잘못된 정류소가 선택될 가능성이 있습니다.

좌표계 변환이 필요한지 확인하고, 필요하다면 변환 로직을 추가하세요. 현재 구현이 올바른지 검증하기 위해 다음 스크립트를 실행하세요:


🏁 Script executed:

#!/bin/bash
# TM 좌표계 사용 여부 확인
rg -A 5 "tmX|tmY" --type java

# 좌표 변환 관련 유틸리티 존재 여부 확인
fd -e java -x grep -l "coordinate.*convert\|TM.*WGS\|좌표.*변환" {} \;

Length of output: 3133


🏁 Script executed:

#!/bin/bash
# BusInfoService.java의 시작 부분과 메서드 시그니처, 그리고 거리 계산 로직 전후 맥락 확인
sed -n '1,300p' src/main/java/com/wayble/server/direction/service/BusInfoService.java

Length of output: 8448


🏁 Script executed:

#!/bin/bash
# getBusInfo 호출 위치 확인
rg -n "getBusInfo" -g "*.java"

Length of output: 529


TM 좌표계와 위경도 좌표계 일치시키기

현재 BusInfoService.findClosestStation 메서드에서 station.tmX/tmY(TM 좌표)와 x/y(위경도)를 그대로 사용해 유클리드 거리를 계산하고 있어 잘못된 정류소가 선택될 수 있습니다.

조치 사항:

  • src/main/java/com/wayble/server/direction/service/BusInfoService.java
    findClosestStation 메서드 내 거리 계산 로직(Math.sqrt(Math.pow(stationX - x, 2) + Math.pow(stationY - y, 2)))
  • src/main/java/com/wayble/server/direction/service/TransportationService.java
    getBusInfo 호출부에서 전달하는 x(위도), y(경도)

위 문제를 해결하려면 다음 중 하나를 선택해 적용하세요:

  • TM 좌표(TM=Tangent Mercator, EPSG:5179 등)를 WGS84 위경도(EPSG:4326)로 변환 후 거리 계산
  • 사용자로부터 받은 위경도(x,y)를 TM 좌표로 변환 후 거리 계산

Proj4J, GeoTools와 같은 검증된 라이브러리를 쓰거나, 공식 변환 공식을 직접 구현해 좌표계를 통일한 뒤 거리 계산 로직을 수정해 주세요.

🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/BusInfoService.java around
line 188 and in
src/main/java/com/wayble/server/direction/service/TransportationService.java
where getBusInfo is called, you are mixing TM coordinates (station.tmX/tmY) with
WGS84 lat/lon (x/y) when computing Euclidean distance; choose one approach and
convert coordinates so both sides are in the same CRS before distance
calculation. Fix by adding a coordinate transformation step (use a library such
as Proj4J or GeoTools, or implement the official EPSG:5179 <-> EPSG:4326
formulas), then either (A) convert station.tmX/tmY to WGS84 and compute
great-circle/haversine distance or (B) convert incoming x/y (lat,lon) to TM and
compute planar Euclidean distance; implement the conversion inside
BusInfoService (or convert once in TransportationService before calling
getBusInfo), update the distance calculation to use the unified coordinate
values, add the chosen library dependency and unit tests verifying
nearest-station results.

Comment on lines +282 to +283
if (!Objects.equals(currentEdge.getRoute(), nextEdge.getRoute())) break;
j++;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Route 객체 비교 시 null 안전성 보장 필요

Route 객체를 비교할 때 equals 메서드 사용 시 null 처리가 명시적이지 않습니다. Objects.equals가 null-safe하긴 하지만, Route 클래스의 equals 구현을 확인해야 합니다.

Route 클래스의 equals 메서드 구현을 확인하세요:


🏁 Script executed:

#!/bin/bash
# Route 클래스의 equals 메서드 구현 확인
ast-grep --pattern 'class Route {
  $$$
  equals($_) {
    $$$
  }
  $$$
}'

# Route 클래스 전체 구조 확인
fd -e java -x grep -l "class Route\|record Route" {} \; | xargs cat

Length of output: 1037


Route 객체 논리적 동등성 비교 보강 필요

Objects.equals를 사용하면 null 안전성은 확보되나, Route 클래스에 equals/​hashCode가 오버라이드되어 있지 않아 참조 동등성만 비교됩니다. 다른 인스턴스인 동일 노선을 논리적으로 같은 것으로 간주하려면 아래를 반영해주세요.

• 파일: src/main/java/com/wayble/server/direction/entity/transportation/Route.java
– 현재 equals/​hashCode 미구현

추천 조치:

  • Route 클래스에 비즈니스 키(예: routeId) 기반으로 equals/​hashCode를 구현
    또는
  • 비교 로직을 명시적으로 routeId 간 비교로 변경
    if (!Objects.equals(
          currentEdge.getRoute().getRouteId(),
          nextEdge.getRoute().getRouteId()
        )) {
        break;
    }
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 282-283, the code uses Objects.equals(currentEdge.getRoute(),
nextEdge.getRoute()) which only does reference equality because Route lacks
equals/hashCode; fix by either implementing equals and hashCode in
src/main/java/com/wayble/server/direction/entity/transportation/Route.java based
on the business key (e.g., routeId) with proper null handling, or change the
comparison here to compare routeId explicitly (e.g., compare
currentEdge.getRoute().getRouteId() and nextEdge.getRoute().getRouteId() with
null-safety) so logically identical routes are treated as equal.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (12)
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1)

12-16: 중첩 record 선언 가독성 개선 제안

중괄호와 중첩 record 선언이 같은 라인에 있어 가독성이 떨어집니다. 선언을 라인 분리하면 코드 가독성이 좋아집니다.

아래처럼 정리하는 것을 권장합니다:

-) {     public record Location(
-        String name,
-        double latitude,
-        double longitude
-) {}
+) {
+    public record Location(
+            String name,
+            double latitude,
+            double longitude
+    ) {}
src/main/java/com/wayble/server/direction/dto/response/BusInfo.java (1)

5-11: 도착 정보 타입 명확화 제안

arrival1/arrival2가 문자열로 포맷된 문구인지(예: "3분 후") 또는 초/분 단위의 수치인지가 불명확합니다. API 소비자 일관성을 위해 Duration(또는 초/분 단위 수치)로 노출하고, 포맷은 클라이언트 레이어에서 처리하도록 분리하는 것을 권장합니다.

src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (1)

16-20: 좌표 타입 명확화 검토

tmX/tmY가 숫자 좌표라면 String 대신 Double 등 수치형으로 파싱하여 제공하는 것이 이후 계산(거리/좌표 변환)에 안전합니다. 원본 API가 문자열을 반환하더라도 DTO에서는 수치형을 사용하고, 파싱 오류 시 null 처리 정책을 명확히 하는 것을 권장합니다.

src/main/java/com/wayble/server/common/config/HttpClientConfig.java (1)

3-3: 불필요한 @requiredargsconstructor 제거

현재 final 필드나 생성자 주입이 없어 @requiredargsconstructor는 의미가 없습니다.

-import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@
 @Configuration
-@RequiredArgsConstructor
 public class HttpClientConfig {

Also applies to: 11-11

src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1)

34-37: 필드 타입의 도메인 모델링 및 파싱 안정성 검토 권고

busType1, busType2, term이 모두 String으로 선언되어 있습니다. 실제 API는 숫자 코드/숫자 문자열로 내려오는 경우가 많아, 서비스에서 정수/불리언으로 변환 시 NumberFormatException/빈 문자열 처리 예외가 발생할 수 있습니다.

권장:

  • 서비스 단 파싱 시 빈 문자열/비숫자 케이스를 안전하게 처리(디폴트/Optional 등).
  • 장기적으로는 도메인 친화적으로 타입 모델링(예: term → Integer, busType → enum 혹은 Boolean 매핑 유틸)하여 변환 로직을 중앙화.

필요하다면 변환 유틸/매퍼를 제안 드리겠습니다.

src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (4)

46-49: JSON 필드명 일관성: 'Longitude' → 'longitude'로 스네이크/카멜 규칙 유지

대문자 L로 시작하는 Longitude는 다른 필드와 네이밍 규칙이 불일치합니다. 클라이언트 UX/문서 일관성을 위해 소문자 camelCase를 권장합니다. 다만, 이는 API 호환성에 영향이 있으므로 변경 시 버전 호환성을 확인해주세요.

     public record LocationInfo(
             Double latitude,
-            Double Longitude
+            Double longitude
     ) {}

34-38: 불린/리스트 필드의 네이밍 개선 제안 ('is' 접두사 제거)

Jackson Record 컴포넌트명 기반 직렬화에서는 JSON 키가 'isShuttleBus', 'isLowFloor'로 노출됩니다. API 응답 키로는 'shuttleBus', 'lowFloor'가 더 자연스럽고 보편적입니다. 변경 시 클라이언트 영향 범위를 확인해주세요.

     public record BusInfo(
-            boolean isShuttleBus, // routeName에 "마포" 포함시 true
-            @Nullable List<Boolean> isLowFloor, // Open API(busType1,busType2) 기반 저상 여부 리스트
+            boolean shuttleBus, // routeName에 "마포" 포함시 true
+            @Nullable List<Boolean> lowFloor, // Open API(busType1,busType2) 기반 저상 여부 리스트
             @Nullable Integer dispatchInterval // Open API(term) 기반 배차간격
     ){}

15-23: null 필드 미노출을 위한 @JsonInclude 적용 제안

moveInfo, routeName, busInfo, subwayInfo가 @nullable입니다. null 값을 응답에서 생략하면 페이로드가 간결해지고 클라이언트 처리가 단순해집니다. 전역 설정이 없다면 Step에 한정해 적용을 권장합니다.

 import com.wayble.server.direction.entity.DirectionType;
 import org.springframework.lang.Nullable;
 import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonInclude;
@@
-    public record Step(
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    public record Step(
             DirectionType mode, // 예: START, WALK, SUBWAY, BUS, FINISH
             @Nullable List<MoveInfo> moveInfo, // 같은 Step으로 이동한 정류장(Node) 정보 (중간 정류장만)
             @Nullable String routeName,
             Integer moveNumber, // 같은 Step(route)로 이동한 횟수
             @Nullable BusInfo busInfo, // 버스일 경우에만 생성, 이외의 경우 null
             @Nullable SubwayInfo subwayInfo, // 지하철일 경우에만 생성, 이외의 경우 null
             String from,
             String to
     ) {}

51-56: 내부 전용 타입은 응답 DTO 파일에서 분리 권장

NodeInfo는 “서비스 내부에서 사용”이라고 주석 처리되어 있으나, public nested record로 외부에 노출 가능한 형태입니다. 응답 패키지/클래스에서 분리하거나 접근 제한(패키지 프라이빗)으로 오해를 줄이는 것이 좋습니다.

src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1)

7-9: LGTM, 기본 CRUD 리포지토리 정의 적절

현재 요구사항 기준으로 충분합니다. 다만 마을버스 판별 로직에서 routeName의 특정 키워드(예: "마포") 포함 여부를 빈번히 조회한다면, 파생 쿼리 추가를 고려할 수 있습니다.

예시:

 public interface RouteRepository extends JpaRepository<Route, Long>{
-    
+    Optional<Route> findByRouteNameContaining(String keyword);
 }
src/main/java/com/wayble/server/direction/service/BusInfoService.java (1)

177-198: 좌표 유효성 검증 추가 필요

좌표 파싱 시 null/empty 체크는 하지만, 유효한 좌표 범위인지 확인하지 않습니다. 한국의 TM 좌표계 범위를 벗어난 값에 대한 검증이 필요합니다.

                 double stationX = Double.parseDouble(tmXStr);
                 double stationY = Double.parseDouble(tmYStr);
                 
+                // TM 좌표계 유효 범위 검증 (한국 중부원점 기준)
+                if (stationX < 100000 || stationX > 300000 || stationY < 400000 || stationY > 600000) {
+                    log.warn("정류소 좌표가 유효 범위를 벗어남: {} - tmX={}, tmY={}", 
+                            station.stNm(), stationX, stationY);
+                    continue;
+                }
+                
                 double distance = Math.sqrt(Math.pow(stationX - x, 2) + Math.pow(stationY - y, 2));
src/main/java/com/wayble/server/direction/service/TransportationService.java (1)

293-301: moveInfo 리스트 처리 로직 개선 가능

빈 리스트를 null로 설정하는 대신 빈 리스트를 유지하는 것이 일관성 있고 null 체크를 줄일 수 있습니다.

             List<TransportationResponseDto.MoveInfo> moveInfoList = new ArrayList<>();
             for (int k = i + 1; k < j; k++) {
                 Edge e = pathEdges.get(k);
                 if (e.getStartNode() != null && e.getStartNode().getStationName() != null) {
                     moveInfoList.add(new TransportationResponseDto.MoveInfo(e.getStartNode().getStationName()));
                 }
             }
-            if (moveInfoList.isEmpty()) moveInfoList = null;
+            // moveInfoList를 빈 리스트로 유지하면 클라이언트에서 null 체크 불필요
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7463c2 and f19b48f.

📒 Files selected for processing (15)
  • src/main/java/com/wayble/server/ServerApplication.java (2 hunks)
  • src/main/java/com/wayble/server/common/config/HttpClientConfig.java (1 hunks)
  • src/main/java/com/wayble/server/direction/controller/TransportationController.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (0 hunks)
  • src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/response/BusInfo.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/Arrival.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/BusInfoService.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/FacilityService.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/TransportationService.java (5 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
  • Entity (9-36)
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (1)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (2)
src/main/java/com/wayble/server/direction/external/kric/KricProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/common/config/WebClientConfig.java (1)
  • Configuration (10-36)
src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (1)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
src/main/java/com/wayble/server/direction/service/FacilityService.java (1)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (4)
  • Schema (9-37)
  • NodeInfo (27-31)
  • Step (14-20)
  • LocationInfo (33-36)
src/main/java/com/wayble/server/direction/dto/response/BusInfo.java (3)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (4)
  • Schema (9-37)
  • Step (14-20)
  • LocationInfo (33-36)
  • NodeInfo (27-31)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/wayblezone/dto/WaybleZoneDetailResponseDto.java (1)
  • Builder (9-28)
src/main/java/com/wayble/server/ServerApplication.java (3)
src/main/java/com/wayble/server/common/client/tmap/TMapProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/common/config/WebClientConfig.java (1)
  • Configuration (10-36)
src/main/java/com/wayble/server/direction/external/kric/KricProperties.java (1)
  • ConfigurationProperties (5-10)
src/main/java/com/wayble/server/direction/controller/TransportationController.java (2)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (2)
  • Schema (5-17)
  • Location (12-16)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (2)
  • Schema (9-37)
  • Step (14-20)
src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (4)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (3)
  • Schema (9-37)
  • Step (14-20)
  • NodeInfo (27-31)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
src/main/java/com/wayble/server/common/client/tmap/dto/response/TMapParsingResponse.java (1)
  • Builder (8-74)
src/main/java/com/wayble/server/common/client/tmap/dto/response/TMapResponse.java (1)
  • TMapResponse (5-43)
src/main/java/com/wayble/server/direction/service/TransportationService.java (3)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (3)
  • Schema (9-37)
  • Step (14-20)
  • NodeInfo (27-31)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)
  • Entity (9-36)
🔇 Additional comments (7)
src/main/java/com/wayble/server/direction/dto/request/TransportationRequestDto.java (2)

6-16: 요청 DTO 구조는 적절합니다

레코드 구조(중첩 Location 포함)와 필드 선택(double vs Integer 선택 포함)가 목적에 부합합니다. null 허용이 필요한 cursor/size만 래퍼 타입을 사용한 점도 일관됩니다.


1-1: 레거시 패키지 참조 없음 확인

com.wayble.server.direction.dto.TransportationRequestDto에 대한 참조가 코드 전반에서 모두 제거되어 추가 조치가 필요 없습니다.

src/main/java/com/wayble/server/direction/controller/TransportationController.java (1)

4-5: DTO 패키지 경로 변경 반영 확인 완료

요청/응답 DTO의 패키지 이동에 따른 import 업데이트가 정확합니다. 컨트롤러 시그니처 및 Swagger 스키마 참조도 일치합니다.

src/main/java/com/wayble/server/direction/service/FacilityService.java (1)

3-3: 올바른 임포트 경로 업데이트

TransportationResponseDto가 새로운 dto.response 패키지로 이동한 것을 반영한 적절한 변경입니다.

src/main/java/com/wayble/server/ServerApplication.java (1)

5-5: OpenDataProperties 설정 추가 완료

새로운 외부 API 연동을 위한 OpenDataProperties 설정이 적절히 추가되었습니다.

Also applies to: 20-20

src/main/java/com/wayble/server/direction/service/BusInfoService.java (1)

71-85: busId가 null일 때 여러 노선 정보 처리 로직 확인 필요

busId가 null일 때 최대 1개 노선만 처리하도록 제한하고 있지만(71행), 실제로는 리스트의 첫 번째 항목에서만 isLowFloordispatchInterval을 추출합니다. 이 동작이 의도된 것인지 확인이 필요합니다.

busId가 null일 때의 사용 케이스와 여러 노선이 있을 때 첫 번째 노선만 선택하는 로직이 비즈니스 요구사항과 일치하는지 확인해주세요. 필요하다면 주석을 추가하여 이 동작의 의도를 명확히 하는 것이 좋겠습니다.

src/main/java/com/wayble/server/direction/service/TransportationService.java (1)

4-5: DTO 패키지 재구성 및 BusInfoService 의존성 추가 확인

DTO 패키지가 request/response로 재구성되고 BusInfoService가 추가된 것이 적절합니다.

Also applies to: 29-29

Comment on lines +17 to +24
@Value("${http.client.request-timeout:30}")
private int requestTimeout;

@Bean
public HttpClient httpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(connectTimeout))
.build();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

requestTimeout 프로퍼티가 적용되지 않아 외부 API 호출이 무기한 대기할 수 있습니다

HttpClient는 connectTimeout만 클라이언트 레벨에서 설정됩니다. 요청 타임아웃은 HttpRequest 단위에서 설정해야 하는데, 현재 requestTimeout 값이 어디에도 쓰이지 않습니다. 외부 OpenData API 호출 실패 시 쓰레드가 블로킹될 수 있습니다.

제안 1: requestTimeout을 다른 빈으로 노출해 서비스에서 HttpRequest.Builder.timeout(Duration)로 사용

     @Bean
     public HttpClient httpClient() {
         return HttpClient.newBuilder()
                 .connectTimeout(Duration.ofSeconds(connectTimeout))
                 .build();
     }
+
+    @Bean
+    public Duration httpClientRequestTimeout() {
+        return Duration.ofSeconds(requestTimeout);
+    }

추가 작업(다른 파일): BusInfoService에서 HttpRequest 생성 시 builder.timeout(httpClientRequestTimeout) 적용. 적용 여부 확인 부탁드립니다. 적용되어 있다면 이 코멘트는 무시하셔도 됩니다.


🏁 Script executed:

#!/bin/bash
# Search for usage of the requestTimeout field elsewhere in the codebase
rg "requestTimeout" -n src

# Search for any HttpRequest.Builder.timeout(...) invocations
rg "timeout(" -n src

Length of output: 219


🏁 Script executed:

#!/bin/bash
# HttpRequest 수준의 timeout 설정 사용 여부 확인
rg -n -F 'timeout(' src

# HttpRequest.Builder 생성 위치 확인
rg -n 'HttpRequest.newBuilder' src

Length of output: 632


HttpClientConfig의 requestTimeout 프로퍼티 미사용: 제거 또는 일관된 사용 적용 필요

확인 결과, BusInfoService에서 openDataProperties.timeout()으로 이미 HttpRequest.Builder.timeout(...)을 설정하고 있어,
HttpClientConfig에 정의된 requestTimeout 필드 및 빈은 실제로 사용되지 않고 있습니다.

따라서 다음 중 하나를 선택해 반영해 주세요:

  • 제거:
    • 파일 src/main/java/com/wayble/server/common/config/HttpClientConfig.java
    • Lines 17–24 (private int requestTimeout; 및 관련 빈 노출 메서드)
    → 미사용 코드 제거로 가독성·유지보수성 향상

  • 일관된 공통 설정으로 활용:
    HttpClientConfigDuration httpClientRequestTimeout() 빈 등록
    BusInfoServicebuilder.timeout(Duration.ofSeconds(openDataProperties.timeout())) 대신 해당 빈 사용

위 방안 중 프로젝트 정책에 맞는 쪽으로 선택하여 반영 부탁드립니다.

🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/common/config/HttpClientConfig.java around
lines 17–24, the requestTimeout field and the exposed HttpClient bean are
unused; either remove the unused private int requestTimeout and the associated
bean method (lines 17–24) to eliminate dead code, OR convert that configuration
into a shared Duration/timeout bean (e.g. Duration httpClientRequestTimeout())
and update BusInfoService to inject and use that bean instead of calling
openDataProperties.timeout(), ensuring consistent timeout configuration across
the app.

Comment on lines +1 to +11
package com.wayble.server.direction.dto.response;

import java.util.List;

public record BusInfo(List<BusArrival> buses, String stationName) {
public record BusArrival(
String busNumber,
String arrival1,
String arrival2
) {}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

이름 충돌/중복 가능성 확인: BusInfo 중복 선언 여부 점검

응답 DTO 내부에 동명이인의 중첩 타입(TransportationResponseDto.BusInfo)이 존재하면 혼동/충돌을 야기할 수 있습니다. 코드베이스 내 BusInfo 선언을 점검해 주세요.

다음 스크립트로 BusInfo record 선언을 모두 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# BusInfo record 선언 위치 확인
rg -n --glob '!**/build/**' --glob '!**/out/**' $'record\\s+BusInfo\\b' -A 3

Length of output: 1119


BusInfo 레코드 중복 선언 발견 – 이름 충돌 위험

코드베이스에 동일한 단순 이름 BusInfo를 가진 레코드가 두 곳에서 선언되어 있어, import 시 충돌 및 혼동이 발생할 수 있습니다. 다음 파일을 참조해 중복을 해소해주세요.

• src/main/java/com/wayble/server/direction/dto/response/BusInfo.java
• src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (34–37행: public record BusInfo(...))

제안

  • 하나의 BusInfo 레코드명을 더 구체적인 이름(예: StationBusInfo, TransportationBusInfo)으로 변경
  • 변경 시 API 계약(직렬화 포맷) 영향 여부 점검 후 리팩터링
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/dto/response/BusInfo.java around
lines 1–11 (and noting the conflicting record in
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java
lines 34–37), there is a duplicate simple record name BusInfo causing
import/name collisions; rename the record in one location to a more specific
name (e.g., StationBusInfo or TransportationBusInfo), update all imports/usages
and tests to the new name, verify and adjust any JSON
serialization/deserialization configuration or API contracts affected by the
rename, and run the build/tests to ensure no references remain to the old
BusInfo.

Comment on lines +5 to +11
public record BusInfo(List<BusArrival> buses, String stationName) {
public record BusArrival(
String busNumber,
String arrival1,
String arrival2
) {}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

PR 목표 대비 필드 누락: 저상 여부/마을버스 여부/배차 간격 포함 필요

현재 BusArrival에는 노선번호와 도착 정보 문자열만 포함되어 있어, PR 목표(저상버스 여부, 마을버스 여부, 배차 간격)가 표현되지 않습니다. OpenData DTO(Arrival: busType1/busType2/term)와의 매핑을 고려해 최소한 다음 정보가 필요합니다.

  • 저상버스 여부: arrival1/arrival2 각각에 대해 Boolean
  • 마을버스 여부: Boolean (또는 버스 유형 코드/문자열)
  • 배차 간격: Integer (분)

하위 호환을 유지하면서 필드를 확장하는 패치를 제안합니다.

-public record BusInfo(List<BusArrival> buses, String stationName) {
-    public record BusArrival(
-        String busNumber, 
-        String arrival1, 
-        String arrival2
-    ) {}
-}
+public record BusInfo(List<BusArrival> buses, String stationName) {
+    public record BusArrival(
+            String busNumber,
+            String arrival1,
+            String arrival2,
+            Boolean lowFloor1,   // 1st 도착 버스 저상 여부
+            Boolean lowFloor2,   // 2nd 도착 버스 저상 여부
+            Boolean townBus,     // 마을버스 여부(또는 유형 코드로 대체)
+            Integer headway      // 배차 간격(분)
+    ) {}
+}

필요하시다면 BusInfoService에서 Arrival DTO 매핑 코드도 함께 제안드리겠습니다.

📝 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.

Suggested change
public record BusInfo(List<BusArrival> buses, String stationName) {
public record BusArrival(
String busNumber,
String arrival1,
String arrival2
) {}
}
public record BusInfo(List<BusArrival> buses, String stationName) {
public record BusArrival(
String busNumber,
String arrival1,
String arrival2,
Boolean lowFloor1, // 1st 도착 버스 저상 여부
Boolean lowFloor2, // 2nd 도착 버스 저상 여부
Boolean townBus, // 마을버스 여부(또는 유형 코드로 대체)
Integer headway // 배차 간격(분)
) {}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/dto/response/BusInfo.java around
lines 5-11, the BusArrival record lacks fields for low-floor flags per arrival,
village-bus/type info, and dispatch interval (term) required by the PR; update
the BusArrival record to keep existing String busNumber, arrival1, arrival2
fields and add Boolean arrival1LowFloor, Boolean arrival2LowFloor, String
busType (or isVillageBus Boolean if you prefer a simple flag) and Integer
termMinutes (dispatch interval in minutes), initialize them to nullable defaults
so callers that only use the old fields keep working, and then update the
BusInfoService mapping code to populate these new fields from the OpenData
Arrival (busType1/busType2/term) values.

Comment on lines +3 to +7
public record Arrival (
Integer busType1, // 1이면 저상
Integer busType2, // 1이면 저상
Integer term // 배차 간격
) {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

알 수 없는 필드 무시 설정 추가 권장

대외 API 스키마 변경 시 역직렬화 실패를 방지하려면 @JsonIgnoreProperties(ignoreUnknown = true)를 부여하는 것이 안전합니다. 상위/형제 DTO에도 동일한 정책을 적용하고 있으므로 일관성 차원에서도 권장됩니다.

아래 패치를 적용해 주세요:

-package com.wayble.server.direction.external.opendata.dto;
+package com.wayble.server.direction.external.opendata.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
-public record Arrival (    
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record Arrival (
     Integer busType1, // 1이면 저상
     Integer busType2, // 1이면 저상
     Integer term     // 배차 간격
 ) {}
📝 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.

Suggested change
public record Arrival (
Integer busType1, // 1이면 저상
Integer busType2, // 1이면 저상
Integer term // 배차 간격
) {}
package com.wayble.server.direction.external.opendata.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record Arrival (
Integer busType1, // 1이면 저상
Integer busType2, // 1이면 저상
Integer term // 배차 간격
) {}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/external/opendata/dto/Arrival.java
around lines 3 to 7, add the Jackson annotation
@JsonIgnoreProperties(ignoreUnknown = true) to the Arrival record declaration
and import com.fasterxml.jackson.annotation.JsonIgnoreProperties so unknown
fields are ignored during deserialization; update sibling DTOs the same way for
consistency.

Comment on lines +28 to +30
public record MsgBody(
@JsonProperty("itemList") List<Item> itemList
) {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

OpenAPI 응답에서 단일 객체가 itemList로 내려오는 변종 대응 필요

여러 공공데이터 API가 단건 응답 시 배열 대신 객체로 내려보내는 케이스가 있습니다. Jackson에 단일 값을 배열로 허용하도록 설정하면 파싱 실패를 예방할 수 있습니다.

-    public record MsgBody(
-        @JsonProperty("itemList") List<Item> itemList
-    ) {}
+    public record MsgBody(
+        @JsonProperty("itemList")
+        @com.fasterxml.jackson.annotation.JsonFormat(with = com.fasterxml.jackson.annotation.JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+        List<Item> itemList
+    ) {}

추가 import가 필요하면 아래를 추가하세요:

import com.fasterxml.jackson.annotation.JsonFormat;
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java
around lines 28 to 30, the MsgBody.record's itemList fails when the API returns
a single object instead of an array; annotate the itemList property with
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) so Jackson
will accept a single object as a one-element array, and add the import statement
import com.fasterxml.jackson.annotation.JsonFormat; to the file.

Comment on lines +142 to +147

String uri = openDataProperties.baseUrl() +
openDataProperties.endpoints().stationByName() +
"?serviceKey=" + serviceKey +
"&stSrch=" + stationName +
"&resultType=json";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

URL 인코딩 처리 확인 필요

stationName 파라미터가 한글이나 특수문자를 포함할 경우 URL 인코딩이 필요할 수 있습니다. 현재 인코딩 없이 직접 URL에 포함시키고 있습니다.

+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;

             String uri = openDataProperties.baseUrl() + 
                     openDataProperties.endpoints().stationByName() +
                     "?serviceKey=" + serviceKey +
-                    "&stSrch=" + stationName +
+                    "&stSrch=" + URLEncoder.encode(stationName, StandardCharsets.UTF_8) +
                     "&resultType=json";
📝 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.

Suggested change
String uri = openDataProperties.baseUrl() +
openDataProperties.endpoints().stationByName() +
"?serviceKey=" + serviceKey +
"&stSrch=" + stationName +
"&resultType=json";
// at the top of the file, alongside the other imports
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
// …
String uri = openDataProperties.baseUrl() +
openDataProperties.endpoints().stationByName() +
"?serviceKey=" + serviceKey +
"&stSrch=" + URLEncoder.encode(stationName, StandardCharsets.UTF_8) +
"&resultType=json";
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/BusInfoService.java around
lines 142-147, the code concatenates stationName (and other query values)
directly into the URL which breaks for Korean or special characters; update the
construction to URL-encode the query parameters (at minimum stationName, and
preferably serviceKey) using URLEncoder.encode(value, StandardCharsets.UTF_8)
(or build the URI with a proper URI/URIBuilder), replace the plain concatenation
with the encoded values, and add the necessary import (java.net.URLEncoder and
java.nio.charset.StandardCharsets) so the request URL is safe for non-ASCII
characters.

Comment on lines 278 to +284
int j = i + 1;
while (j < pathEdges.size()) {
Edge nextEdge = pathEdges.get(j);
String nextRouteName = (nextEdge.getRoute() != null) ? nextEdge.getRoute().getRouteName() : null;

// 같은 노선이 아니면 중단
if (nextEdge.getEdgeType() != currentType ||
!Objects.equals(currentRouteName, nextRouteName)) {
if (nextEdge.getEdgeType() != currentType) break;
if (!Objects.equals(currentEdge.getRoute(), nextEdge.getRoute())) break;
j++;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Route 객체 비교 로직의 안정성 개선 필요

Route 객체를 직접 비교하고 있는데, equals 메서드가 제대로 구현되어 있는지 확인이 필요합니다. Route ID로 비교하는 것이 더 명확할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# Route 엔티티의 equals 메서드 구현 확인
ast-grep --pattern 'class Route {
  $$$
  equals($_) {
    $$$
  }
  $$$
}'

# Route 엔티티 파일 내용 확인
fd -e java -x grep -l "class Route" | xargs cat | grep -A 20 "class Route"

Length of output: 632


Route.equals 미구현으로 인한 참조 비교 문제: routeId 기반 비교 또는 equals/hashCode 구현 필요

현재 Objects.equals(currentEdge.getRoute(), nextEdge.getRoute())는 Route 클래스에 equals/hashCode가 오버라이드되어 있지 않아 단순 참조(identity) 비교를 수행합니다. 같은 노선이라도 서로 다른 인스턴스로 로딩될 경우 의도치 않게 false가 반환될 수 있습니다.

수정 사항 제안:

  • src/main/java/com/wayble/server/direction/service/TransportationService.java (278–284줄)
    • 기존:
      if (!Objects.equals(currentEdge.getRoute(), nextEdge.getRoute())) break;
    • 대안1: routeId로 직접 비교
      if (!Objects.equals(currentEdge.getRoute().getRouteId(),
                          nextEdge.getRoute().getRouteId())) break;
    • 대안2: Route 클래스에 equals/hashCode 오버라이드 추가
      @Override
      public boolean equals(Object o) { 
        if (this == o) return true;
        if (!(o instanceof Route)) return false;
        Route that = (Route) o;
        return Objects.equals(routeId, that.routeId);
      }
      
      @Override
      public int hashCode() {
        return Objects.hash(routeId);
      }
  • 위 둘 중 하나를 선택해 일관된 비교 로직을 적용해 주세요.
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 278–284, Objects.equals(currentEdge.getRoute(),
nextEdge.getRoute()) currently does identity comparison because Route does not
override equals/hashCode; replace this with a null-safe routeId comparison
(compare currentEdge.getRoute().getRouteId() to
nextEdge.getRoute().getRouteId()) or alternatively implement equals() and
hashCode() in the Route class based on routeId (and ensure both methods handle
null routeId), then update the comparison accordingly so two Route instances
with the same routeId are treated as equal.

Comment on lines 316 to 329
boolean isShuttle = routeName != null && routeName.contains("마포"); // 마을버스 구분

Long stationId = currentEdge.getStartNode() != null ? currentEdge.getStartNode().getId() : null;
List<Boolean> lowFloors = null;
List<Integer> intervals = null;
try {
if (stationId != null) {
TransportationResponseDto.BusInfo busInfoData = busInfoService.getBusInfo(currentEdge.getStartNode().getStationName(), null, currentEdge.getStartNode().getLatitude(), currentEdge.getStartNode().getLongitude());
busInfo = busInfoData;
}
} catch (Exception e) {
log.error("버스 정보 조회 실패: {}", e.getMessage(), e);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

BusInfoService 호출 시 예외 처리 개선 필요

busInfoService.getBusInfo()가 null을 반환할 수 있는데, 이에 대한 처리가 없습니다. BusInfoService의 89행에서 예외 발생 시 null을 반환하므로 이를 처리해야 합니다.

                 try {
                     if (stationId != null) {
                         TransportationResponseDto.BusInfo busInfoData = busInfoService.getBusInfo(currentEdge.getStartNode().getStationName(), null, currentEdge.getStartNode().getLatitude(), currentEdge.getStartNode().getLongitude());
-                        busInfo = busInfoData;
+                        if (busInfoData != null) {
+                            busInfo = busInfoData;
+                        } else {
+                            log.warn("버스 정보가 null로 반환됨: {}", currentEdge.getStartNode().getStationName());
+                            busInfo = new TransportationResponseDto.BusInfo(false, new ArrayList<>(), null);
+                        }
                     }
                 } catch (Exception e) {
                     log.error("버스 정보 조회 실패: {}", e.getMessage(), e);
+                    busInfo = new TransportationResponseDto.BusInfo(false, new ArrayList<>(), null);
                 }
📝 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.

Suggested change
boolean isShuttle = routeName != null && routeName.contains("마포"); // 마을버스 구분
Long stationId = currentEdge.getStartNode() != null ? currentEdge.getStartNode().getId() : null;
List<Boolean> lowFloors = null;
List<Integer> intervals = null;
try {
if (stationId != null) {
TransportationResponseDto.BusInfo busInfoData = busInfoService.getBusInfo(currentEdge.getStartNode().getStationName(), null, currentEdge.getStartNode().getLatitude(), currentEdge.getStartNode().getLongitude());
busInfo = busInfoData;
}
} catch (Exception e) {
log.error("버스 정보 조회 실패: {}", e.getMessage(), e);
}
}
boolean isShuttle = routeName != null && routeName.contains("마포"); // 마을버스 구분
Long stationId = currentEdge.getStartNode() != null ? currentEdge.getStartNode().getId() : null;
List<Boolean> lowFloors = null;
List<Integer> intervals = null;
try {
if (stationId != null) {
TransportationResponseDto.BusInfo busInfoData =
busInfoService.getBusInfo(
currentEdge.getStartNode().getStationName(),
null,
currentEdge.getStartNode().getLatitude(),
currentEdge.getStartNode().getLongitude()
);
if (busInfoData != null) {
busInfo = busInfoData;
} else {
log.warn("버스 정보가 null로 반환됨: {}", currentEdge.getStartNode().getStationName());
busInfo = new TransportationResponseDto.BusInfo(false, new ArrayList<>(), null);
}
}
} catch (Exception e) {
log.error("버스 정보 조회 실패: {}", e.getMessage(), e);
busInfo = new TransportationResponseDto.BusInfo(false, new ArrayList<>(), null);
}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 316-329, the call to busInfoService.getBusInfo(...) can return null
but the code assumes a non-null result; update the try block to check the
returned busInfoData for null before assigning to busInfo, and if it's null log
a warning and leave busInfo as null or set a safe default; ensure downstream
code that uses busInfo guards against null to avoid NPEs.

Copy link
Member

@seung-in-Yoo seung-in-Yoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다! 👍 공공데이터 포털 api key 가져오면 WebClient 사용에서 호환이 잘 안되는건가요!?

@hyoinYang
Copy link
Contributor Author

수고하셨습니다! 👍 공공데이터 포털 api key 가져오면 WebClient 사용에서 호환이 잘 안되는건가요!?

맞아요 ㅠㅠ 공공데이터 포털 api key는 /를 포함하고 있는데 이것 때문에 서비스키가 중복 인코딩되는 경우가 잦아요 막는 방법이 있다곤 하는데 전 계속 시도해도 안 돼서 일단 HttpClient로 구현했습니다!

@hyoinYang hyoinYang merged commit 4c014e9 into develop Aug 13, 2025
1 check was pending
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/main/java/com/wayble/server/direction/service/TransportationService.java (3)

282-283: Route 객체 참조 비교 문제: routeId 기반 비교로 변경 필요

현재 Objects.equals(currentEdge.getRoute(), nextEdge.getRoute())는 Route 클래스에 equals/hashCode가 오버라이드되어 있지 않아 단순 참조 비교를 수행합니다. 동일한 노선이라도 서로 다른 인스턴스일 경우 의도치 않게 false가 반환될 수 있습니다.

routeId 기반 비교로 변경하세요:

-if (!Objects.equals(currentEdge.getRoute(), nextEdge.getRoute())) break;
+if (!Objects.equals(currentEdge.getRoute().getRouteId(), nextEdge.getRoute().getRouteId())) break;

323-323: getBusInfo 호출 시 routeId 파라미터 전달 승인

이전 리뷰에서 지적된 null 파라미터 문제가 해결되어 currentEdge.getRoute().getRouteId()를 적절히 전달하고 있습니다.


321-328: BusInfoService 호출 시 예외 처리 강화 필요

busInfoService.getBusInfo()가 null을 반환할 수 있는데, 현재 코드는 반환값에 대한 null 체크 없이 바로 할당하고 있습니다.

null 체크를 추가하세요:

try {
    if (stationId != null) {
        TransportationResponseDto.BusInfo busInfoData = busInfoService.getBusInfo(currentEdge.getStartNode().getStationName(), currentEdge.getRoute().getRouteId(), currentEdge.getStartNode().getLatitude(), currentEdge.getStartNode().getLongitude());
-       busInfo = busInfoData;
+       if (busInfoData != null) {
+           busInfo = busInfoData;
+       } else {
+           log.warn("버스 정보가 null로 반환됨: {}", currentEdge.getStartNode().getStationName());
+       }
    }
} catch (Exception e) {
    log.error("버스 정보 조회 실패: {}", e.getMessage(), e);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f19b48f and 1aec814.

📒 Files selected for processing (4)
  • src/main/java/com/wayble/server/common/config/HttpClientConfig.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/BusInfoService.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/TransportationService.java (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/wayble/server/common/config/HttpClientConfig.java
  • src/main/java/com/wayble/server/direction/service/BusInfoService.java
  • src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/wayble/server/direction/service/TransportationService.java (2)
src/main/java/com/wayble/server/direction/dto/TransportationResponseDto.java (3)
  • Schema (9-37)
  • Step (14-20)
  • NodeInfo (27-31)
src/main/java/com/wayble/server/direction/dto/TransportationRequestDto.java (1)
  • Schema (5-17)
🔇 Additional comments (7)
src/main/java/com/wayble/server/direction/service/TransportationService.java (7)

4-5: 패키지 구조 개선 승인

DTO 클래스들을 request/response 패키지로 분리한 것은 코드 구조를 명확하게 하는 좋은 개선사항입니다.


29-29: BusInfoService 의존성 추가 승인

버스 정보 조회를 위한 새로운 서비스 의존성이 적절히 추가되었습니다.


218-218: 로깅 레벨 개선 승인

경로를 찾을 수 없는 경우를 warn에서 info로 변경한 것은 적절합니다. 이는 일반적인 비즈니스 로직의 결과이므로 경고 수준의 로그가 아닙니다.


262-275: 도보 구간 Step 생성 로직 승인

도보 구간에 대해 새로운 Step 구조에 맞게 null 값들을 적절히 설정한 것이 올바릅니다.


286-301: 중간 정류장 수집 로직 개선

moveInfo 리스트를 구성하는 로직이 명확하고 적절합니다. 중간 정류장만 수집하고 빈 리스트는 null로 처리하는 것도 좋습니다.


330-349: 지하철 시설 정보 처리 로직 승인

지하철역 시설 정보 조회 로직이 잘 구현되었습니다. 예외 발생 시 기본값으로 빈 리스트와 false를 설정하는 것도 적절합니다.


351-362: 새로운 Step 구조 적용 승인

moveNumber, moveInfoList, busInfo, subwayInfo 등 새로운 필드들이 적절히 Step 생성자에 전달되고 있습니다. PR 목표에 부합하는 구현입니다.

TransportationResponseDto.BusInfo busInfo = null;
TransportationResponseDto.SubwayInfo subwayInfo = null;
if (currentType == DirectionType.BUS) {
boolean isShuttle = routeName != null && routeName.contains("마포"); // 마을버스 구분
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

마을버스 구분 로직 개선 필요

현재 routeName에 "마포"가 포함되어 있는지로만 마을버스를 구분하고 있는데, 이는 너무 단순한 방식입니다. 더 정확한 구분 로직이 필요합니다.

마을버스 구분을 위한 더 정교한 로직을 구현하거나, Route 엔티티에 버스 타입 정보를 추가하는 것을 고려해보세요:

-boolean isShuttle = routeName != null && routeName.contains("마포"); // 마을버스 구분
+boolean isShuttle = isVillageBus(routeName); // 별도 메서드로 분리
private boolean isVillageBus(String routeName) {
    if (routeName == null) return false;
    // 마을버스 패턴 정의 (예: 마포01, 마포02 등)
    return routeName.matches("^마포\\d+$") || 
           routeName.startsWith("마을") ||
           routeName.matches("^\\d{2,3}번$"); // 기타 마을버스 패턴
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around line 316, the current village-bus check uses routeName != null &&
routeName.contains("마포"), which is too simplistic; replace this with a robust
detection or use explicit type information from the Route entity: add a private
helper method isVillageBus(String routeName) that validates null and matches
stronger patterns (e.g. specific prefixes, numeric suffix patterns, or known
"마을" keywords) and call it instead of the contains check, or preferably add a
busType (enum or string) to the Route entity, populate it at data ingestion, and
use route.getBusType() == VILLAGE_BUS to decide isShuttle; update the single
location where isShuttle is computed to use the new helper/entity field and
adjust any affected unit tests.

@seung-in-Yoo
Copy link
Member

수고하셨습니다! 👍 공공데이터 포털 api key 가져오면 WebClient 사용에서 호환이 잘 안되는건가요!?

맞아요 ㅠㅠ 공공데이터 포털 api key는 /를 포함하고 있는데 이것 때문에 서비스키가 중복 인코딩되는 경우가 잦아요 막는 방법이 있다곤 하는데 전 계속 시도해도 안 돼서 일단 HttpClient로 구현했습니다!

헉 그렇군요,,, 이건 저도 나중에 참고해서 짜야겠네요 수고하셨습니다!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 feature 기능 구현 및 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants