Compare commits

..

1 커밋

작성자 SHA1 메시지 날짜
da7e8f0e41 feat(batch): 비정상 궤적 포함 저장 플래그 추가 — 강화학습 데이터 수집용
GPS 스푸핑 등 비정상 운항 패턴의 강화학습 분류기 고도화를 위해,
비정상 궤적을 정상 테이블(5min/hourly/daily)과 캐시(L1/L2)에도
포함 저장하는 설정 플래그 추가.

- vessel.batch.track.include-abnormal-in-tracks 플래그 (기본 false)
- 5min: isAbnormal 시에도 filteredTracks에 포함 (플래그 true)
- Hourly/Daily: correctedTrack null 시에도 originalTrack 포함 (플래그 true)
- 비정상 검출 + t_abnormal_tracks 기록은 플래그와 무관하게 항상 유지
- prod 환경 true 설정 (강화학습 데이터 수집)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:56:16 +09:00
4개의 변경된 파일33개의 추가작업 그리고 48개의 파일을 삭제

파일 보기

@ -4,14 +4,6 @@
## [Unreleased] ## [Unreleased]
## [2026-03-27.3]
### 추가
- 비정상 궤적 포함 저장 플래그 (`include-abnormal-in-tracks`) — 강화학습 데이터 수집용
### 수정
- REST API 경로 client_id 수집 누락 수정 — JWT 쿠키 파싱 공용 메서드 추출
## [2026-03-27.2] ## [2026-03-27.2]
### 수정 ### 수정

파일 보기

@ -191,11 +191,7 @@ public class GisControllerV2 {
) )
@RequestBody VesselTracksRequest request, @RequestBody VesselTracksRequest request,
HttpServletRequest httpRequest) { HttpServletRequest httpRequest) {
return gisServiceV2.getVesselTracksV2(request, getClientIp(httpRequest), getClientId(httpRequest)); return gisServiceV2.getVesselTracksV2(request, getClientIp(httpRequest));
}
private String getClientId(HttpServletRequest request) {
return gc.mda.signal_batch.global.config.WebSocketStompConfig.extractClientIdFromRequest(request);
} }
private String getClientIp(HttpServletRequest request) { private String getClientIp(HttpServletRequest request) {

파일 보기

@ -285,7 +285,7 @@ public class GisServiceV2 {
/** /**
* 선박별 항적 조회 V2 (캐시 + Semaphore + 간소화 + ChnPrmShip enrichment) * 선박별 항적 조회 V2 (캐시 + Semaphore + 간소화 + ChnPrmShip enrichment)
*/ */
public List<CompactVesselTrack> getVesselTracksV2(VesselTracksRequest request, String clientIp, String clientId) { public List<CompactVesselTrack> getVesselTracksV2(VesselTracksRequest request, String clientIp) {
String queryId = "rest-vessels-" + UUID.randomUUID().toString().substring(0, 8); String queryId = "rest-vessels-" + UUID.randomUUID().toString().substring(0, 8);
long startMs = System.currentTimeMillis(); long startMs = System.currentTimeMillis();
boolean slotAcquired = false; boolean slotAcquired = false;
@ -329,7 +329,7 @@ public class GisServiceV2 {
result.size(), request.getVessels().size(), result.size(), request.getVessels().size(),
dailyTrackCacheManager.isEnabled(), request.isIncludeChnPrmShip()); dailyTrackCacheManager.isEnabled(), request.isIncludeChnPrmShip());
enqueueRestMetric(queryId, request, result, startMs, clientIp, clientId); enqueueRestMetric(queryId, request, result, startMs, clientIp);
return result; return result;
@ -347,7 +347,7 @@ public class GisServiceV2 {
} }
private void enqueueRestMetric(String queryId, VesselTracksRequest request, private void enqueueRestMetric(String queryId, VesselTracksRequest request,
List<CompactVesselTrack> result, long startMs, String clientIp, String clientId) { List<CompactVesselTrack> result, long startMs, String clientIp) {
try { try {
int totalPoints = result.stream().mapToInt(CompactVesselTrack::getPointCount).sum(); int totalPoints = result.stream().mapToInt(CompactVesselTrack::getPointCount).sum();
long responseBytes = (long) result.size() * 200 + (long) totalPoints * 40; long responseBytes = (long) result.size() * 200 + (long) totalPoints * 40;
@ -367,7 +367,6 @@ public class GisServiceV2 {
.elapsedMs(System.currentTimeMillis() - startMs) .elapsedMs(System.currentTimeMillis() - startMs)
.status("COMPLETED") .status("COMPLETED")
.clientIp(clientIp) .clientIp(clientIp)
.clientId(clientId)
.build()); .build());
} catch (Exception e) { } catch (Exception e) {
log.debug("Failed to enqueue REST metric: {}", e.getMessage()); log.debug("Failed to enqueue REST metric: {}", e.getMessage());

파일 보기

@ -235,16 +235,11 @@ public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
return "unknown"; return "unknown";
} }
private String extractEmailFromJwtCookie(HttpServletRequest request) {
return extractClientIdFromRequest(request);
}
}
/** /**
* GC_SESSION 쿠키에서 JWT payload의 email 클레임 추출 (REST/WebSocket 공용). * GC_SESSION 쿠키에서 JWT payload의 email 클레임 추출.
* JWT 검증은 nginx auth_request에서 이미 완료 여기서는 payload 디코딩만 수행. * JWT 검증은 nginx auth_request에서 이미 완료 여기서는 payload 디코딩만 수행.
*/ */
public static String extractClientIdFromRequest(HttpServletRequest request) { private String extractEmailFromJwtCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies(); Cookie[] cookies = request.getCookies();
if (cookies == null) return null; if (cookies == null) return null;
@ -258,10 +253,12 @@ public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
if (token == null || token.isEmpty()) return null; if (token == null || token.isEmpty()) return null;
try { try {
// JWT: header.payload.signature payload만 Base64URL 디코딩
String[] parts = token.split("\\."); String[] parts = token.split("\\.");
if (parts.length < 2) return null; if (parts.length < 2) return null;
String payload = new String(Base64.getUrlDecoder().decode(parts[1])); String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
// 간단한 JSON 파싱 (Jackson 의존 없이): "email":"value" 추출
int emailIdx = payload.indexOf("\"email\""); int emailIdx = payload.indexOf("\"email\"");
if (emailIdx < 0) return null; if (emailIdx < 0) return null;
@ -276,3 +273,4 @@ public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
} }
} }
} }
}