Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
532d11c
[test] 경사로가 있을 떄의 웨이블존 추천 경로 테스트 코드
zyovn Aug 12, 2025
54dd3eb
[feat] FacilityDocument 엔티티, dto 정의
KiSeungMin Aug 12, 2025
fd99f0f
[feat] FacilityDocument 조회 컨트롤러, dto 정의 완료
KiSeungMin Aug 12, 2025
a471f5d
[feat] FacilityDocument repository 로직 작성 완료
KiSeungMin Aug 12, 2025
72e9bec
[feat] FacilityDocument controller 로직 작성 완료
KiSeungMin Aug 12, 2025
2aa9d57
[fix] 웨이블존 추천 경로 - 경사로 가중치, 중복 좌표 제거 로직 수정
zyovn Aug 12, 2025
65f002a
[feat] FacilityTest 테스트 코드 더미 데이터 생성 완료
KiSeungMin Aug 12, 2025
9a9dc01
[fix] 피드백 반영
zyovn Aug 12, 2025
e879b45
[feat] FacilityTest 테스트 코드 응답 확인 완료
KiSeungMin Aug 12, 2025
b8e3229
[feat] FacilityTest 테스트 코드 응답 확인 완료
KiSeungMin Aug 12, 2025
0484f6c
[feat] Facility 거리순 조회 쿼리 로직 구현 완료
KiSeungMin Aug 12, 2025
7585475
[feat] Facility 조회 로직 테스트 검증 완료
KiSeungMin Aug 12, 2025
e46e73a
[fix] 상수로 변경
zyovn Aug 12, 2025
dbb4fb4
Merge pull request #126 from Wayble-Project/feature/jeongbin
zyovn Aug 12, 2025
9d051e3
Merge branch 'develop' into feature/seungmin
KiSeungMin Aug 12, 2025
d99ca0a
[refactor] 웨이블존 추천 경로가 30km 이상일 경우, 예외 반환
zyovn Aug 12, 2025
80b0ff2
[test] 에러 로그 저장 테스트
KiSeungMin Aug 12, 2025
03ed0a3
[feat] 지도에서 경사로, 엘리베이터를 거리순으로 조회하는 로직 구현
KiSeungMin Aug 12, 2025
0df86d8
[test] 에러 로그 저장 테스트
KiSeungMin Aug 12, 2025
1361a68
[fix] 웨이블존 추천 경로 총 거리 계산 로직 수정
zyovn Aug 12, 2025
7c93c29
[feat] 클라이언트 예외 디스코드 알림에서 제외
KiSeungMin Aug 12, 2025
d7cf1e1
[chore] workflow 원복
KiSeungMin Aug 12, 2025
42b18ff
[fix] 피드백 반영
zyovn Aug 12, 2025
d67da3f
Merge pull request #132 from Wayble-Project/feature/jeongbin
zyovn Aug 12, 2025
4d4ee76
Merge branch 'develop' into feature/seungmin
KiSeungMin Aug 12, 2025
a6efa39
[refactor] 엔드포인트에서 userId 제거
KiSeungMin Aug 12, 2025
6341141
[fix] 디스코드 알림 프로필이 local로 되어 있어 수정
KiSeungMin Aug 12, 2025
6075a52
[refactor] dto 응답 형식을 camelCase로 통일
KiSeungMin Aug 12, 2025
d7463c2
[fix] 배포 시 에러 로그 사라지는 버그 수정, 예외 디스코드 알림 범위 수정, 프론트 요구사항 반영
KiSeungMin Aug 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions .github/workflows/cd-develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,19 @@ jobs:
run: |
echo "🧹 Cleaning up all existing containers"

# Stop and remove specific containers
# Stop and remove specific containers (로그는 볼륨에 보존됨)
sudo docker stop github-actions-demo || true
sudo docker rm github-actions-demo || true

sudo docker stop elasticsearch || true
sudo docker rm elasticsearch || true

