[DvirIngest]
- Changed naming [Vehicle/Stat] - Added to check engine status and gps data
This commit is contained in:
parent
26d05d35f0
commit
1a6ab4669d
|
|
@ -1,7 +1,7 @@
|
|||
package com.goi.integration.samsara.client;
|
||||
|
||||
import com.goi.integration.common.dto.ExtIngestResult;
|
||||
import com.goi.integration.samsara.dto.ExtSamsaraInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.ExtInspectionIngestCommand;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -27,7 +27,7 @@ public class OprIngestClient {
|
|||
// @Autowired
|
||||
// private ObjectMapper objectMapper;
|
||||
|
||||
public ExtIngestResult ingestInspection(ExtSamsaraInspectionIngestCommand command) {
|
||||
public ExtIngestResult ingestInspection(ExtInspectionIngestCommand command) {
|
||||
// String json = objectMapper
|
||||
// .writerWithDefaultPrettyPrinter()
|
||||
// .writeValueAsString(command);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import org.springframework.stereotype.Component;
|
|||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class SamsaraClient {
|
||||
|
|
@ -38,4 +39,23 @@ public class SamsaraClient {
|
|||
.bodyToMono(String.class)
|
||||
.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vehicle live stats (engine + gps)
|
||||
* Raw JSON 반환
|
||||
*/
|
||||
public String getVehicleStatsFeed(List<String> vehicleExternalIds) {
|
||||
|
||||
String vehicleIdsParam = String.join(",", vehicleExternalIds);
|
||||
|
||||
return webClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/fleet/vehicles/stats/feed")
|
||||
.queryParam("vehicleIds", vehicleIdsParam)
|
||||
.queryParam("types", "engineStates,gps") // 나중에 필요하면 추가.
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.goi.integration.samsara.controller;
|
||||
|
||||
import com.goi.integration.samsara.job.SamsaraDvirIngestJob;
|
||||
import com.goi.integration.samsara.job.DvirIngestJob;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
|||
@RequiredArgsConstructor
|
||||
public class SamsaraTestController {
|
||||
|
||||
private final SamsaraDvirIngestJob dvirJob;
|
||||
private final DvirIngestJob dvirJob;
|
||||
|
||||
@PostMapping("/samsara-dvir")
|
||||
public String runDvirNow() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.goi.integration.samsara.controller;
|
||||
|
||||
import com.goi.integration.samsara.dto.VehicleStatResponseDto;
|
||||
import com.goi.integration.samsara.service.VehicleStatService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/vehicle")
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleController {
|
||||
|
||||
private final VehicleStatService vehicleStatService;
|
||||
|
||||
@GetMapping("/stat")
|
||||
public List<VehicleStatResponseDto> getStats(
|
||||
@RequestParam List<String> vehicleIds
|
||||
) {
|
||||
return vehicleStatService.getVehicleStats(vehicleIds);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,9 +12,9 @@ import java.util.List;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ExtSamsaraInspectionIngestCommand {
|
||||
public class ExtInspectionIngestCommand {
|
||||
private String source;
|
||||
private String recordType;
|
||||
private LocalDateTime fetchedAt;
|
||||
private List<ExtSamsaraInspectionRecordDto> records;
|
||||
private List<ExtInspectionRecordDto> records;
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ExtSamsaraInspectionRecordDto {
|
||||
public class ExtInspectionRecordDto {
|
||||
private String externalId; // inspection id
|
||||
private String vehicleExternalId; // vehicle.id
|
||||
private String driverExternalId; // signatoryUser.id
|
||||
|
|
@ -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 VehicleStatResponseDto {
|
||||
String vesVehicleExternalId;
|
||||
Boolean vesEngineOn;
|
||||
BigDecimal vesLatitude;
|
||||
BigDecimal vesLongitude;
|
||||
Instant vesGpsTime; // UTC 기준 절대 시점
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.goi.integration.common.config.ScheduleJobConfigDto;
|
|||
import com.goi.integration.common.config.ScheduleJobConfigProvider;
|
||||
import com.goi.integration.common.dto.ExtIngestResult;
|
||||
import com.goi.integration.samsara.client.SamsaraClient;
|
||||
import com.goi.integration.samsara.service.SamsaraInspectionIngestService;
|
||||
import com.goi.integration.samsara.service.InspectionIngestService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -17,13 +17,13 @@ import java.time.Instant;
|
|||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SamsaraDvirIngestJob {
|
||||
public class DvirIngestJob {
|
||||
|
||||
private static final String JOB_CODE = "SAMSARA_DVIR";
|
||||
|
||||
private final ScheduleJobConfigProvider configProvider;
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final SamsaraInspectionIngestService ingestService;
|
||||
private final InspectionIngestService ingestService;
|
||||
|
||||
@Scheduled(cron = "${ext.samsara.jobs.dvir.cron:0 */10 * * * *}")
|
||||
public void run() {
|
||||
|
|
@ -6,8 +6,8 @@ import com.goi.integration.common.dto.ExtIngestResult;
|
|||
import com.goi.integration.common.util.DateTimeUtil;
|
||||
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
||||
import com.goi.integration.samsara.client.OprIngestClient;
|
||||
import com.goi.integration.samsara.dto.ExtSamsaraInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.ExtSamsaraInspectionRecordDto;
|
||||
import com.goi.integration.samsara.dto.ExtInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.ExtInspectionRecordDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -19,7 +19,7 @@ import java.util.List;
|
|||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SamsaraInspectionIngestService {
|
||||
public class InspectionIngestService {
|
||||
|
||||
private static final String JOB_CODE = "SAMSARA_DVIR";
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ public class SamsaraInspectionIngestService {
|
|||
}
|
||||
|
||||
//
|
||||
List<ExtSamsaraInspectionRecordDto> records = new ArrayList<>();
|
||||
List<ExtInspectionRecordDto> records = new ArrayList<>();
|
||||
log.info("[{}] data={}", JOB_CODE, data.size());
|
||||
|
||||
// data[] 각 node 가 하나의 inspection (preTrip / postTrip)
|
||||
|
|
@ -69,7 +69,7 @@ public class SamsaraInspectionIngestService {
|
|||
|
||||
// record DTO
|
||||
records.add(
|
||||
ExtSamsaraInspectionRecordDto.builder()
|
||||
ExtInspectionRecordDto.builder()
|
||||
.externalId(externalId)
|
||||
.vehicleExternalId(vehicleExtId)
|
||||
.driverExternalId(driverExtId)
|
||||
|
|
@ -84,8 +84,8 @@ public class SamsaraInspectionIngestService {
|
|||
}
|
||||
|
||||
// ingest command 생성
|
||||
ExtSamsaraInspectionIngestCommand command =
|
||||
ExtSamsaraInspectionIngestCommand.builder()
|
||||
ExtInspectionIngestCommand command =
|
||||
ExtInspectionIngestCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.recordType("DVIR")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class VehicleStatService {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 차량들의 실시간 상태 (engine + gps)
|
||||
*/
|
||||
public List<VehicleStatResponseDto> getVehicleStats(List<String> vehicleExternalIds) {
|
||||
|
||||
if (vehicleExternalIds == null || vehicleExternalIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// call Samsara API
|
||||
String rawJson = samsaraClient.getVehicleStatsFeed(vehicleExternalIds);
|
||||
if (rawJson == null || rawJson.isBlank()) {
|
||||
log.warn("Samsara stats feed returned empty response");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// parsing response
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawJson);
|
||||
JsonNode dataArray = root.path("data");
|
||||
|
||||
if (!dataArray.isArray()) {
|
||||
log.warn("Unexpected Samsara response format: missing data[]");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// set response
|
||||
List<VehicleStatResponseDto> result = new ArrayList<>();
|
||||
for (JsonNode vehicleNode : dataArray) {
|
||||
parseVehicleNode(vehicleNode).ifPresent(result::add);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse Samsara vehicle stats feed", e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
Parsing 필요한 정보만
|
||||
=============================== */
|
||||
|
||||
private Optional<VehicleStatResponseDto> parseVehicleNode(JsonNode vehicleNode) {
|
||||
|
||||
// Samsara vehicle ID (external)
|
||||
String vehicleId = vehicleNode.path("id").asText(null);
|
||||
if (vehicleId == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Boolean engineOn = extractLatestEngineState(vehicleNode.path("engineStates"));
|
||||
GpsInfo gpsInfo = extractLatestGps(vehicleNode.path("gps"));
|
||||
|
||||
return Optional.of(
|
||||
VehicleStatResponseDto.builder()
|
||||
.vesVehicleExternalId(vehicleId)
|
||||
.vesEngineOn(engineOn)
|
||||
.vesLatitude(gpsInfo.latitude)
|
||||
.vesLongitude(gpsInfo.longitude)
|
||||
.vesGpsTime(gpsInfo.time)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 최신 engine 상태 추출
|
||||
* @return true = On, false = Off, null = unknown
|
||||
*/
|
||||
private Boolean extractLatestEngineState(JsonNode engineStatesNode) {
|
||||
|
||||
if (!engineStatesNode.isArray() || engineStatesNode.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonNode latest = null;
|
||||
Instant latestTime = null;
|
||||
|
||||
for (JsonNode node : engineStatesNode) {
|
||||
Instant time = parseInstant(node.path("time").asText(null));
|
||||
if (time != null && (latestTime == null || time.isAfter(latestTime))) {
|
||||
latestTime = time;
|
||||
latest = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (latest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = latest.path("value").asText("");
|
||||
return "On".equalsIgnoreCase(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 최신 GPS 정보 추출
|
||||
*/
|
||||
private GpsInfo extractLatestGps(JsonNode gpsArrayNode) {
|
||||
|
||||
if (!gpsArrayNode.isArray() || gpsArrayNode.isEmpty()) {
|
||||
return new GpsInfo(null, null, null);
|
||||
}
|
||||
|
||||
JsonNode latest = null;
|
||||
Instant latestTime = null;
|
||||
|
||||
Iterator<JsonNode> it = gpsArrayNode.elements();
|
||||
while (it.hasNext()) {
|
||||
JsonNode node = it.next();
|
||||
Instant time = parseInstant(node.path("time").asText(null));
|
||||
if (time != null && (latestTime == null || time.isAfter(latestTime))) {
|
||||
latestTime = time;
|
||||
latest = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (latest == null) {
|
||||
return new GpsInfo(null, null, null);
|
||||
}
|
||||
|
||||
BigDecimal lat = latest.hasNonNull("latitude")
|
||||
? latest.get("latitude").decimalValue()
|
||||
: null;
|
||||
|
||||
BigDecimal lon = latest.hasNonNull("longitude")
|
||||
? latest.get("longitude").decimalValue()
|
||||
: null;
|
||||
|
||||
return new GpsInfo(lat, lon, latestTime);
|
||||
}
|
||||
|
||||
private Instant parseInstant(String value) {
|
||||
try {
|
||||
return value != null ? Instant.parse(value) : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
Internal helper DTO
|
||||
=============================== */
|
||||
|
||||
private static class GpsInfo {
|
||||
BigDecimal latitude;
|
||||
BigDecimal longitude;
|
||||
Instant time;
|
||||
|
||||
GpsInfo(BigDecimal latitude, BigDecimal longitude, Instant time) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue