-
Notifications
You must be signed in to change notification settings - Fork 1
[release]진행상황 main branch에 반영 #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
532d11c
54dd3eb
fd99f0f
a471f5d
72e9bec
2aa9d57
65f002a
9a9dc01
e879b45
b8e3229
0484f6c
7585475
e46e73a
dbb4fb4
9d051e3
d99ca0a
80b0ff2
03ed0a3
0df86d8
1361a68
7c93c29
d7cf1e1
42b18ff
d67da3f
4d4ee76
a6efa39
6341141
6075a52
d7463c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| # jar 파일 복제 | ||||||||||||||||||||||||||||||||
| COPY ${JAR_FILE} app.jar | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
|
|
||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent ❓ Verification inconclusiveValidation 메시지 추출 로직의 안전성 보강 및 상수화 권장 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를 제거: // 제거 대상
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 구문을 확인하여 #!/bin/bash
# GlobalExceptionHandler.java의 import 목록 출력
file=$(fd --extension java GlobalExceptionHandler.java)
echo "Analyzing: $file"
sed -n '1,50p' "$file"GlobalExceptionHandler.java의 상단 import 구문과 #!/bin/bash
# 파일 경로 찾기
file=$(fd --extension java GlobalExceptionHandler.java)
echo "Analyzing: $file"
# 상단 1~50줄 출력 (import 및 메서드 시그니처 확인)
sed -n '1,50p' "$file"Validation 메시지 안전 추출 및 HTTP 상태 코드 상수화 권장 아래 사항을 반영해주세요:
- 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 |
||
|
|
||
|
|
||
| /** | ||
| * 모든 예상하지 못한 예외 처리 | ||
| */ | ||
|
|
@@ -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( | ||
| "🚨 서버 에러 발생", | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
| /** | ||
| * 예외의 스택트레이스에서 실제 에러 발생 위치를 추출 | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 초기화되지 않은 rampMarkers 필드
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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @PostConstruct | ||||||||||||||||||||||||||||||
| public void init() { | ||||||||||||||||||||||||||||||
|
|
@@ -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<>()) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docker system prune 옵션 사용 수정 제안: --volumes=false는 비호환 가능성
Docker CLI의 boolean 플래그는 보통 지정 시 true, 미지정 시 false로 동작합니다. 일부 환경에서
--volumes=false가 인식되지 않을 수 있으니, 단순히 생략하는 것이 호환성이 좋습니다.다음처럼 수정해 주세요:
📝 Committable suggestion
🤖 Prompt for AI Agents