echo "📋 Ensuring log directory exists on host"
sudo mkdir -p /var/log/wayble
sudo chmod 755 /var/log/wayble

echo "🧯 Cleaning up unused Docker networks"
sudo docker system prune -f || true
echo "🧯 Cleaning up unused Docker networks (excluding volumes)"
sudo docker system prune -f --volumes=false || true

Comment on lines +79 to 85
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

docker system prune 옵션 사용 수정 제안: --volumes=false는 비호환 가능성

Docker CLI의 boolean 플래그는 보통 지정 시 true, 미지정 시 false로 동작합니다. 일부 환경에서 --volumes=false가 인식되지 않을 수 있으니, 단순히 생략하는 것이 호환성이 좋습니다.

다음처럼 수정해 주세요:

-echo "🧯 Cleaning up unused Docker networks (excluding volumes)"
-sudo docker system prune -f --volumes=false || true
+echo "🧯 Cleaning up unused Docker resources (excluding volumes)"
+sudo docker system prune -f || true
📝 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
echo "📋 Ensuring log directory exists on host"
sudo mkdir -p /var/log/wayble
sudo chmod 755 /var/log/wayble
echo "🧯 Cleaning up unused Docker networks"
sudo docker system prune -f || true
echo "🧯 Cleaning up unused Docker networks (excluding volumes)"
sudo docker system prune -f --volumes=false || true
echo "📋 Ensuring log directory exists on host"
sudo mkdir -p /var/log/wayble
sudo chmod 755 /var/log/wayble
echo "🧯 Cleaning up unused Docker resources (excluding volumes)"
sudo docker system prune -f || true
🤖 Prompt for AI Agents
.github/workflows/cd-develop.yml around lines 79 to 85: the workflow uses `sudo
docker system prune -f --volumes=false` which may be incompatible in some Docker
CLI versions; remove the `--volumes=false` flag and run `sudo docker system
prune -f || true` so the command remains portable and still ignores errors.

- name: Create Docker network if not exists
run: |
Expand Down Expand Up @@ -184,6 +187,18 @@ jobs:
exit 1
fi

# 로그 파일 상태 확인
echo "=== Log Directory Status ==="
ls -la /var/log/wayble/ || echo "Log directory not found"

if [ -f "/var/log/wayble/wayble-error.log" ]; then
echo "✅ Error log file exists"
echo "📊 Error log file size: $(du -h /var/log/wayble/wayble-error.log | cut -f1)"
echo "📅 Last modified: $(stat -c %y /var/log/wayble/wayble-error.log)"
else
echo "ℹ️ No error log file yet (normal for new deployment)"
fi

# ✅ 배포 성공 알림 (Discord)
- name: Send success webhook to Discord
if: success()
Expand All @@ -204,7 +219,6 @@ jobs:




