From 154483289907c75ebe80ac7dd6a7fbe829b61b41 Mon Sep 17 00:00:00 2001 From: htlee Date: Sat, 21 Feb 2026 01:15:12 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20ST=5FAsText=20WKT=20=EA=B3=B5=EB=B0=B1?= =?UTF-8?q?=20=EB=B6=88=EC=9D=BC=EC=B9=98=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?daily=20merge=20=EC=A0=84=EB=9F=89=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostGIS ST_AsText()는 "LINESTRING M (...)" (공백 포함) 반환하지만 Java 코드는 "LINESTRING M(...)" (공백 없음) 형식만 매칭하여 앱 재시작 후 DB fallback/warmup 데이터가 전량 필터되는 버그 수정. - DB 읽기 시 WKT 정규화 (CacheWarmup, CacheBasedReader 4곳) - merge processor regex에 \s* 방어적 처리 (Daily/Hourly) - countWktPoints indexOf('(') 기반으로 개선 - BatchAdmin: dailyJob 수동 실행 시 timeBucket 파라미터 추가 - DataPipeline: L3 표시를 cachedDays → totalVessels로 변경 Co-Authored-By: Claude Opus 4.6 --- frontend/src/i18n/en.ts | 2 +- frontend/src/pages/DataPipeline.tsx | 4 ++-- .../batch/processor/DailyTrackMergeProcessor.java | 6 ++++-- .../batch/processor/HourlyTrackMergeProcessor.java | 6 ++++-- .../batch/reader/CacheBasedDailyTrackReader.java | 7 ++++++- .../batch/reader/CacheBasedHourlyTrackReader.java | 7 ++++++- .../mda/signal_batch/global/config/CacheWarmupService.java | 7 ++++++- .../monitoring/controller/BatchAdminController.java | 7 +++++-- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/frontend/src/i18n/en.ts b/frontend/src/i18n/en.ts index c33c0f0..bcd9e5b 100644 --- a/frontend/src/i18n/en.ts +++ b/frontend/src/i18n/en.ts @@ -83,7 +83,7 @@ const en = { 'pipeline.aisLatest': 'AIS Latest', 'pipeline.processLatest': 'Process Latest', 'pipeline.cacheOverview': 'Cache Overview', - 'pipeline.cachedDays': 'days cached', + 'pipeline.cachedDays': ' days cached', 'pipeline.totalHitRate': 'Total Hit Rate', 'pipeline.dailyThroughput': 'Daily Throughput Trend', 'pipeline.totalProcessed': 'Total Processed', diff --git a/frontend/src/pages/DataPipeline.tsx b/frontend/src/pages/DataPipeline.tsx index a0b02ee..dbc57da 100644 --- a/frontend/src/pages/DataPipeline.tsx +++ b/frontend/src/pages/DataPipeline.tsx @@ -157,8 +157,8 @@ export default function DataPipeline() {
L3 (Daily)
-
{cacheDetails.l3_daily?.cachedDays ?? 0}
-
{t('pipeline.cachedDays')}
+
{formatNumber(cacheDetails.l3_daily?.totalVessels)}
+
{cacheDetails.l3_daily?.cachedDays ?? 0}{t('pipeline.cachedDays')}
{cache && ( diff --git a/src/main/java/gc/mda/signal_batch/batch/processor/DailyTrackMergeProcessor.java b/src/main/java/gc/mda/signal_batch/batch/processor/DailyTrackMergeProcessor.java index 44041b1..4b7d770 100644 --- a/src/main/java/gc/mda/signal_batch/batch/processor/DailyTrackMergeProcessor.java +++ b/src/main/java/gc/mda/signal_batch/batch/processor/DailyTrackMergeProcessor.java @@ -35,7 +35,7 @@ import java.util.regex.Pattern; public class DailyTrackMergeProcessor implements ItemProcessor, AbnormalDetectionResult>, StepExecutionListener { - private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\((.+)\\)"); + private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\s*\\((.+)\\)"); private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final AbnormalTrackDetector abnormalTrackDetector; @@ -217,7 +217,9 @@ public class DailyTrackMergeProcessor private int countWktPoints(String wkt) { if (wkt == null || !wkt.startsWith("LINESTRING M")) return 0; try { - String coords = wkt.substring("LINESTRING M(".length(), wkt.length() - 1); + int parenIdx = wkt.indexOf('('); + if (parenIdx < 0) return 0; + String coords = wkt.substring(parenIdx + 1, wkt.length() - 1); return coords.split(",").length; } catch (Exception e) { return 0; diff --git a/src/main/java/gc/mda/signal_batch/batch/processor/HourlyTrackMergeProcessor.java b/src/main/java/gc/mda/signal_batch/batch/processor/HourlyTrackMergeProcessor.java index 12d397a..2d0c2ad 100644 --- a/src/main/java/gc/mda/signal_batch/batch/processor/HourlyTrackMergeProcessor.java +++ b/src/main/java/gc/mda/signal_batch/batch/processor/HourlyTrackMergeProcessor.java @@ -35,7 +35,7 @@ import java.util.regex.Pattern; public class HourlyTrackMergeProcessor implements ItemProcessor, AbnormalDetectionResult>, StepExecutionListener { - private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\((.+)\\)"); + private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\s*\\((.+)\\)"); private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final AbnormalTrackDetector abnormalTrackDetector; @@ -212,7 +212,9 @@ public class HourlyTrackMergeProcessor private int countWktPoints(String wkt) { if (wkt == null || !wkt.startsWith("LINESTRING M")) return 0; try { - String coords = wkt.substring("LINESTRING M(".length(), wkt.length() - 1); + int parenIdx = wkt.indexOf('('); + if (parenIdx < 0) return 0; + String coords = wkt.substring(parenIdx + 1, wkt.length() - 1); return coords.split(",").length; } catch (Exception e) { return 0; diff --git a/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedDailyTrackReader.java b/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedDailyTrackReader.java index fcbe38b..093e1ca 100644 --- a/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedDailyTrackReader.java +++ b/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedDailyTrackReader.java @@ -146,10 +146,15 @@ public class CacheBasedDailyTrackReader implements ItemReader> || (endPos == null && rs.getString("end_position") != null)) { parseFailCount[0]++; } + // ST_AsText()는 "LINESTRING M (...)" 공백 포함 반환 → Java 내부 형식으로 정규화 + String geomWkt = rs.getString("geom_text"); + if (geomWkt != null) { + geomWkt = geomWkt.replace("LINESTRING M (", "LINESTRING M("); + } VesselTrack track = VesselTrack.builder() .mmsi(mmsi) .timeBucket(rs.getTimestamp("time_bucket").toLocalDateTime()) - .trackGeom(rs.getString("geom_text")) + .trackGeom(geomWkt) .distanceNm(rs.getBigDecimal("distance_nm")) .avgSpeed(rs.getBigDecimal("avg_speed")) .maxSpeed(rs.getBigDecimal("max_speed")) diff --git a/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedHourlyTrackReader.java b/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedHourlyTrackReader.java index 86bd699..f080891 100644 --- a/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedHourlyTrackReader.java +++ b/src/main/java/gc/mda/signal_batch/batch/reader/CacheBasedHourlyTrackReader.java @@ -146,10 +146,15 @@ public class CacheBasedHourlyTrackReader implements ItemReader || (endPos == null && rs.getString("end_position") != null)) { parseFailCount[0]++; } + // ST_AsText()는 "LINESTRING M (...)" 공백 포함 반환 → 정규화 + String geomWkt = rs.getString("geom_text"); + if (geomWkt != null) { + geomWkt = geomWkt.replace("LINESTRING M (", "LINESTRING M("); + } VesselTrack track = VesselTrack.builder() .mmsi(mmsi) .timeBucket(rs.getTimestamp("time_bucket").toLocalDateTime()) - .trackGeom(rs.getString("geom_text")) + .trackGeom(geomWkt) .distanceNm(rs.getBigDecimal("distance_nm")) .avgSpeed(rs.getBigDecimal("avg_speed")) .maxSpeed(rs.getBigDecimal("max_speed")) diff --git a/src/main/java/gc/mda/signal_batch/global/config/CacheWarmupService.java b/src/main/java/gc/mda/signal_batch/global/config/CacheWarmupService.java index b4d61f3..255c8e6 100644 --- a/src/main/java/gc/mda/signal_batch/global/config/CacheWarmupService.java +++ b/src/main/java/gc/mda/signal_batch/global/config/CacheWarmupService.java @@ -169,10 +169,15 @@ public class CacheWarmupService { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { + // ST_AsText()는 "LINESTRING M (...)" 공백 포함 반환 → Java 내부 형식으로 정규화 + String geomWkt = rs.getString("track_geom"); + if (geomWkt != null) { + geomWkt = geomWkt.replace("LINESTRING M (", "LINESTRING M("); + } VesselTrack track = VesselTrack.builder() .mmsi(rs.getString("mmsi")) .timeBucket(rs.getTimestamp("time_bucket").toLocalDateTime()) - .trackGeom(rs.getString("track_geom")) + .trackGeom(geomWkt) .distanceNm(BigDecimal.valueOf(rs.getDouble("distance_nm"))) .avgSpeed(BigDecimal.valueOf(rs.getDouble("avg_speed"))) .maxSpeed(BigDecimal.valueOf(rs.getDouble("max_speed"))) diff --git a/src/main/java/gc/mda/signal_batch/monitoring/controller/BatchAdminController.java b/src/main/java/gc/mda/signal_batch/monitoring/controller/BatchAdminController.java index 3c81f6f..2925099 100644 --- a/src/main/java/gc/mda/signal_batch/monitoring/controller/BatchAdminController.java +++ b/src/main/java/gc/mda/signal_batch/monitoring/controller/BatchAdminController.java @@ -87,8 +87,11 @@ public class BatchAdminController { .addString("endTime", end.withNano(0).toString()) .addLong("executionTime", System.currentTimeMillis()); - // vesselTrackAggregationJob의 경우 timeBucket 파라미터 추가 - if ("vesselTrackAggregationJob".equals(jobName)) { + // timeBucket 파라미터 추가 (vesselTrack: 5분 단위, daily: 일 시작) + if ("dailyAggregationJob".equals(jobName)) { + LocalDateTime timeBucket = start.toLocalDate().atStartOfDay(); + paramsBuilder.addString("timeBucket", timeBucket.toString()); + } else { LocalDateTime timeBucket = start.withSecond(0).withNano(0) .minusMinutes(start.getMinute() % 5); paramsBuilder.addString("timeBucket", timeBucket.toString());