From 68c22b3211dd6f436c24bc7ae42c4c7f09fd9922 Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 20 Feb 2026 00:46:29 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20UTC=20=ED=83=80=EC=9E=84=EC=A1=B4=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=20+=20Daily=20=EC=BA=90=EC=8B=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20fallback=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DateTimeParseUtil: zdt.toLocalDateTime() → withZoneSameInstant(systemDefault) 변환 클라이언트 UTC 시각("Z" 접미사)이 KST로 변환되지 않아 9시간 밀리는 버그 수정 - GisServiceV2 queryWithCache: Daily 캐시 HIT 후 요청 MMSI 미존재 시 DB fallback(hourly/5min 계층 조회) 추가 Co-Authored-By: Claude Opus 4.6 --- .../domain/gis/service/GisServiceV2.java | 25 ++++++++++++++++++- .../global/util/DateTimeParseUtil.java | 4 ++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java b/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java index ecefb2b..8036216 100644 --- a/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java +++ b/src/main/java/gc/mda/signal_batch/domain/gis/service/GisServiceV2.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -327,7 +328,7 @@ public class GisServiceV2 { Set requestedMmsis = new HashSet<>(request.getVessels()); - // 1. 캐시에서 조회 (캐시된 날짜) + // 1. 캐시에서 조회 (캐시된 날짜) + 누락 MMSI 부분 DB fallback if (split.hasCachedData()) { List cachedTracks = dailyTrackCacheManager.getCachedTracksMultipleDays(split.getCachedDates()); @@ -343,6 +344,28 @@ public class GisServiceV2 { allTracks.addAll(filteredCached); log.debug("[CacheQuery] cached {} days -> {} tracks (filtered from {})", split.getCachedDates().size(), filteredCached.size(), totalCachedCount); + + // Daily 캐시에 없는 MMSI → DB fallback (hourly/5min 계층 조회) + Set cachedMmsis = filteredCached.stream() + .map(CompactVesselTrack::getVesselId) + .collect(Collectors.toSet()); + Set missingMmsis = new HashSet<>(requestedMmsis); + missingMmsis.removeAll(cachedMmsis); + if (!missingMmsis.isEmpty()) { + LocalDate minDate = split.getCachedDates().stream().min(Comparator.naturalOrder()).orElse(null); + LocalDate maxDate = split.getCachedDates().stream().max(Comparator.naturalOrder()).orElse(null); + if (minDate != null) { + LocalDateTime cacheStart = minDate.atStartOfDay().isBefore(startTime) ? startTime : minDate.atStartOfDay(); + LocalDateTime cacheEnd = maxDate.plusDays(1).atStartOfDay().isAfter(endTime) ? endTime : maxDate.plusDays(1).atStartOfDay(); + VesselTracksRequest fallbackReq = VesselTracksRequest.builder() + .startTime(cacheStart).endTime(cacheEnd) + .vessels(new ArrayList<>(missingMmsis)).build(); + List dbResult = gisService.getVesselTracks(fallbackReq); + allTracks.addAll(dbResult); + log.info("[CACHE-MONITOR] queryWithCache daily PARTIAL → DB fallback: cacheHit={}, cacheMiss={}, dbTracks={}", + cachedMmsis.size(), missingMmsis.size(), dbResult.size()); + } + } } // 2. DB에서 조회 (캐시 미적중 과거 날짜) diff --git a/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java b/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java index d6648e1..525597d 100644 --- a/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java +++ b/src/main/java/gc/mda/signal_batch/global/util/DateTimeParseUtil.java @@ -1,6 +1,7 @@ package gc.mda.signal_batch.global.util; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -76,11 +77,12 @@ public class DateTimeParseUtil { String trimmed = dateTimeString.trim(); // Try zoned formatters first for Z or offset patterns + // UTC/Offset → 시스템 타임존(KST)으로 변환 후 LocalDateTime 추출 if (trimmed.contains("Z") || trimmed.matches(".*[+-]\\d{2}:\\d{2}$")) { for (DateTimeFormatter formatter : ZONED_FORMATTERS) { try { ZonedDateTime zdt = ZonedDateTime.parse(trimmed, formatter); - return zdt.toLocalDateTime(); + return zdt.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime(); } catch (DateTimeParseException ignored) { // Try next formatter }