# on: #이 워크플로우가 언제 실행될지 트리거를 정의함.
# pull_request:
# types : [closed] #누군가가 Pull request를 닫았을 때 실행됨.
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ FROM openjdk:17
# 인자 설정 - JAR_File
ARG JAR_FILE=build/libs/*.jar

# 로그 디렉토리 생성 및 권한 설정
RUN mkdir -p /app/logs && chmod 755 /app/logs

# 작업 디렉토리 설정
WORKDIR /app

Comment on lines +9 to +14
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

컨테이너를 비루트 사용자로 실행하고 로그 디렉터리 소유권을 명시적으로 설정하는 것을 권장

현재 로그 디렉터리는 755 권한만 설정되어 있고, 컨테이너는 루트로 실행됩니다. 보안/격리 강화를 위해 전용 사용자/그룹을 만들고 /app 전체(특히 /app/logs)의 소유권을 넘긴 뒤 USER를 전환하는 것을 권장합니다. 호스트 볼륨(/var/log/wayble)을 마운트하면 컨테이너 내부 권한과의 정합성도 중요합니다.

다음과 같이 RUN/USER를 보완해 주세요(선택지):

 # 로그 디렉토리 생성 및 권한 설정
-RUN mkdir -p /app/logs && chmod 755 /app/logs
+RUN mkdir -p /app/logs && chmod 755 /app/logs

 # 작업 디렉토리 설정
 WORKDIR /app
+
+# 비루트 사용자 생성 및 소유권 이전
+RUN groupadd -g 10001 app && useradd -r -u 10001 -g app app \
+    && chown -R app:app /app
+USER app

추가로, 이미지 슬림화가 필요하다면 베이스 이미지를 slim 변형으로 교체하는 것도 고려 가능합니다(선택):

-FROM openjdk:17
+FROM openjdk:17-jdk-slim
📝 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
# 로그 디렉토리 생성 및 권한 설정
RUN mkdir -p /app/logs && chmod 755 /app/logs
# 작업 디렉토리 설정
WORKDIR /app
# 로그 디렉토리 생성 및 권한 설정
RUN mkdir -p /app/logs && chmod 755 /app/logs
# 작업 디렉토리 설정
WORKDIR /app
# 비루트 사용자 생성 및 소유권 이전
RUN groupadd -g 10001 app && useradd -r -u 10001 -g app app \
&& chown -R app:app /app
USER app
🤖 Prompt for AI Agents
In Dockerfile around lines 9 to 14, the Docker image currently creates /app/logs
with chmod 755 but leaves the container running as root; create a dedicated
non-root user and group, chown /app and /app/logs to that user:group, and switch
to that user using USER so the container runs non-root; ensure any host-mounted
volume path (e.g., /var/log/wayble) has compatible ownership/permissions or
document that it must be owned by the same uid/gid or be writable by the
container user; optionally replace the base image with its slim variant to
reduce image size.

# jar 파일 복제
COPY ${JAR_FILE} app.jar

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class GlobalExceptionHandler {

@ExceptionHandler(ApplicationException.class)
public ResponseEntity<CommonResponse> handleApplicationException(ApplicationException e, WebRequest request) {
// 비즈니스 예외 로그 기록 (간결하게)

String path = ((ServletWebRequest) request).getRequest().getRequestURI();
String method = ((ServletWebRequest) request).getRequest().getMethod();

Expand All @@ -47,36 +47,31 @@ public ResponseEntity<CommonResponse> handleApplicationException(ApplicationExce

CommonResponse commonResponse = CommonResponse.error(e.getErrorCase());

HttpStatus status = HttpStatus.valueOf(e.getErrorCase().getHttpStatusCode());
//sendToDiscord(e, request, status);

return ResponseEntity
.status(e.getErrorCase().getHttpStatusCode())
.body(commonResponse);
}

@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<CommonResponse> handleValidException(BindingResult bindingResult,
MethodArgumentNotValidException ex,
WebRequest request) {
String message = bindingResult.getAllErrors().get(0).getDefaultMessage();
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<CommonResponse> handleValidException(MethodArgumentNotValidException ex, WebRequest request) {
String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();

// 에러 로그 기록
String path = ((ServletWebRequest) request).getRequest().getRequestURI();
String method = ((ServletWebRequest) request).getRequest().getMethod();
String errorLocation = getErrorLocation(ex);

log.error("Validation Exception 발생 - Method: {}, Path: {}, Message: {}, Location: {}",
method, path, message, errorLocation, ex);
log.warn("Validation Exception - Method: {}, Path: {}, Message: {}, Location: {}",
method, path, message, errorLocation);

CommonResponse commonResponse = CommonResponse.error(400, message);

sendToDiscord(ex, request, HttpStatus.BAD_REQUEST);

return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(commonResponse);
}
Comment on lines +55 to 72
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Validation 메시지 추출 로직의 안전성 보강 및 상수화 권장

getAllErrors().get(0) 접근은 방어 로직이 없어 NPE/IOOBE 가능성이 있습니다(드물지만 안전하게). 또한 400을 매직넘버 대신 HttpStatus 상수로 일관 사용 권장.

다음처럼 보완해 주세요:

-    public ResponseEntity<CommonResponse> handleValidException(MethodArgumentNotValidException ex, WebRequest request) {
-        String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+    public ResponseEntity<CommonResponse> handleValidException(MethodArgumentNotValidException ex, WebRequest request) {
+        String message = null;
+        if (ex.getBindingResult() != null) {
+            if (!ex.getBindingResult().getFieldErrors().isEmpty()) {
+                message = ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
+            } else if (!ex.getBindingResult().getAllErrors().isEmpty()) {
+                message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+            }
+        }
+        if (message == null || message.isBlank()) {
+            message = "유효성 검사 실패";
+        }
@@
-        CommonResponse commonResponse = CommonResponse.error(400, message);
+        CommonResponse commonResponse = CommonResponse.error(HttpStatus.BAD_REQUEST.value(), message);

추가로, 시그니처 변경으로 import org.springframework.validation.BindingResult;가 더 이상 사용되지 않습니다. 정리해 주세요.

다음 코드에서 불필요한 import를 제거:

// 제거 대상
import org.springframework.validation.BindingResult;

#!/bin/bash
# Locate and display the imports in GlobalExceptionHandler.java
file_path=$(fd -e java GlobalExceptionHandler.java)
echo "File: $file_path"
sed -n '1,30p' "$file_path"

GlobalExceptionHandler.java의 상단 import 구문을 확인하여 org.springframework.validation.BindingResult가 실제로 불필요하게 선언되어 있는지 검증합니다.

#!/bin/bash
# GlobalExceptionHandler.java의 import 목록 출력
file=$(fd --extension java GlobalExceptionHandler.java)
echo "Analyzing: $file"
sed -n '1,50p' "$file"

GlobalExceptionHandler.java의 상단 import 구문과 getBindingResult() 호출부를 확인합니다.

#!/bin/bash
# 파일 경로 찾기
file=$(fd --extension java GlobalExceptionHandler.java)
echo "Analyzing: $file"
# 상단 1~50줄 출력 (import 및 메서드 시그니처 확인)
sed -n '1,50p' "$file"

Validation 메시지 안전 추출 및 HTTP 상태 코드 상수화 권장

아래 사항을 반영해주세요:

  • getAllErrors().get(0) 호출 전 방어 로직 추가
  • HTTP 상태 코드는 매직 넘버(400) 대신 HttpStatus.BAD_REQUEST.value() 사용
  • 사용되지 않는 org.springframework.validation.BindingResult import 제거
-    public ResponseEntity<CommonResponse> handleValidException(MethodArgumentNotValidException ex, WebRequest request) {
-        String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+    public ResponseEntity<CommonResponse> handleValidException(MethodArgumentNotValidException ex, WebRequest request) {
+        String message = null;
+        var bindingResult = ex.getBindingResult();
+        if (bindingResult != null) {
+            if (!bindingResult.getFieldErrors().isEmpty()) {
+                message = bindingResult.getFieldErrors().get(0).getDefaultMessage();
+            } else if (!bindingResult.getAllErrors().isEmpty()) {
+                message = bindingResult.getAllErrors().get(0).getDefaultMessage();
+            }
+        }
+        if (message == null || message.isBlank()) {
+            message = "유효성 검사 실패";
+        }
@@
-        CommonResponse commonResponse = CommonResponse.error(400, message);
+        CommonResponse commonResponse = CommonResponse.error(HttpStatus.BAD_REQUEST.value(), message);
// 제거 대상
-import org.springframework.validation.BindingResult;
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/common/exception/GlobalExceptionHandler.java
around lines 55 to 72, add defensive checks before calling
ex.getBindingResult().getAllErrors().get(0) to avoid
IndexOutOfBounds/NullPointer (e.g., check that getBindingResult() and
getAllErrors() are non-null and not empty, and provide a safe default message if
none present), replace the hardcoded 400 with HttpStatus.BAD_REQUEST.value()
when constructing the CommonResponse, and remove the unused import
org.springframework.validation.BindingResult from the file.



/**
* 모든 예상하지 못한 예외 처리
*/
Expand Down Expand Up @@ -107,6 +102,12 @@ private void sendToDiscord(Exception ex, WebRequest request, HttpStatus status)
return;
}

