// Filename: internal/scheduler/scheduler.go package scheduler import ( "gemini-balancer/internal/repository" "gemini-balancer/internal/service" "time" "github.com/go-co-op/gocron" "github.com/sirupsen/logrus" ) type Scheduler struct { gocronScheduler *gocron.Scheduler logger *logrus.Entry statsService *service.StatsService keyRepo repository.KeyRepository // healthCheckService *service.HealthCheckService // 健康检查任务预留 } func NewScheduler(statsSvc *service.StatsService, keyRepo repository.KeyRepository, logger *logrus.Logger) *Scheduler { s := gocron.NewScheduler(time.UTC) s.TagsUnique() return &Scheduler{ gocronScheduler: s, logger: logger.WithField("component", "Scheduler📆"), statsService: statsSvc, keyRepo: keyRepo, } } func (s *Scheduler) Start() { s.logger.Info("Starting scheduler and registering jobs...") // --- 任务注册 --- // 使用CRON表达式,精确定义“每小时的第5分钟”执行 _, err := s.gocronScheduler.Cron("5 * * * *").Tag("stats-aggregation").Do(func() { s.logger.Info("Executing hourly request stats aggregation...") if err := s.statsService.AggregateHourlyStats(); err != nil { s.logger.WithError(err).Error("Hourly stats aggregation failed.") } else { s.logger.Info("Hourly stats aggregation completed successfully.") } }) if err != nil { s.logger.Errorf("Failed to schedule [stats-aggregation]: %v", err) } // 任务二:(预留) 自动健康检查 (例如:每10分钟一次) /* _, err = s.gocronScheduler.Every(10).Minutes().Tag("auto-health-check").Do(func() { s.logger.Info("Executing periodic health check for all groups...") // s.healthCheckService.StartGlobalCheckTask() // 伪代码 }) if err != nil { s.logger.Errorf("Failed to schedule [auto-health-check]: %v", err) } */ // [NEW] --- 任务三: 清理软删除的API Keys --- // Executes once daily at 3:15 AM UTC. _, err = s.gocronScheduler.Cron("15 3 * * *").Tag("cleanup-soft-deleted-keys").Do(func() { s.logger.Info("Executing daily cleanup of soft-deleted API keys...") // Let's assume a retention period of 7 days for now. // In a real scenario, this should come from settings. const retentionDays = 7 count, err := s.keyRepo.HardDeleteSoftDeletedBefore(time.Now().AddDate(0, 0, -retentionDays)) if err != nil { s.logger.WithError(err).Error("Daily cleanup of soft-deleted keys failed.") } else if count > 0 { s.logger.Infof("Daily cleanup completed: Permanently deleted %d expired soft-deleted keys.", count) } else { s.logger.Info("Daily cleanup completed: No expired soft-deleted keys found to delete.") } }) if err != nil { s.logger.Errorf("Failed to schedule [cleanup-soft-deleted-keys]: %v", err) } // --- 任务注册结束 --- s.gocronScheduler.StartAsync() // 异步启动,不阻塞应用主线程 s.logger.Info("Scheduler started.") } func (s *Scheduler) Stop() { s.logger.Info("Stopping scheduler...") s.gocronScheduler.Stop() s.logger.Info("Scheduler stopped.") }