perf: API 응답 크기 최적화 + Swagger 최신화 #83

병합
htlee feature/dashboard-phase-1 에서 develop 로 2 commits 를 머지했습니다 2026-03-01 22:55:26 +09:00
6개의 변경된 파일72개의 추가작업 그리고 11개의 파일을 삭제
Showing only changes of commit 0249a1fb90 - Show all commits

파일 보기

@ -1,5 +1,9 @@
package gc.mda.signal_batch.domain.gis.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
@ -17,16 +21,20 @@ import java.nio.file.Paths;
@RestController
@RequestMapping("/api/tiles")
@Tag(name = "지도 타일 API", description = "WebP 형식의 배경 지도 타일 서비스 (World/ENC)")
public class MapTileController {
private static final String TILE_BASE_PATH = "/devdata/MAPS/WORLD_webp";
private static final String TILE_ENC_PATH = "/devdata/MAPS/ENC_RAS_webp";
@GetMapping("/world/{z}/{x}/{y}.webp")
@Operation(summary = "세계지도 타일 조회", description = "ZXY 좌표 기반 세계지도 WebP 타일을 반환합니다")
@ApiResponse(responseCode = "200", description = "WebP 이미지 타일")
@ApiResponse(responseCode = "404", description = "타일 없음")
public ResponseEntity<Resource> getWorldTile(
@PathVariable int z,
@PathVariable int x,
@PathVariable int y) {
@Parameter(description = "줌 레벨 (0~18)") @PathVariable int z,
@Parameter(description = "X 좌표 (열)") @PathVariable int x,
@Parameter(description = "Y 좌표 (행)") @PathVariable int y) {
try {
// 안전한 경로 생성
@ -59,10 +67,13 @@ public class MapTileController {
}
@GetMapping("/enc/{z}/{x}/{y}.webp")
@Operation(summary = "ENC 해도 타일 조회", description = "ZXY 좌표 기반 전자해도(ENC) WebP 타일을 반환합니다")
@ApiResponse(responseCode = "200", description = "WebP 이미지 타일")
@ApiResponse(responseCode = "404", description = "타일 없음")
public ResponseEntity<Resource> getEncTile(
@PathVariable int z,
@PathVariable int x,
@PathVariable int y) {
@Parameter(description = "줌 레벨 (0~18)") @PathVariable int z,
@Parameter(description = "X 좌표 (열)") @PathVariable int x,
@Parameter(description = "Y 좌표 (행)") @PathVariable int y) {
try {
// 안전한 경로 생성
@ -95,6 +106,7 @@ public class MapTileController {
}
@GetMapping("/health")
@Operation(summary = "타일 서비스 상태 확인", description = "타일 디렉토리 존재 여부로 서비스 상태를 확인합니다")
public ResponseEntity<String> checkTileService() {
File baseDir = new File(TILE_BASE_PATH);
if (baseDir.exists() && baseDir.isDirectory()) {

파일 보기

@ -12,6 +12,8 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@ -155,10 +157,10 @@ public class VesselPositionService {
return RecentVesselPositionDto.builder()
.mmsi(mmsi)
.imo(imo > 0 ? imo : null)
.lon(rs.getDouble("lon"))
.lat(rs.getDouble("lat"))
.sog(rs.getBigDecimal("sog"))
.cog(rs.getBigDecimal("cog"))
.lon(Math.round(rs.getDouble("lon") * 1_000_000) / 1_000_000.0)
.lat(Math.round(rs.getDouble("lat") * 1_000_000) / 1_000_000.0)
.sog(scaleDecimal(rs.getBigDecimal("sog"), 1))
.cog(scaleDecimal(rs.getBigDecimal("cog"), 1))
.shipNm(rs.getString("ship_nm"))
.shipTy(shipTy)
.shipKindCode(shipKindCode)
@ -168,4 +170,8 @@ public class VesselPositionService {
.build();
}
}
private static BigDecimal scaleDecimal(BigDecimal value, int scale) {
return value != null ? value.setScale(scale, RoundingMode.HALF_UP) : null;
}
}

파일 보기

@ -1,5 +1,6 @@
package gc.mda.signal_batch.global.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@ -36,6 +37,7 @@ public class JacksonConfig {
return Jackson2ObjectMapperBuilder.json()
.modules(javaTimeModule)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
}
}

파일 보기

@ -3,6 +3,10 @@ package gc.mda.signal_batch.monitoring.controller;
import gc.mda.signal_batch.monitoring.service.BatchMetadataCleanupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -641,7 +645,35 @@ public class BatchAdminController {
* 프론트엔드에서 Stacked Bar (처리 건수) + Duration Line (소요시간) 차트 표시.
*/
@GetMapping("/daily-stats")
@Operation(summary = "일별 처리 통계", description = "최근 7일간 일별 Job별 배치 처리 통계를 조회합니다 (대시보드 차트용)")
@Operation(
summary = "일별 처리 통계",
description = "최근 7일간 일별 Job별 배치 처리 통계를 조회합니다 (대시보드 차트용)"
)
@ApiResponse(
responseCode = "200",
description = "일별 통계 + 24시간 상태 요약",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = Object.class),
examples = @ExampleObject(value = """
{
"dailyStats": [
{
"date": "2026-02-28",
"totalWrite": 285000,
"jobs": {
"incrementalVesselJob": { "writeCount": 120000, "execCount": 288, "avgDuration": 45 },
"trackBuildJob": { "writeCount": 95000, "execCount": 288, "avgDuration": 30 },
"hourlyAggregationJob": { "writeCount": 50000, "execCount": 24, "avgDuration": 15 },
"dailyAggregationJob": { "writeCount": 20000, "execCount": 1, "avgDuration": 40 }
}
}
],
"statusSummary": { "completed": 601, "failed": 0, "stopped": 0 }
}
""")
)
)
public ResponseEntity<Map<String, Object>> getDailyStatistics() {
try {
long start = System.currentTimeMillis();

파일 보기

@ -1,6 +1,8 @@
package gc.mda.signal_batch.monitoring.controller;
import gc.mda.signal_batch.global.util.ConcurrentUpdateManager;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@ -13,11 +15,13 @@ import java.util.Map;
@RestController
@RequestMapping("/admin/locks")
@RequiredArgsConstructor
@Tag(name = "Lock 모니터링 API", description = "배치 동시성 Lock 상태 및 Deadlock 정보 조회")
public class LockMonitorController {
private final ConcurrentUpdateManager concurrentUpdateManager;
@GetMapping("/statistics")
@Operation(summary = "Lock 통계 조회", description = "현재 활성 Lock 및 Lock 사용 통계를 조회합니다")
public ResponseEntity<Map<String, Object>> getLockStatistics() {
Map<String, Object> response = new HashMap<>();
response.put("lockStats", concurrentUpdateManager.getLockStatistics());
@ -28,6 +32,7 @@ public class LockMonitorController {
}
@GetMapping("/deadlocks")
@Operation(summary = "Deadlock 정보 조회", description = "감지된 Deadlock 이력 정보를 조회합니다")
public ResponseEntity<Map<String, Object>> getDeadlockInfo() {
Map<String, Object> response = new HashMap<>();
response.put("deadlocks", concurrentUpdateManager.getDeadlockInfo());

파일 보기

@ -2,6 +2,10 @@ server:
shutdown: graceful
servlet:
context-path: /signal-batch
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
min-response-size: 1024
spring:
application: