package main import ( "context" "database/sql" "encoding/json" "flag" "fmt" _ "github.com/go-sql-driver/mysql" _ "github.com/taosdata/driver-go/v3/taosWS" "lingniu/vehicle-data-platform/apps/api/internal/config" "lingniu/vehicle-data-platform/apps/api/internal/dailygeo" "log" "os" "os/signal" "syscall" "time" ) func main() { if e := run(); e != nil { log.Fatal(e) } } func run() error { mode := flag.String("mode", "status", "status, backfill, resolve, or live") from := flag.String("from", "", "first historical date, defaults to first hydrogen day") to := flag.String("to", "", "last historical date, defaults to yesterday") concurrency := flag.Int("concurrency", 3, "maximum concurrent geocoding requests (1-8)") flag.Parse() cfg := config.Load() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() db, e := sql.Open("mysql", cfg.MySQLDSN) if e != nil { return e } defer db.Close() db.SetMaxOpenConns(12) if *from == "" { if e = db.QueryRowContext(ctx, `SELECT COALESCE(DATE_FORMAT(MIN(stat_date),'%Y-%m-%d'),'') FROM vehicle_open_daily_energy WHERE energy_type='HYDROGEN'`).Scan(from); e != nil { return e } } if *to == "" { *to = time.Now().In(dailygeo.Shanghai).AddDate(0, 0, -1).Format("2006-01-02") } first, e := time.ParseInLocation("2006-01-02", *from, dailygeo.Shanghai) if e != nil { return e } last, e := time.ParseInLocation("2006-01-02", *to, dailygeo.Shanghai) if e != nil { return e } if first.After(last) { return fmt.Errorf("invalid date range") } if *mode == "status" { return printStatus(ctx, db, *from, *to) } if cfg.AMapAPIKey == "" { return fmt.Errorf("AMAP_API_KEY required") } td, e := sql.Open(cfg.TDengineDriver, cfg.TDengineDSN) if e != nil { return e } defer td.Close() td.SetMaxOpenConns(2) w := dailygeo.New(db, td, cfg.TDengineDatabase, &dailygeo.AMap{Key: cfg.AMapAPIKey}) w.Concurrency = *concurrency if *mode == "live" { w.Run(ctx) return ctx.Err() } if *mode != "backfill" && *mode != "resolve" { return fmt.Errorf("invalid mode") } return w.WithLock(ctx, "vehicle_daily_geography_backfill", func() error { if *mode == "backfill" { for d := first; !d.After(last); d = d.AddDate(0, 0, 1) { rctx, c := context.WithTimeout(ctx, 90*time.Second) n, e := w.RefreshDay(rctx, d.Format("2006-01-02")) c() if e != nil { return e } log.Printf("daily_geography_seed date=%s points=%d", d.Format("2006-01-02"), n) } } for { n, failed, e := w.ResolvePending(ctx, *from, *to, 1000) log.Printf("daily_geography_batch resolved=%d failed=%d error=%v", n, failed, e) if ctx.Err() != nil { return ctx.Err() } if e != nil && failed == 0 { return e } if n+failed == 0 { break } } return printStatus(ctx, db, *from, *to) }) } func printStatus(ctx context.Context, db *sql.DB, from, to string) error { rows, e := db.QueryContext(ctx, `SELECT status,COUNT(*) FROM vehicle_daily_geography WHERE stat_date BETWEEN ? AND ? GROUP BY status`, from, to) if e != nil { return e } defer rows.Close() counts := map[string]int{} for rows.Next() { var s string var n int if e = rows.Scan(&s, &n); e != nil { return e } counts[s] = n } if e = rows.Err(); e != nil { return e } rows.Close() var missing int e = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_open_daily_energy h LEFT JOIN vehicle_daily_geography g ON g.vin=h.vin COLLATE utf8mb4_unicode_ci AND g.stat_date=h.stat_date WHERE h.energy_type='HYDROGEN' AND h.stat_date BETWEEN ? AND ? AND g.vin IS NULL`, from, to).Scan(&missing) if e != nil { return e } return json.NewEncoder(os.Stdout).Encode(map[string]any{"from": from, "to": to, "counts": counts, "hydrogenDaysMissing": missing}) }