package jobs import ( "context" "fmt" "log" "strconv" "strings" "time" "stock-user-system/internal/config" "stock-user-system/internal/services" ) var chinaLoc, _ = time.LoadLocation("Asia/Shanghai") // StartStockSyncScheduler 启动盘后数据同步调度器。 func StartStockSyncScheduler(cfg *config.Config, svc *services.StockSyncService) { if !cfg.DataSyncEnabled { log.Println("stock sync scheduler disabled") return } hour, minute, err := parseSyncTime(cfg.DataSyncTime) if err != nil { log.Printf("invalid DATA_SYNC_TIME %q, scheduler not started: %v", cfg.DataSyncTime, err) return } go func() { for { next := nextSyncTime(hour, minute, cfg.DataSyncWeekdays) wait := time.Until(next) log.Printf("next stock sync scheduled at %s (in %v)", next.Format(time.RFC3339), wait) time.Sleep(wait) if _, err := svc.Sync(context.Background(), "schedule"); err != nil { log.Printf("scheduled stock sync failed: %v", err) } else { log.Println("scheduled stock sync completed") } } }() } func parseSyncTime(s string) (int, int, error) { parts := strings.Split(s, ":") if len(parts) != 2 { return 0, 0, fmt.Errorf("expected HH:MM") } h, err := strconv.Atoi(parts[0]) if err != nil { return 0, 0, err } m, err := strconv.Atoi(parts[1]) if err != nil { return 0, 0, err } if h < 0 || h > 23 || m < 0 || m > 59 { return 0, 0, fmt.Errorf("invalid time") } return h, m, nil } func nextSyncTime(hour, minute int, weekdaysOnly bool) time.Time { now := time.Now().In(chinaLoc) today := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, chinaLoc) candidate := today if now.Equal(candidate) || now.After(candidate) { candidate = candidate.Add(24 * time.Hour) } if weekdaysOnly { for candidate.Weekday() == time.Saturday || candidate.Weekday() == time.Sunday { candidate = candidate.Add(24 * time.Hour) } } return candidate }