fix(gateway): prefer known JT808 identity snapshot
This commit is contained in:
@@ -612,6 +612,13 @@ func (r *MySQLResolver) enrichResolvedJT808SourceMetadata(ctx context.Context, e
|
||||
func (r *MySQLResolver) lookupJT808Registration(ctx context.Context, phone string) (vin string, deviceID string, plate string, cacheStatus string, err error) {
|
||||
phone = normalizePhone(phone)
|
||||
now := time.Now()
|
||||
// The immutable snapshot is the authoritative vehicle binding. Registration
|
||||
// and authentication frames can create a short-lived local session whose VIN
|
||||
// is still "unknown"; letting that session win would permanently mask a known
|
||||
// snapshot because every location frame refreshes the session TTL.
|
||||
if snapshot, ok := r.snapshotRegistration(phone); ok && snapshot.canResolveVehicle() {
|
||||
return snapshot.vin, snapshot.deviceID, snapshot.plate, "snapshot", nil
|
||||
}
|
||||
var stale registrationCacheEntry
|
||||
hasStale := false
|
||||
r.lookupMu.Lock()
|
||||
@@ -659,6 +666,11 @@ func (r *MySQLResolver) lookupJT808Registration(ctx context.Context, phone strin
|
||||
return entry.vin, entry.deviceID, entry.plate, "", nil
|
||||
}
|
||||
|
||||
func (e registrationCacheEntry) canResolveVehicle() bool {
|
||||
vin := strings.TrimSpace(e.vin)
|
||||
return (vin != "" && !strings.EqualFold(vin, "unknown")) || strings.TrimSpace(e.plate) != ""
|
||||
}
|
||||
|
||||
func addIdentityMetadata(parsed map[string]any, source string, value string) {
|
||||
if parsed == nil {
|
||||
return
|
||||
|
||||
@@ -74,6 +74,62 @@ func TestSnapshotOnlyResolverMissDoesNotQueryMySQL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKnownSnapshotRecoversAfterUnknownJT808SessionCache(t *testing.T) {
|
||||
db, mock := newMockDB(t)
|
||||
defer db.Close()
|
||||
|
||||
resolver := NewMySQLResolverWithOptions(db, "vehicle_identity_binding", MySQLResolverOptions{
|
||||
SnapshotOnlyLookups: true,
|
||||
DisableRegistrationWrites: true,
|
||||
LookupCacheTTL: 10 * time.Minute,
|
||||
})
|
||||
resolver.snapshot.Store(&identitySnapshot{
|
||||
bindings: map[string]string{},
|
||||
identifiers: map[string]vehicleIdentifierMatch{},
|
||||
registrations: map[string]registrationCacheEntry{
|
||||
"64100005824": {vin: "LB9A32A2XR0LS1401", plate: "粤AGP9713"},
|
||||
},
|
||||
sources: map[string]sourceMetadata{},
|
||||
})
|
||||
|
||||
// A reconnect normally authenticates before the first location report. The
|
||||
// authentication frame has no VIN and therefore creates an unknown session.
|
||||
if _, err := resolver.Resolve(context.Background(), envelope.FrameEnvelope{
|
||||
Protocol: envelope.ProtocolJT808,
|
||||
MessageID: JT808AuthMessageID,
|
||||
Phone: "64100005824",
|
||||
Parsed: map[string]any{},
|
||||
}); err != nil {
|
||||
t.Fatalf("resolve authentication frame: %v", err)
|
||||
}
|
||||
if entry := resolver.registrationCache["64100005824"]; entry.vin != "unknown" {
|
||||
t.Fatalf("authentication session vin = %q, want unknown setup", entry.vin)
|
||||
}
|
||||
|
||||
resolved, err := resolver.Resolve(context.Background(), envelope.FrameEnvelope{
|
||||
Protocol: envelope.ProtocolJT808,
|
||||
MessageID: "0x0200",
|
||||
Phone: "64100005824",
|
||||
Parsed: map[string]any{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("resolve location frame: %v", err)
|
||||
}
|
||||
if resolved.VIN != "LB9A32A2XR0LS1401" || resolved.Plate != "粤AGP9713" {
|
||||
t.Fatalf("resolved identity = vin:%q plate:%q", resolved.VIN, resolved.Plate)
|
||||
}
|
||||
identityMetadata, _ := resolved.Parsed["identity"].(map[string]any)
|
||||
if identityMetadata["cache_status"] != "snapshot" {
|
||||
t.Fatalf("identity metadata = %#v, want authoritative snapshot", identityMetadata)
|
||||
}
|
||||
if entry := resolver.registrationCache["64100005824"]; entry.vin != "LB9A32A2XR0LS1401" {
|
||||
t.Fatalf("location frame did not heal session cache: %+v", entry)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("snapshot-only recovery should not query mysql: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshSnapshotFailureKeepsLastKnownGoodSnapshot(t *testing.T) {
|
||||
db, mock := newMockDB(t)
|
||||
defer db.Close()
|
||||
|
||||
Reference in New Issue
Block a user