Files
xlgo-core/cron/cron.go
T
杭州明婳科技 2dfcbbbb22 init
2026-04-30 23:26:14 +08:00

466 lines
9.8 KiB
Go

package cron
import (
"context"
"fmt"
"strings"
"sync"
"time"
)
// Task 定时任务
type Task struct {
Name string // 任务名称
Schedule Schedule // 调度规则
Handler TaskHandler // 任务处理函数
Enabled bool // 是否启用
LastRun time.Time // 上次运行时间
NextRun time.Time // 下次运行时间
RunCount int // 运行次数
}
// TaskHandler 任务处理函数
type TaskHandler func(ctx context.Context) error
// Schedule 调度接口
type Schedule interface {
Next(now time.Time) time.Time
}
// Scheduler 调度器
type Scheduler struct {
tasks map[string]*Task
mu sync.RWMutex
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
running bool
}
// NewScheduler 创建调度器
func NewScheduler() *Scheduler {
ctx, cancel := context.WithCancel(context.Background())
return &Scheduler{
tasks: make(map[string]*Task),
ctx: ctx,
cancel: cancel,
}
}
// AddTask 添加任务
func (s *Scheduler) AddTask(name string, schedule Schedule, handler TaskHandler) *Task {
task := &Task{
Name: name,
Schedule: schedule,
Handler: handler,
Enabled: true,
NextRun: schedule.Next(time.Now()),
}
s.mu.Lock()
s.tasks[name] = task
s.mu.Unlock()
return task
}
// RemoveTask 移除任务
func (s *Scheduler) RemoveTask(name string) {
s.mu.Lock()
delete(s.tasks, name)
s.mu.Unlock()
}
// EnableTask 启用任务
func (s *Scheduler) EnableTask(name string) error {
s.mu.Lock()
defer s.mu.Unlock()
task, ok := s.tasks[name]
if !ok {
return fmt.Errorf("任务不存在: %s", name)
}
task.Enabled = true
task.NextRun = task.Schedule.Next(time.Now())
return nil
}
// DisableTask 禁用任务
func (s *Scheduler) DisableTask(name string) error {
s.mu.Lock()
defer s.mu.Unlock()
task, ok := s.tasks[name]
if !ok {
return fmt.Errorf("任务不存在: %s", name)
}
task.Enabled = false
return nil
}
// GetTask 获取任务
func (s *Scheduler) GetTask(name string) (*Task, error) {
s.mu.RLock()
defer s.mu.RUnlock()
task, ok := s.tasks[name]
if !ok {
return nil, fmt.Errorf("任务不存在: %s", name)
}
return task, nil
}
// ListTasks 获取所有任务
func (s *Scheduler) ListTasks() []*Task {
s.mu.RLock()
defer s.mu.RUnlock()
tasks := make([]*Task, 0, len(s.tasks))
for _, task := range s.tasks {
tasks = append(tasks, task)
}
return tasks
}
// RunTask 立即运行任务
func (s *Scheduler) RunTask(name string) error {
s.mu.RLock()
task, ok := s.tasks[name]
s.mu.RUnlock()
if !ok {
return fmt.Errorf("任务不存在: %s", name)
}
return s.runTask(task)
}
// runTask 执行任务
func (s *Scheduler) runTask(task *Task) error {
task.LastRun = time.Now()
task.RunCount++
err := task.Handler(s.ctx)
s.mu.Lock()
task.NextRun = task.Schedule.Next(time.Now())
s.mu.Unlock()
return err
}
// Start 启动调度器
func (s *Scheduler) Start() {
s.mu.Lock()
if s.running {
s.mu.Unlock()
return
}
s.running = true
s.mu.Unlock()
s.wg.Add(1)
go s.run()
}
// Stop 停止调度器
func (s *Scheduler) Stop() {
s.mu.Lock()
if !s.running {
s.mu.Unlock()
return
}
s.running = false
s.mu.Unlock()
s.cancel()
s.wg.Wait()
}
// run 运行调度循环
func (s *Scheduler) run() {
defer s.wg.Done()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-s.ctx.Done():
return
case <-ticker.C:
s.checkAndRun()
}
}
}
// checkAndRun 检查并运行到期任务
func (s *Scheduler) checkAndRun() {
now := time.Now()
s.mu.RLock()
for _, task := range s.tasks {
if task.Enabled && !task.NextRun.IsZero() && now.After(task.NextRun) {
go s.runTask(task)
}
}
s.mu.RUnlock()
}
// IntervalSchedule 固定间隔调度
type IntervalSchedule struct {
Interval time.Duration
}
// Next 计算下次运行时间
func (s *IntervalSchedule) Next(now time.Time) time.Time {
return now.Add(s.Interval)
}
// Every 每隔指定时间运行
func Every(interval time.Duration) *IntervalSchedule {
return &IntervalSchedule{Interval: interval}
}
// DailySchedule 每日定时调度
type DailySchedule struct {
Hour int
Minute int
}
// Next 计算下次运行时间
func (s *DailySchedule) Next(now time.Time) time.Time {
next := time.Date(now.Year(), now.Month(), now.Day(), s.Hour, s.Minute, 0, 0, now.Location())
if next.Before(now) || next.Equal(now) {
next = next.Add(24 * time.Hour)
}
return next
}
// Daily 每日指定时间运行
func Daily(hour, minute int) *DailySchedule {
return &DailySchedule{Hour: hour, Minute: minute}
}
// WeeklySchedule 每周定时调度
type WeeklySchedule struct {
Day time.Weekday
Hour int
Minute int
}
// Next 计算下次运行时间
func (s *WeeklySchedule) Next(now time.Time) time.Time {
daysUntil := int(s.Day) - int(now.Weekday())
if daysUntil <= 0 {
daysUntil += 7
}
next := time.Date(now.Year(), now.Month(), now.Day()+daysUntil, s.Hour, s.Minute, 0, 0, now.Location())
return next
}
// Weekly 每周指定时间运行
func Weekly(day time.Weekday, hour, minute int) *WeeklySchedule {
return &WeeklySchedule{Day: day, Hour: hour, Minute: minute}
}
// FullCronSchedule 完整 Cron 表达式调度
// 格式: "分钟 小时 日 月 星期" (5字段)
// 示例: "0 12 * * *" 每天12点
// "0 0 1 * *" 每月1号凌晨
// "0 9-17 * * 1-5" 周一到周五 9点到17点每小时
type FullCronSchedule struct {
Minute string // 分钟: 0-59, "*", "*/n", "a-b", "a,b,c"
Hour string // 小时: 0-23, "*", "*/n", "a-b", "a,b,c"
Day string // 日: 1-31, "*", "*/n", "a-b", "a,b,c"
Month string // 月: 1-12, "*", "*/n", "a-b", "a,b,c"
Weekday string // 星期: 0-6 (周日=0), "*", "*/n", "a-b", "a,b,c"
}
// Next 计算下次运行时间
func (s *FullCronSchedule) Next(now time.Time) time.Time {
// 从下一分钟开始查找
next := now.Add(time.Minute)
next = time.Date(next.Year(), next.Month(), next.Day(), next.Hour(), next.Minute(), 0, 0, next.Location())
// 最多查找一年(防止无效表达式死循环)
maxAttempts := 366 * 24 * 60
for i := 0; i < maxAttempts; i++ {
if s.match(next) {
return next
}
next = next.Add(time.Minute)
}
return time.Time{}
}
// match 检查时间是否匹配表达式
func (s *FullCronSchedule) match(t time.Time) bool {
return s.matchField(s.Minute, t.Minute(), 0, 59) &&
s.matchField(s.Hour, t.Hour(), 0, 23) &&
s.matchField(s.Day, t.Day(), 1, 31) &&
s.matchField(s.Month, int(t.Month()), 1, 12) &&
s.matchField(s.Weekday, int(t.Weekday()), 0, 6)
}
// matchField 匹配单个字段
func (s *FullCronSchedule) matchField(field string, value int, min, max int) bool {
if field == "*" {
return true
}
// 处理范围 "a-b"
if strings.Contains(field, "-") {
parts := strings.Split(field, "-")
if len(parts) == 2 {
start := parseInt(parts[0])
end := parseInt(parts[1])
return value >= start && value <= end
}
}
// 处理步长 "*/n"
if strings.HasPrefix(field, "*/") {
step := parseInt(strings.TrimPrefix(field, "*/"))
if step > 0 {
return (value - min) % step == 0
}
return true
}
// 处理列表 "a,b,c"
for _, p := range strings.Split(field, ",") {
if parseInt(strings.TrimSpace(p)) == value {
return true
}
}
return false
}
// ParseCron 解析完整 Cron 表达式
// 格式: "分钟 小时 日 月 星期"
// 示例:
//
// "0 12 * * *" - 每天12:00
// "*/15 * * * *" - 每15分钟
// "0 9-17 * * 1-5" - 工作日9-17点每小时
// "0 0 1 * *" - 每月1号凌晨
// "0 0 * * 0" - 每周日凌晨
func ParseCron(expr string) *FullCronSchedule {
fields := strings.Fields(expr)
if len(fields) != 5 {
// 默认每分钟执行
return &FullCronSchedule{"*", "*", "*", "*", "*"}
}
return &FullCronSchedule{
Minute: fields[0],
Hour: fields[1],
Day: fields[2],
Month: fields[3],
Weekday: fields[4],
}
}
// CronSchedule 简化 Cron 调度(仅分钟和小时)
type CronSchedule struct {
Minute string // 分钟: "*" 或具体值如 "0,15,30"
Hour string // 小时: "*" 或具体值如 "8,12"
}
// Next 计算下次运行时间
func (s *CronSchedule) Next(now time.Time) time.Time {
for i := 1; i <= 60*24; i++ { // 最多查找24小时
next := now.Add(time.Duration(i) * time.Minute)
if s.matchMinute(next.Minute()) && s.matchHour(next.Hour()) {
return next
}
}
return time.Time{}
}
func (s *CronSchedule) matchMinute(minute int) bool {
return s.Minute == "*" || s.matchValue(s.Minute, minute)
}
func (s *CronSchedule) matchHour(hour int) bool {
return s.Hour == "*" || s.matchValue(s.Hour, hour)
}
func (s *CronSchedule) matchValue(pattern string, value int) bool {
for _, p := range splitPattern(pattern) {
if p == value {
return true
}
}
return false
}
func splitPattern(pattern string) []int {
var values []int
for _, p := range split(pattern, ',') {
v := parseInt(p)
if v >= 0 {
values = append(values, v)
}
}
return values
}
func split(s string, sep byte) []string {
var parts []string
start := 0
for i := 0; i < len(s); i++ {
if s[i] == sep {
parts = append(parts, s[start:i])
start = i + 1
}
}
parts = append(parts, s[start:])
return parts
}
func parseInt(s string) int {
var v int
for _, c := range s {
if c >= '0' && c <= '9' {
v = v*10 + int(c-'0')
}
}
return v
}
// Cron 创建类 Cron 调度
func Cron(minute, hour string) *CronSchedule {
return &CronSchedule{Minute: minute, Hour: hour}
}
// 全局调度器
var globalScheduler *Scheduler
var schedulerOnce sync.Once
// GetScheduler 获取全局调度器
func GetScheduler() *Scheduler {
schedulerOnce.Do(func() {
globalScheduler = NewScheduler()
})
return globalScheduler
}
// AddTask 添加任务到全局调度器
func AddTask(name string, schedule Schedule, handler TaskHandler) *Task {
return GetScheduler().AddTask(name, schedule, handler)
}
// Start 启动全局调度器
func Start() {
GetScheduler().Start()
}
// Stop 停止全局调度器
func Stop() {
GetScheduler().Stop()
}