// 특정 예외 타입 및 경로에 대한 Discord 알림 제외
if (shouldSkipDiscordNotification(ex, path)) {
log.debug("Discord 알림 제외 - Exception: {}, Path: {}", ex.getClass().getSimpleName(), path);
return;
}

// Embed 필드 구성
DiscordWebhookPayload.Embed embed = new DiscordWebhookPayload.Embed(
"🚨 서버 에러 발생",
Expand Down Expand Up @@ -136,6 +137,108 @@ private void sendToDiscord(Exception ex, WebRequest request, HttpStatus status)
}
}

/**
* Discord 알림을 보내지 않을 예외인지 판단
*/
private boolean shouldSkipDiscordNotification(Exception ex, String path) {
String exceptionName = ex.getClass().getSimpleName();
String message = ex.getMessage();

// 1. NoResourceFoundException 제외 (static resource 요청)
if ("NoResourceFoundException".equals(exceptionName)) {
return true;
}

// 2. 특정 경로 패턴 제외
if (isIgnoredPath(path)) {
return true;
}

// 3. 봇이나 크롤러 요청으로 인한 에러 제외
if (isBotOrCrawlerRequest(message)) {
return true;
}

// 4. 기타 불필요한 예외들
if (isIgnoredException(exceptionName, message)) {
return true;
}

return false;
}

