refactor(go): simplify identity table keys

This commit is contained in:
lingniu
2026-07-03 07:22:18 +08:00
parent 04bafb4cd6
commit bb0d001d72
4 changed files with 26 additions and 8 deletions

View File

@@ -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`

View File

@@ -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。
## 字段提升规则

View File

@@ -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),

View File

@@ -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()