feat(monitor): match motion to report intervals

This commit is contained in:
lingniu
2026-07-16 09:55:10 +08:00
parent 3bbae099bc
commit e0e7e28762
9 changed files with 95 additions and 33 deletions

View File

@@ -39,17 +39,18 @@ type MonitorWorkspaceResponse struct {
}
type MonitorMapPoint struct {
VIN string `json:"vin"`
Plate string `json:"plate"`
Protocol string `json:"protocol"`
Protocols []string `json:"protocols"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
SpeedKmh float64 `json:"speedKmh"`
SOCPercent float64 `json:"socPercent"`
TotalMileageKm float64 `json:"totalMileageKm"`
LastSeen string `json:"lastSeen"`
Status string `json:"status"`
VIN string `json:"vin"`
Plate string `json:"plate"`
Protocol string `json:"protocol"`
Protocols []string `json:"protocols"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
SpeedKmh float64 `json:"speedKmh"`
SOCPercent float64 `json:"socPercent"`
TotalMileageKm float64 `json:"totalMileageKm"`
LastSeen string `json:"lastSeen"`
ReportIntervalMs *int64 `json:"reportIntervalMs,omitempty"`
Status string `json:"status"`
}
type MonitorMapCluster struct {
@@ -1024,6 +1025,7 @@ type VehicleRealtimeRow struct {
SOCPercent float64 `json:"socPercent"`
TotalMileageKm float64 `json:"totalMileageKm"`
LastSeen string `json:"lastSeen"`
ReportIntervalMs *int64 `json:"reportIntervalMs,omitempty"`
}
type HistoryLocationRow struct {

View File

@@ -394,6 +394,7 @@ func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
}
groupSQL := `FROM vehicle_realtime_location l ` +
`LEFT JOIN vehicle_identity_binding b ON b.vin = l.vin ` +
`LEFT JOIN vehicle_realtime_snapshot s ON s.vin = l.vin AND s.protocol = l.protocol ` +
`WHERE ` + strings.Join(where, " AND ") + ` ` +
`GROUP BY l.vin, b.plate, b.phone, b.oem ` +
havingSQL
@@ -414,7 +415,8 @@ func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.speed_kmh AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS speed_kmh, ` +
`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.soc_percent AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS soc_percent, ` +
`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.total_mileage_km AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS total_mileage_km, ` +
`COALESCE(DATE_FORMAT(MAX(l.updated_at), '%Y-%m-%d %H:%i:%s'), '') AS last_seen ` +
`COALESCE(DATE_FORMAT(MAX(l.updated_at), '%Y-%m-%d %H:%i:%s'), '') AS last_seen, ` +
`CAST(SUBSTRING_INDEX(GROUP_CONCAT(COALESCE(s.access_report_interval_ms, -1) ORDER BY ` + orderExpr + `), ',', 1) AS SIGNED) AS report_interval_ms ` +
groupSQL +
`ORDER BY MAX(l.updated_at) DESC, l.vin ASC LIMIT ? OFFSET ?`,
Args: args,

View File

@@ -401,7 +401,8 @@ func (s *ProductionStore) VehicleRealtime(ctx context.Context, query url.Values)
var onlineProtocols string
var online int
var longitude, latitude, speed, soc, mileage string
if err := rows.Scan(&row.VIN, &row.Plate, &row.Phone, &row.OEM, &protocols, &onlineProtocols, &row.SourceCount, &row.OnlineSourceCount, &online, &row.BindingStatus, &row.PrimaryProtocol, &longitude, &latitude, &speed, &soc, &mileage, &row.LastSeen); err != nil {
var reportIntervalMs int64
if err := rows.Scan(&row.VIN, &row.Plate, &row.Phone, &row.OEM, &protocols, &onlineProtocols, &row.SourceCount, &row.OnlineSourceCount, &online, &row.BindingStatus, &row.PrimaryProtocol, &longitude, &latitude, &speed, &soc, &mileage, &row.LastSeen, &reportIntervalMs); err != nil {
return Page[VehicleRealtimeRow]{}, err
}
row.Protocols = splitCSV(protocols)
@@ -413,6 +414,9 @@ func (s *ProductionStore) VehicleRealtime(ctx context.Context, query url.Values)
row.SpeedKmh = parseFloatString(speed)
row.SOCPercent = parseFloatString(soc)
row.TotalMileageKm = parseFloatString(mileage)
if reportIntervalMs > 0 {
row.ReportIntervalMs = &reportIntervalMs
}
items = append(items, row)
}
if err := rows.Err(); err != nil {

View File

@@ -200,7 +200,7 @@ func TestBuildRealtimeLocationSQL(t *testing.T) {
func TestBuildVehicleRealtimeSQL(t *testing.T) {
query := url.Values{"vin": {"粤A"}, "protocol": {"JT808"}, "online": {"online"}, "limit": {"10"}, "offset": {"20"}}
built := buildVehicleRealtimeSQL(query)
for _, want := range []string{"vehicle_realtime_location", "vehicle_identity_binding", "GROUP BY l.vin", "GROUP_CONCAT(DISTINCT l.protocol", "online_source_count", "vehicle_realtime_count"} {
for _, want := range []string{"vehicle_realtime_location", "vehicle_identity_binding", "vehicle_realtime_snapshot", "access_report_interval_ms", "GROUP BY l.vin", "GROUP_CONCAT(DISTINCT l.protocol", "online_source_count", "vehicle_realtime_count"} {
if !strings.Contains(built.Text+built.CountText, want) {
t.Fatalf("SQL missing %q: %s / %s", want, built.Text, built.CountText)
}

View File

@@ -606,7 +606,8 @@ func buildMonitorMapResponse(vehicles Page[VehicleRealtimeRow], query url.Values
VIN: vehicle.VIN, Plate: vehicle.Plate, Protocol: vehicle.PrimaryProtocol, Protocols: vehicle.Protocols,
Longitude: vehicle.Longitude, Latitude: vehicle.Latitude, SpeedKmh: vehicle.SpeedKmh,
SOCPercent: vehicle.SOCPercent, TotalMileageKm: vehicle.TotalMileageKm, LastSeen: vehicle.LastSeen,
Status: monitorVehicleStatus(vehicle),
ReportIntervalMs: vehicle.ReportIntervalMs,
Status: monitorVehicleStatus(vehicle),
})
}
if zoom >= monitorPointZoom && len(points) <= monitorPointLimit {

View File

@@ -312,8 +312,9 @@ func TestMonitorWorkspaceSharesOneRealtimeSnapshot(t *testing.T) {
}
func TestMonitorMapUsesMeaningfulClustersAndReleasesPointsAtDetailZoom(t *testing.T) {
reportIntervalMs := int64(30000)
vehicles := Page[VehicleRealtimeRow]{Items: []VehicleRealtimeRow{
{VIN: "NEAR-1", Plate: "粤A00001", Online: true, Longitude: 113.260, Latitude: 23.130},
{VIN: "NEAR-1", Plate: "粤A00001", Online: true, Longitude: 113.260, Latitude: 23.130, ReportIntervalMs: &reportIntervalMs},
{VIN: "NEAR-2", Plate: "粤A00002", Online: true, Longitude: 113.265, Latitude: 23.135},
{VIN: "SINGLE", Plate: "粤A00003", Online: true, Longitude: 113.600, Latitude: 23.500},
}, Total: 3, Limit: 3}
@@ -333,6 +334,9 @@ func TestMonitorMapUsesMeaningfulClustersAndReleasesPointsAtDetailZoom(t *testin
if detail.Mode != "points" || len(detail.Points) != 3 || len(detail.Clusters) != 0 {
t.Fatalf("zoom 11 must release controlled data as exact points: %+v", detail)
}
if detail.Points[0].ReportIntervalMs == nil || *detail.Points[0].ReportIntervalMs != reportIntervalMs {
t.Fatalf("map point must preserve the primary protocol report interval: %+v", detail.Points[0])
}
}
func BenchmarkMonitorMapTenThousandVehicles(b *testing.B) {