Skip to content

Conversation

@seung-in-Yoo
Copy link
Member

@seung-in-Yoo seung-in-Yoo commented Jul 31, 2025

✔️ 연관 이슈

  • X

📝 작업 내용

현재까지의 변경사항을 main으로 병합시킵니다.

스크린샷 (선택)

Summary by CodeRabbit

  • 신규 기능

    • 웨이블 추천 경로 API가 추가되어 시작/도착 좌표를 기반으로 보행 경로와 접근성 마커 정보를 제공합니다.
    • 보행 경로 안내를 위한 다양한 엔티티 및 데이터 구조가 도입되었습니다.
  • 기능 개선

    • 사용자 정보 등록/수정 시 장애 유형 및 이동 보조기구를 복수 선택(리스트)할 수 있도록 개선되었습니다.
  • 버그 수정

    • 걷기 경로 API의 엔드포인트가 명확하게 분리되어 사용성이 향상되었습니다.
  • 기타

    • 장애 유형 및 이동 보조기구의 데이터 저장 방식이 개선되어 여러 값을 저장할 수 있습니다.

zyovn and others added 30 commits July 24, 2025 16:55
[refactor] 유저 엔티티 (장애유형,이동보조수단) 복수 선택 가능을 위한 리팩토링 진행
zyovn and others added 2 commits July 31, 2025 02:28
@seung-in-Yoo seung-in-Yoo self-assigned this Jul 31, 2025
@seung-in-Yoo seung-in-Yoo added the 💡 feature 기능 구현 및 개발 label Jul 31, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 31, 2025

Walkthrough

이 변경사항은 웨이블 추천 경로 API의 도입과 사용자 장애 유형 및 이동 보조기구 필드의 리스트화, 그리고 그래프 기반 경로 탐색 기능의 추가를 포함합니다. 신규 엔티티와 DTO, 컨버터, 서비스, 유틸리티, 예외, Swagger 문서화 등이 대거 추가 및 수정되었습니다.

Changes

Cohort / File(s) Change Summary
그래프 및 경로 탐색 기능 추가
src/main/java/com/wayble/server/direction/entity/Edge.java, .../Node.java, .../WaybleMarker.java, .../type/Type.java, .../init/GraphInit.java, .../service/WaybleDijkstraService.java, .../service/util/HaversineUtil.java
그래프 구조(노드, 엣지, 마커, 타입) 및 초기화 클래스, 다익스트라 기반 경로 탐색 서비스, Haversine 거리 계산 유틸리티 클래스 신규 추가
웨이블 추천 경로 API 도입
src/main/java/com/wayble/server/direction/controller/WalkingController.java, .../controller/swagger/WalkingSwagger.java, .../dto/response/WayblePathResponse.java, .../service/WalkingService.java, .../exception/WalkingErrorCase.java
WalkingController에 /wayble GET 엔드포인트 추가, Swagger 문서화, WayblePathResponse 응답 레코드 및 서비스 메서드 추가, 관련 에러케이스 확장
사용자 장애/보조기구 필드 리스트화 및 컨버터 도입
src/main/java/com/wayble/server/common/converter/StringListConverter.java, .../user/entity/User.java, .../user/dto/UserInfoRegisterRequestDto.java, .../user/dto/UserInfoUpdateRequestDto.java
User 엔티티 및 관련 DTO의 disabilityType, mobilityAid 필드를 List<String>으로 변경, JPA 컨버터 추가 및 적용
불필요한 임포트 제거
src/main/java/com/wayble/server/user/controller/UserController.java
사용하지 않는 JwtTokenProvider, AuthService 임포트 제거

Sequence Diagram(s)

웨이블 추천 경로 API 시퀀스

sequenceDiagram
    participant Client
    participant WalkingController
    participant WalkingService
    participant GraphInit
    participant WaybleDijkstraService

    Client->>WalkingController: GET /api/v1/directions/wayble (startLat, startLon, endLat, endLon)
    WalkingController->>WalkingService: findWayblePath(startLat, startLon, endLat, endLon)
    WalkingService->>GraphInit: getNodeMap()
    WalkingService->>WalkingService: findNearestNode(startLat, startLon)
    WalkingService->>WalkingService: findNearestNode(endLat, endLon)
    WalkingService->>WaybleDijkstraService: createWayblePath(startNodeId, endNodeId)
    WaybleDijkstraService->>GraphInit: getGraph(), getNodeMap(), getMarkerMap()
    WaybleDijkstraService-->>WalkingService: WayblePathResponse
    WalkingService-->>WalkingController: WayblePathResponse
    WalkingController-->>Client: CommonResponse<WayblePathResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🔧 refactor

Suggested reviewers

  • zyovn

Poem

🐇
경로 따라 hop hop hop,
웨이블 길을 찾아서 점프!
장애 유형도 리스트로,
보조기구도 쏙쏙쏙,
그래프 위를 달려가며
리뷰어님, 힘내세요!
코드 속엔 봄바람이 솔솔~ 🌸

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

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

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 4

🔭 Outside diff range comments (2)
src/main/java/com/wayble/server/direction/controller/WalkingController.java (2)

23-34: 파라미터 명명 규칙 불일치

두 엔드포인트 간 좌표 파라미터 명명이 일치하지 않습니다:

  • /walking: startX, startY, endX, endY 사용
  • /wayble: startLat, startLon, endLat, endLon 사용

일관성을 위해 동일한 명명 규칙을 사용하는 것이 좋습니다.

     @GetMapping("/wayble")
     public CommonResponse<WayblePathResponse> getWayblePath(
-            @RequestParam double startLat,
-            @RequestParam double startLon,
-            @RequestParam double endLat,
-            @RequestParam double endLon
+            @RequestParam double startY,
+            @RequestParam double startX,
+            @RequestParam double endY,
+            @RequestParam double endX
     ) {
-        return CommonResponse.success(walkingService.findWayblePath(startLat, startLon, endLat, endLon));
+        return CommonResponse.success(walkingService.findWayblePath(startY, startX, endY, endX));
     }

Also applies to: 37-45


24-31: 좌표 파라미터 검증 부재

좌표 값에 대한 검증이 없습니다. 유효하지 않은 좌표값이 들어올 경우 예상치 못한 동작이 발생할 수 있습니다.

@Valid 어노테이션과 함께 DTO를 사용하거나, 파라미터 레벨에서 검증을 추가하는 것을 권장합니다:

+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;

     public CommonResponse<WayblePathResponse> getWayblePath(
-            @RequestParam double startLat,
-            @RequestParam double startLon,
-            @RequestParam double endLat,
-            @RequestParam double endLon
+            @RequestParam @Min(-90) @Max(90) double startLat,
+            @RequestParam @Min(-180) @Max(180) double startLon,
+            @RequestParam @Min(-90) @Max(90) double endLat,
+            @RequestParam @Min(-180) @Max(180) double endLon
     ) {

Also applies to: 38-43

🧹 Nitpick comments (12)
src/main/java/com/wayble/server/common/converter/StringListConverter.java (1)

15-15: Spring 환경에서의 ObjectMapper 사용 개선 제안

현재 정적 ObjectMapper를 사용하고 있지만, Spring 환경에서는 설정된 ObjectMapper 빈을 주입받아 사용하는 것이 더 좋은 방법입니다.

다음과 같이 개선할 수 있습니다:

-    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private final ObjectMapper objectMapper;
+    
+    public StringListConverter() {
+        this.objectMapper = new ObjectMapper();
+    }

또는 Spring의 ApplicationContext를 통해 ObjectMapper 빈을 가져오는 방법도 고려해볼 수 있습니다.

src/main/java/com/wayble/server/direction/entity/Edge.java (1)

9-9: geometry 필드 타입 개선을 고려해 보세요.

현재 List<double[]> 타입을 사용하고 있는데, 좌표를 나타내는 더 명확한 타입을 고려해 볼 수 있습니다.

다음과 같은 개선안을 제안합니다:

-        List<double[]> geometry
+        List<Point> geometry

또는 좌표 쌍을 명확히 하기 위해:

-        List<double[]> geometry
+        List<Coordinate> geometry

여기서 Point나 Coordinate는 lat, lon을 포함하는 별도의 레코드가 될 수 있습니다.

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

3-3: 생성자를 private으로 만들어 인스턴스화를 방지하세요.

유틸리티 클래스는 인스턴스화되지 않아야 합니다.

 public class HaversineUtil {
+
+    private HaversineUtil() {
+        // 유틸리티 클래스는 인스턴스화하지 않습니다
+    }
src/main/java/com/wayble/server/direction/entity/type/Type.java (1)

15-15: NONE 타입의 사용 목적을 문서화하세요.

NONE 타입이 어떤 경우에 사용되는지 명확하게 하기 위해 JavaDoc 주석을 추가하는 것을 고려해 보세요.

+    /**
+     * 특별한 접근성 시설이 없는 일반적인 위치를 나타냅니다.
+     */
     NONE("해당없음");
src/main/java/com/wayble/server/direction/entity/Node.java (1)

5-6: 좌표 유효성 검증을 고려해 보세요.

위도와 경도 값에 대한 유효성 검증을 추가하는 것을 고려해 볼 수 있습니다.

좌표 유효성을 보장하기 위해 compact constructor를 추가할 수 있습니다:

 public record Node(
         Long id,
         double lat,
         double lon
 ) {
+    public Node {
+        if (lat < -90 || lat > 90) {
+            throw new IllegalArgumentException("위도는 -90도에서 90도 사이여야 합니다: " + lat);
+        }
+        if (lon < -180 || lon > 180) {
+            throw new IllegalArgumentException("경도는 -180도에서 180도 사이여야 합니다: " + lon);
+        }
+    }
 }
src/main/java/com/wayble/server/direction/entity/WaybleMarker.java (1)

6-8: Node와의 코드 중복을 고려해 보세요.

WaybleMarker와 Node 레코드 모두 id, lat, lon 필드를 가지고 있습니다. 공통 인터페이스나 상속 관계를 고려해 볼 수 있습니다.

다음과 같은 개선안을 고려해 볼 수 있습니다:

+public sealed interface Locatable permits Node, WaybleMarker {
+    Long id();
+    double lat();
+    double lon();
+}
+
-public record WaybleMarker(
-        Long id,
-        double lat,
-        double lon,
-        Type type
-) {
+public record WaybleMarker(
+        Long id,
+        double lat,
+        double lon,
+        Type type
+) implements Locatable {
 }

또는 컴포지션을 사용하여:

-public record WaybleMarker(
-        Long id,
-        double lat,
-        double lon,
-        Type type
-) {
+public record WaybleMarker(
+        Node node,
+        Type type
+) {
 }
src/main/java/com/wayble/server/direction/service/WalkingService.java (1)

53-60: 가장 가까운 노드 검색 성능 최적화를 고려해보세요.

현재 구현은 모든 노드를 순회하며 거리를 계산하므로 O(n) 시간 복잡도를 가집니다. 노드 수가 많아질 경우 성능 문제가 발생할 수 있습니다.

프로덕션 환경에서는 다음과 같은 공간 인덱싱 구조를 고려해보세요:

  • KD-트리 또는 R-트리를 사용한 공간 인덱싱
  • 그리드 기반 인덱싱으로 검색 범위 제한
  • PostGIS와 같은 공간 데이터베이스 활용

현재 구현은 동작하지만, 확장성을 위해 향후 개선이 필요할 수 있습니다.

src/main/java/com/wayble/server/direction/init/GraphInit.java (1)

68-70: 마커 연결 엣지의 거리를 절반으로 줄이는 로직에 대한 설명이 필요합니다.

웨이블 마커가 있는 엣지의 거리를 50%로 줄이는 로직이 있습니다. 이는 접근성이 좋은 경로를 우선하기 위한 것으로 보이나, 주석으로 의도를 명확히 하면 좋겠습니다.

            boolean isWaybleMarker = markerMap.containsKey(edge.from()) || markerMap.containsKey(edge.to());
+           // 웨이블 마커가 있는 경로는 접근성이 좋으므로 가중치를 50%로 감소
            double distance = isWaybleMarker ? edge.length() * 0.5 : edge.length();
src/main/java/com/wayble/server/direction/dto/response/WayblePathResponse.java (1)

36-48: 정적 팩토리 메서드가 중복됩니다.

@Builder가 이미 있으므로 정적 팩토리 메서드 of()는 불필요합니다. 빌더 패턴만 사용하는 것이 일관성 있습니다.

-    public static WayblePathResponse of(
-            int distance,
-            int time,
-            List<WayblePoint> points,
-            List<double[]> polyline
-    ) {
-        return WayblePathResponse.builder()
-                .distance(distance)
-                .time(time)
-                .points(points)
-                .polyline(polyline)
-                .build();
-    }
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (3)

85-86: 비현실적인 평균 보행 속도

평균 보행 속도가 1.0 m/s로 설정되어 있는데, 이는 일반적인 성인 보행 속도(약 1.4 m/s)보다 느립니다. 장애인을 고려한 것이라면 주석으로 명시하는 것이 좋습니다.

     private double calculateTime(List<Long> path) {
-        double averageSpeed = 1.0;
+        // 휠체어 사용자 및 보행 장애인을 고려한 평균 속도 (일반 성인: 약 1.4 m/s)
+        double averageSpeed = 1.0;

51-54: 중복된 스트림 연산 패턴

동일한 edge 찾기 패턴이 여러 곳에서 반복됩니다. 헬퍼 메서드로 추출하면 가독성과 성능을 개선할 수 있습니다.

+    private Optional<Edge> findEdge(long from, long to) {
+        return graphInit.getGraph().getOrDefault(from, List.of()).stream()
+                .filter(edge -> edge.to() == to)
+                .findFirst();
+    }
+
     private List<double[]> createPolyLine(List<Long> path) {
         // ...
-            Edge edge = adjacencyList.getOrDefault(from, List.of()).stream()
-                    .filter(e -> e.to() == to)
-                    .findFirst()
-                    .orElse(null);
+            Edge edge = findEdge(from, to).orElse(null);

Also applies to: 94-97, 112-116


42-82: createPolyLine 메서드의 복잡도

이 메서드는 너무 복잡합니다. edge가 null인 경우와 geometry가 없는 경우를 별도로 처리하는 로직을 분리하면 가독성이 향상됩니다.

메서드를 더 작은 단위로 분리하여 가독성을 개선하시겠습니까? 리팩토링 코드를 제공해드릴 수 있습니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 81db6d0 and 2eae9b1.

📒 Files selected for processing (17)
  • src/main/java/com/wayble/server/common/converter/StringListConverter.java (1 hunks)
  • src/main/java/com/wayble/server/direction/controller/WalkingController.java (3 hunks)
  • src/main/java/com/wayble/server/direction/controller/swagger/WalkingSwagger.java (2 hunks)
  • src/main/java/com/wayble/server/direction/dto/response/WayblePathResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/entity/Edge.java (1 hunks)
  • src/main/java/com/wayble/server/direction/entity/Node.java (1 hunks)
  • src/main/java/com/wayble/server/direction/entity/WaybleMarker.java (1 hunks)
  • src/main/java/com/wayble/server/direction/entity/type/Type.java (1 hunks)
  • src/main/java/com/wayble/server/direction/exception/WalkingErrorCase.java (1 hunks)
  • src/main/java/com/wayble/server/direction/init/GraphInit.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/WalkingService.java (2 hunks)
  • src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/util/HaversineUtil.java (1 hunks)
  • src/main/java/com/wayble/server/user/controller/UserController.java (0 hunks)
  • src/main/java/com/wayble/server/user/dto/UserInfoRegisterRequestDto.java (2 hunks)
  • src/main/java/com/wayble/server/user/dto/UserInfoUpdateRequestDto.java (1 hunks)
  • src/main/java/com/wayble/server/user/entity/User.java (3 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/wayble/server/user/controller/UserController.java
🧰 Additional context used
🧠 Learnings (1)
src/main/java/com/wayble/server/user/entity/User.java (1)

Learnt from: seung-in-Yoo
PR: #37
File: src/main/java/com/wayble/server/user/entity/User.java:61-62
Timestamp: 2025-07-13T15:08:59.318Z
Learning: Java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 import 없이 사용 가능하다.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: run-docker-image-on-ec2
🔇 Additional comments (23)
src/main/java/com/wayble/server/user/dto/UserInfoUpdateRequestDto.java (2)

7-7: Import 문 추가가 적절합니다.

List<String> 타입 사용을 위한 import 구문이 올바르게 추가되었습니다.


15-16: 필드 타입 변경이 올바르게 적용되었습니다.

disabilityTypemobilityAid 필드가 String에서 List<String>으로 정확하게 변경되어 다중 값을 지원할 수 있게 되었습니다. 주석도 여전히 적절하며 필드의 사용 조건을 명확히 설명하고 있습니다.

src/main/java/com/wayble/server/user/dto/UserInfoRegisterRequestDto.java (2)

11-11: Import 문 추가가 적절합니다.

List<String> 타입 사용을 위한 import 구문이 올바르게 추가되었습니다.


28-29: 필드 타입 변경이 일관성 있게 적용되었습니다.

disabilityTypemobilityAid 필드가 String에서 List<String>으로 변경되어 UserInfoUpdateRequestDto와 일관성을 유지하며 다중 값을 지원합니다. 조건부 필드의 특성상 validation annotation이 없는 것도 적절합니다.

src/main/java/com/wayble/server/common/converter/StringListConverter.java (3)

14-15: JPA 컨버터 구현이 올바릅니다.

AttributeConverter 인터페이스를 적절히 구현하여 List<String>과 JSON 문자열 간의 변환을 처리합니다. 정적 ObjectMapper 인스턴스 사용으로 성능도 고려되었습니다.


17-25: 데이터베이스 변환 로직이 안전하게 구현되었습니다.

null 처리와 예외 상황에 대한 적절한 fallback("[]")을 제공하며, 로깅을 통해 변환 실패를 추적할 수 있습니다.


27-36: 엔티티 속성 변환 로직이 견고합니다.

null 및 빈 문자열 처리, TypeReference를 통한 안전한 역직렬화, 그리고 예외 발생 시 빈 리스트 반환으로 안정성을 확보했습니다.

src/main/java/com/wayble/server/user/entity/User.java (4)

3-3: 컨버터 Import가 적절히 추가되었습니다.

새로 생성된 StringListConverter에 대한 import 구문이 올바르게 추가되었습니다.


62-64: 장애 유형 필드의 JPA 변환 설정이 올바릅니다.

@Convert 어노테이션을 사용하여 StringListConverter를 적용하고, 필드 타입을 List<String>으로 변경하여 다중 장애 유형을 지원할 수 있게 되었습니다.


67-69: 이동 보조 수단 필드의 JPA 변환 설정이 적절합니다.

disabilityType 필드와 일관성 있게 @Convert 어노테이션과 List<String> 타입을 적용하여 다중 이동 보조 수단을 지원합니다.


124-126: Setter 메서드가 새로운 타입에 맞게 업데이트되었습니다.

setDisabilityTypesetMobilityAid 메서드의 매개변수 타입이 List<String>으로 올바르게 변경되어 필드 타입과 일치합니다.

src/main/java/com/wayble/server/direction/entity/Edge.java (1)

5-11: 레코드 설계가 잘 되어 있습니다.

Edge 레코드가 그래프의 간선을 표현하는 데 적절하게 설계되었습니다. 불변성이 보장되고 필요한 정보들이 잘 캡슐화되어 있습니다.

src/main/java/com/wayble/server/direction/service/util/HaversineUtil.java (2)

3-16: Haversine 공식 구현이 정확합니다.

지리적 두 점 간의 대원거리 계산이 올바르게 구현되어 있습니다. 수학적 공식과 구현이 일치합니다.


5-5: 지구 반지름 상수가 정확합니다.

6371km(6371e3 미터)는 지구의 평균 반지름으로 적절한 값입니다.

src/main/java/com/wayble/server/direction/entity/type/Type.java (2)

6-22: 접근성 시설 유형 enum이 잘 정의되어 있습니다.

각 접근성 시설 유형이 적절한 한국어 설명과 함께 명확하게 정의되어 있습니다.


6-15: enum 상수들의 일관성을 확인하세요.

모든 enum 상수들이 일관된 네이밍 컨벤션을 따르고 있고, 설명이 명확합니다. 향후 새로운 접근성 시설 유형을 추가할 때도 이 패턴을 유지하시기 바랍니다.

src/main/java/com/wayble/server/direction/entity/Node.java (1)

3-8: 노드 레코드가 간결하고 명확합니다.

그래프 노드를 표현하는 데 필요한 최소한의 정보(ID, 위도, 경도)가 적절하게 정의되어 있습니다.

src/main/java/com/wayble/server/direction/entity/WaybleMarker.java (1)

5-11: WaybleMarker 레코드가 적절하게 설계되어 있습니다.

접근성 마커에 필요한 위치 정보와 시설 유형이 잘 캡슐화되어 있습니다.

src/main/java/com/wayble/server/direction/exception/WalkingErrorCase.java (1)

11-14: 에러 케이스 추가가 적절합니다.

그래프 기반 경로 탐색을 위한 새로운 에러 케이스들이 적절하게 추가되었습니다. HTTP 상태 코드도 서버 오류(500)와 클라이언트 오류(400)로 올바르게 구분되어 있습니다.

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

4-4: Swagger API 문서화가 잘 작성되었습니다.

웨이블 추천 경로 API에 대한 Swagger 문서가 명확하고 일관성 있게 작성되었습니다. 파라미터 명명 규칙도 적절합니다.

Also applies to: 32-48

src/main/java/com/wayble/server/direction/dto/response/WayblePathResponse.java (1)

19-23: 좌표 순서의 일관성을 확인하세요.

WayblePoint는 위도(lat), 경도(lon) 순서를 사용하지만, polyline 예제는 [경도, 위도] 순서(GeoJSON 표준)를 보여줍니다. 이는 API 사용자에게 혼란을 줄 수 있습니다.

좌표 순서를 통일하거나, Schema description에 순서를 명시해주세요:

-        @Schema(description = "좌표 리스트", example = "[[127.0247425,37.4941736],[127.0249966,37.4942539]]")
+        @Schema(description = "좌표 리스트 [경도, 위도]", example = "[[127.0247425,37.4941736],[127.0249966,37.4942539]]")
src/main/java/com/wayble/server/direction/controller/WalkingController.java (1)

17-17: 기존 API 경로 변경으로 인한 Breaking Change 가능성 확인 필요

컨트롤러의 베이스 경로가 /api/v1/directions/walking/api/v1/directions로 변경되어 기존 클라이언트 호출에 영향이 있을 수 있습니다. 아래 사항을 점검해주세요:

  • 파일: src/main/java/com/wayble/server/direction/controller/WalkingController.java
    • @RequestMapping("/api/v1/directions") 적용으로 기존 /walking 서브 경로가 이동됨
  • 파라미터 네이밍 일관성
    • 기존 엔드포인트: startX / startY vs 신규 엔드포인트: startLat / startLon
  • 좌표 유효성 검증 로직 추가 필요 여부
  • API 문서·설정·레거시 클라이언트 코드에서 /api/v1/directions/walking 경로 참조 여부를 수동으로 확인
src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java (1)

19-20: 허용 오차 값과 주석 불일치

주석은 "11cm의 오차 허용"이라고 되어 있으나, 실제 TOLERANCE 값은 0.000001도입니다. 위도/경도 0.000001도는 약 11cm가 아닙니다.

-    // 11cm의 오차 허용
-    private static final double TOLERANCE = 0.000001;
+    // 약 0.11m의 오차 허용 (위도 기준)
+    private static final double TOLERANCE = 0.000001;

Likely an incorrect or invalid review comment.

Comment on lines +33 to +62
@PostConstruct
public void init() {
ObjectMapper objectMapper = new ObjectMapper();

try {
// 그래프
try (InputStream graphStream = getClass().getResourceAsStream("/seocho_pedestrian.json")) {
if (graphStream == null) {
throw new ApplicationException(WalkingErrorCase.GRAPH_FILE_NOT_FOUND);
}
JsonNode root = objectMapper.readTree(graphStream);
nodes = Arrays.asList(objectMapper.convertValue(root.get("nodes"), Node[].class));
edges = Arrays.asList(objectMapper.convertValue(root.get("edges"), Edge[].class));

nodeMap = nodes.stream().collect(Collectors.toMap(Node::id, node -> node));
}

// 웨이블 마커
try (InputStream markerStream = getClass().getResourceAsStream("/wayble_markers.json")) {
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
}
markerMap = findWaybleMarkers();
adjacencyList = buildAdjacencyList();
} catch (IOException e) {
log.error("🚨 그래프 초기화 실패: {}", e.getMessage());
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

초기화 시 데이터 검증을 추가하세요.

그래프 데이터 로드 후 다음과 같은 검증을 추가하면 런타임 오류를 방지할 수 있습니다:

            markerMap = findWaybleMarkers();
            adjacencyList = buildAdjacencyList();
+           
+           // 데이터 검증
+           if (nodes.isEmpty() || edges.isEmpty()) {
+               log.error("🚨 그래프 데이터가 비어있습니다");
+               throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
+           }
+           
+           log.info("✅ 그래프 초기화 완료: 노드 {}, 엣지 {}, 마커 {}", 
+                   nodes.size(), edges.size(), markers.size());
📝 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
@PostConstruct
public void init() {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 그래프
try (InputStream graphStream = getClass().getResourceAsStream("/seocho_pedestrian.json")) {
if (graphStream == null) {
throw new ApplicationException(WalkingErrorCase.GRAPH_FILE_NOT_FOUND);
}
JsonNode root = objectMapper.readTree(graphStream);
nodes = Arrays.asList(objectMapper.convertValue(root.get("nodes"), Node[].class));
edges = Arrays.asList(objectMapper.convertValue(root.get("edges"), Edge[].class));
nodeMap = nodes.stream().collect(Collectors.toMap(Node::id, node -> node));
}
// 웨이블 마커
try (InputStream markerStream = getClass().getResourceAsStream("/wayble_markers.json")) {
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
}
markerMap = findWaybleMarkers();
adjacencyList = buildAdjacencyList();
} catch (IOException e) {
log.error("🚨 그래프 초기화 실패: {}", e.getMessage());
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
}
@PostConstruct
public void init() {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 그래프
try (InputStream graphStream = getClass().getResourceAsStream("/seocho_pedestrian.json")) {
if (graphStream == null) {
throw new ApplicationException(WalkingErrorCase.GRAPH_FILE_NOT_FOUND);
}
JsonNode root = objectMapper.readTree(graphStream);
nodes = Arrays.asList(objectMapper.convertValue(root.get("nodes"), Node[].class));
edges = Arrays.asList(objectMapper.convertValue(root.get("edges"), Edge[].class));
nodeMap = nodes.stream().collect(Collectors.toMap(Node::id, node -> node));
}
// 웨이블 마커
try (InputStream markerStream = getClass().getResourceAsStream("/wayble_markers.json")) {
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
}
markerMap = findWaybleMarkers();
adjacencyList = buildAdjacencyList();
// 데이터 검증
if (nodes.isEmpty() || edges.isEmpty()) {
log.error("🚨 그래프 데이터가 비어있습니다");
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
log.info("✅ 그래프 초기화 완료: 노드 {}, 엣지 {}, 마커 {}",
nodes.size(), edges.size(), markers.size());
} catch (IOException e) {
log.error("🚨 그래프 초기화 실패: {}", e.getMessage());
throw new ApplicationException(WalkingErrorCase.GRAPH_INIT_FAILED);
}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/init/GraphInit.java between lines
33 and 62, after loading the graph data, add validation checks to ensure that
the nodes and edges lists are not null or empty and that nodeMap contains all
expected keys. If any validation fails, throw an ApplicationException with an
appropriate error case to prevent runtime errors during graph usage.

Comment on lines +80 to +96
private Map<Long, Type> findWaybleMarkers() {
Map<Long, Type> waybleMarkers = new HashMap<>();

for (WaybleMarker marker : markers) {
long nearNode = nodes.stream()
.min(Comparator.comparingDouble(
n -> HaversineUtil.haversine(marker.lat(), marker.lon(), n.lat(), n.lon())
))
.map(Node::id)
.orElse(marker.id());

if (nearNode != marker.id()) {
waybleMarkers.put(nearNode, marker.type());
}
}
return waybleMarkers;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

마커와 노드 ID 비교 로직에 문제가 있습니다.

findWaybleMarkers 메서드에서 마커 ID와 노드 ID를 비교하는 로직이 잘못되었습니다. 마커와 노드는 서로 다른 엔티티이므로 ID 공간이 다를 수 있습니다.

다음과 같이 수정하세요:

-                .orElse(marker.id());
+                .orElseThrow(() -> new ApplicationException(WalkingErrorCase.NODE_NOT_FOUND));

-        if (nearNode != marker.id()) {
-            waybleMarkers.put(nearNode, marker.type());
-        }
+        waybleMarkers.put(nearNode, marker.type());

또는 거리 임계값을 설정하여 너무 먼 노드는 제외하는 로직을 추가하는 것도 고려해보세요.

📝 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
private Map<Long, Type> findWaybleMarkers() {
Map<Long, Type> waybleMarkers = new HashMap<>();
for (WaybleMarker marker : markers) {
long nearNode = nodes.stream()
.min(Comparator.comparingDouble(
n -> HaversineUtil.haversine(marker.lat(), marker.lon(), n.lat(), n.lon())
))
.map(Node::id)
.orElse(marker.id());
if (nearNode != marker.id()) {
waybleMarkers.put(nearNode, marker.type());
}
}
return waybleMarkers;
}
private Map<Long, Type> findWaybleMarkers() {
Map<Long, Type> waybleMarkers = new HashMap<>();
for (WaybleMarker marker : markers) {
long nearNode = nodes.stream()
.min(Comparator.comparingDouble(
n -> HaversineUtil.haversine(marker.lat(), marker.lon(), n.lat(), n.lon())
))
.map(Node::id)
.orElseThrow(() -> new ApplicationException(WalkingErrorCase.NODE_NOT_FOUND));
waybleMarkers.put(nearNode, marker.type());
}
return waybleMarkers;
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/init/GraphInit.java between lines
80 and 96, the current logic incorrectly compares marker IDs with node IDs,
which belong to different ID spaces. To fix this, avoid directly comparing these
IDs; instead, implement a distance threshold to determine if a node is close
enough to a marker before associating them. Modify the method to calculate the
nearest node's distance to the marker and only add it to waybleMarkers if this
distance is below a defined threshold, thereby excluding nodes that are too far.

Comment on lines +27 to +28
// 노드 간 5초 대기 시간 추가 (횡단 보도, 보행자 상황 등 반영)
int totalTime = (int) Math.round(calculateTime(path)) + path.size() * 5;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

매직 넘버를 상수로 추출 필요

하드코딩된 값들이 여러 곳에 산재되어 있습니다. 유지보수성을 위해 상수로 추출하는 것이 좋습니다.

+    private static final int WAIT_TIME_PER_NODE_SECONDS = 5;
+    private static final double AVERAGE_WALKING_SPEED_MPS = 1.0;
+
     public WayblePathResponse createWayblePath(long start, long end) {
         List<Long> path = dijkstra(start, end);
         Map<Long, Type> markerMap = graphInit.getMarkerMap();
 
         int totalDistance = (int) Math.round(calculateDistance(path));
         // 노드 간 5초 대기 시간 추가 (횡단 보도, 보행자 상황 등 반영)
-        int totalTime = (int) Math.round(calculateTime(path)) + path.size() * 5;
+        int totalTime = (int) Math.round(calculateTime(path)) + path.size() * WAIT_TIME_PER_NODE_SECONDS;

     private double calculateTime(List<Long> path) {
-        double averageSpeed = 1.0;
+        double averageSpeed = AVERAGE_WALKING_SPEED_MPS;

Also applies to: 85-86

🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java
at lines 27-28 and also lines 85-86, the hardcoded number 5 used for waiting
time between nodes should be extracted into a named constant. Define a private
static final constant with a descriptive name (e.g., NODE_WAIT_TIME_SECONDS) at
the class level and replace all occurrences of the literal 5 with this constant
to improve maintainability.

Comment on lines +121 to +153
private List<Long> dijkstra(long start, long end) {
Map<Long, Double> dist = new HashMap<>();
Map<Long, Long> prev = new HashMap<>();
PriorityQueue<double[]> pq = new PriorityQueue<>(Comparator.comparingDouble(value -> value[1]));

graphInit.getGraph().keySet().forEach(key ->
dist.put(key, Double.POSITIVE_INFINITY)
);
dist.put(start, 0.0);
pq.add(new double[]{start, 0.0});

while (!pq.isEmpty()) {
double[] current = pq.poll();
long u = (long) current[0];
if (u == end) break;

for (Edge edge : graphInit.getGraph().getOrDefault(u, List.of())) {
double alt = dist.get(u) + edge.length();
if (alt < dist.get(edge.to())) {
dist.put(edge.to(), alt);
prev.put(edge.to(), u);
pq.add(new double[]{edge.to(), alt});
}
}
}
List<Long> path = new ArrayList<>();

for (Long at = end; at != null; at = prev.get(at)) {
path.add(at);
}
Collections.reverse(path);
return path;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Dijkstra 알고리즘 구현의 잠재적 문제점

  1. 경로를 찾을 수 없는 경우에 대한 처리가 없습니다
  2. 시작 노드가 그래프에 없는 경우 NPE 발생 가능
     private List<Long> dijkstra(long start, long end) {
+        // 시작/종료 노드 존재 여부 확인
+        if (!graphInit.getGraph().containsKey(start) || !graphInit.getGraph().containsKey(end)) {
+            throw new IllegalArgumentException("시작 또는 종료 노드가 그래프에 존재하지 않습니다");
+        }
+        
         Map<Long, Double> dist = new HashMap<>();
         Map<Long, Long> prev = new HashMap<>();
         // ... 기존 코드 ...
         
         List<Long> path = new ArrayList<>();
 
         for (Long at = end; at != null; at = prev.get(at)) {
             path.add(at);
         }
+        
+        // 경로를 찾을 수 없는 경우
+        if (path.size() == 1 && !path.get(0).equals(start)) {
+            throw new IllegalStateException("경로를 찾을 수 없습니다");
+        }
+        
         Collections.reverse(path);
         return path;
     }
📝 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
private List<Long> dijkstra(long start, long end) {
Map<Long, Double> dist = new HashMap<>();
Map<Long, Long> prev = new HashMap<>();
PriorityQueue<double[]> pq = new PriorityQueue<>(Comparator.comparingDouble(value -> value[1]));
graphInit.getGraph().keySet().forEach(key ->
dist.put(key, Double.POSITIVE_INFINITY)
);
dist.put(start, 0.0);
pq.add(new double[]{start, 0.0});
while (!pq.isEmpty()) {
double[] current = pq.poll();
long u = (long) current[0];
if (u == end) break;
for (Edge edge : graphInit.getGraph().getOrDefault(u, List.of())) {
double alt = dist.get(u) + edge.length();
if (alt < dist.get(edge.to())) {
dist.put(edge.to(), alt);
prev.put(edge.to(), u);
pq.add(new double[]{edge.to(), alt});
}
}
}
List<Long> path = new ArrayList<>();
for (Long at = end; at != null; at = prev.get(at)) {
path.add(at);
}
Collections.reverse(path);
return path;
}
private List<Long> dijkstra(long start, long end) {
// 시작/종료 노드 존재 여부 확인
if (!graphInit.getGraph().containsKey(start) || !graphInit.getGraph().containsKey(end)) {
throw new IllegalArgumentException("시작 또는 종료 노드가 그래프에 존재하지 않습니다");
}
Map<Long, Double> dist = new HashMap<>();
Map<Long, Long> prev = new HashMap<>();
PriorityQueue<double[]> pq = new PriorityQueue<>(Comparator.comparingDouble(value -> value[1]));
graphInit.getGraph().keySet().forEach(key ->
dist.put(key, Double.POSITIVE_INFINITY)
);
dist.put(start, 0.0);
pq.add(new double[]{start, 0.0});
while (!pq.isEmpty()) {
double[] current = pq.poll();
long u = (long) current[0];
if (u == end) break;
for (Edge edge : graphInit.getGraph().getOrDefault(u, List.of())) {
double alt = dist.get(u) + edge.length();
if (alt < dist.get(edge.to())) {
dist.put(edge.to(), alt);
prev.put(edge.to(), u);
pq.add(new double[]{edge.to(), alt});
}
}
}
List<Long> path = new ArrayList<>();
for (Long at = end; at != null; at = prev.get(at)) {
path.add(at);
}
// 경로를 찾을 수 없는 경우
if (path.size() == 1 && !path.get(0).equals(start)) {
throw new IllegalStateException("경로를 찾을 수 없습니다");
}
Collections.reverse(path);
return path;
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/WaybleDijkstraService.java
lines 121 to 153, the dijkstra method lacks handling for cases where the start
node is not present in the graph, which can cause a NullPointerException, and it
does not handle situations where no path exists between start and end nodes. To
fix this, first check if the start node exists in the graph before proceeding
and return an empty list or appropriate response if it does not. Additionally,
after the algorithm completes, verify if a path to the end node was found by
checking if the distance is still infinity or if the path list is empty, and
return an empty list if no path exists.

@seung-in-Yoo seung-in-Yoo merged commit a2ca784 into main Jul 31, 2025
3 checks passed
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