/**
* 무시할 경로인지 확인
*/
private boolean isIgnoredPath(String path) {
String[] ignoredPaths = {
"/favicon.ico",
"/index.html",
"/robots.txt",
"/sitemap.xml",
"/apple-touch-icon",
"/.well-known/",
"/wp-admin/",
"/admin/",
"/phpmyadmin/",
"/xmlrpc.php",
"/.env",
"/config.php"
};

for (String ignoredPath : ignoredPaths) {
if (path.contains(ignoredPath)) {
return true;
}
}

return false;
}

/**
* 봇이나 크롤러 요청인지 확인
*/
private boolean isBotOrCrawlerRequest(String message) {
if (message == null) return false;

String[] botIndicators = {
"No static resource",
"Could not resolve view",
"favicon",
"robots.txt"
};

for (String indicator : botIndicators) {
if (message.contains(indicator)) {
return true;
}
}

return false;
}

/**
* 무시할 예외인지 확인
*/
private boolean isIgnoredException(String exceptionName, String message) {
// 클라이언트 연결 종료 관련
if ("ClientAbortException".equals(exceptionName) ||
"BrokenPipeException".equals(exceptionName)) {
return true;
}

// 타임아웃 관련 (너무 빈번한 경우)
if (message != null && (
message.contains("Connection timed out") ||
message.contains("Read timed out") ||
message.contains("Connection reset")
)) {
return true;
}

return false;
}

