feat: 중국허가선박 최신 위치 조회 API #50

병합
htlee feature/dashboard-phase-1 에서 develop 로 1 commits 를 머지했습니다 2026-02-19 23:04:22 +09:00
2개의 변경된 파일119개의 추가작업 그리고 0개의 파일을 삭제

파일 보기

@ -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<ChnPrmShipPositionDto> 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();
}
}

파일 보기

@ -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();
}
}