refactor: simplify jt808 mileage stat pipeline

This commit is contained in:
lingniu
2026-07-01 02:04:58 +08:00
parent 0fc91f512c
commit 7081800b84
26 changed files with 92 additions and 975 deletions

View File

@@ -1,75 +0,0 @@
package com.lingniu.ingest.vehiclestat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
public final class DailyMileageCalculator {
private final ZoneId zoneId;
public DailyMileageCalculator(ZoneId zoneId) {
if (zoneId == null) {
throw new IllegalArgumentException("zoneId must not be null");
}
this.zoneId = zoneId;
}
public OptionalDouble calculate(LocalDate statDate,
DailyMileageStrategy strategy,
List<MileagePoint> points) {
if (statDate == null) {
throw new IllegalArgumentException("statDate must not be null");
}
if (strategy == null) {
throw new IllegalArgumentException("strategy must not be null");
}
if (points == null || points.isEmpty()) {
return OptionalDouble.empty();
}
// 两种策略都只基于同一 VIN 的里程点计算,不能跨车辆 join
// 上层 repository 负责按 VIN 过滤,避免大车队查询时扩大扫描面。
double value = switch (strategy) {
case CURRENT_LAST_MINUS_PREVIOUS_LAST -> currentLastMinusPreviousLast(statDate, points);
case DAY_MAX_MINUS_DAY_MIN -> dayMaxMinusDayMin(statDate, points);
};
return Double.isFinite(value) && value >= 0 ? OptionalDouble.of(value) : OptionalDouble.empty();
}
private double currentLastMinusPreviousLast(LocalDate statDate, List<MileagePoint> points) {
// 适用于累计里程单调递增的设备:当天最后一帧减前一日最后一帧。
Optional<MileagePoint> previousLast = points.stream()
.filter(point -> localDate(point).isBefore(statDate))
.max(Comparator.comparing(MileagePoint::eventTime));
Optional<MileagePoint> currentLast = points.stream()
.filter(point -> localDate(point).isEqual(statDate))
.max(Comparator.comparing(MileagePoint::eventTime));
if (previousLast.isEmpty() || currentLast.isEmpty()) {
return Double.NaN;
}
return currentLast.get().totalMileageKm() - previousLast.get().totalMileageKm();
}
private double dayMaxMinusDayMin(LocalDate statDate, List<MileagePoint> points) {
// 兜底策略:仅使用当天数据,适合缺少前一日最后点但当天点足够的场景。
List<MileagePoint> currentDay = points.stream()
.filter(point -> localDate(point).isEqual(statDate))
.toList();
if (currentDay.size() < 2) {
return Double.NaN;
}
double min = currentDay.stream().mapToDouble(MileagePoint::totalMileageKm).min().orElse(Double.NaN);
double max = currentDay.stream().mapToDouble(MileagePoint::totalMileageKm).max().orElse(Double.NaN);
return max - min;
}
private LocalDate localDate(MileagePoint point) {
return point.eventTime().atZone(zoneId).toLocalDate();
}
}

View File

@@ -1,49 +0,0 @@
package com.lingniu.ingest.vehiclestat;
import java.time.LocalDate;
import java.util.Optional;
import java.util.OptionalDouble;
public final class DailyVehicleStatService {
private final VehicleStatRepository repository;
private final VehicleStatRuleRepository ruleRepository;
private final DailyMileageCalculator mileageCalculator;
public DailyVehicleStatService(VehicleStatRepository repository,
VehicleStatRuleRepository ruleRepository,
DailyMileageCalculator mileageCalculator) {
if (repository == null) {
throw new IllegalArgumentException("repository must not be null");
}
if (ruleRepository == null) {
throw new IllegalArgumentException("ruleRepository must not be null");
}
if (mileageCalculator == null) {
throw new IllegalArgumentException("mileageCalculator must not be null");
}
this.repository = repository;
this.ruleRepository = ruleRepository;
this.mileageCalculator = mileageCalculator;
}
public Optional<VehicleDailyStatResult> calculateAndSave(String vin, LocalDate statDate) {
VehicleStatRule rule = ruleRepository.ruleFor(vin);
// 统计是从里程点二次计算出来的派生数据,不作为 32960 全字段历史查询的数据源。
OptionalDouble dailyMileage = mileageCalculator.calculate(
statDate,
rule.dailyMileageStrategy(),
repository.mileagePoints(vin, statDate));
if (dailyMileage.isEmpty()) {
return Optional.empty();
}
VehicleDailyStatResult result = new VehicleDailyStatResult(
vin,
statDate,
dailyMileage,
rule.dailyMileageStrategy());
repository.saveDailyStat(result);
return Optional.of(result);
}
}

View File

@@ -1,126 +0,0 @@
package com.lingniu.ingest.vehiclestat;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
public final class FileVehicleStatRepository implements VehicleStatRepository {
private final Path pointsFile;
private final Path dailyStatsFile;
public FileVehicleStatRepository(Path root) {
if (root == null) {
throw new IllegalArgumentException("root must not be null");
}
Path absoluteRoot = root.toAbsolutePath();
// 当前实现是轻量本地 TSV适合开发和小规模派生统计生产历史全字段查询走 DuckDB RAW 索引。
this.pointsFile = absoluteRoot.resolve("mileage-points.tsv");
this.dailyStatsFile = absoluteRoot.resolve("daily-stats.tsv");
}
@Override
public synchronized void appendMileagePoint(String vin, MileagePoint point) {
if (point == null) {
throw new IllegalArgumentException("point must not be null");
}
appendLine(pointsFile, clean(vin) + '\t' + point.eventTime() + '\t' + point.totalMileageKm());
}
@Override
public synchronized List<MileagePoint> mileagePoints(String vin, LocalDate statDate) {
String normalizedVin = clean(vin);
List<MileagePoint> out = new ArrayList<>();
for (String line : readLines(pointsFile)) {
String[] parts = line.split("\\t", -1);
if (parts.length != 3 || !normalizedVin.equals(parts[0])) {
continue;
}
try {
// statDate 由计算器按策略判断;这里返回该 VIN 的点,避免仓储层混入业务口径。
out.add(new MileagePoint(Instant.parse(parts[1]), Double.parseDouble(parts[2])));
} catch (RuntimeException ignored) {
// Ignore corrupt rows instead of making the whole statistics API unavailable.
}
}
return out;
}
@Override
public synchronized void saveDailyStat(VehicleDailyStatResult result) {
if (result == null) {
throw new IllegalArgumentException("result must not be null");
}
String mileage = result.dailyMileageKm().isPresent()
? Double.toString(result.dailyMileageKm().getAsDouble())
: "";
appendLine(dailyStatsFile,
clean(result.vin()) + '\t'
+ result.statDate() + '\t'
+ result.dailyMileageStrategy().name() + '\t'
+ mileage);
}
@Override
public synchronized Optional<VehicleDailyStatResult> findDailyStat(String vin, LocalDate statDate) {
String normalizedVin = clean(vin);
VehicleDailyStatResult latest = null;
for (String line : readLines(dailyStatsFile)) {
String[] parts = line.split("\\t", -1);
if (parts.length != 4 || !normalizedVin.equals(parts[0])) {
continue;
}
try {
LocalDate rowDate = LocalDate.parse(parts[1]);
if (!rowDate.equals(statDate)) {
continue;
}
DailyMileageStrategy strategy = DailyMileageStrategy.valueOf(parts[2]);
OptionalDouble mileage = parts[3].isBlank()
? OptionalDouble.empty()
: OptionalDouble.of(Double.parseDouble(parts[3]));
latest = new VehicleDailyStatResult(normalizedVin, rowDate, mileage, strategy);
} catch (RuntimeException ignored) {
// Ignore corrupt rows; later valid rows can still provide the answer.
}
}
return Optional.ofNullable(latest);
}
private static void appendLine(Path file, String line) {
try {
Files.createDirectories(file.getParent());
Files.writeString(file, line + System.lineSeparator(), StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {
throw new IllegalStateException("write vehicle stat file failed: " + file, e);
}
}
private static List<String> readLines(Path file) {
if (!Files.isRegularFile(file)) {
return List.of();
}
try {
return Files.readAllLines(file, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new IllegalStateException("read vehicle stat file failed: " + file, e);
}
}
private static String clean(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("vin must not be blank");
}
// TSV 文件没有转义层VIN 中的控制字符统一替换,防止破坏行结构。
return value.trim().replace('\t', '_').replace('\n', '_').replace('\r', '_');
}
}

View File

@@ -24,16 +24,6 @@ public final class JdbcVehicleStatMetricRepository implements VehicleStatReposit
ensureSchema();
}
@Override
public void appendMileagePoint(String vin, MileagePoint point) {
// Production metrics are written as final metric rows; raw source points stay in RAW/locations history.
}
@Override
public List<MileagePoint> mileagePoints(String vin, LocalDate statDate) {
return List.of();
}
@Override
public void saveDailyStat(VehicleDailyStatResult result) {
if (result == null || result.dailyMileageKm().isEmpty()) {

View File

@@ -1,15 +0,0 @@
package com.lingniu.ingest.vehiclestat;
import java.time.Instant;
public record MileagePoint(Instant eventTime, double totalMileageKm) {
public MileagePoint {
if (eventTime == null) {
throw new IllegalArgumentException("eventTime must not be null");
}
if (!Double.isFinite(totalMileageKm)) {
throw new IllegalArgumentException("totalMileageKm must be finite");
}
}
}

View File

@@ -17,33 +17,25 @@ import java.util.Map;
@RestController
@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat", name = "enabled", havingValue = "true")
@ConditionalOnBean(DailyVehicleStatService.class)
@ConditionalOnBean(VehicleStatRepository.class)
@RequestMapping(path = "/api/vehicle-stat", produces = MediaType.APPLICATION_JSON_VALUE)
public final class VehicleStatController {
private final VehicleStatRepository repository;
private final DailyVehicleStatService dailyStatService;
public VehicleStatController(VehicleStatRepository repository,
DailyVehicleStatService dailyStatService) {
public VehicleStatController(VehicleStatRepository repository) {
if (repository == null) {
throw new IllegalArgumentException("repository must not be null");
}
if (dailyStatService == null) {
throw new IllegalArgumentException("dailyStatService must not be null");
}
this.repository = repository;
this.dailyStatService = dailyStatService;
}
@GetMapping("/{vin}/daily")
public ResponseEntity<Map<String, Object>> daily(
@PathVariable String vin,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
@RequestParam(defaultValue = "true") boolean calculateIfMissing) {
var result = repository.findDailyStat(vin, date)
.or(() -> calculateIfMissing ? dailyStatService.calculateAndSave(vin, date) : java.util.Optional.empty());
return result.map(stat -> ResponseEntity.ok(toJson(stat)))
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
return repository.findDailyStat(vin, date)
.map(stat -> ResponseEntity.ok(toJson(stat)))
.orElseGet(() -> ResponseEntity.notFound().build());
}

View File

@@ -8,19 +8,12 @@ import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStreamProcessor;
public final class VehicleStatEnvelopeIngestor implements EnvelopeIngestor {
private final VehicleStatEventProcessor processor;
private final Jt808MileageStreamProcessor jt808MileageProcessor;
public VehicleStatEnvelopeIngestor(VehicleStatEventProcessor processor) {
this(processor, null);
}
public VehicleStatEnvelopeIngestor(VehicleStatEventProcessor processor,
Jt808MileageStreamProcessor jt808MileageProcessor) {
if (processor == null) {
throw new IllegalArgumentException("processor must not be null");
public VehicleStatEnvelopeIngestor(Jt808MileageStreamProcessor jt808MileageProcessor) {
if (jt808MileageProcessor == null) {
throw new IllegalArgumentException("jt808MileageProcessor must not be null");
}
this.processor = processor;
this.jt808MileageProcessor = jt808MileageProcessor;
}
@@ -29,10 +22,9 @@ public final class VehicleStatEnvelopeIngestor implements EnvelopeIngestor {
if (!envelope.hasTelemetrySnapshot()) {
return;
}
if (processJt808Mileage(envelope)) {
return;
if ("JT808".equalsIgnoreCase(envelope.getSource())) {
jt808MileageProcessor.process(envelope);
}
processor.process(envelope);
}
@Override
@@ -44,11 +36,12 @@ public final class VehicleStatEnvelopeIngestor implements EnvelopeIngestor {
return EnvelopeIngestResult.skipped(
envelope.getEventId(), envelope.getVin(), "envelope telemetry_snapshot is required");
}
if (processJt808Mileage(envelope)) {
if ("JT808".equalsIgnoreCase(envelope.getSource())) {
jt808MileageProcessor.process(envelope);
return EnvelopeIngestResult.processed(envelope.getEventId(), envelope.getVin());
}
processor.process(envelope);
return EnvelopeIngestResult.processed(envelope.getEventId(), envelope.getVin());
return EnvelopeIngestResult.skipped(
envelope.getEventId(), envelope.getVin(), "vehicle-stat only accepts JT808 telemetry");
} catch (IllegalArgumentException ex) {
return envelope == null
? EnvelopeIngestResult.invalid(ex.getMessage())
@@ -56,14 +49,6 @@ public final class VehicleStatEnvelopeIngestor implements EnvelopeIngestor {
}
}
private boolean processJt808Mileage(VehicleEnvelope envelope) {
if (jt808MileageProcessor != null && "JT808".equalsIgnoreCase(envelope.getSource())) {
jt808MileageProcessor.process(envelope);
return true;
}
return false;
}
private static VehicleEnvelope parse(byte[] kafkaValue) {
if (kafkaValue == null || kafkaValue.length == 0) {
throw new IllegalArgumentException("VehicleEnvelope bytes must not be empty");

View File

@@ -1,54 +0,0 @@
package com.lingniu.ingest.vehiclestat;
import com.lingniu.ingest.sink.mq.proto.TelemetryField;
import com.lingniu.ingest.sink.mq.proto.VehicleEnvelope;
import java.time.Instant;
import java.util.OptionalDouble;
public final class VehicleStatEventProcessor {
private static final String TOTAL_MILEAGE_KEY = "total_mileage_km";
private final VehicleStatRepository repository;
public VehicleStatEventProcessor(VehicleStatRepository repository) {
if (repository == null) {
throw new IllegalArgumentException("repository must not be null");
}
this.repository = repository;
}
public void process(VehicleEnvelope envelope) {
if (envelope == null) {
throw new IllegalArgumentException("envelope must not be null");
}
if (!envelope.hasTelemetrySnapshot()) {
return;
}
// 统计消费的是已经标准化后的 telemetry_snapshot不重新解析 RAW .bin。
OptionalDouble totalMileage = totalMileage(envelope);
if (totalMileage.isEmpty()) {
return;
}
repository.appendMileagePoint(
envelope.getVin(),
new MileagePoint(Instant.ofEpochMilli(envelope.getEventTimeMs()), totalMileage.getAsDouble()));
}
private static OptionalDouble totalMileage(VehicleEnvelope envelope) {
for (TelemetryField field : envelope.getTelemetrySnapshot().getFieldsList()) {
if (!TOTAL_MILEAGE_KEY.equals(field.getKey())) {
continue;
}
try {
double value = Double.parseDouble(field.getValue());
return Double.isFinite(value) ? OptionalDouble.of(value) : OptionalDouble.empty();
} catch (NumberFormatException ex) {
return OptionalDouble.empty();
}
}
return OptionalDouble.empty();
}
}

View File

@@ -1,62 +0,0 @@
package com.lingniu.ingest.vehiclestat;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.sink.EventSink;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.concurrent.CompletableFuture;
public final class VehicleStatEventSink implements EventSink {
private final VehicleStatRepository repository;
private final DailyVehicleStatService dailyStatService;
private final ZoneId zoneId;
public VehicleStatEventSink(VehicleStatRepository repository,
DailyVehicleStatService dailyStatService,
ZoneId zoneId) {
if (repository == null) {
throw new IllegalArgumentException("repository must not be null");
}
if (dailyStatService == null) {
throw new IllegalArgumentException("dailyStatService must not be null");
}
this.repository = repository;
this.dailyStatService = dailyStatService;
this.zoneId = zoneId == null ? ZoneId.of("Asia/Shanghai") : zoneId;
}
@Override
public String name() {
return "vehicle-stat";
}
@Override
public boolean accepts(VehicleEvent event) {
// 直接 EventSink 路径只服务老的 Realtime 派生统计32960 RAW 历史链路不依赖它。
return event instanceof VehicleEvent.Realtime realtime
&& realtime.payload() != null
&& realtime.payload().totalMileageKm() != null;
}
@Override
public CompletableFuture<Void> publish(VehicleEvent event) {
if (!accepts(event)) {
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Void> future = new CompletableFuture<>();
try {
VehicleEvent.Realtime realtime = (VehicleEvent.Realtime) event;
repository.appendMileagePoint(
realtime.vin(),
new MileagePoint(realtime.eventTime(), realtime.payload().totalMileageKm()));
LocalDate statDate = LocalDate.ofInstant(realtime.eventTime(), zoneId);
dailyStatService.calculateAndSave(realtime.vin(), statDate);
future.complete(null);
} catch (RuntimeException e) {
future.completeExceptionally(e);
}
return future;
}
}

View File

@@ -1,15 +1,10 @@
package com.lingniu.ingest.vehiclestat;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
public interface VehicleStatRepository {
void appendMileagePoint(String vin, MileagePoint point);
List<MileagePoint> mileagePoints(String vin, LocalDate statDate);
void saveDailyStat(VehicleDailyStatResult result);
Optional<VehicleDailyStatResult> findDailyStat(String vin, LocalDate statDate);

View File

@@ -1,13 +0,0 @@
package com.lingniu.ingest.vehiclestat;
public record VehicleStatRule(String vin, DailyMileageStrategy dailyMileageStrategy) {
public VehicleStatRule {
if (vin == null || vin.isBlank()) {
throw new IllegalArgumentException("vin must not be blank");
}
if (dailyMileageStrategy == null) {
dailyMileageStrategy = DailyMileageStrategy.CURRENT_LAST_MINUS_PREVIOUS_LAST;
}
}
}

View File

@@ -1,6 +0,0 @@
package com.lingniu.ingest.vehiclestat;
public interface VehicleStatRuleRepository {
VehicleStatRule ruleFor(String vin);
}

View File

@@ -2,17 +2,10 @@ package com.lingniu.ingest.vehiclestat.config;
import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor;
import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink;
import com.lingniu.ingest.vehiclestat.DailyMileageCalculator;
import com.lingniu.ingest.vehiclestat.DailyMileageStrategy;
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository;
import com.lingniu.ingest.vehiclestat.VehicleStatController;
import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor;
import com.lingniu.ingest.vehiclestat.VehicleStatEventSink;
import com.lingniu.ingest.vehiclestat.VehicleStatEventProcessor;
import com.lingniu.ingest.vehiclestat.VehicleStatRepository;
import com.lingniu.ingest.vehiclestat.VehicleStatRule;
import com.lingniu.ingest.vehiclestat.VehicleStatRuleRepository;
import com.lingniu.ingest.vehiclestat.jt808.InMemoryJt808MileageStateStore;
import com.lingniu.ingest.vehiclestat.jt808.Jt808LocationPointExtractor;
import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStateStore;
@@ -27,7 +20,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -51,43 +43,6 @@ public class VehicleStatAutoConfiguration {
return new JdbcVehicleStatMetricRepository(jdbcTemplate);
}
@Bean
@ConditionalOnMissingBean
public VehicleStatRuleRepository vehicleStatRuleRepository() {
return vin -> new VehicleStatRule(vin, DailyMileageStrategy.CURRENT_LAST_MINUS_PREVIOUS_LAST);
}
@Bean
@ConditionalOnMissingBean
public DailyMileageCalculator dailyMileageCalculator(VehicleStatProperties props) {
return new DailyMileageCalculator(ZoneId.of(props.getZoneId()));
}
@Bean
@ConditionalOnBean(VehicleStatRepository.class)
@ConditionalOnMissingBean
public DailyVehicleStatService dailyVehicleStatService(VehicleStatRepository repository,
VehicleStatRuleRepository ruleRepository,
DailyMileageCalculator mileageCalculator) {
return new DailyVehicleStatService(repository, ruleRepository, mileageCalculator);
}
@Bean
@ConditionalOnBean(VehicleStatRepository.class)
@ConditionalOnMissingBean
public VehicleStatEventProcessor vehicleStatEventProcessor(VehicleStatRepository repository) {
return new VehicleStatEventProcessor(repository);
}
@Bean
@ConditionalOnBean(VehicleStatEventProcessor.class)
@ConditionalOnMissingBean
public VehicleStatEnvelopeIngestor vehicleStatEnvelopeIngestor(
VehicleStatEventProcessor processor,
ObjectProvider<Jt808MileageStreamProcessor> jt808MileageProcessor) {
return new VehicleStatEnvelopeIngestor(processor, jt808MileageProcessor.getIfAvailable());
}
@Bean
@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat.jt808", name = "enabled", havingValue = "true")
@ConditionalOnMissingBean
@@ -110,6 +65,7 @@ public class VehicleStatAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat.jt808", name = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat.jt808", name = "state-store", havingValue = "memory")
@ConditionalOnMissingBean(Jt808MileageStateStore.class)
public Jt808MileageStateStore inMemoryJt808MileageStateStore() {
return new InMemoryJt808MileageStateStore();
@@ -128,20 +84,18 @@ public class VehicleStatAutoConfiguration {
}
@Bean
@ConditionalOnBean({VehicleStatRepository.class, DailyVehicleStatService.class})
@ConditionalOnBean(Jt808MileageStreamProcessor.class)
@ConditionalOnMissingBean
public VehicleStatEventSink vehicleStatEventSink(VehicleStatRepository repository,
DailyVehicleStatService dailyStatService,
VehicleStatProperties props) {
return new VehicleStatEventSink(repository, dailyStatService, ZoneId.of(props.getZoneId()));
public VehicleStatEnvelopeIngestor vehicleStatEnvelopeIngestor(
Jt808MileageStreamProcessor jt808MileageProcessor) {
return new VehicleStatEnvelopeIngestor(jt808MileageProcessor);
}
@Bean
@ConditionalOnBean(DailyVehicleStatService.class)
@ConditionalOnBean(VehicleStatRepository.class)
@ConditionalOnMissingBean
public VehicleStatController vehicleStatController(VehicleStatRepository repository,
DailyVehicleStatService dailyStatService) {
return new VehicleStatController(repository, dailyStatService);
public VehicleStatController vehicleStatController(VehicleStatRepository repository) {
return new VehicleStatController(repository);
}
@Bean

View File

@@ -28,7 +28,7 @@ public class VehicleStatProperties {
public static class Jt808 {
private boolean enabled;
private String stateStore = "memory";
private String stateStore = "redis";
private String redisKeyPrefix = "vehicle:mileage:jt808:daily:";
private long stateTtlDays = 3;