refactor: store runtime identity and mileage in mysql metrics
This commit is contained in:
@@ -37,7 +37,7 @@ lingniu-vehicle-ingest/
|
|||||||
│ │ ├── ingest-codec-common/ 公共编解码工具(BCD/CRC/BCC/bit utils)
|
│ │ ├── ingest-codec-common/ 公共编解码工具(BCD/CRC/BCC/bit utils)
|
||||||
│ │ ├── ingest-core/ Pipeline / Dispatcher / Disruptor / Session 桥
|
│ │ ├── ingest-core/ Pipeline / Dispatcher / Disruptor / Session 桥
|
||||||
│ │ ├── session-core/ 设备会话 + 鉴权 + Token + Redis/Memory SessionStore
|
│ │ ├── session-core/ 设备会话 + 鉴权 + Token + Redis/Memory SessionStore
|
||||||
│ │ ├── vehicle-identity/ 跨协议车辆身份解析 + 外部标识绑定(memory/file/mysql)
|
│ │ ├── vehicle-identity/ 跨协议车辆身份解析 + MySQL 外部标识绑定
|
||||||
│ │ └── observability/ metrics / tracing / health
|
│ │ └── observability/ metrics / tracing / health
|
||||||
│ ├── protocols/
|
│ ├── protocols/
|
||||||
│ │ ├── protocol-gb32960/ GB/T 32960
|
│ │ ├── protocol-gb32960/ GB/T 32960
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ Vehicle analytics:
|
|||||||
```bash
|
```bash
|
||||||
KAFKA_BROKERS=127.0.0.1:9092 \
|
KAFKA_BROKERS=127.0.0.1:9092 \
|
||||||
HTTP_PORT=20300 \
|
HTTP_PORT=20300 \
|
||||||
VEHICLE_STAT_FILE_PATH=./target/split-vehicle-stat \
|
|
||||||
java --sun-misc-unsafe-memory-access=allow \
|
java --sun-misc-unsafe-memory-access=allow \
|
||||||
-jar modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar
|
-jar modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
The analytics app can calculate daily mileage from JT808 telemetry only. It consumes `vehicle.event.jt808.v1`, keeps the rolling per-vehicle daily state in Redis or memory, and saves the current daily result into the common `vehicle-stat` metric repository.
|
The analytics app can calculate daily mileage from JT808 telemetry only. It consumes `vehicle.event.jt808.v1`, keeps the rolling per-vehicle daily state in Redis, and saves the current daily result into the common `vehicle-stat` metric repository.
|
||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
|
|
||||||
There is no separate JT808 daily-mileage table. Runtime state is stored under the configured JT808 state store so the stream can continue after restart. The derived metric is written through `VehicleStatRepository.saveDailyStat(...)`; production uses the common JDBC/MySQL metric table `vehicle_stat_metric`. Local development can force the file fallback with `VEHICLE_STAT_REPOSITORY_TYPE=file`, which writes `daily-stats.tsv` under `VEHICLE_STAT_FILE_PATH`.
|
There is no separate JT808 daily-mileage table. Runtime state is stored under the configured JT808 state store so the stream can continue after restart. The derived metric is written through `VehicleStatRepository.saveDailyStat(...)` into the common JDBC/MySQL metric table `vehicle_stat_metric`.
|
||||||
|
|
||||||
The JT808 daily-mileage value is calculated from the GPS total mileage reported in location additional information:
|
The JT808 daily-mileage value is calculated from the GPS total mileage reported in location additional information:
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ Set these in Portainer or Nacos, without committing secrets:
|
|||||||
```text
|
```text
|
||||||
KAFKA_TOPIC_JT808_EVENT=vehicle.event.jt808.v1
|
KAFKA_TOPIC_JT808_EVENT=vehicle.event.jt808.v1
|
||||||
VEHICLE_STAT_JT808_MILEAGE_ENABLED=true
|
VEHICLE_STAT_JT808_MILEAGE_ENABLED=true
|
||||||
VEHICLE_STAT_REPOSITORY_TYPE=jdbc
|
|
||||||
VEHICLE_STAT_JT808_STATE_STORE=redis
|
VEHICLE_STAT_JT808_STATE_STORE=redis
|
||||||
MYSQL_JDBC_URL=<jdbc-url>
|
MYSQL_JDBC_URL=<jdbc-url>
|
||||||
MYSQL_USERNAME=<user>
|
MYSQL_USERNAME=<user>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ deploy/local/launchctl/
|
|||||||
|
|
||||||
JT808 注册身份绑定:
|
JT808 注册身份绑定:
|
||||||
|
|
||||||
- 默认可以使用 `VEHICLE_IDENTITY_STORE=file` 或 `memory`。
|
- 生产默认使用 `VEHICLE_IDENTITY_STORE=mysql`,不再使用 file/memory 作为运行时身份绑定仓储。
|
||||||
- 生产需要把 0x0100 注册信息维护到 MySQL 时,设置 `VEHICLE_IDENTITY_STORE=mysql`。
|
- 生产需要把 0x0100 注册信息维护到 MySQL 时,设置 `VEHICLE_IDENTITY_STORE=mysql`。
|
||||||
- 需要提供 `VEHICLE_IDENTITY_MYSQL_JDBC_URL`、`VEHICLE_IDENTITY_MYSQL_USERNAME`、`VEHICLE_IDENTITY_MYSQL_PASSWORD`。
|
- 需要提供 `VEHICLE_IDENTITY_MYSQL_JDBC_URL`、`VEHICLE_IDENTITY_MYSQL_USERNAME`、`VEHICLE_IDENTITY_MYSQL_PASSWORD`。
|
||||||
- 服务启动时自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表。`vehicle_identity_binding` 只维护 `plate`、`vin` 两列;`vehicle_identity_binding_registration` 按 `protocol + phone` 保存 JT808 0x0100 注册字段。
|
- 服务启动时自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表。`vehicle_identity_binding` 只维护 `plate`、`vin` 两列;`vehicle_identity_binding_registration` 按 `protocol + phone` 保存 JT808 0x0100 注册字段。
|
||||||
@@ -263,7 +263,7 @@ JT808 真实转发链路已验证:
|
|||||||
- `jt808_locations` 中位置行数已超过 `8783`。
|
- `jt808_locations` 中位置行数已超过 `8783`。
|
||||||
- 在旧的字段宽表开启模式下,`telemetry_fields` 中 `protocol='JT808'` 的字段行数曾超过 `15000`;当前高吞吐默认不要求该表持续增长。
|
- 在旧的字段宽表开启模式下,`telemetry_fields` 中 `protocol='JT808'` 的字段行数曾超过 `15000`;当前高吞吐默认不要求该表持续增长。
|
||||||
- 分页 API 已用终端号 `13079963301` 验证第一页和 nextCursor 第二页,样例位置包含 `longitude=118.913846`、`latitude=31.927309`、`statusFlag=3` 和 `archive://2026/06/29/JT808/...` 原始帧引用。
|
- 分页 API 已用终端号 `13079963301` 验证第一页和 nextCursor 第二页,样例位置包含 `longitude=118.913846`、`latitude=31.927309`、`statusFlag=3` 和 `archive://2026/06/29/JT808/...` 原始帧引用。
|
||||||
- 注册帧样例:终端号 `13079963320`、deviceId `9963320`、deviceType `SEG-9888G`、plate `沪A61559F`、maker `70112`、province `31`、city `113`、plateColor `2`。当前本机 live 进程仍使用早期生成的 `memory` 身份存储,正式行里 `vin` 仍为 `unknown`;模板已改为 `file`,生产 VIN 反写仍需要 MySQL identity store。
|
- 注册帧样例:终端号 `13079963320`、deviceId `9963320`、deviceType `SEG-9888G`、plate `沪A61559F`、maker `70112`、province `31`、city `113`、plateColor `2`。生产 VIN 反写依赖 MySQL identity store,后续 raw/event 的 VIN 解析也从 MySQL 绑定读取。
|
||||||
|
|
||||||
## JT808 本机 smoke / 轻压测工具
|
## JT808 本机 smoke / 轻压测工具
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
<groupId>com.lingniu.ingest</groupId>
|
<groupId>com.lingniu.ingest</groupId>
|
||||||
<artifactId>observability</artifactId>
|
<artifactId>observability</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lingniu.ingest</groupId>
|
||||||
|
<artifactId>vehicle-identity</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lingniu.ingest</groupId>
|
<groupId>com.lingniu.ingest</groupId>
|
||||||
<artifactId>protocol-gb32960</artifactId>
|
<artifactId>protocol-gb32960</artifactId>
|
||||||
|
|||||||
@@ -78,9 +78,13 @@ lingniu:
|
|||||||
store: ${SESSION_STORE:memory}
|
store: ${SESSION_STORE:memory}
|
||||||
ttl: ${SESSION_TTL:30m}
|
ttl: ${SESSION_TTL:30m}
|
||||||
identity:
|
identity:
|
||||||
store: ${VEHICLE_IDENTITY_STORE:file}
|
store: ${VEHICLE_IDENTITY_STORE:mysql}
|
||||||
file:
|
mysql:
|
||||||
path: ${VEHICLE_IDENTITY_FILE:./data/vehicle-identity.jsonl}
|
jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}
|
||||||
|
username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root}
|
||||||
|
password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:}
|
||||||
|
table-name: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}
|
||||||
|
initialize-schema: ${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true}
|
||||||
sink:
|
sink:
|
||||||
mq:
|
mq:
|
||||||
enabled: ${KAFKA_ENABLED:true}
|
enabled: ${KAFKA_ENABLED:true}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.lingniu.ingest.gb32960app;
|
package com.lingniu.ingest.gb32960app;
|
||||||
|
|
||||||
import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration;
|
import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration;
|
||||||
|
import com.lingniu.ingest.identity.MySqlVehicleIdentityService;
|
||||||
|
import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration;
|
||||||
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
|
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
|
||||||
import com.lingniu.ingest.protocol.gb32960.config.Gb32960AutoConfiguration;
|
import com.lingniu.ingest.protocol.gb32960.config.Gb32960AutoConfiguration;
|
||||||
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer;
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer;
|
||||||
@@ -27,6 +29,7 @@ class Gb32960IngestAppCompositionTest {
|
|||||||
.withConfiguration(AutoConfigurations.of(
|
.withConfiguration(AutoConfigurations.of(
|
||||||
IngestCoreAutoConfiguration.class,
|
IngestCoreAutoConfiguration.class,
|
||||||
SessionCoreAutoConfiguration.class,
|
SessionCoreAutoConfiguration.class,
|
||||||
|
VehicleIdentityAutoConfiguration.class,
|
||||||
SinkMqAutoConfiguration.class,
|
SinkMqAutoConfiguration.class,
|
||||||
SinkArchiveAutoConfiguration.class,
|
SinkArchiveAutoConfiguration.class,
|
||||||
Gb32960AutoConfiguration.class))
|
Gb32960AutoConfiguration.class))
|
||||||
@@ -36,6 +39,8 @@ class Gb32960IngestAppCompositionTest {
|
|||||||
"lingniu.ingest.gb32960.enabled=true",
|
"lingniu.ingest.gb32960.enabled=true",
|
||||||
"lingniu.ingest.gb32960.server.enabled=true",
|
"lingniu.ingest.gb32960.server.enabled=true",
|
||||||
"lingniu.ingest.gb32960.port=0",
|
"lingniu.ingest.gb32960.port=0",
|
||||||
|
"lingniu.ingest.identity.store=mysql",
|
||||||
|
"lingniu.ingest.identity.mysql.initialize-schema=false",
|
||||||
"lingniu.ingest.session.store=memory",
|
"lingniu.ingest.session.store=memory",
|
||||||
"lingniu.ingest.sink.mq.enabled=true",
|
"lingniu.ingest.sink.mq.enabled=true",
|
||||||
"lingniu.ingest.sink.mq.type=kafka",
|
"lingniu.ingest.sink.mq.type=kafka",
|
||||||
@@ -53,6 +58,7 @@ class Gb32960IngestAppCompositionTest {
|
|||||||
contextRunner.run(context -> {
|
contextRunner.run(context -> {
|
||||||
assertThat(context).hasSingleBean(Gb32960MessageDecoder.class);
|
assertThat(context).hasSingleBean(Gb32960MessageDecoder.class);
|
||||||
assertThat(context).hasSingleBean(Gb32960NettyServer.class);
|
assertThat(context).hasSingleBean(Gb32960NettyServer.class);
|
||||||
|
assertThat(context).hasSingleBean(MySqlVehicleIdentityService.class);
|
||||||
assertThat(context).hasSingleBean(KafkaEventSink.class);
|
assertThat(context).hasSingleBean(KafkaEventSink.class);
|
||||||
assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);
|
assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);
|
||||||
assertThat(context).hasSingleBean(ArchiveStore.class);
|
assertThat(context).hasSingleBean(ArchiveStore.class);
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ class Gb32960IngestAppDefaultsTest {
|
|||||||
.containsEntry("lingniu.ingest.sink.mq.consumer.enabled", false)
|
.containsEntry("lingniu.ingest.sink.mq.consumer.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.sink.archive.enabled", "${SINK_ARCHIVE_ENABLED:true}")
|
.containsEntry("lingniu.ingest.sink.archive.enabled", "${SINK_ARCHIVE_ENABLED:true}")
|
||||||
.containsEntry("lingniu.ingest.sink.archive.path", "${SINK_ARCHIVE_PATH:./archive/}")
|
.containsEntry("lingniu.ingest.sink.archive.path", "${SINK_ARCHIVE_PATH:./archive/}")
|
||||||
|
.containsEntry("lingniu.ingest.identity.store", "${VEHICLE_IDENTITY_STORE:mysql}")
|
||||||
|
.containsEntry("lingniu.ingest.identity.mysql.table-name",
|
||||||
|
"${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}")
|
||||||
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.vehicle-state.enabled", false)
|
.containsEntry("lingniu.ingest.vehicle-state.enabled", false)
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ lingniu:
|
|||||||
store: ${SESSION_STORE:memory}
|
store: ${SESSION_STORE:memory}
|
||||||
ttl: ${SESSION_TTL:30m}
|
ttl: ${SESSION_TTL:30m}
|
||||||
identity:
|
identity:
|
||||||
store: ${VEHICLE_IDENTITY_STORE:file}
|
store: ${VEHICLE_IDENTITY_STORE:mysql}
|
||||||
file:
|
|
||||||
path: ${VEHICLE_IDENTITY_FILE:./data/vehicle-identity.jsonl}
|
|
||||||
mysql:
|
mysql:
|
||||||
jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}
|
jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}
|
||||||
username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root}
|
username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.lingniu.ingest.jt808app;
|
package com.lingniu.ingest.jt808app;
|
||||||
|
|
||||||
import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration;
|
import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration;
|
||||||
|
import com.lingniu.ingest.identity.MySqlVehicleIdentityService;
|
||||||
import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration;
|
import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration;
|
||||||
import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder;
|
import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder;
|
||||||
import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration;
|
import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration;
|
||||||
@@ -37,6 +38,8 @@ class Jt808IngestAppCompositionTest {
|
|||||||
.withPropertyValues(
|
.withPropertyValues(
|
||||||
"lingniu.ingest.jt808.enabled=true",
|
"lingniu.ingest.jt808.enabled=true",
|
||||||
"lingniu.ingest.jt808.port=0",
|
"lingniu.ingest.jt808.port=0",
|
||||||
|
"lingniu.ingest.identity.store=mysql",
|
||||||
|
"lingniu.ingest.identity.mysql.initialize-schema=false",
|
||||||
"lingniu.ingest.session.store=memory",
|
"lingniu.ingest.session.store=memory",
|
||||||
"lingniu.ingest.sink.mq.enabled=true",
|
"lingniu.ingest.sink.mq.enabled=true",
|
||||||
"lingniu.ingest.sink.mq.type=kafka",
|
"lingniu.ingest.sink.mq.type=kafka",
|
||||||
@@ -54,6 +57,7 @@ class Jt808IngestAppCompositionTest {
|
|||||||
contextRunner.run(context -> {
|
contextRunner.run(context -> {
|
||||||
assertThat(context).hasSingleBean(Jt808MessageDecoder.class);
|
assertThat(context).hasSingleBean(Jt808MessageDecoder.class);
|
||||||
assertThat(context).hasSingleBean(Jt808NettyServer.class);
|
assertThat(context).hasSingleBean(Jt808NettyServer.class);
|
||||||
|
assertThat(context).hasSingleBean(MySqlVehicleIdentityService.class);
|
||||||
assertThat(context).hasSingleBean(KafkaEventSink.class);
|
assertThat(context).hasSingleBean(KafkaEventSink.class);
|
||||||
assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);
|
assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);
|
||||||
assertThat(context).hasSingleBean(ArchiveStore.class);
|
assertThat(context).hasSingleBean(ArchiveStore.class);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Jt808IngestAppDefaultsTest {
|
|||||||
.containsEntry("lingniu.ingest.sink.mq.topics.realtime", "${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1}")
|
.containsEntry("lingniu.ingest.sink.mq.topics.realtime", "${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1}")
|
||||||
.containsEntry("lingniu.ingest.sink.mq.topics.raw-archive", "${KAFKA_TOPIC_JT808_RAW:vehicle.raw.jt808.v1}")
|
.containsEntry("lingniu.ingest.sink.mq.topics.raw-archive", "${KAFKA_TOPIC_JT808_RAW:vehicle.raw.jt808.v1}")
|
||||||
.containsEntry("lingniu.ingest.sink.mq.topics.dlq", "${KAFKA_TOPIC_JT808_DLQ:vehicle.dlq.jt808.v1}")
|
.containsEntry("lingniu.ingest.sink.mq.topics.dlq", "${KAFKA_TOPIC_JT808_DLQ:vehicle.dlq.jt808.v1}")
|
||||||
.containsEntry("lingniu.ingest.identity.store", "${VEHICLE_IDENTITY_STORE:file}")
|
.containsEntry("lingniu.ingest.identity.store", "${VEHICLE_IDENTITY_STORE:mysql}")
|
||||||
.containsEntry("lingniu.ingest.identity.mysql.table-name",
|
.containsEntry("lingniu.ingest.identity.mysql.table-name",
|
||||||
"${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}")
|
"${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}")
|
||||||
.containsEntry("lingniu.ingest.identity.mysql.jdbc-url",
|
.containsEntry("lingniu.ingest.identity.mysql.jdbc-url",
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ lingniu:
|
|||||||
enabled: ${VEHICLE_STATE_ENABLED:false}
|
enabled: ${VEHICLE_STATE_ENABLED:false}
|
||||||
vehicle-stat:
|
vehicle-stat:
|
||||||
enabled: ${VEHICLE_STAT_ENABLED:true}
|
enabled: ${VEHICLE_STAT_ENABLED:true}
|
||||||
repository-type: ${VEHICLE_STAT_REPOSITORY_TYPE:jdbc}
|
|
||||||
file-path: ${VEHICLE_STAT_FILE_PATH:./target/vehicle-stat/}
|
|
||||||
zone-id: ${VEHICLE_STAT_ZONE_ID:Asia/Shanghai}
|
zone-id: ${VEHICLE_STAT_ZONE_ID:Asia/Shanghai}
|
||||||
jt808:
|
jt808:
|
||||||
enabled: ${VEHICLE_STAT_JT808_MILEAGE_ENABLED:false}
|
enabled: ${VEHICLE_STAT_JT808_MILEAGE_ENABLED:false}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import com.lingniu.ingest.sink.mq.SinkMqAutoConfiguration;
|
|||||||
import com.lingniu.ingest.vehiclestate.VehicleStateEnvelopeIngestor;
|
import com.lingniu.ingest.vehiclestate.VehicleStateEnvelopeIngestor;
|
||||||
import com.lingniu.ingest.vehiclestate.config.VehicleStateAutoConfiguration;
|
import com.lingniu.ingest.vehiclestate.config.VehicleStateAutoConfiguration;
|
||||||
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
|
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
|
||||||
import com.lingniu.ingest.vehiclestat.FileVehicleStatRepository;
|
import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository;
|
||||||
import com.lingniu.ingest.vehiclestat.VehicleStatController;
|
import com.lingniu.ingest.vehiclestat.VehicleStatController;
|
||||||
import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor;
|
import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor;
|
||||||
import com.lingniu.ingest.vehiclestat.VehicleStatEventProcessor;
|
import com.lingniu.ingest.vehiclestat.VehicleStatEventProcessor;
|
||||||
@@ -16,22 +16,17 @@ import com.lingniu.ingest.vehiclestat.VehicleStatRepository;
|
|||||||
import com.lingniu.ingest.vehiclestat.config.VehicleStatAutoConfiguration;
|
import com.lingniu.ingest.vehiclestat.config.VehicleStatAutoConfiguration;
|
||||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
class VehicleAnalyticsAppCompositionTest {
|
class VehicleAnalyticsAppCompositionTest {
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createsStatsBeansWithoutProtocolListenerOrEventFileStore() {
|
void createsStatsBeansWithoutProtocolListenerOrEventFileStore() {
|
||||||
new ApplicationContextRunner()
|
new ApplicationContextRunner()
|
||||||
@@ -41,6 +36,7 @@ class VehicleAnalyticsAppCompositionTest {
|
|||||||
VehicleStatAutoConfiguration.class))
|
VehicleStatAutoConfiguration.class))
|
||||||
.withAllowBeanDefinitionOverriding(true)
|
.withAllowBeanDefinitionOverriding(true)
|
||||||
.withBean("kafkaProducer", KafkaProducer.class, VehicleAnalyticsAppCompositionTest::kafkaProducer)
|
.withBean("kafkaProducer", KafkaProducer.class, VehicleAnalyticsAppCompositionTest::kafkaProducer)
|
||||||
|
.withBean(JdbcTemplate.class, () -> mock(JdbcTemplate.class))
|
||||||
.withPropertyValues(
|
.withPropertyValues(
|
||||||
"lingniu.ingest.sink.mq.enabled=true",
|
"lingniu.ingest.sink.mq.enabled=true",
|
||||||
"lingniu.ingest.sink.mq.type=kafka",
|
"lingniu.ingest.sink.mq.type=kafka",
|
||||||
@@ -48,13 +44,12 @@ class VehicleAnalyticsAppCompositionTest {
|
|||||||
"lingniu.ingest.sink.mq.consumer.enabled=false",
|
"lingniu.ingest.sink.mq.consumer.enabled=false",
|
||||||
"lingniu.ingest.vehicle-state.enabled=false",
|
"lingniu.ingest.vehicle-state.enabled=false",
|
||||||
"lingniu.ingest.vehicle-stat.enabled=true",
|
"lingniu.ingest.vehicle-stat.enabled=true",
|
||||||
"lingniu.ingest.vehicle-stat.file-path=" + tempDir.resolve("vehicle-stat"),
|
|
||||||
"lingniu.ingest.event-file-store.enabled=false",
|
"lingniu.ingest.event-file-store.enabled=false",
|
||||||
"lingniu.ingest.event-history.enabled=false",
|
"lingniu.ingest.event-history.enabled=false",
|
||||||
"lingniu.ingest.gb32960.enabled=false")
|
"lingniu.ingest.gb32960.enabled=false")
|
||||||
.run(context -> {
|
.run(context -> {
|
||||||
assertThat(context).hasSingleBean(VehicleStatRepository.class);
|
assertThat(context).hasSingleBean(VehicleStatRepository.class);
|
||||||
assertThat(context).hasSingleBean(FileVehicleStatRepository.class);
|
assertThat(context).hasSingleBean(JdbcVehicleStatMetricRepository.class);
|
||||||
assertThat(context).hasSingleBean(DailyVehicleStatService.class);
|
assertThat(context).hasSingleBean(DailyVehicleStatService.class);
|
||||||
assertThat(context).hasSingleBean(VehicleStatEventProcessor.class);
|
assertThat(context).hasSingleBean(VehicleStatEventProcessor.class);
|
||||||
assertThat(context).hasSingleBean(VehicleStatEnvelopeIngestor.class);
|
assertThat(context).hasSingleBean(VehicleStatEnvelopeIngestor.class);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class VehicleAnalyticsAppDefaultsTest {
|
|||||||
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.vehicle-stat.enabled", "${VEHICLE_STAT_ENABLED:true}")
|
.containsEntry("lingniu.ingest.vehicle-stat.enabled", "${VEHICLE_STAT_ENABLED:true}")
|
||||||
.containsEntry("lingniu.ingest.vehicle-stat.repository-type", "${VEHICLE_STAT_REPOSITORY_TYPE:jdbc}")
|
|
||||||
.containsEntry("lingniu.ingest.vehicle-state.enabled", "${VEHICLE_STATE_ENABLED:false}")
|
.containsEntry("lingniu.ingest.vehicle-state.enabled", "${VEHICLE_STATE_ENABLED:false}")
|
||||||
.containsEntry("lingniu.ingest.sink.mq.consumer.enabled", "${KAFKA_CONSUMER_ENABLED:true}")
|
.containsEntry("lingniu.ingest.sink.mq.consumer.enabled", "${KAFKA_CONSUMER_ENABLED:true}")
|
||||||
.containsEntry(
|
.containsEntry(
|
||||||
|
|||||||
@@ -60,16 +60,13 @@ lingniu:
|
|||||||
rate-limit:
|
rate-limit:
|
||||||
per-vin-qps: 50
|
per-vin-qps: 50
|
||||||
identity:
|
identity:
|
||||||
store: ${VEHICLE_IDENTITY_STORE:file}
|
store: ${VEHICLE_IDENTITY_STORE:mysql}
|
||||||
file:
|
|
||||||
path: ${VEHICLE_IDENTITY_FILE:./data/vehicle-identity.jsonl}
|
|
||||||
mysql:
|
mysql:
|
||||||
table: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_binding}
|
jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}
|
||||||
jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:}
|
username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root}
|
||||||
username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:}
|
|
||||||
password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:}
|
password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:}
|
||||||
driver-class-name: ${VEHICLE_IDENTITY_MYSQL_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
|
table-name: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}
|
||||||
refresh-interval: ${VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL:60s}
|
initialize-schema: ${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true}
|
||||||
sink:
|
sink:
|
||||||
mq:
|
mq:
|
||||||
enabled: ${KAFKA_ENABLED:true}
|
enabled: ${KAFKA_ENABLED:true}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.lingniu.ingest.yutongmqttapp;
|
package com.lingniu.ingest.yutongmqttapp;
|
||||||
|
|
||||||
import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration;
|
import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration;
|
||||||
|
import com.lingniu.ingest.identity.MySqlVehicleIdentityService;
|
||||||
import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration;
|
import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration;
|
||||||
import com.lingniu.ingest.inbound.mqtt.client.MqttEndpointManager;
|
import com.lingniu.ingest.inbound.mqtt.client.MqttEndpointManager;
|
||||||
import com.lingniu.ingest.inbound.mqtt.config.MqttInboundAutoConfiguration;
|
import com.lingniu.ingest.inbound.mqtt.config.MqttInboundAutoConfiguration;
|
||||||
@@ -37,6 +38,8 @@ class YutongMqttAppCompositionTest {
|
|||||||
"lingniu.ingest.mqtt.endpoints[0].uri=tcp://127.0.0.1:1883",
|
"lingniu.ingest.mqtt.endpoints[0].uri=tcp://127.0.0.1:1883",
|
||||||
"lingniu.ingest.mqtt.endpoints[0].topic=/yutong/#",
|
"lingniu.ingest.mqtt.endpoints[0].topic=/yutong/#",
|
||||||
"lingniu.ingest.mqtt.endpoints[0].profile=yutong",
|
"lingniu.ingest.mqtt.endpoints[0].profile=yutong",
|
||||||
|
"lingniu.ingest.identity.store=mysql",
|
||||||
|
"lingniu.ingest.identity.mysql.initialize-schema=false",
|
||||||
"lingniu.ingest.sink.mq.enabled=true",
|
"lingniu.ingest.sink.mq.enabled=true",
|
||||||
"lingniu.ingest.sink.mq.type=kafka",
|
"lingniu.ingest.sink.mq.type=kafka",
|
||||||
"lingniu.ingest.sink.mq.bootstrap-servers=localhost:9092",
|
"lingniu.ingest.sink.mq.bootstrap-servers=localhost:9092",
|
||||||
@@ -54,6 +57,7 @@ class YutongMqttAppCompositionTest {
|
|||||||
assertThat(context).hasSingleBean(MqttEndpointManager.class);
|
assertThat(context).hasSingleBean(MqttEndpointManager.class);
|
||||||
assertThat(context).hasSingleBean(MqttProfileRegistry.class);
|
assertThat(context).hasSingleBean(MqttProfileRegistry.class);
|
||||||
assertThat(context).hasSingleBean(MqttRealtimeHandler.class);
|
assertThat(context).hasSingleBean(MqttRealtimeHandler.class);
|
||||||
|
assertThat(context).hasSingleBean(MySqlVehicleIdentityService.class);
|
||||||
assertThat(context).hasSingleBean(KafkaEventSink.class);
|
assertThat(context).hasSingleBean(KafkaEventSink.class);
|
||||||
assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);
|
assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);
|
||||||
assertThat(context).hasSingleBean(ArchiveStore.class);
|
assertThat(context).hasSingleBean(ArchiveStore.class);
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ class YutongMqttAppDefaultsTest {
|
|||||||
.containsEntry("lingniu.ingest.sink.mq.topics.realtime", "${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1}")
|
.containsEntry("lingniu.ingest.sink.mq.topics.realtime", "${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1}")
|
||||||
.containsEntry("lingniu.ingest.sink.mq.topics.raw-archive", "${KAFKA_TOPIC_YUTONG_MQTT_RAW:vehicle.raw.mqtt-yutong.v1}")
|
.containsEntry("lingniu.ingest.sink.mq.topics.raw-archive", "${KAFKA_TOPIC_YUTONG_MQTT_RAW:vehicle.raw.mqtt-yutong.v1}")
|
||||||
.containsEntry("lingniu.ingest.sink.mq.topics.dlq", "${KAFKA_TOPIC_YUTONG_MQTT_DLQ:vehicle.dlq.mqtt-yutong.v1}")
|
.containsEntry("lingniu.ingest.sink.mq.topics.dlq", "${KAFKA_TOPIC_YUTONG_MQTT_DLQ:vehicle.dlq.mqtt-yutong.v1}")
|
||||||
.containsEntry("lingniu.ingest.identity.store", "${VEHICLE_IDENTITY_STORE:file}")
|
.containsEntry("lingniu.ingest.identity.store", "${VEHICLE_IDENTITY_STORE:mysql}")
|
||||||
.containsEntry("lingniu.ingest.identity.mysql.table", "${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_binding}")
|
.containsEntry("lingniu.ingest.identity.mysql.table-name",
|
||||||
|
"${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}")
|
||||||
.containsEntry("lingniu.ingest.sink.archive.enabled", "${SINK_ARCHIVE_ENABLED:true}")
|
.containsEntry("lingniu.ingest.sink.archive.enabled", "${SINK_ARCHIVE_ENABLED:true}")
|
||||||
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.lingniu.ingest.identity.config;
|
package com.lingniu.ingest.identity.config;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.lingniu.ingest.identity.FileVehicleIdentityService;
|
|
||||||
import com.lingniu.ingest.identity.InMemoryVehicleIdentityService;
|
|
||||||
import com.lingniu.ingest.identity.MySqlVehicleIdentityService;
|
import com.lingniu.ingest.identity.MySqlVehicleIdentityService;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityRegistry;
|
import com.lingniu.ingest.identity.VehicleIdentityRegistry;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
||||||
@@ -14,7 +12,6 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
@EnableConfigurationProperties(VehicleIdentityProperties.class)
|
@EnableConfigurationProperties(VehicleIdentityProperties.class)
|
||||||
@@ -28,16 +25,8 @@ public class VehicleIdentityAutoConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean({VehicleIdentityResolver.class, VehicleIdentityRegistry.class})
|
@ConditionalOnMissingBean({VehicleIdentityResolver.class, VehicleIdentityRegistry.class})
|
||||||
@ConditionalOnProperty(prefix = "lingniu.ingest.identity", name = "store", havingValue = "file")
|
@ConditionalOnProperty(prefix = "lingniu.ingest.identity", name = "store", havingValue = "mysql",
|
||||||
public FileVehicleIdentityService fileVehicleIdentityService(VehicleIdentityProperties properties,
|
matchIfMissing = true)
|
||||||
ObjectMapper objectMapper) {
|
|
||||||
// 文件实现同时提供 resolver 和 registry,协议层既能查绑定,也能在注册/鉴权时补写绑定。
|
|
||||||
return new FileVehicleIdentityService(Path.of(properties.getFile().getPath()), objectMapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean({VehicleIdentityResolver.class, VehicleIdentityRegistry.class})
|
|
||||||
@ConditionalOnProperty(prefix = "lingniu.ingest.identity", name = "store", havingValue = "mysql")
|
|
||||||
public MySqlVehicleIdentityService mysqlVehicleIdentityService(VehicleIdentityProperties properties,
|
public MySqlVehicleIdentityService mysqlVehicleIdentityService(VehicleIdentityProperties properties,
|
||||||
ObjectMapper objectMapper) {
|
ObjectMapper objectMapper) {
|
||||||
VehicleIdentityProperties.Mysql mysql = properties.getMysql();
|
VehicleIdentityProperties.Mysql mysql = properties.getMysql();
|
||||||
@@ -45,12 +34,4 @@ public class VehicleIdentityAutoConfiguration {
|
|||||||
mysql.getJdbcUrl(), mysql.getUsername(), mysql.getPassword());
|
mysql.getJdbcUrl(), mysql.getUsername(), mysql.getPassword());
|
||||||
return new MySqlVehicleIdentityService(dataSource, mysql, objectMapper);
|
return new MySqlVehicleIdentityService(dataSource, mysql, objectMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean({VehicleIdentityResolver.class, VehicleIdentityRegistry.class})
|
|
||||||
@ConditionalOnProperty(prefix = "lingniu.ingest.identity", name = "store", havingValue = "memory",
|
|
||||||
matchIfMissing = true)
|
|
||||||
public InMemoryVehicleIdentityService vehicleIdentityService() {
|
|
||||||
return new InMemoryVehicleIdentityService();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,14 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
@ConfigurationProperties(prefix = "lingniu.ingest.identity")
|
@ConfigurationProperties(prefix = "lingniu.ingest.identity")
|
||||||
public class VehicleIdentityProperties {
|
public class VehicleIdentityProperties {
|
||||||
|
|
||||||
private String store = "memory";
|
private String store = "mysql";
|
||||||
private File file = new File();
|
|
||||||
private Mysql mysql = new Mysql();
|
private Mysql mysql = new Mysql();
|
||||||
|
|
||||||
public String getStore() { return store; }
|
public String getStore() { return store; }
|
||||||
public void setStore(String store) { this.store = store; }
|
public void setStore(String store) { this.store = store; }
|
||||||
public File getFile() { return file; }
|
|
||||||
public void setFile(File file) { this.file = file; }
|
|
||||||
public Mysql getMysql() { return mysql; }
|
public Mysql getMysql() { return mysql; }
|
||||||
public void setMysql(Mysql mysql) { this.mysql = mysql; }
|
public void setMysql(Mysql mysql) { this.mysql = mysql; }
|
||||||
|
|
||||||
public static class File {
|
|
||||||
private String path = "./data/vehicle-identity.jsonl";
|
|
||||||
|
|
||||||
public String getPath() { return path; }
|
|
||||||
public void setPath(String path) { this.path = path; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Mysql {
|
public static class Mysql {
|
||||||
private String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
|
private String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
|
||||||
private String username = "root";
|
private String username = "root";
|
||||||
|
|||||||
@@ -3,42 +3,31 @@ package com.lingniu.ingest.identity.config;
|
|||||||
import com.lingniu.ingest.identity.VehicleIdentityRegistry;
|
import com.lingniu.ingest.identity.VehicleIdentityRegistry;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class VehicleIdentityAutoConfigurationTest {
|
class VehicleIdentityAutoConfigurationTest {
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
private final ApplicationContextRunner runner = new ApplicationContextRunner()
|
private final ApplicationContextRunner runner = new ApplicationContextRunner()
|
||||||
.withConfiguration(AutoConfigurations.of(VehicleIdentityAutoConfiguration.class));
|
.withConfiguration(AutoConfigurations.of(VehicleIdentityAutoConfiguration.class));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createsDefaultIdentityService() {
|
void createsMysqlIdentityServiceByDefault() {
|
||||||
runner.run(context -> {
|
|
||||||
assertThat(context).hasSingleBean(VehicleIdentityResolver.class);
|
|
||||||
assertThat(context).hasSingleBean(VehicleIdentityRegistry.class);
|
|
||||||
assertThat(context.getBean(VehicleIdentityResolver.class))
|
|
||||||
.isSameAs(context.getBean(VehicleIdentityRegistry.class));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void createsFileIdentityServiceWhenConfigured() {
|
|
||||||
runner
|
runner
|
||||||
.withPropertyValues(
|
.withPropertyValues(
|
||||||
"lingniu.ingest.identity.store=file",
|
"lingniu.ingest.identity.mysql.jdbc-url=jdbc:h2:mem:auto-identity-default;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1",
|
||||||
"lingniu.ingest.identity.file.path=" + tempDir.resolve("identity.jsonl"))
|
"lingniu.ingest.identity.mysql.username=sa",
|
||||||
|
"lingniu.ingest.identity.mysql.password=",
|
||||||
|
"lingniu.ingest.identity.mysql.table-name=vehicle_identity_bindings")
|
||||||
.run(context -> {
|
.run(context -> {
|
||||||
assertThat(context).hasSingleBean(VehicleIdentityResolver.class);
|
assertThat(context).hasSingleBean(VehicleIdentityResolver.class);
|
||||||
|
assertThat(context).hasSingleBean(VehicleIdentityRegistry.class);
|
||||||
|
assertThat(context.getBean(VehicleIdentityResolver.class))
|
||||||
|
.isSameAs(context.getBean(VehicleIdentityRegistry.class));
|
||||||
assertThat(context.getBean(VehicleIdentityResolver.class).getClass().getSimpleName())
|
assertThat(context.getBean(VehicleIdentityResolver.class).getClass().getSimpleName())
|
||||||
.isEqualTo("FileVehicleIdentityService");
|
.isEqualTo("MySqlVehicleIdentityService");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,4 +49,14 @@ class VehicleIdentityAutoConfigurationTest {
|
|||||||
.isEqualTo("MySqlVehicleIdentityService");
|
.isEqualTo("MySqlVehicleIdentityService");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unsupportedStoreModeDoesNotCreateRuntimeFallback() {
|
||||||
|
runner
|
||||||
|
.withPropertyValues("lingniu.ingest.identity.store=file")
|
||||||
|
.run(context -> {
|
||||||
|
assertThat(context).doesNotHaveBean(VehicleIdentityResolver.class);
|
||||||
|
assertThat(context).doesNotHaveBean(VehicleIdentityRegistry.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink;
|
|||||||
import com.lingniu.ingest.vehiclestat.DailyMileageCalculator;
|
import com.lingniu.ingest.vehiclestat.DailyMileageCalculator;
|
||||||
import com.lingniu.ingest.vehiclestat.DailyMileageStrategy;
|
import com.lingniu.ingest.vehiclestat.DailyMileageStrategy;
|
||||||
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
|
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
|
||||||
import com.lingniu.ingest.vehiclestat.FileVehicleStatRepository;
|
|
||||||
import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository;
|
import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository;
|
||||||
import com.lingniu.ingest.vehiclestat.VehicleStatController;
|
import com.lingniu.ingest.vehiclestat.VehicleStatController;
|
||||||
import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor;
|
import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor;
|
||||||
@@ -33,7 +32,6 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
|
||||||
@@ -48,20 +46,11 @@ public class VehicleStatAutoConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnBean(JdbcTemplate.class)
|
@ConditionalOnBean(JdbcTemplate.class)
|
||||||
@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat", name = "repository-type",
|
|
||||||
havingValue = "jdbc", matchIfMissing = true)
|
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public VehicleStatRepository jdbcVehicleStatMetricRepository(JdbcTemplate jdbcTemplate) {
|
public VehicleStatRepository jdbcVehicleStatMetricRepository(JdbcTemplate jdbcTemplate) {
|
||||||
return new JdbcVehicleStatMetricRepository(jdbcTemplate);
|
return new JdbcVehicleStatMetricRepository(jdbcTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public VehicleStatRepository vehicleStatRepository(VehicleStatProperties props) {
|
|
||||||
// 无 JDBC 指标库时才回落本地文件,主要用于开发和单元验证。
|
|
||||||
return new FileVehicleStatRepository(Path.of(props.getFilePath()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public VehicleStatRuleRepository vehicleStatRuleRepository() {
|
public VehicleStatRuleRepository vehicleStatRuleRepository() {
|
||||||
|
|||||||
@@ -5,33 +5,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
@ConfigurationProperties(prefix = "lingniu.ingest.vehicle-stat")
|
@ConfigurationProperties(prefix = "lingniu.ingest.vehicle-stat")
|
||||||
public class VehicleStatProperties {
|
public class VehicleStatProperties {
|
||||||
|
|
||||||
/** 派生统计本地文件根目录;不是 32960 RAW archive 或 DuckDB 历史库目录。 */
|
|
||||||
private String filePath = "./target/vehicle-stat/";
|
|
||||||
|
|
||||||
/** 指标仓储类型:有 JDBC 数据源时生产使用 jdbc;本地无数据库时回落 file。 */
|
|
||||||
private String repositoryType = "jdbc";
|
|
||||||
|
|
||||||
/** 统计自然日口径,默认按国内业务使用东八区。 */
|
/** 统计自然日口径,默认按国内业务使用东八区。 */
|
||||||
private String zoneId = "Asia/Shanghai";
|
private String zoneId = "Asia/Shanghai";
|
||||||
|
|
||||||
private Jt808 jt808 = new Jt808();
|
private Jt808 jt808 = new Jt808();
|
||||||
|
|
||||||
public String getFilePath() {
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilePath(String filePath) {
|
|
||||||
this.filePath = filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRepositoryType() {
|
|
||||||
return repositoryType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRepositoryType(String repositoryType) {
|
|
||||||
this.repositoryType = repositoryType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getZoneId() {
|
public String getZoneId() {
|
||||||
return zoneId;
|
return zoneId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink;
|
|||||||
import com.lingniu.ingest.vehiclestat.DailyMileageCalculator;
|
import com.lingniu.ingest.vehiclestat.DailyMileageCalculator;
|
||||||
import com.lingniu.ingest.vehiclestat.DailyMileageStrategy;
|
import com.lingniu.ingest.vehiclestat.DailyMileageStrategy;
|
||||||
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
|
import com.lingniu.ingest.vehiclestat.DailyVehicleStatService;
|
||||||
import com.lingniu.ingest.vehiclestat.FileVehicleStatRepository;
|
|
||||||
import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository;
|
import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository;
|
||||||
import com.lingniu.ingest.vehiclestat.MileagePoint;
|
import com.lingniu.ingest.vehiclestat.MileagePoint;
|
||||||
import com.lingniu.ingest.vehiclestat.VehicleStatController;
|
import com.lingniu.ingest.vehiclestat.VehicleStatController;
|
||||||
@@ -71,17 +70,14 @@ class VehicleStatAutoConfigurationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createsFileRepositoryWhenNoRepositoryBeanExists() {
|
void doesNotCreateRepositoryWithoutJdbcMetricStore() {
|
||||||
new ApplicationContextRunner()
|
new ApplicationContextRunner()
|
||||||
.withConfiguration(AutoConfigurations.of(VehicleStatAutoConfiguration.class))
|
.withConfiguration(AutoConfigurations.of(VehicleStatAutoConfiguration.class))
|
||||||
.withPropertyValues("lingniu.ingest.vehicle-stat.enabled=true")
|
.withPropertyValues("lingniu.ingest.vehicle-stat.enabled=true")
|
||||||
.run(context -> {
|
.run(context -> {
|
||||||
assertThat(context).hasSingleBean(VehicleStatRepository.class);
|
assertThat(context).doesNotHaveBean(VehicleStatRepository.class);
|
||||||
assertThat(context).hasSingleBean(FileVehicleStatRepository.class);
|
assertThat(context).doesNotHaveBean(DailyVehicleStatService.class);
|
||||||
assertThat(context).hasSingleBean(VehicleStatRuleRepository.class);
|
assertThat(context).doesNotHaveBean(VehicleStatEventProcessor.class);
|
||||||
assertThat(context).hasSingleBean(DailyMileageCalculator.class);
|
|
||||||
assertThat(context).hasSingleBean(DailyVehicleStatService.class);
|
|
||||||
assertThat(context).hasSingleBean(VehicleStatEventProcessor.class);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +138,7 @@ class VehicleStatAutoConfigurationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void canForceFileRepositoryEvenWhenJdbcTemplateExists() {
|
void ignoresFileRepositoryTypeAndKeepsJdbcMetricRepository() {
|
||||||
new ApplicationContextRunner()
|
new ApplicationContextRunner()
|
||||||
.withConfiguration(AutoConfigurations.of(
|
.withConfiguration(AutoConfigurations.of(
|
||||||
JdbcTemplateAutoConfiguration.class,
|
JdbcTemplateAutoConfiguration.class,
|
||||||
@@ -156,8 +152,7 @@ class VehicleStatAutoConfigurationTest {
|
|||||||
"lingniu.ingest.vehicle-stat.repository-type=file")
|
"lingniu.ingest.vehicle-stat.repository-type=file")
|
||||||
.run(context -> {
|
.run(context -> {
|
||||||
assertThat(context).hasSingleBean(VehicleStatRepository.class);
|
assertThat(context).hasSingleBean(VehicleStatRepository.class);
|
||||||
assertThat(context).hasSingleBean(FileVehicleStatRepository.class);
|
assertThat(context).hasSingleBean(JdbcVehicleStatMetricRepository.class);
|
||||||
assertThat(context).doesNotHaveBean(JdbcVehicleStatMetricRepository.class);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user