/**
* 예외의 스택트레이스에서 실제 에러 발생 위치를 추출
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum WalkingErrorCase implements ErrorCase {
GRAPH_FILE_NOT_FOUND(500, 8002, "그래프 파일을 찾을 수 없습니다."),
GRAPH_INIT_FAILED(500, 8003, "그래프 초기화에 실패했습니다."),
NODE_NOT_FOUND(400, 8004, "해당 위도, 경도 근처의 노드가 존재하지 않습니다."),
DISTANCE_LIMIT_EXCEEDED(400, 8005, "경로가 허용 최대 거리(30km)를 초과하였습니다."),
;

private final Integer httpStatusCode;
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/wayble/server/direction/init/GraphInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public class GraphInit {
private Map<Long, List<Edge>> adjacencyList;
private Map<Long, Node> nodeMap;
private Map<Long, Type> markerMap;
private Set<Long> rampMarkers = Collections.emptySet();

private static final double RAMP_PENALTY = 5.0;
private static final double MARKER_DISCOUNT = 0.5;
Comment on lines +32 to +35
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

초기화되지 않은 rampMarkers 필드

rampMarkers가 빈 Set으로 초기화되어 있어 실제로 RAMP 패널티가 적용되지 않습니다. wayble_markers.json에서 RAMP 타입 마커들을 로드하여 이 Set을 채워야 합니다.

 private Set<Long> rampMarkers = Collections.emptySet();
 
 private static final double RAMP_PENALTY = 5.0;
 private static final double MARKER_DISCOUNT = 0.5;
 
 @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();
+        // RAMP 타입 마커들의 노드 ID를 rampMarkers Set에 추가
+        rampMarkers = markerMap.entrySet().stream()
+                .filter(entry -> entry.getValue() == Type.RAMP)
+                .map(Map.Entry::getKey)
+                .collect(Collectors.toSet());
         adjacencyList = buildAdjacencyList();
📝 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 Set<Long> rampMarkers = Collections.emptySet();
private static final double RAMP_PENALTY = 5.0;
private static final double MARKER_DISCOUNT = 0.5;
markers = markerStream != null
? objectMapper.readValue(markerStream, new TypeReference<>() {})
: new ArrayList<>();
markerMap = findWaybleMarkers();
// RAMP 타입 마커들의 노드 ID를 rampMarkers Set에 추가
rampMarkers = markerMap.entrySet().stream()
.filter(entry -> entry.getValue() == Type.RAMP)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
adjacencyList = buildAdjacencyList();
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/init/GraphInit.java around lines 32
to 35, rampMarkers is initialized to an empty Set so RAMP penalties never apply;
replace this by loading wayble_markers.json at initialization and populate
rampMarkers with the IDs of markers whose type == "RAMP" (e.g., in the class
constructor or init method used before graph computations), parsing the JSON
resource, collecting marker IDs into a Set<Long>, and assigning it (preferably
as an immutable/defensive copy). Ensure any IO/parsing exceptions are handled or
propagated appropriately so rampMarkers is reliably populated before use.


@PostConstruct
public void init() {
Expand Down Expand Up @@ -66,7 +70,12 @@ private Map<Long, List<Edge>> buildAdjacencyList() {

for (Edge edge : edges) {
boolean isWaybleMarker = markerMap.containsKey(edge.from()) || markerMap.containsKey(edge.to());
double distance = isWaybleMarker ? edge.length() * 0.5 : edge.length();
boolean isRamp = rampMarkers.contains(edge.from()) || rampMarkers.contains(edge.to());

double distance = edge.length();

if (isRamp) distance *= RAMP_PENALTY;
else if (isWaybleMarker) distance *= MARKER_DISCOUNT;

// 양방향
adjacencyList.computeIfAbsent(edge.from(), k -> new ArrayList<>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class WalkingService {
private final GraphInit graphInit;
private final WaybleDijkstraService waybleDijkstraService;

private static final double NODE_SEARCH_RADIUS = 1000;

public TMapParsingResponse callTMapApi(TMapRequest request) {
try {
TMapResponse response = tMapClient.response(request);
Expand All @@ -52,6 +54,7 @@ public WayblePathResponse findWayblePath(

private long findNearestNode(double lat, double lon) {
return graphInit.getNodeMap().values().stream()
.filter(node -> HaversineUtil.haversine(lat, lon, node.lat(), node.lon()) <= NODE_SEARCH_RADIUS)
.min(Comparator.comparingDouble(
node -> HaversineUtil.haversine(lat, lon, node.lat(), node.lon())
))
Expand Down
Loading
Loading