diff --git a/src/main/java/gc/mda/signal_batch/domain/gis/controller/GisControllerV2.java b/src/main/java/gc/mda/signal_batch/domain/gis/controller/GisControllerV2.java index dcf181a..76ccdeb 100644 --- a/src/main/java/gc/mda/signal_batch/domain/gis/controller/GisControllerV2.java +++ b/src/main/java/gc/mda/signal_batch/domain/gis/controller/GisControllerV2.java @@ -1,6 +1,8 @@ package gc.mda.signal_batch.domain.gis.controller; +import gc.mda.signal_batch.batch.reader.ChnPrmShipCacheManager; import gc.mda.signal_batch.domain.gis.dto.GisBoundaryResponse; +import gc.mda.signal_batch.domain.vessel.dto.ChnPrmShipPositionDto; import gc.mda.signal_batch.domain.vessel.dto.CompactVesselTrack; import gc.mda.signal_batch.domain.vessel.dto.VesselStatsResponse; import gc.mda.signal_batch.domain.vessel.dto.VesselTracksRequest; @@ -41,6 +43,7 @@ public class GisControllerV2 { private final GisService gisService; private final GisServiceV2 gisServiceV2; private final VesselPositionService vesselPositionService; + private final ChnPrmShipCacheManager chnPrmShipCacheManager; @GetMapping("/haegu/boundaries") @Operation( @@ -209,4 +212,27 @@ public class GisControllerV2 { return vesselPositionService.getRecentVesselPositions(minutes); } + + @GetMapping("/vessels/chnprmship/recent-positions") + @Operation( + summary = "중국허가선박 최신 위치 조회", + description = "지정된 시간(분) 이내에 신호가 수신된 중국허가선박의 최신 위치 정보를 반환합니다. 전용 캐시(TTL 7일)에서 조회합니다." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ChnPrmShipPositionDto.class)))), + @ApiResponse(responseCode = "400", description = "잘못된 파라미터 (minutes: 1~10080)") + }) + public List getChnPrmShipRecentPositions( + @Parameter(description = "조회 시간 범위 (분, 1~10080=7일)", example = "60") + @RequestParam(defaultValue = "60") int minutes) { + + if (minutes <= 0 || minutes > 10080) { + throw new IllegalArgumentException("Minutes must be between 1 and 10080"); + } + + return chnPrmShipCacheManager.getByTimeRange(minutes).stream() + .map(ChnPrmShipPositionDto::from) + .toList(); + } } diff --git a/src/main/java/gc/mda/signal_batch/domain/vessel/dto/ChnPrmShipPositionDto.java b/src/main/java/gc/mda/signal_batch/domain/vessel/dto/ChnPrmShipPositionDto.java new file mode 100644 index 0000000..4ab9906 --- /dev/null +++ b/src/main/java/gc/mda/signal_batch/domain/vessel/dto/ChnPrmShipPositionDto.java @@ -0,0 +1,93 @@ +package gc.mda.signal_batch.domain.vessel.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import gc.mda.signal_batch.domain.vessel.model.AisTargetEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.OffsetDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "중국허가선박 최신 위치 정보") +public class ChnPrmShipPositionDto { + + @Schema(description = "MMSI", example = "412329919") + private String mmsi; + + @Schema(description = "IMO 번호", example = "9123456") + private Long imo; + + @Schema(description = "선박명", example = "LURONGYU57027") + private String name; + + @Schema(description = "호출부호", example = "BVZQ") + private String callsign; + + @Schema(description = "선종 (AIS vessel type)", example = "Fishing") + private String vesselType; + + @Schema(description = "위도", example = "39.011652") + private Double lat; + + @Schema(description = "경도", example = "121.706705") + private Double lon; + + @Schema(description = "대지속력 (knots)", example = "8.5") + private Double sog; + + @Schema(description = "대지침로 (degrees)", example = "245.3") + private Double cog; + + @Schema(description = "선수방향 (degrees)", example = "244.0") + private Double heading; + + @Schema(description = "선박 길이 (m)", example = "45") + private Integer length; + + @Schema(description = "선박 폭 (m)", example = "12") + private Integer width; + + @Schema(description = "흘수 (m)", example = "3.5") + private Double draught; + + @Schema(description = "목적지", example = "BUSAN") + private String destination; + + @Schema(description = "항해 상태", example = "Under way using engine") + private String status; + + @Schema(description = "선종 분류 코드 (000020:어선 등)", example = "000020") + private String signalKindCode; + + @Schema(description = "위치 메시지 시각 (UTC)", example = "2026-02-19T12:00:00Z") + private OffsetDateTime messageTimestamp; + + public static ChnPrmShipPositionDto from(AisTargetEntity entity) { + return ChnPrmShipPositionDto.builder() + .mmsi(entity.getMmsi()) + .imo(entity.getImo()) + .name(entity.getName()) + .callsign(entity.getCallsign()) + .vesselType(entity.getVesselType()) + .lat(entity.getLat()) + .lon(entity.getLon()) + .sog(entity.getSog()) + .cog(entity.getCog()) + .heading(entity.getHeading()) + .length(entity.getLength()) + .width(entity.getWidth()) + .draught(entity.getDraught()) + .destination(entity.getDestination()) + .status(entity.getStatus()) + .signalKindCode(entity.getSignalKindCode()) + .messageTimestamp(entity.getMessageTimestamp()) + .build(); + } +}