Files
ez-api/internal/cron/quota_reset.go
zenfun 05caed37c2 refactor(cron): migrate cron jobs to foundation scheduler
Replace custom goroutine-based scheduling in cron jobs with centralized
foundation scheduler. Each cron job now exposes a RunOnce method called
by the scheduler instead of managing its own ticker loop.

Changes:
- Remove interval/enabled config from cron job structs
- Convert Start() methods to RunOnce() for all cron jobs
- Add scheduler setup in main.go with configurable intervals
- Update foundation dependency to v0.6.0 for scheduler support
- Update tests to validate RunOnce nil-safety
2025-12-31 20:42:25 +08:00

77 lines
1.9 KiB
Go

package cron
import (
"context"
"log/slog"
"strings"
"time"
"github.com/ez-api/ez-api/internal/model"
"github.com/ez-api/ez-api/internal/service"
"gorm.io/gorm"
)
type QuotaResetter struct {
db *gorm.DB
sync *service.SyncService
}
func NewQuotaResetter(db *gorm.DB, sync *service.SyncService) *QuotaResetter {
return &QuotaResetter{db: db, sync: sync}
}
// RunOnce executes a single quota reset check. Called by scheduler.
func (q *QuotaResetter) RunOnce(ctx context.Context) {
if q == nil || q.db == nil {
return
}
if err := q.resetOnce(ctx); err != nil {
slog.Default().Warn("quota reset failed", "err", err)
}
}
func (q *QuotaResetter) resetOnce(ctx context.Context) error {
if q == nil || q.db == nil {
return nil
}
now := time.Now().UTC()
var keys []model.Key
if err := q.db.Where("quota_reset_type IN ? AND (quota_reset_at IS NULL OR quota_reset_at <= ?)", []string{"daily", "monthly"}, now).Find(&keys).Error; err != nil {
return err
}
for i := range keys {
resetType := strings.ToLower(strings.TrimSpace(keys[i].QuotaResetType))
nextAt, ok := nextQuotaReset(now, resetType)
if !ok {
continue
}
if err := q.db.Model(&keys[i]).Updates(map[string]any{
"quota_used": 0,
"quota_reset_at": nextAt,
}).Error; err != nil {
slog.Default().Warn("quota reset update failed", "key_id", keys[i].ID, "err", err)
continue
}
keys[i].QuotaUsed = 0
keys[i].QuotaResetAt = &nextAt
if q.sync != nil {
_ = q.sync.SyncKey(&keys[i])
}
}
return nil
}
func nextQuotaReset(now time.Time, resetType string) (time.Time, bool) {
now = now.UTC()
switch resetType {
case "daily":
next := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, 1)
return next, true
case "monthly":
next := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0)
return next, true
default:
return time.Time{}, false
}
}