feat: Swagger 문서 보강, Kafka 조건부 설정, AIS 응답 DTO 개선

- Swagger @Operation/@Schema 상세 설명 추가 (검색, 필터, 폴리곤 API)
- Kafka 조건부 활성화 (KafkaAutoConfiguration exclude + @ConditionalOnProperty)
- kafka.enabled=false일 때 Kafka 빈 미생성 (@Nullable 처리)
- AisTargetResponseDto에 classType, core20Mmsi 필드 및 @Schema 추가
- ApiResponse에 @Schema 어노테이션 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
htlee 2026-02-16 10:40:24 +09:00
부모 cfc80bbb0d
커밋 ce9244ca0a
10개의 변경된 파일157개의 추가작업 그리고 59개의 파일을 삭제

파일 보기

@ -2,10 +2,11 @@ package com.snp.batch;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication @SpringBootApplication(exclude = KafkaAutoConfiguration.class)
@EnableScheduling @EnableScheduling
@ConfigurationPropertiesScan @ConfigurationPropertiesScan
public class SnpBatchApplication { public class SnpBatchApplication {

파일 보기

@ -1,5 +1,6 @@
package com.snp.batch.common.web; package com.snp.batch.common.web;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -14,26 +15,19 @@ import lombok.NoArgsConstructor;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Schema(description = "공통 API 응답 래퍼")
public class ApiResponse<T> { public class ApiResponse<T> {
/** @Schema(description = "성공 여부", example = "true")
* 성공 여부
*/
private boolean success; private boolean success;
/** @Schema(description = "응답 메시지", example = "Success")
* 메시지
*/
private String message; private String message;
/** @Schema(description = "응답 데이터")
* 응답 데이터
*/
private T data; private T data;
/** @Schema(description = "에러 코드 (실패 시에만 존재)", example = "NOT_FOUND", nullable = true)
* 에러 코드 (실패 )
*/
private String errorCode; private String errorCode;
/** /**

파일 보기

@ -15,9 +15,9 @@ import java.util.List;
* Swagger/OpenAPI 3.0 설정 * Swagger/OpenAPI 3.0 설정
* *
* Swagger UI 접속 URL: * Swagger UI 접속 URL:
* - Swagger UI: http://localhost:8081/swagger-ui/index.html * - Swagger UI: http://localhost:8041/snp-api/swagger-ui/index.html
* - API 문서 (JSON): http://localhost:8081/v3/api-docs * - API 문서 (JSON): http://localhost:8041/snp-api/v3/api-docs
* - API 문서 (YAML): http://localhost:8081/v3/api-docs.yaml * - API 문서 (YAML): http://localhost:8041/snp-api/v3/api-docs.yaml
* *
* 주요 기능: * 주요 기능:
* - REST API 자동 문서화 * - REST API 자동 문서화
@ -62,17 +62,19 @@ public class SwaggerConfig {
.description(""" .description("""
## SNP Batch 시스템 REST API 문서 ## SNP Batch 시스템 REST API 문서
Spring Batch 기반 데이터 통합 시스템의 REST API 문서입니다. 해양 데이터 통합 배치 시스템의 REST API 문서입니다.
### 제공 API ### 제공 API
- **Batch API**: 배치 Job 실행 관리 - **Batch Management API**: 배치 Job 실행, 이력 조회, 스케줄 관리
- **Product API**: 샘플 제품 데이터 CRUD (샘플용) - **AIS Target API**: AIS 선박 위치 정보 조회 (캐시 기반, 공간/조건 검색)
### 주요 기능 ### 주요 기능
- 배치 Job 실행 중지 - 배치 Job 실행 중지
- Job 실행 이력 조회 - Job 실행 이력 조회
- 스케줄 관리 (Quartz) - 스케줄 관리 (Quartz)
- 제품 데이터 CRUD (샘플) - AIS 선박 실시간 위치 조회 (MMSI 단건/다건, 시간/공간 범위 검색)
- 항해 조건 필터 검색 (SOG, COG, Heading, 목적지, 항행상태)
- 폴리곤/WKT 범위 검색, 거리 포함 검색, 항적 조회
### 버전 정보 ### 버전 정보
- API Version: v1.0.0 - API Version: v1.0.0

파일 보기

@ -6,6 +6,7 @@ import com.snp.batch.jobs.aistarget.cache.AisTargetCacheManager;
import com.snp.batch.jobs.aistarget.classifier.AisClassTypeClassifier; import com.snp.batch.jobs.aistarget.classifier.AisClassTypeClassifier;
import com.snp.batch.jobs.aistarget.kafka.AisTargetKafkaProducer; import com.snp.batch.jobs.aistarget.kafka.AisTargetKafkaProducer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
@ -16,11 +17,12 @@ import java.util.List;
* 동작: * 동작:
* 1. ClassType 분류 (Core20 캐시 기반 A/B 분류) * 1. ClassType 분류 (Core20 캐시 기반 A/B 분류)
* 2. 캐시에 최신 위치 정보 업데이트 (classType, core20Mmsi 포함) * 2. 캐시에 최신 위치 정보 업데이트 (classType, core20Mmsi 포함)
* 3. Kafka 토픽으로 AIS Target 정보 전송 (서브청크 분할) * 3. Kafka 토픽으로 AIS Target 정보 전송 (서브청크 분할, 활성화된 경우에만)
* *
* 참고: * 참고:
* - DB 저장은 별도 Job(aisTargetDbSyncJob)에서 15분 주기로 수행 * - DB 저장은 별도 Job(aisTargetDbSyncJob)에서 15분 주기로 수행
* - Kafka 전송 실패는 기본적으로 로그만 남기고 다음 처리 계속 * - Kafka 전송 실패는 기본적으로 로그만 남기고 다음 처리 계속
* - Kafka가 비활성화(enabled=false)이면 kafkaProducer가 null이므로 전송 단계를 스킵
*/ */
@Slf4j @Slf4j
@Component @Component
@ -28,12 +30,13 @@ public class AisTargetDataWriter extends BaseWriter<AisTargetEntity> {
private final AisTargetCacheManager cacheManager; private final AisTargetCacheManager cacheManager;
private final AisClassTypeClassifier classTypeClassifier; private final AisClassTypeClassifier classTypeClassifier;
@Nullable
private final AisTargetKafkaProducer kafkaProducer; private final AisTargetKafkaProducer kafkaProducer;
public AisTargetDataWriter( public AisTargetDataWriter(
AisTargetCacheManager cacheManager, AisTargetCacheManager cacheManager,
AisClassTypeClassifier classTypeClassifier, AisClassTypeClassifier classTypeClassifier,
AisTargetKafkaProducer kafkaProducer) { @Nullable AisTargetKafkaProducer kafkaProducer) {
super("AisTarget"); super("AisTarget");
this.cacheManager = cacheManager; this.cacheManager = cacheManager;
this.classTypeClassifier = classTypeClassifier; this.classTypeClassifier = classTypeClassifier;
@ -54,9 +57,9 @@ public class AisTargetDataWriter extends BaseWriter<AisTargetEntity> {
log.debug("AIS Target 캐시 업데이트 완료: {} 건 (캐시 크기: {})", log.debug("AIS Target 캐시 업데이트 완료: {} 건 (캐시 크기: {})",
items.size(), cacheManager.size()); items.size(), cacheManager.size());
// 3. Kafka 전송 (설정 enabled=true 경우) // 3. Kafka 전송 (kafkaProducer 빈이 존재하는 경우에만)
if (!kafkaProducer.isEnabled()) { if (kafkaProducer == null) {
log.debug("AIS Kafka 전송 비활성화 - topic 전송 스킵"); log.debug("AIS Kafka Producer 미등록 - topic 전송 스킵");
return; return;
} }

파일 보기

@ -0,0 +1,23 @@
package com.snp.batch.jobs.aistarget.kafka;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Kafka 조건부 활성화 설정
*
* SnpBatchApplication에서 KafkaAutoConfiguration을 기본 제외한 ,
* app.batch.ais-target.kafka.enabled=true인 경우에만 재활성화한다.
*
* enabled=false(기본값)이면 KafkaTemplate Kafka 관련 빈이 전혀 생성되지 않는다.
*/
@Configuration
@ConditionalOnProperty(
name = "app.batch.ais-target.kafka.enabled",
havingValue = "true"
)
@Import(KafkaAutoConfiguration.class)
public class AisTargetKafkaConfig {
}

파일 보기

@ -6,6 +6,7 @@ import com.snp.batch.jobs.aistarget.batch.entity.AisTargetEntity;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,9 +22,15 @@ import java.util.concurrent.atomic.AtomicInteger;
* - key: MMSI * - key: MMSI
* - value: AisTargetKafkaMessage(JSON) * - value: AisTargetKafkaMessage(JSON)
* - 실패 기본적으로 로그만 남기고 계속 진행 (failOnSendError=false) * - 실패 기본적으로 로그만 남기고 계속 진행 (failOnSendError=false)
*
* app.batch.ais-target.kafka.enabled=true인 경우에만 빈으로 등록된다.
*/ */
@Slf4j @Slf4j
@Component @Component
@ConditionalOnProperty(
name = "app.batch.ais-target.kafka.enabled",
havingValue = "true"
)
@RequiredArgsConstructor @RequiredArgsConstructor
public class AisTargetKafkaProducer { public class AisTargetKafkaProducer {

파일 보기

@ -7,13 +7,17 @@ import com.snp.batch.jobs.aistarget.web.dto.AisTargetSearchRequest;
import com.snp.batch.jobs.aistarget.web.service.AisTargetService; import com.snp.batch.jobs.aistarget.web.service.AisTargetService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -25,6 +29,7 @@ import java.util.Map;
* - 캐시 미스 DB 조회 캐시 업데이트 * - 캐시 미스 DB 조회 캐시 업데이트
*/ */
@Slf4j @Slf4j
@Validated
@RestController @RestController
@RequestMapping("/api/ais-target") @RequestMapping("/api/ais-target")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -37,7 +42,11 @@ public class AisTargetController {
@Operation( @Operation(
summary = "MMSI로 최신 위치 조회", summary = "MMSI로 최신 위치 조회",
description = "특정 MMSI의 최신 위치 정보를 조회합니다 (캐시 우선)" description = "특정 MMSI의 최신 위치 정보를 조회합니다 (캐시 우선)",
responses = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "해당 MMSI의 위치 정보 없음")
}
) )
@GetMapping("/{mmsi}") @GetMapping("/{mmsi}")
public ResponseEntity<ApiResponse<AisTargetResponseDto>> getLatestByMmsi( public ResponseEntity<ApiResponse<AisTargetResponseDto>> getLatestByMmsi(
@ -98,7 +107,7 @@ public class AisTargetController {
@GetMapping("/search") @GetMapping("/search")
public ResponseEntity<ApiResponse<List<AisTargetResponseDto>>> search( public ResponseEntity<ApiResponse<List<AisTargetResponseDto>>> search(
@Parameter(description = "조회 범위 (분)", required = true, example = "5") @Parameter(description = "조회 범위 (분)", required = true, example = "5")
@RequestParam Integer minutes, @RequestParam @Min(value = 1, message = "minutes는 최소 1분 이상이어야 합니다") Integer minutes,
@Parameter(description = "중심 경도", example = "129.0") @Parameter(description = "중심 경도", example = "129.0")
@RequestParam(required = false) Double centerLon, @RequestParam(required = false) Double centerLon,
@Parameter(description = "중심 위도", example = "35.0") @Parameter(description = "중심 위도", example = "35.0")
@ -128,6 +137,10 @@ public class AisTargetController {
@Operation( @Operation(
summary = "시간/공간 범위로 선박 검색 (POST)", summary = "시간/공간 범위로 선박 검색 (POST)",
responses = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "검색 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "요청 파라미터 검증 실패 (minutes 누락 또는 1 미만)")
},
description = """ description = """
POST 방식으로 검색 조건을 전달합니다. POST 방식으로 검색 조건을 전달합니다.
@ -167,6 +180,10 @@ public class AisTargetController {
@Operation( @Operation(
summary = "항해 조건 필터 검색", summary = "항해 조건 필터 검색",
responses = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "필터 검색 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "요청 파라미터 검증 실패")
},
description = """ description = """
속도(SOG), 침로(COG), 선수방위(Heading), 목적지, 항행상태로 선박을 필터링합니다. 속도(SOG), 침로(COG), 선수방위(Heading), 목적지, 항행상태로 선박을 필터링합니다.
@ -218,30 +235,30 @@ public class AisTargetController {
"headingCondition": "LT", "headingCondition": "LT",
"headingValue": 180.0, "headingValue": 180.0,
"destination": "BUSAN", "destination": "BUSAN",
"statusList": ["0", "1", "5"] "statusList": ["Under way using engine", "At anchor", "Moored"]
} }
``` ```
--- ---
## 항행상태 코드 (statusList) ## 항행상태 (statusList)
| 코드 | 상태 | statusList에는 **텍스트 문자열** 전달해야 합니다 (대소문자 무시).
| | 설명 |
|------|------| |------|------|
| 0 | Under way using engine (기관 사용 항해 ) | | Under way using engine | 기관 사용 항해 |
| 1 | At anchor (정박 ) | | At anchor | 정박 |
| 2 | Not under command (조종불능) | | Not under command | 조종불능 |
| 3 | Restricted manoeuverability (조종제한) | | Restricted manoeuverability | 조종제한 |
| 4 | Constrained by her draught (흘수제약) | | Constrained by her draught | 흘수제약 |
| 5 | Moored (계류 ) | | Moored | 계류 |
| 6 | Aground (좌초) | | Aground | 좌초 |
| 7 | Engaged in Fishing (어로 ) | | Engaged in Fishing | 어로 |
| 8 | Under way sailing ( 항해 ) | | Under way sailing | 항해 |
| 9-10 | Reserved for future use | | Power Driven Towing Astern | 예인선 (후방) |
| 11 | Power-driven vessel towing astern | | Power Driven Towing Alongside | 예인선 (측방) |
| 12 | Power-driven vessel pushing ahead | | AIS Sart | 비상위치지시기 |
| 13 | Reserved for future use | | N/A | 정보없음 |
| 14 | AIS-SART, MOB-AIS, EPIRB-AIS |
| 15 | Undefined (default) |
--- ---
**참고:** 모든 필터는 선택사항이며, 미지정 해당 필드는 조건에서 제외됩니다 (전체 포함). **참고:** 모든 필터는 선택사항이며, 미지정 해당 필드는 조건에서 제외됩니다 (전체 포함).
@ -269,6 +286,10 @@ public class AisTargetController {
@Operation( @Operation(
summary = "폴리곤 범위 내 선박 검색", summary = "폴리곤 범위 내 선박 검색",
responses = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "검색 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "요청 파라미터 검증 실패 (coordinates 또는 minutes 누락)")
},
description = """ description = """
폴리곤 범위 선박을 검색합니다. 폴리곤 범위 선박을 검색합니다.
@ -283,7 +304,7 @@ public class AisTargetController {
) )
@PostMapping("/search/polygon") @PostMapping("/search/polygon")
public ResponseEntity<ApiResponse<List<AisTargetResponseDto>>> searchByPolygon( public ResponseEntity<ApiResponse<List<AisTargetResponseDto>>> searchByPolygon(
@RequestBody PolygonSearchRequest request) { @Valid @RequestBody PolygonSearchRequest request) {
log.info("폴리곤 검색 요청 - minutes: {}, points: {}", log.info("폴리곤 검색 요청 - minutes: {}, points: {}",
request.getMinutes(), request.getCoordinates().length); request.getMinutes(), request.getCoordinates().length);
@ -299,6 +320,10 @@ public class AisTargetController {
@Operation( @Operation(
summary = "WKT 범위 내 선박 검색", summary = "WKT 범위 내 선박 검색",
responses = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "검색 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "요청 파라미터 검증 실패 (wkt 또는 minutes 누락)")
},
description = """ description = """
WKT(Well-Known Text) 형식으로 정의된 범위 선박을 검색합니다. WKT(Well-Known Text) 형식으로 정의된 범위 선박을 검색합니다.
@ -313,7 +338,7 @@ public class AisTargetController {
) )
@PostMapping("/search/wkt") @PostMapping("/search/wkt")
public ResponseEntity<ApiResponse<List<AisTargetResponseDto>>> searchByWkt( public ResponseEntity<ApiResponse<List<AisTargetResponseDto>>> searchByWkt(
@RequestBody WktSearchRequest request) { @Valid @RequestBody WktSearchRequest request) {
log.info("WKT 검색 요청 - minutes: {}, wkt: {}", request.getMinutes(), request.getWkt()); log.info("WKT 검색 요청 - minutes: {}, wkt: {}", request.getMinutes(), request.getWkt());
List<AisTargetResponseDto> result = aisTargetService.searchByWkt( List<AisTargetResponseDto> result = aisTargetService.searchByWkt(
@ -405,11 +430,17 @@ public class AisTargetController {
* 폴리곤 검색 요청 DTO * 폴리곤 검색 요청 DTO
*/ */
@lombok.Data @lombok.Data
@Schema(description = "폴리곤 범위 검색 요청")
public static class PolygonSearchRequest { public static class PolygonSearchRequest {
@Parameter(description = "조회 범위 (분)", required = true, example = "5") @NotNull(message = "minutes는 필수입니다")
private int minutes; @Min(value = 1, message = "minutes는 최소 1분 이상이어야 합니다")
@Schema(description = "조회 범위 (분)", example = "5", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer minutes;
@Parameter(description = "폴리곤 좌표 [[lon, lat], ...]", required = true) @NotNull(message = "coordinates는 필수입니다")
@Schema(description = "폴리곤 좌표 [[경도, 위도], ...] (닫힌 형태: 첫점=끝점)",
example = "[[129.0, 35.0], [130.0, 35.0], [130.0, 36.0], [129.0, 36.0], [129.0, 35.0]]",
requiredMode = Schema.RequiredMode.REQUIRED)
private double[][] coordinates; private double[][] coordinates;
} }
@ -417,12 +448,17 @@ public class AisTargetController {
* WKT 검색 요청 DTO * WKT 검색 요청 DTO
*/ */
@lombok.Data @lombok.Data
@Schema(description = "WKT 범위 검색 요청")
public static class WktSearchRequest { public static class WktSearchRequest {
@Parameter(description = "조회 범위 (분)", required = true, example = "5") @NotNull(message = "minutes는 필수입니다")
private int minutes; @Min(value = 1, message = "minutes는 최소 1분 이상이어야 합니다")
@Schema(description = "조회 범위 (분)", example = "5", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer minutes;
@Parameter(description = "WKT 문자열", required = true, @NotNull(message = "wkt는 필수입니다")
example = "POLYGON((129 35, 130 35, 130 36, 129 36, 129 35))") @Schema(description = "WKT 문자열 (POLYGON, MULTIPOLYGON 지원)",
example = "POLYGON((129 35, 130 35, 130 36, 129 36, 129 35))",
requiredMode = Schema.RequiredMode.REQUIRED)
private String wkt; private String wkt;
} }
} }

파일 보기

@ -22,34 +22,66 @@ import java.time.OffsetDateTime;
public class AisTargetResponseDto { public class AisTargetResponseDto {
// 선박 식별 정보 // 선박 식별 정보
@Schema(description = "MMSI (Maritime Mobile Service Identity) 번호", example = "440123456")
private Long mmsi; private Long mmsi;
@Schema(description = "IMO 번호 (0인 경우 미등록)", example = "9137960")
private Long imo; private Long imo;
@Schema(description = "선박명", example = "ROYAUME DES OCEANS")
private String name; private String name;
@Schema(description = "호출 부호", example = "4SFTEST")
private String callsign; private String callsign;
@Schema(description = "선박 유형 (외부 API 원본 텍스트)", example = "Vessel")
private String vesselType; private String vesselType;
// 위치 정보 // 위치 정보
@Schema(description = "위도 (WGS84)", example = "35.0796")
private Double lat; private Double lat;
@Schema(description = "경도 (WGS84)", example = "129.0756")
private Double lon; private Double lon;
// 항해 정보 // 항해 정보
@Schema(description = "선수방위 (degrees, 0-360)", example = "36.0")
private Double heading; private Double heading;
private Double sog; // Speed over Ground
private Double cog; // Course over Ground @Schema(description = "대지속력 (knots)", example = "12.5")
private Integer rot; // Rate of Turn private Double sog;
@Schema(description = "대지침로 (degrees, 0-360)", example = "36.2")
private Double cog;
@Schema(description = "회전율 (Rate of Turn)", example = "0")
private Integer rot;
// 선박 제원 // 선박 제원
@Schema(description = "선박 길이 (미터)", example = "19")
private Integer length; private Integer length;
@Schema(description = "선박 폭 (미터)", example = "15")
private Integer width; private Integer width;
@Schema(description = "흘수 (미터)", example = "5.5")
private Double draught; private Double draught;
// 목적지 정보 // 목적지 정보
@Schema(description = "목적지", example = "BUSAN")
private String destination; private String destination;
@Schema(description = "예정 도착 시간 (UTC)")
private OffsetDateTime eta; private OffsetDateTime eta;
@Schema(description = "항행상태 (텍스트)", example = "Under way using engine")
private String status; private String status;
// 타임스탬프 // 타임스탬프
@Schema(description = "AIS 메시지 발생 시각 (UTC)")
private OffsetDateTime messageTimestamp; private OffsetDateTime messageTimestamp;
@Schema(description = "데이터 수신 시각 (UTC)")
private OffsetDateTime receivedDate; private OffsetDateTime receivedDate;
// 데이터 소스 (캐시/DB) // 데이터 소스 (캐시/DB)

파일 보기

@ -117,7 +117,7 @@ app:
schedule: schedule:
cron: "15 * * * * ?" # 매 분 15초 실행 cron: "15 * * * * ?" # 매 분 15초 실행
kafka: kafka:
enabled: true enabled: false
topic: tp_Global_AIS_Signal topic: tp_Global_AIS_Signal
send-chunk-size: 5000 send-chunk-size: 5000
fail-on-send-error: false fail-on-send-error: false

파일 보기

@ -169,7 +169,7 @@ app:
schedule: schedule:
cron: "15 * * * * ?" # 매 분 15초 실행 cron: "15 * * * * ?" # 매 분 15초 실행
kafka: kafka:
enabled: true enabled: false # true로 변경 시 Kafka 브로커 연결 필요
topic: tp_Global_AIS_Signal topic: tp_Global_AIS_Signal
send-chunk-size: 5000 send-chunk-size: 5000
fail-on-send-error: false fail-on-send-error: false