From bb0d001d72caacd41a1d3c1b80166a537b0843a6 Mon Sep 17 00:00:00 2001 From: lingniu Date: Fri, 3 Jul 2026 07:22:18 +0800 Subject: [PATCH] refactor(go): simplify identity table keys --- .../production-data-plane-inventory.md | 2 ++ docs/architecture/storage-minimal-contract.md | 5 +++-- .../internal/identity/resolver.go | 8 ++------ .../internal/identity/resolver_test.go | 19 +++++++++++++++++++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/architecture/production-data-plane-inventory.md b/docs/architecture/production-data-plane-inventory.md index c1935f44..2da9adf6 100644 --- a/docs/architecture/production-data-plane-inventory.md +++ b/docs/architecture/production-data-plane-inventory.md @@ -69,6 +69,8 @@ NATS 部署在 Kafka ECS 内网 `172.17.111.56:4222`。 | `vehicle_realtime_location` | `protocol`、`vin`、`plate`、经纬度、速度、总里程、SOC、事件时间 | Realtime API/projector | 每协议每 VIN 最新位置业务缓存 | | `vehicle_daily_mileage` | `vin`、`stat_date`、`protocol`、`daily_mileage_km`、首末总里程、样本数 | Stat writer | 基于总里程差值的每日里程;无 VIN 数据不进入正式统计表 | +身份表使用业务主键:`vehicle_identity_binding` 以 `vin` 为主键,`jt808_registration` 以 `phone` 为主键;两者不保留代理自增主键和 `created_at`。 + 日里程表使用 `(vin, stat_date, protocol)` 作为业务主键,不保留自增 `id` 和 `created_at`;首末总里程和样本数保留用于解释差值计算。 两张实时当前态表均使用 `(protocol, vin)` 作为业务主键,不保留自增 `id` 和 `created_at`;最新更新时间使用 `updated_at`。 diff --git a/docs/architecture/storage-minimal-contract.md b/docs/architecture/storage-minimal-contract.md index 80a95a53..49ad767a 100644 --- a/docs/architecture/storage-minimal-contract.md +++ b/docs/architecture/storage-minimal-contract.md @@ -24,8 +24,8 @@ | MySQL | `vehicle_realtime_snapshot` | 每个协议+VIN 的最新事件时间、接收时间、车牌、事件 ID | phone、device、source endpoint、完整 JSON | | MySQL | `vehicle_realtime_location` | 每个协议+VIN 的最新位置核心字段 | raw、parsed JSON、消息头内部字段 | | MySQL | `vehicle_daily_mileage` | 日期、VIN、协议、日里程、首末总里程、样本数 | 临时 vehicle key、泛化 metric key/value、自增 id、created_at、每帧细节、位置点列表 | -| MySQL | `vehicle_identity_binding` | 人工维护或导入的 VIN、车牌、phone、device_id 映射 | 注册历史、协议状态 | -| MySQL | `jt808_registration` | JT808 phone 主键下的注册、鉴权、VIN 匹配状态、来源端点 | GB32960/MQTT 注册信息、位置历史 | +| MySQL | `vehicle_identity_binding` | 人工维护或导入的 VIN、车牌、phone、device_id 映射 | 注册历史、协议状态、自增 id、created_at | +| MySQL | `jt808_registration` | JT808 phone 主键下的注册、鉴权、VIN 匹配状态、来源端点 | GB32960/MQTT 注册信息、位置历史、created_at | | Redis | `vehicle:realtime-raw:{protocol}:{vin}` | 每协议最新完整 parsed 状态 | 历史数据、统计结果 | ## 当前应收敛的重复点 @@ -59,6 +59,7 @@ 6. identity 层只保留两张表。 - `vehicle_identity_binding`:VIN 与 plate/phone/device_id 的映射,供 808 等协议反查 VIN。 - `jt808_registration`:808 注册、鉴权、最新活跃和 VIN 匹配状态。 + - 状态:`vehicle_identity_binding` 使用 VIN 主键;`jt808_registration` 使用 phone 主键;两张表都不保留代理自增主键和 `created_at`。 - 生产:RDS 已在上线前删除旧 `vehicle_identity_binding_registration`、`vehicle_identity_bindings`,gateway 启动会自动创建最小 schema。 ## 字段提升规则 diff --git a/go/vehicle-gateway/internal/identity/resolver.go b/go/vehicle-gateway/internal/identity/resolver.go index 4ed21dd4..f55ca47f 100644 --- a/go/vehicle-gateway/internal/identity/resolver.go +++ b/go/vehicle-gateway/internal/identity/resolver.go @@ -71,17 +71,14 @@ func identityBindingTableSQL(table string) string { table = "vehicle_identity_binding" } return `CREATE TABLE IF NOT EXISTS ` + table + ` ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - vin VARCHAR(32) NOT NULL, + vin VARCHAR(32) PRIMARY KEY, plate VARCHAR(32) NULL, phone VARCHAR(32) NULL, device_id VARCHAR(64) NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_identity_plate (plate), UNIQUE KEY uk_identity_phone (phone), - UNIQUE KEY uk_identity_device (device_id), - KEY idx_identity_vin (vin) + UNIQUE KEY uk_identity_device (device_id) )` } @@ -103,7 +100,6 @@ const jt808RegistrationTableSQL = `CREATE TABLE IF NOT EXISTS jt808_registration latest_registered_at DATETIME NULL, latest_authenticated_at DATETIME NULL, latest_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_jt808_registration_device (device_id), KEY idx_jt808_registration_plate (plate), diff --git a/go/vehicle-gateway/internal/identity/resolver_test.go b/go/vehicle-gateway/internal/identity/resolver_test.go index 66033371..75bc3822 100644 --- a/go/vehicle-gateway/internal/identity/resolver_test.go +++ b/go/vehicle-gateway/internal/identity/resolver_test.go @@ -3,6 +3,7 @@ package identity import ( "context" "database/sql" + "strings" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -202,6 +203,24 @@ func TestMySQLResolverEnsuresMinimalIdentitySchema(t *testing.T) { } } +func TestIdentitySchemaUsesBusinessKeysOnly(t *testing.T) { + binding := identityBindingTableSQL("vehicle_identity_binding") + registration := jt808RegistrationTableSQL + for _, sqlText := range []string{binding, registration} { + for _, column := range []string{"id BIGINT", "AUTO_INCREMENT", "created_at"} { + if strings.Contains(sqlText, column) { + t.Fatalf("identity schema should not contain %s:\n%s", column, sqlText) + } + } + } + if !strings.Contains(binding, "vin VARCHAR(32) PRIMARY KEY") { + t.Fatalf("binding table should key by vin:\n%s", binding) + } + if !strings.Contains(registration, "phone VARCHAR(32) PRIMARY KEY") { + t.Fatalf("registration table should key by phone:\n%s", registration) + } +} + func newMockDB(t *testing.T) (*sql.DB, sqlmock.Sqlmock) { t.Helper() db, mock, err := sqlmock.New()