golang封装可扩展的crontab

1. 导入cron第三方包

Go 复制代码
go get github.com/robfig/cron/v3

2. 申明变量

Go 复制代码
// 单个cron的interface
type RunCronItem interface {
	Run()         // 计划任务执行体
	Start() error // 初始化用的,第一次启动的时候,调用,后续不再执行
	Name() string // 计划任务配置名
}

// 申明所有计划任务的配置
type CronTaskItem struct {
	name    string
	runItem RunCronItem
}

// JobStatus 任务状态
type JobStatus struct {
	LastRun     time.Time
	LastSuccess bool
	IsRunning   bool
	Mutex       sync.Mutex
}

// CronManager Cron任务管理器
type CronManager struct {
	cron      *cron.Cron
	jobs      map[string]func()
	jobStatus map[string]*JobStatus
	config    config.ProxyConfig // 此处为配置文件
}

3. 配置文件

config.yaml

Go 复制代码
cron_jobs:
  start_file_resend:
    Schedule: "0 */2 * * * *"  # 每2分钟将trace文件发送到客户端
    Enabled: false
    Description: "每2分钟将文件发到云端"

config.go

Go 复制代码
import (
	"flag"
	"github.com/spf13/viper"
	"log"
)

type ProxyConfig struct {
...
	LogLevel     uint32                   `mapstructure:"log_level"`
	Log          LogConfig                `mapstructure:"log"`
	CronJobs     map[string]CronJobConfig `mapstructure:"cron_jobs"` //此处为计划任务的配置
....

}

4. 创建计划任务管理器

Go 复制代码
// NewCronManager 创建Cron管理器
func NewCronManager(appConfig config.ProxyConfig) *CronManager {
	// 创建Cron实例,使用秒级精度(支持6位cron表达式)
	c := cron.New(cron.WithSeconds())

	manager := &CronManager{
		cron:      c,
		jobs:      make(map[string]func()),
		jobStatus: make(map[string]*JobStatus),
		config:    appConfig,
	}

	return manager
}

// AddJob 添加任务
func (m *CronManager) AddJob(name string, jobItem RunCronItem) error {
	// 查找配置
	jobConfig, exists := m.config.CronJobs[name]
	if !exists {
		return fmt.Errorf("定时任务名称找不到:%s", name)
	}

	if !jobConfig.Enabled {
		logger.Log.Error("定时任务未启用", zap.String("name", name))
		return nil
	}

	// 初始化任务状态
	m.jobStatus[name] = &JobStatus{}

	// 包装任务函数,添加并发控制
	wrappedFunc := func() {
		status := m.jobStatus[name]

		// 检查任务是否正在运行
		status.Mutex.Lock()
		if status.IsRunning {
			logger.Log.Info("定时任务已经运行中,请稍后", zap.String("name", name))
			status.Mutex.Unlock()
			return
		}

		// 标记任务开始运行
		status.IsRunning = true
		status.LastRun = time.Now()
		status.Mutex.Unlock()

		// 执行任务
		logger.Log.Debug("定时任务正在执行", zap.String("name", name))
		startTime := time.Now()

		defer func() {
			// 捕获panic,确保任务状态正确更新
			if r := recover(); r != nil {
				logger.Log.Error(fmt.Sprintf("定时任务 %s panicked: %v", name, r))
				status.LastSuccess = false
			}

			// 更新任务状态
			status.Mutex.Lock()
			status.IsRunning = false
			status.Mutex.Unlock()

			duration := time.Since(startTime)
			logger.Log.Debug(fmt.Sprintf("定时任务完成:%s, duration: %v", name, duration))
		}()

		// 执行实际任务
		jobItem.Run()
		status.LastSuccess = true
	}

	// 添加任务到cron
	m.jobs[name] = wrappedFunc
	_, err := m.cron.AddFunc(jobConfig.Schedule, wrappedFunc)
	if err != nil {
		return fmt.Errorf("failed to add job %s: %v", name, err)
	}

	logger.Log.Debug(fmt.Sprintf("添加定时任务成功: %s with schedule: %s", name, jobConfig.Schedule))

	return nil
}

// StartAllJobs 启动所有配置中启用的任务
func (m *CronManager) StartAllJobs() error {
	for name := range m.config.CronJobs {
		// 这里不直接添加任务,因为任务函数需要外部提供
		// 外部需要先调用 AddJob 添加具体的任务函数
		logger.Log.Debug(fmt.Sprintf("启用所有配置中的定时任务: %s", name))
	}

	m.cron.Start()
	logger.Log.Debug("定时任务已全部开启")
	return nil
}

// Start 启动所有任务
func (m *CronManager) Start() {
	m.cron.Start()
	logger.Log.Debug("Cron manager started")
}

// Stop 停止所有任务
func (m *CronManager) Stop() {
	m.cron.Stop()
	logger.Log.Debug("Cron manager stopped")
}

// GetJobStatus 获取任务状态
func (m *CronManager) GetJobStatus(name string) (*JobStatus, error) {
	status, exists := m.jobStatus[name]
	if !exists {
		return nil, fmt.Errorf("job %s not found", name)
	}
	return status, nil
}

// ListJobs 列出所有任务
func (m *CronManager) ListJobs() []string {
	var jobs []string
	for name := range m.jobs {
		jobs = append(jobs, name)
	}
	return jobs
}

5. 配置cron和启动

Go 复制代码
// 此处配置您的计划任务列表
func crontMangerList() []CronTaskItem {
	cronTaskList := []RunCronItem{
		... // 这里可以配置你自己的计划任务的struct,返回满足interface RunCronItem

	}
	crontaskItems := make([]CronTaskItem, 0, len(cronTaskList))
	for _, item := range cronTaskList {
		crontaskItems = append(crontaskItems, CronTaskItem{
			name:    item.Name(),
			runItem: item,
		})
	}
	return crontaskItems
}

// 启动job客户端
func InitCronJobClient(config config.ProxyConfig) {
	manager := NewCronManager(config)
	// 添加任务,使用 WaitGroup 跟踪
	cronList := crontMangerList()
	for _, cronItem := range cronList {
		err := manager.AddJob(cronItem.name, cronItem.runItem)
		if err != nil {
			logger.Log.Error("加入定时任务失败job:%s err:%v", zap.String("job_name", cronItem.name), zap.Error(err))
		}
		err = cronItem.runItem.Start()
		if err != nil {
			panic(fmt.Sprintf("计划任务初始化失败:%v", err))
		}
	}

	// 启动 Cron 管理器
	manager.Start()
	logger.Log.Info("CronJob InitCronJobClient")
}
相关推荐
weixin_467209283 小时前
Qt Creator打开项目提示no valid settings file could be found
开发语言·qt
MeowRain3 小时前
Java类加载流程
后端
九转成圣3 小时前
JWT 全面解析与 Spring Boot 实战教程
java·spring boot·后端
国服第二切图仔3 小时前
Rust开发之使用match和if let处理Result错误
开发语言·网络·rust
2501_938773994 小时前
从字节码生成看 Lua VM 前端与后端协同:编译器与执行器衔接逻辑
开发语言·前端·lua
huangql5204 小时前
Nginx 从零到精通 - 最详细的循序渐进教程
开发语言·网络·nginx
绝无仅有4 小时前
某游戏大厂 Java 面试题深度解析(四)
后端·mysql·架构
Victor3564 小时前
Redis(97)Redis的日志文件如何管理?
后端
=>>漫反射=>>4 小时前
【Spring Boot Starter 设计思考:分离模式是否适用于所有场景】
java·spring boot·后端·设计规范·自动装配