diff --git a/src/main/java/com/goi/integration/samsara/client/SamsaraClient.java b/src/main/java/com/goi/integration/samsara/client/SamsaraClient.java index 98be3ee..4accfa1 100644 --- a/src/main/java/com/goi/integration/samsara/client/SamsaraClient.java +++ b/src/main/java/com/goi/integration/samsara/client/SamsaraClient.java @@ -44,7 +44,7 @@ public class SamsaraClient { * Vehicle live stats (engine + gps) * Raw JSON 반환 */ - public String getVehicleStatsFeed(List vehicleExternalIds) { + public String getVehicleGpsFeed(List vehicleExternalIds) { String vehicleIdsParam = String.join(",", vehicleExternalIds); @@ -58,4 +58,51 @@ public class SamsaraClient { .bodyToMono(String.class) .block(); } + + /** + * Vehicle odometer history (OBD odometer meters) + * Raw JSON 반환 + */ + public String getVehicleOdometerHistory( + List vehicleExternalIds, + Instant startTime, + Instant endTime + ) { + + String vehicleIdsParam = String.join(",", vehicleExternalIds); + + return webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/fleet/vehicles/stats/history") + .queryParam("vehicleIds", vehicleIdsParam) + .queryParam("startTime", startTime.toString()) + .queryParam("endTime", endTime.toString()) + .queryParam("types", "obdOdometerMeters") + .build()) + .retrieve() + .bodyToMono(String.class) + .block(); + } + + /** + * Vehicle Safety Event + * Raw JSON 반환 + * + */ + public String getVehicleSafetyEvents(Instant startTime, Instant endTime, List vehicleExternalIds) { + + String vehicleIdsParam = String.join(",", vehicleExternalIds); + + return webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/fleet/safety-events") + .queryParam("startTime", startTime.toString()) + .queryParam("endTime", endTime.toString()) + .queryParam("vehicleIds", vehicleIdsParam) + .build()) + .retrieve() + .bodyToMono(String.class) + .block(); + } + } \ No newline at end of file diff --git a/src/main/java/com/goi/integration/samsara/controller/VehicleController.java b/src/main/java/com/goi/integration/samsara/controller/VehicleController.java index 5c69cf4..a36abb1 100644 --- a/src/main/java/com/goi/integration/samsara/controller/VehicleController.java +++ b/src/main/java/com/goi/integration/samsara/controller/VehicleController.java @@ -1,10 +1,13 @@ package com.goi.integration.samsara.controller; -import com.goi.integration.samsara.dto.VehicleStatResponseDto; -import com.goi.integration.samsara.service.VehicleStatService; +import com.goi.integration.samsara.dto.VehicleOdometerHistoryResponseDto; +import com.goi.integration.samsara.dto.VehicleGpsResponseDto; +import com.goi.integration.samsara.service.VehicleOdometerHistoryService; +import com.goi.integration.samsara.service.VehicleGpsService; import lombok.RequiredArgsConstructor; +import java.time.Instant; import java.util.List; import org.springframework.web.bind.annotation.*; @@ -14,12 +17,36 @@ import org.springframework.web.bind.annotation.*; @RequiredArgsConstructor public class VehicleController { - private final VehicleStatService vehicleStatService; + private final VehicleGpsService vehicleStatService; + private final VehicleOdometerHistoryService vehicleOdometerHistoryService; - @GetMapping("/stat") - public List getStats( + @GetMapping("/stat/gps") + public List getGps( @RequestParam List vehicleIds ) { - return vehicleStatService.getVehicleStats(vehicleIds); + return vehicleStatService.getVehicleGps(vehicleIds); } + + /** + * Vehicle odometer history summary (window-based) + */ + @GetMapping("/stat/odometer/history") + public List getOdometerHistory( + @RequestParam List vehicleIds, + @RequestParam Instant startTime, + @RequestParam Instant endTime + ) { + return vehicleOdometerHistoryService.getOdometerHistory( + vehicleIds, startTime, endTime + ); + } + +// @GetMapping("/safety/events") +// public List getSafetyEvents( +// @RequestParam Instant startTime, +// @RequestParam Instant endTime, +// @RequestParam List vehicleIds +// ) { +// return vehicleSafetyEventService.getSafetyEvents(vehicleIds, startTime, endTime); +// } } diff --git a/src/main/java/com/goi/integration/samsara/dto/VehicleStatResponseDto.java b/src/main/java/com/goi/integration/samsara/dto/VehicleGpsResponseDto.java similarity index 53% rename from src/main/java/com/goi/integration/samsara/dto/VehicleStatResponseDto.java rename to src/main/java/com/goi/integration/samsara/dto/VehicleGpsResponseDto.java index 286ecc7..c42bb37 100644 --- a/src/main/java/com/goi/integration/samsara/dto/VehicleStatResponseDto.java +++ b/src/main/java/com/goi/integration/samsara/dto/VehicleGpsResponseDto.java @@ -12,10 +12,10 @@ import java.time.Instant; @NoArgsConstructor @AllArgsConstructor @Builder -public class VehicleStatResponseDto { - String vesVehicleExternalId; - Boolean vesEngineOn; - BigDecimal vesLatitude; - BigDecimal vesLongitude; - Instant vesGpsTime; // UTC 기준 절대 시점 +public class VehicleGpsResponseDto { + private String vesVehicleExternalId; + private Boolean vesEngineOn; + private BigDecimal vesLatitude; + private BigDecimal vesLongitude; + private Instant vesGpsTime; // UTC 기준 절대 시점 } diff --git a/src/main/java/com/goi/integration/samsara/dto/VehicleOdometerHistoryResponseDto.java b/src/main/java/com/goi/integration/samsara/dto/VehicleOdometerHistoryResponseDto.java new file mode 100644 index 0000000..920f101 --- /dev/null +++ b/src/main/java/com/goi/integration/samsara/dto/VehicleOdometerHistoryResponseDto.java @@ -0,0 +1,21 @@ +package com.goi.integration.samsara.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class VehicleOdometerHistoryResponseDto { + private String vohVehicleExternalId; + private Instant vohFirstSampleTime; + private Instant vohLastSampleTime; + private Long vohFirstOdometerMeters; + private Long vohLastOdometerMeters; + private Integer vohSampleCount; +} diff --git a/src/main/java/com/goi/integration/samsara/dto/VehicleSafetyEventResponseDto.java b/src/main/java/com/goi/integration/samsara/dto/VehicleSafetyEventResponseDto.java new file mode 100644 index 0000000..36c9d7a --- /dev/null +++ b/src/main/java/com/goi/integration/samsara/dto/VehicleSafetyEventResponseDto.java @@ -0,0 +1,21 @@ +package com.goi.integration.samsara.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.Instant; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class VehicleSafetyEventResponseDto { + private String vesVehicleExternalId; + private Boolean vesEngineOn; + private BigDecimal vesLatitude; + private BigDecimal vesLongitude; + private Instant vesGpsTime; // UTC 기준 절대 시점 +} diff --git a/src/main/java/com/goi/integration/samsara/service/VehicleStatService.java b/src/main/java/com/goi/integration/samsara/service/VehicleGpsService.java similarity index 91% rename from src/main/java/com/goi/integration/samsara/service/VehicleStatService.java rename to src/main/java/com/goi/integration/samsara/service/VehicleGpsService.java index 403af16..4da9533 100644 --- a/src/main/java/com/goi/integration/samsara/service/VehicleStatService.java +++ b/src/main/java/com/goi/integration/samsara/service/VehicleGpsService.java @@ -3,7 +3,7 @@ package com.goi.integration.samsara.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.goi.integration.samsara.client.SamsaraClient; -import com.goi.integration.samsara.dto.VehicleStatResponseDto; +import com.goi.integration.samsara.dto.VehicleGpsResponseDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,7 +20,7 @@ import java.util.Optional; @Service @RequiredArgsConstructor @Slf4j -public class VehicleStatService { +public class VehicleGpsService { private final SamsaraClient samsaraClient; private final ObjectMapper objectMapper; @@ -28,14 +28,14 @@ public class VehicleStatService { /** * 차량들의 실시간 상태 (engine + gps) */ - public List getVehicleStats(List vehicleExternalIds) { + public List getVehicleGps(List vehicleExternalIds) { if (vehicleExternalIds == null || vehicleExternalIds.isEmpty()) { return List.of(); } // call Samsara API - String rawJson = samsaraClient.getVehicleStatsFeed(vehicleExternalIds); + String rawJson = samsaraClient.getVehicleGpsFeed(vehicleExternalIds); if (rawJson == null || rawJson.isBlank()) { log.warn("Samsara stats feed returned empty response"); return List.of(); @@ -52,7 +52,7 @@ public class VehicleStatService { } // set response - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (JsonNode vehicleNode : dataArray) { parseVehicleNode(vehicleNode).ifPresent(result::add); } @@ -69,7 +69,7 @@ public class VehicleStatService { Parsing 필요한 정보만 =============================== */ - private Optional parseVehicleNode(JsonNode vehicleNode) { + private Optional parseVehicleNode(JsonNode vehicleNode) { // Samsara vehicle ID (external) String vehicleId = vehicleNode.path("id").asText(null); @@ -81,7 +81,7 @@ public class VehicleStatService { GpsInfo gpsInfo = extractLatestGps(vehicleNode.path("gps")); return Optional.of( - VehicleStatResponseDto.builder() + VehicleGpsResponseDto.builder() .vesVehicleExternalId(vehicleId) .vesEngineOn(engineOn) .vesLatitude(gpsInfo.latitude) diff --git a/src/main/java/com/goi/integration/samsara/service/VehicleOdometerHistoryService.java b/src/main/java/com/goi/integration/samsara/service/VehicleOdometerHistoryService.java new file mode 100644 index 0000000..82211cd --- /dev/null +++ b/src/main/java/com/goi/integration/samsara/service/VehicleOdometerHistoryService.java @@ -0,0 +1,99 @@ +package com.goi.integration.samsara.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.goi.integration.samsara.client.SamsaraClient; +import com.goi.integration.samsara.dto.VehicleOdometerHistoryResponseDto; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class VehicleOdometerHistoryService { + + private final SamsaraClient samsaraClient; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public List getOdometerHistory( + List vehicleExternalIds, + Instant startTime, + Instant endTime + ) { + // api 호출 + String rawJson = samsaraClient.getVehicleOdometerHistory(vehicleExternalIds, startTime, endTime); + + return parseOdometerHistory(rawJson); + } + + private List parseOdometerHistory(String rawJson) { + + List results = new ArrayList<>(); + + try { + JsonNode root = objectMapper.readTree(rawJson); + JsonNode dataArray = root.path("data"); + + for (JsonNode vehicleNode : dataArray) { + + String vehicleId = vehicleNode.path("id").asText(); + JsonNode odoArray = vehicleNode.path("obdOdometerMeters"); + + if (!odoArray.isArray() || odoArray.isEmpty()) { + // 데이터 없음 + results.add( + VehicleOdometerHistoryResponseDto.builder() + .vohVehicleExternalId(vehicleId) + .vohSampleCount(0) + .build() + ); + continue; + } + + Iterator it = odoArray.elements(); + + JsonNode first = it.next(); + JsonNode last = first; + + int count = 1; + + while (it.hasNext()) { + last = it.next(); + count++; + } + + // 거리 계산은 opr 에서. 여기선 이전 누적치를 모름. + long firstValue = first.path("value").asLong(); + long lastValue = last.path("value").asLong(); + + Instant firstTime = Instant.parse(first.path("time").asText()); + Instant lastTime = Instant.parse(last.path("time").asText()); + + results.add( + VehicleOdometerHistoryResponseDto.builder() + .vohVehicleExternalId(vehicleId) + .vohFirstSampleTime(firstTime) + .vohLastSampleTime(lastTime) + .vohFirstOdometerMeters(firstValue) + .vohLastOdometerMeters(lastValue) + .vohSampleCount(count) + .build() + ); + } + + } catch (Exception e) { + log.error("[ODOMETER][PARSE_FAIL]", e); + throw new RuntimeException("Failed to parse vehicle odometer history", e); + } + + return results; + } +}