From 27891baada30ee4e7dcde97369416546fc238e73 Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 20 Feb 2026 02:05:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20recent-positions=20IMO=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20+=20=EC=84=A0=EB=B0=95?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EB=B3=B4=EC=9C=A0=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RecentVesselPositionDto에 imo 필드 추가 (0이면 null 처리) - GET /api/v2/shipimg/valid-list: 선박사진 보유 IMO 전체 목록 + 썸네일 경로 - 초기 로딩 시 선박 썸네일 즉시 표시 용도 (85K+ IMO) Co-Authored-By: Claude Opus 4.6 --- .../controller/ShipImageControllerV2.java | 23 +++++++++++++++++ .../domain/ship/dto/ValidShipImageDto.java | 21 ++++++++++++++++ .../ship/repository/ShipImageRepository.java | 25 +++++++++++++++++++ .../domain/ship/service/ShipImageService.java | 16 ++++++++++++ .../vessel/dto/RecentVesselPositionDto.java | 3 +++ .../vessel/service/VesselPositionService.java | 4 +++ 6 files changed, 92 insertions(+) create mode 100644 src/main/java/gc/mda/signal_batch/domain/ship/dto/ValidShipImageDto.java diff --git a/src/main/java/gc/mda/signal_batch/domain/ship/controller/ShipImageControllerV2.java b/src/main/java/gc/mda/signal_batch/domain/ship/controller/ShipImageControllerV2.java index 831b899..83c47fa 100644 --- a/src/main/java/gc/mda/signal_batch/domain/ship/controller/ShipImageControllerV2.java +++ b/src/main/java/gc/mda/signal_batch/domain/ship/controller/ShipImageControllerV2.java @@ -1,6 +1,7 @@ package gc.mda.signal_batch.domain.ship.controller; import gc.mda.signal_batch.domain.ship.dto.ShipImageDto; +import gc.mda.signal_batch.domain.ship.dto.ValidShipImageDto; import gc.mda.signal_batch.domain.ship.service.ShipImageService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -57,4 +58,26 @@ public class ShipImageControllerV2 { return ResponseEntity.ok(images); } + + @GetMapping("/valid-list") + @Operation( + summary = "선박 사진 보유 IMO 전체 목록", + description = "선박 사진이 등록된 모든 IMO 번호와 대표 썸네일 경로를 반환합니다. 초기 로딩 시 선박 썸네일 표시 용도입니다." + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ValidShipImageDto.class))) + ) + }) + public ResponseEntity> getValidShipImageList() { + long start = System.currentTimeMillis(); + + List result = shipImageService.getAllValidShipImages(); + + log.info("Valid ship image list: {} IMOs ({}ms)", result.size(), System.currentTimeMillis() - start); + + return ResponseEntity.ok(result); + } } diff --git a/src/main/java/gc/mda/signal_batch/domain/ship/dto/ValidShipImageDto.java b/src/main/java/gc/mda/signal_batch/domain/ship/dto/ValidShipImageDto.java new file mode 100644 index 0000000..38c1fd8 --- /dev/null +++ b/src/main/java/gc/mda/signal_batch/domain/ship/dto/ValidShipImageDto.java @@ -0,0 +1,21 @@ +package gc.mda.signal_batch.domain.ship.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "선박 사진 보유 IMO 정보 (썸네일 경로 포함)") +public class ValidShipImageDto { + + @Schema(description = "IMO 번호", example = "9141833") + private Integer imo; + + @Schema(description = "썸네일 이미지 경로", example = "/shipimg/8161/816100_1.jpg") + private String thumbnailPath; +} diff --git a/src/main/java/gc/mda/signal_batch/domain/ship/repository/ShipImageRepository.java b/src/main/java/gc/mda/signal_batch/domain/ship/repository/ShipImageRepository.java index a97989f..8e7540e 100644 --- a/src/main/java/gc/mda/signal_batch/domain/ship/repository/ShipImageRepository.java +++ b/src/main/java/gc/mda/signal_batch/domain/ship/repository/ShipImageRepository.java @@ -47,6 +47,26 @@ public class ShipImageRepository { return queryJdbcTemplate.query(FIND_BY_IMO_SQL, ROW_MAPPER, imo); } + private static final String FIND_ALL_VALID_SQL = """ + SELECT DISTINCT ON (lrno) lrno, pic_id + FROM signal.t_snp_ship_img + ORDER BY lrno, dateofphoto DESC NULLS LAST, pic_id DESC + """; + + private static final RowMapper VALID_ROW_MAPPER = (rs, rowNum) -> { + ValidShipImageData data = new ValidShipImageData(); + data.imo = rs.getInt("lrno"); + data.picId = rs.getInt("pic_id"); + return data; + }; + + /** + * 선박 사진이 있는 모든 IMO의 대표 이미지 조회 (IMO당 1건, 최신 사진 우선) + */ + public List findAllValidShipImages() { + return queryJdbcTemplate.query(FIND_ALL_VALID_SQL, VALID_ROW_MAPPER); + } + /** * Repository 내부에서 사용하는 Raw 데이터 클래스 */ @@ -55,4 +75,9 @@ public class ShipImageRepository { public String copyright; public java.time.LocalDate dateOfPhoto; } + + public static class ValidShipImageData { + public Integer imo; + public Integer picId; + } } diff --git a/src/main/java/gc/mda/signal_batch/domain/ship/service/ShipImageService.java b/src/main/java/gc/mda/signal_batch/domain/ship/service/ShipImageService.java index eb80200..9e8cd04 100644 --- a/src/main/java/gc/mda/signal_batch/domain/ship/service/ShipImageService.java +++ b/src/main/java/gc/mda/signal_batch/domain/ship/service/ShipImageService.java @@ -1,8 +1,10 @@ package gc.mda.signal_batch.domain.ship.service; import gc.mda.signal_batch.domain.ship.dto.ShipImageDto; +import gc.mda.signal_batch.domain.ship.dto.ValidShipImageDto; import gc.mda.signal_batch.domain.ship.repository.ShipImageRepository; import gc.mda.signal_batch.domain.ship.repository.ShipImageRepository.ShipImageRawData; +import gc.mda.signal_batch.domain.ship.repository.ShipImageRepository.ValidShipImageData; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -31,6 +33,20 @@ public class ShipImageService { .collect(Collectors.toList()); } + /** + * 선박 사진이 있는 전체 IMO 목록과 썸네일 경로 조회 + */ + public List getAllValidShipImages() { + List rawList = shipImageRepository.findAllValidShipImages(); + + return rawList.stream() + .map(raw -> ValidShipImageDto.builder() + .imo(raw.imo) + .thumbnailPath(buildImagePath(raw.picId) + "_1.jpg") + .build()) + .collect(Collectors.toList()); + } + /** * Raw 데이터를 DTO로 변환 */ diff --git a/src/main/java/gc/mda/signal_batch/domain/vessel/dto/RecentVesselPositionDto.java b/src/main/java/gc/mda/signal_batch/domain/vessel/dto/RecentVesselPositionDto.java index d3117d8..4a5e1d4 100644 --- a/src/main/java/gc/mda/signal_batch/domain/vessel/dto/RecentVesselPositionDto.java +++ b/src/main/java/gc/mda/signal_batch/domain/vessel/dto/RecentVesselPositionDto.java @@ -20,6 +20,9 @@ public class RecentVesselPositionDto { @Schema(description = "MMSI (9자리, 한국선박 440/441로 시작)", example = "440113620") private String mmsi; + @Schema(description = "IMO 번호 (0이면 미부여)", example = "9141833") + private Long imo; + @Schema(description = "경도 (WGS84)", example = "127.0638") private Double lon; diff --git a/src/main/java/gc/mda/signal_batch/domain/vessel/service/VesselPositionService.java b/src/main/java/gc/mda/signal_batch/domain/vessel/service/VesselPositionService.java index 9aa68ef..f41acff 100644 --- a/src/main/java/gc/mda/signal_batch/domain/vessel/service/VesselPositionService.java +++ b/src/main/java/gc/mda/signal_batch/domain/vessel/service/VesselPositionService.java @@ -84,6 +84,7 @@ public class VesselPositionService { String sql = """ SELECT mmsi, + imo, lon, lat, sog, @@ -118,8 +119,11 @@ public class VesselPositionService { String nationalCode = mmsi != null && mmsi.length() >= 3 ? mmsi.substring(0, 3) : "000"; + long imo = rs.getLong("imo"); + return RecentVesselPositionDto.builder() .mmsi(mmsi) + .imo(imo > 0 ? imo : null) .lon(rs.getDouble("lon")) .lat(rs.getDouble("lat")) .sog(rs.getBigDecimal("sog"))