From 12930350452684c7494ce559b5c821ed2edd1b6b Mon Sep 17 00:00:00 2001 From: lingniu Date: Fri, 3 Jul 2026 22:29:46 +0800 Subject: [PATCH] feat(platform): count vehicle list pages --- .../apps/api/internal/platform/mysql_queries.go | 14 +++++++++----- .../apps/api/internal/platform/production_store.go | 11 ++++++++++- .../api/internal/platform/query_builders_test.go | 8 +++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go b/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go index 5bdaab19..65ea079c 100644 --- a/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go +++ b/vehicle-data-platform/apps/api/internal/platform/mysql_queries.go @@ -27,18 +27,22 @@ func buildVehicleListSQL(query url.Values) SQLQuery { where = append(where, "s.protocol = ?") args = append(args, protocol) } + countArgs := append([]any(nil), args...) args = append(args, limit, offset) + fromSQL := `FROM vehicle_realtime_snapshot s ` + + `LEFT JOIN vehicle_identity_binding b ON b.vin = s.vin ` + + `LEFT JOIN vehicle_realtime_location l ON l.vin = s.vin AND l.protocol = s.protocol ` + + `WHERE ` + strings.Join(where, " AND ") return SQLQuery{ Text: `SELECT s.vin, COALESCE(NULLIF(s.plate, ''), b.plate, '') AS plate, COALESCE(b.phone, '') AS phone, COALESCE(b.oem, '') AS oem, s.protocol, ` + `CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN 1 ELSE 0 END AS online, ` + `COALESCE(DATE_FORMAT(s.updated_at, '%Y-%m-%d %H:%i:%s'), '') AS last_seen, ` + `COALESCE(CONCAT(l.longitude, ',', l.latitude), '') AS location_text, ` + `CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 100 ELSE 60 END AS binding_score ` + - `FROM vehicle_realtime_snapshot s ` + - `LEFT JOIN vehicle_identity_binding b ON b.vin = s.vin ` + - `LEFT JOIN vehicle_realtime_location l ON l.vin = s.vin AND l.protocol = s.protocol ` + - `WHERE ` + strings.Join(where, " AND ") + ` ORDER BY s.updated_at DESC LIMIT ? OFFSET ?`, - Args: args, + fromSQL + ` ORDER BY s.updated_at DESC, s.vin ASC, s.protocol ASC LIMIT ? OFFSET ?`, + Args: args, + CountText: `SELECT COUNT(*) ` + fromSQL, + CountArgs: countArgs, } } diff --git a/vehicle-data-platform/apps/api/internal/platform/production_store.go b/vehicle-data-platform/apps/api/internal/platform/production_store.go index bd60b065..18fe7e84 100644 --- a/vehicle-data-platform/apps/api/internal/platform/production_store.go +++ b/vehicle-data-platform/apps/api/internal/platform/production_store.go @@ -67,6 +67,12 @@ func (s *ProductionStore) DashboardSummary(ctx context.Context) (DashboardSummar func (s *ProductionStore) Vehicles(ctx context.Context, query url.Values) (Page[VehicleRow], error) { built := buildVehicleListSQL(query) + total := 0 + if built.CountText != "" { + if err := s.db.QueryRowContext(ctx, built.CountText, built.CountArgs...).Scan(&total); err != nil { + return Page[VehicleRow]{}, err + } + } rows, err := s.db.QueryContext(ctx, built.Text, built.Args...) if err != nil { return Page[VehicleRow]{}, err @@ -86,7 +92,10 @@ func (s *ProductionStore) Vehicles(ctx context.Context, query url.Values) (Page[ return Page[VehicleRow]{}, err } limit, offset := buildLimitOffset(query) - return Page[VehicleRow]{Items: items, Total: len(items), Limit: limit, Offset: offset}, nil + if built.CountText == "" { + total = len(items) + } + return Page[VehicleRow]{Items: items, Total: total, Limit: limit, Offset: offset}, nil } func (s *ProductionStore) VehicleCoverage(ctx context.Context, query url.Values) (Page[VehicleCoverageRow], error) { diff --git a/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go b/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go index b8501018..5f009f66 100644 --- a/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go @@ -9,14 +9,20 @@ import ( func TestBuildVehicleListSQL(t *testing.T) { query := url.Values{"keyword": {"粤A"}, "protocol": {"JT808"}, "limit": {"5"}, "offset": {"10"}} built := buildVehicleListSQL(query) - for _, want := range []string{"vehicle_identity_binding", "vehicle_realtime_snapshot", "vehicle_realtime_location", "ORDER BY s.updated_at DESC"} { + for _, want := range []string{"vehicle_identity_binding", "vehicle_realtime_snapshot", "vehicle_realtime_location", "ORDER BY s.updated_at DESC, s.vin ASC, s.protocol ASC"} { if !strings.Contains(built.Text, want) { t.Fatalf("SQL missing %q: %s", want, built.Text) } } + if !strings.Contains(built.CountText, "COUNT(*)") || strings.Contains(built.CountText, "LIMIT") { + t.Fatalf("count SQL = %s", built.CountText) + } if len(built.Args) != 7 || built.Args[0] != "%粤A%" || built.Args[4] != "JT808" || built.Args[5] != 5 || built.Args[6] != 10 { t.Fatalf("args = %#v", built.Args) } + if len(built.CountArgs) != 5 || built.CountArgs[0] != "%粤A%" || built.CountArgs[4] != "JT808" { + t.Fatalf("count args = %#v", built.CountArgs) + } } func TestBuildVehicleCoverageSQL(t *testing.T) {