[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;
|
package com.goi.integration.samsara.client;
|
||||||
|
|
||||||
import com.goi.integration.common.dto.ExtIngestResult;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -27,7 +27,7 @@ public class OprIngestClient {
|
||||||
// @Autowired
|
// @Autowired
|
||||||
// private ObjectMapper objectMapper;
|
// private ObjectMapper objectMapper;
|
||||||
|
|
||||||
public ExtIngestResult ingestInspection(ExtSamsaraInspectionIngestCommand command) {
|
public ExtIngestResult ingestInspection(ExtInspectionIngestCommand command) {
|
||||||
// String json = objectMapper
|
// String json = objectMapper
|
||||||
// .writerWithDefaultPrettyPrinter()
|
// .writerWithDefaultPrettyPrinter()
|
||||||
// .writeValueAsString(command);
|
// .writeValueAsString(command);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SamsaraClient {
|
public class SamsaraClient {
|
||||||
|
|
@ -38,4 +39,23 @@ public class SamsaraClient {
|
||||||
.bodyToMono(String.class)
|
.bodyToMono(String.class)
|
||||||
.block();
|
.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;
|
package com.goi.integration.samsara.controller;
|
||||||
|
|
||||||
import com.goi.integration.samsara.job.SamsaraDvirIngestJob;
|
import com.goi.integration.samsara.job.DvirIngestJob;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SamsaraTestController {
|
public class SamsaraTestController {
|
||||||
|
|
||||||
private final SamsaraDvirIngestJob dvirJob;
|
private final DvirIngestJob dvirJob;
|
||||||
|
|
||||||
@PostMapping("/samsara-dvir")
|
@PostMapping("/samsara-dvir")
|
||||||
public String runDvirNow() {
|
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
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Builder
|
@Builder
|
||||||
public class ExtSamsaraInspectionIngestCommand {
|
public class ExtInspectionIngestCommand {
|
||||||
private String source;
|
private String source;
|
||||||
private String recordType;
|
private String recordType;
|
||||||
private LocalDateTime fetchedAt;
|
private LocalDateTime fetchedAt;
|
||||||
private List<ExtSamsaraInspectionRecordDto> records;
|
private List<ExtInspectionRecordDto> records;
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Builder
|
@Builder
|
||||||
public class ExtSamsaraInspectionRecordDto {
|
public class ExtInspectionRecordDto {
|
||||||
private String externalId; // inspection id
|
private String externalId; // inspection id
|
||||||
private String vehicleExternalId; // vehicle.id
|
private String vehicleExternalId; // vehicle.id
|
||||||
private String driverExternalId; // signatoryUser.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.config.ScheduleJobConfigProvider;
|
||||||
import com.goi.integration.common.dto.ExtIngestResult;
|
import com.goi.integration.common.dto.ExtIngestResult;
|
||||||
import com.goi.integration.samsara.client.SamsaraClient;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -17,13 +17,13 @@ import java.time.Instant;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SamsaraDvirIngestJob {
|
public class DvirIngestJob {
|
||||||
|
|
||||||
private static final String JOB_CODE = "SAMSARA_DVIR";
|
private static final String JOB_CODE = "SAMSARA_DVIR";
|
||||||
|
|
||||||
private final ScheduleJobConfigProvider configProvider;
|
private final ScheduleJobConfigProvider configProvider;
|
||||||
private final SamsaraClient samsaraClient;
|
private final SamsaraClient samsaraClient;
|
||||||
private final SamsaraInspectionIngestService ingestService;
|
private final InspectionIngestService ingestService;
|
||||||
|
|
||||||
@Scheduled(cron = "${ext.samsara.jobs.dvir.cron:0 */10 * * * *}")
|
@Scheduled(cron = "${ext.samsara.jobs.dvir.cron:0 */10 * * * *}")
|
||||||
public void run() {
|
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.DateTimeUtil;
|
||||||
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
||||||
import com.goi.integration.samsara.client.OprIngestClient;
|
import com.goi.integration.samsara.client.OprIngestClient;
|
||||||
import com.goi.integration.samsara.dto.ExtSamsaraInspectionIngestCommand;
|
import com.goi.integration.samsara.dto.ExtInspectionIngestCommand;
|
||||||
import com.goi.integration.samsara.dto.ExtSamsaraInspectionRecordDto;
|
import com.goi.integration.samsara.dto.ExtInspectionRecordDto;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -19,7 +19,7 @@ import java.util.List;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SamsaraInspectionIngestService {
|
public class InspectionIngestService {
|
||||||
|
|
||||||
private static final String JOB_CODE = "SAMSARA_DVIR";
|
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());
|
log.info("[{}] data={}", JOB_CODE, data.size());
|
||||||
|
|
||||||
// data[] 각 node 가 하나의 inspection (preTrip / postTrip)
|
// data[] 각 node 가 하나의 inspection (preTrip / postTrip)
|
||||||
|
|
@ -69,7 +69,7 @@ public class SamsaraInspectionIngestService {
|
||||||
|
|
||||||
// record DTO
|
// record DTO
|
||||||
records.add(
|
records.add(
|
||||||
ExtSamsaraInspectionRecordDto.builder()
|
ExtInspectionRecordDto.builder()
|
||||||
.externalId(externalId)
|
.externalId(externalId)
|
||||||
.vehicleExternalId(vehicleExtId)
|
.vehicleExternalId(vehicleExtId)
|
||||||
.driverExternalId(driverExtId)
|
.driverExternalId(driverExtId)
|
||||||
|
|
@ -84,8 +84,8 @@ public class SamsaraInspectionIngestService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ingest command 생성
|
// ingest command 생성
|
||||||
ExtSamsaraInspectionIngestCommand command =
|
ExtInspectionIngestCommand command =
|
||||||
ExtSamsaraInspectionIngestCommand.builder()
|
ExtInspectionIngestCommand.builder()
|
||||||
.source("SAMSARA")
|
.source("SAMSARA")
|
||||||
.recordType("DVIR")
|
.recordType("DVIR")
|
||||||
.fetchedAt(LocalDateTime.now())
|
.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