diff --git a/deploy/local/launchctl/README.md b/deploy/local/launchctl/README.md
index b7067c6e..46efa3f6 100644
--- a/deploy/local/launchctl/README.md
+++ b/deploy/local/launchctl/README.md
@@ -62,7 +62,7 @@ history 消费者会按协议和 raw/event 自动拆分 consumer group。设置
- `vehicle-history-live-jt808-event`
- `vehicle-history-live-jt808-raw`
-JT808 身份绑定本地模板默认使用 `file`,可以在没有 MySQL 时持久化人工绑定,避免重启后退回 `unknown`。需要把 0x0100 注册信息维护到 MySQL 并支持真实 VIN 反写时,将 `com.lingniu.jt808.plist.template` 里的 `VEHICLE_IDENTITY_STORE` 改为 `mysql`,并提供 `VEHICLE_IDENTITY_MYSQL_*` 连接参数。服务会自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表:前者只保存 `plate`、`vin`,后者保存 0x0100 注册字段;服务按刷新周期用注册表里的车牌关联 VIN,后续 raw/event 会带真实 VIN。`memory` 仅适合一次性联调,不建议用于生产验证。
+本地模板与 ECS 生产口径一致,身份绑定只使用 MySQL。启动前必须提供 `VEHICLE_IDENTITY_MYSQL_*` 连接参数;服务会自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表:前者只保存 `plate`、`vin`,后者保存注册字段;服务按刷新周期用注册表里的车牌关联 VIN,后续 raw/event 会带真实 VIN。
## 启停
diff --git a/deploy/local/launchctl/com.lingniu.gb32960.plist.template b/deploy/local/launchctl/com.lingniu.gb32960.plist.template
index cfea6336..0c3a4734 100644
--- a/deploy/local/launchctl/com.lingniu.gb32960.plist.template
+++ b/deploy/local/launchctl/com.lingniu.gb32960.plist.template
@@ -37,9 +37,17 @@
写入采用 append-only JSONL,启动时顺序重放到内存索引。这样协议层只依赖 identity SPI, - * 不直接耦合业务库;后续替换成 DB/配置中心时只需新增同接口实现。 - */ -public final class FileVehicleIdentityService implements VehicleIdentityResolver, VehicleIdentityRegistry { - - private static final Logger log = LoggerFactory.getLogger(FileVehicleIdentityService.class); - - private final Path path; - private final ObjectMapper mapper; - private final InMemoryVehicleIdentityService delegate = new InMemoryVehicleIdentityService(); - private final Object writeLock = new Object(); - - public FileVehicleIdentityService(Path path) { - this(path, new ObjectMapper()); - } - - public FileVehicleIdentityService(Path path, ObjectMapper mapper) { - this.path = path.toAbsolutePath(); - this.mapper = mapper; - load(); - } - - @Override - public void bind(VehicleIdentityBinding binding) { - if (binding == null || !binding.hasResolvedVin()) { - return; - } - delegate.bind(binding); - synchronized (writeLock) { - try { - Files.createDirectories(path.getParent()); - try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8, - StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.WRITE)) { - // append-only 允许运行时新增绑定;重启时按文件顺序 replay,后写覆盖内存索引中的旧映射。 - writer.write(mapper.writeValueAsString(BindingLine.from(binding))); - writer.newLine(); - } - } catch (IOException e) { - throw new IllegalStateException("vehicle identity binding persist failed: " + path, e); - } - } - } - - @Override - public VehicleIdentity resolve(VehicleIdentityLookup lookup) { - return delegate.resolve(lookup); - } - - private void load() { - if (!Files.exists(path)) { - return; - } - try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { - String line; - while ((line = reader.readLine()) != null) { - if (line.isBlank()) { - continue; - } - try { - BindingLine binding = mapper.readValue(line, BindingLine.class); - // 单行损坏不影响其它绑定加载,避免一个坏配置拖垮全部接入。 - delegate.bind(binding.toBinding()); - } catch (Exception e) { - log.warn("skip invalid vehicle identity binding line path={} line={}", path, line, e); - } - } - } catch (IOException e) { - throw new IllegalStateException("vehicle identity binding load failed: " + path, e); - } - } - - private record BindingLine( - ProtocolId protocol, - String vin, - String phone, - String deviceId, - String plate - ) { - private static BindingLine from(VehicleIdentityBinding binding) { - return new BindingLine( - binding.protocol(), binding.vin(), binding.phone(), binding.deviceId(), binding.plate()); - } - - private VehicleIdentityBinding toBinding() { - return new VehicleIdentityBinding(protocol, vin, phone, deviceId, plate); - } - } -} diff --git a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/FileVehicleIdentityServiceTest.java b/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/FileVehicleIdentityServiceTest.java deleted file mode 100644 index 163f3898..00000000 --- a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/FileVehicleIdentityServiceTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class FileVehicleIdentityServiceTest { - - @TempDir - Path tempDir; - - @Test - void persistsBindingsAndReloadsThemAfterRestart() throws Exception { - Path store = tempDir.resolve("vehicle-identity.jsonl"); - FileVehicleIdentityService first = new FileVehicleIdentityService(store); - - first.bind(new VehicleIdentityBinding( - ProtocolId.JT808, "LNVIN000000000099", "13900000099", "DEV099", "粤B09999")); - - FileVehicleIdentityService restarted = new FileVehicleIdentityService(store); - - VehicleIdentity byPhone = restarted.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "13900000099", "", "")); - VehicleIdentity byDevice = restarted.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "", "DEV099", "")); - VehicleIdentity byPlate = restarted.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "", "", "粤B09999")); - - assertThat(byPhone.vin()).isEqualTo("LNVIN000000000099"); - assertThat(byPhone.resolved()).isTrue(); - assertThat(byPhone.source()).isEqualTo(VehicleIdentitySource.BOUND_PHONE); - assertThat(byDevice.vin()).isEqualTo("LNVIN000000000099"); - assertThat(byPlate.vin()).isEqualTo("LNVIN000000000099"); - assertThat(Files.readString(store)).contains("\"vin\":\"LNVIN000000000099\""); - } -}