hostSync/core/cron.go

307 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"fmt"
"sync"
"time"
"github.com/evil7/hostsync/utils"
"github.com/robfig/cron/v3"
)
// 全局文件保存互斥锁,防止多个定时任务同时保存文件
var hostsSaveMutex sync.Mutex
// CronManager 定时任务管理器
type CronManager struct {
cron *cron.Cron
jobs map[string]cron.EntryID
mutex sync.RWMutex
}
// NewCronManager 创建定时任务管理器
func NewCronManager() *CronManager {
// 创建支持秒级的cron实例支持6位表达式
c := cron.New(cron.WithSeconds(), cron.WithLocation(time.Local))
return &CronManager{
cron: c,
jobs: make(map[string]cron.EntryID),
mutex: sync.RWMutex{},
}
}
// Start 启动调度器
func (cm *CronManager) Start() {
cm.cron.Start()
}
// Stop 停止调度器
func (cm *CronManager) Stop() {
cm.cron.Stop()
}
// AddJob 添加定时任务
func (cm *CronManager) AddJob(blockName, cronExpr string, task func()) error {
cm.mutex.Lock()
defer cm.mutex.Unlock()
// 如果已存在,先删除
if existingID, exists := cm.jobs[blockName]; exists {
cm.cron.Remove(existingID)
delete(cm.jobs, blockName)
}
// 创建新任务
entryID, err := cm.cron.AddFunc(cronExpr, func() {
utils.LogDebug("执行定时任务: %s", blockName)
task()
})
if err != nil {
return fmt.Errorf("添加定时任务失败: %v", err)
}
cm.jobs[blockName] = entryID
utils.LogDebug("已添加定时任务: %s (%s)", blockName, cronExpr)
return nil
}
// RemoveJob 删除定时任务
func (cm *CronManager) RemoveJob(blockName string) error {
cm.mutex.Lock()
defer cm.mutex.Unlock()
entryID, exists := cm.jobs[blockName]
if !exists {
return fmt.Errorf("定时任务不存在: %s", blockName)
}
cm.cron.Remove(entryID)
delete(cm.jobs, blockName)
utils.LogDebug("已删除定时任务: %s", blockName)
return nil
}
// ListJobs 列出所有定时任务
func (cm *CronManager) ListJobs() map[string]cron.EntryID {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
result := make(map[string]cron.EntryID)
for name, entryID := range cm.jobs {
result[name] = entryID
}
return result
}
// GetJob 获取指定的定时任务
func (cm *CronManager) GetJob(blockName string) (cron.EntryID, bool) {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
entryID, exists := cm.jobs[blockName]
return entryID, exists
}
// LoadFromHosts 从hosts文件加载定时任务
func (cm *CronManager) LoadFromHosts(hm *HostsManager) error {
resolver := NewDNSResolver()
for blockName, block := range hm.Blocks {
if block.CronJob != "" {
task := func(bn string) func() {
return func() {
defer func() {
if r := recover(); r != nil {
utils.LogError("定时任务 %s 发生错误: %v", bn, r)
}
}()
utils.LogInfo("开始执行定时更新: %s", bn)
// 使用全局锁确保hosts文件保存的原子性
hostsSaveMutex.Lock()
defer hostsSaveMutex.Unlock()
// 重新加载hosts文件以获取最新配置
freshHM := NewHostsManager()
if err := freshHM.Load(); err != nil {
utils.LogError("定时任务 %s 重新加载hosts文件失败: %v", bn, err)
return
}
// 检查块是否仍然存在
freshBlock := freshHM.GetBlock(bn)
if freshBlock == nil {
utils.LogError("定时任务 %s 对应的块已不存在,跳过执行", bn)
return
}
// 检查定时任务是否仍然启用
if freshBlock.CronJob == "" {
utils.LogInfo("定时任务 %s 已被禁用,跳过执行", bn)
return
}
// 使用最新的hosts数据执行更新
utils.LogInfo("开始DNS解析更新: %s", bn)
err := resolver.UpdateBlock(freshHM, bn, "", "", "", false)
if err != nil {
utils.LogError("定时更新失败 %s: %v", bn, err)
} else {
// 获取更新后的块信息,记录更新时间
updatedBlock := freshHM.GetBlock(bn)
if updatedBlock != nil && !updatedBlock.UpdateAt.IsZero() {
utils.LogInfo("定时更新完成: %s更新时间: %s", bn, updatedBlock.UpdateAt.Format("2006-01-02 15:04:05"))
} else {
utils.LogInfo("定时更新完成: %s", bn)
}
}
}
}(blockName)
if err := cm.AddJob(blockName, block.CronJob, task); err != nil {
utils.LogError("加载定时任务失败 %s: %v", blockName, err)
}
}
}
return nil
}
// ValidateCronExpression 验证cron表达式
func (cm *CronManager) ValidateCronExpression(expr string) error {
// 使用robfig/cron/v3的解析器验证表达式
parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
_, err := parser.Parse(expr)
return err
}
// ReloadFromHosts 重新从hosts文件同步定时任务配置
func (cm *CronManager) ReloadFromHosts() error {
utils.LogDebug("正在重新同步定时任务配置...")
// 加载最新的hosts文件
hm := NewHostsManager()
if err := hm.Load(); err != nil {
return fmt.Errorf("重新加载hosts文件失败: %v", err)
}
cm.mutex.Lock()
defer cm.mutex.Unlock()
// 收集当前活跃的任务
currentJobs := make(map[string]string) // blockName -> cronExpr
for blockName := range cm.jobs {
currentJobs[blockName] = ""
}
// 收集hosts文件中的任务配置
hostsJobs := make(map[string]string)
for blockName, block := range hm.Blocks {
if block.CronJob != "" {
hostsJobs[blockName] = block.CronJob
}
}
// 删除不再存在的任务
for blockName := range currentJobs {
if _, exists := hostsJobs[blockName]; !exists {
if entryID, exists := cm.jobs[blockName]; exists {
cm.cron.Remove(entryID)
delete(cm.jobs, blockName)
utils.LogInfo("已删除不存在的定时任务: %s", blockName)
}
}
}
// 添加或更新任务
resolver := NewDNSResolver()
for blockName, cronExpr := range hostsJobs {
// 检查是否需要更新任务
needsUpdate := false
if _, exists := cm.jobs[blockName]; !exists {
needsUpdate = true
} else {
// 这里可以进一步检查cron表达式是否有变化
// 目前简化处理,每次都重新创建以确保同步
if existingEntryID, exists := cm.jobs[blockName]; exists {
cm.cron.Remove(existingEntryID)
delete(cm.jobs, blockName)
}
needsUpdate = true
}
if needsUpdate {
task := func(bn string) func() {
return func() {
defer func() {
if r := recover(); r != nil {
utils.LogError("定时任务 %s 发生错误: %v", bn, r)
}
}()
utils.LogInfo("开始执行定时更新: %s", bn)
// 使用全局锁确保hosts文件保存的原子性
hostsSaveMutex.Lock()
defer hostsSaveMutex.Unlock()
// 重新加载hosts文件以获取最新配置
freshHM := NewHostsManager()
if err := freshHM.Load(); err != nil {
utils.LogError("定时任务 %s 重新加载hosts文件失败: %v", bn, err)
return
}
// 检查块是否仍然存在
freshBlock := freshHM.GetBlock(bn)
if freshBlock == nil {
utils.LogError("定时任务 %s 对应的块已不存在,跳过执行", bn)
return
}
// 检查定时任务是否仍然启用
if freshBlock.CronJob == "" {
utils.LogInfo("定时任务 %s 已被禁用,跳过执行", bn)
return
}
// 使用最新的hosts数据执行更新
utils.LogInfo("开始DNS解析更新: %s", bn)
err := resolver.UpdateBlock(freshHM, bn, "", "", "", false)
if err != nil {
utils.LogError("定时更新失败 %s: %v", bn, err)
} else {
// 获取更新后的块信息,记录更新时间
updatedBlock := freshHM.GetBlock(bn)
if updatedBlock != nil && !updatedBlock.UpdateAt.IsZero() {
utils.LogInfo("定时更新完成: %s更新时间: %s", bn, updatedBlock.UpdateAt.Format("2006-01-02 15:04:05"))
} else {
utils.LogInfo("定时更新完成: %s", bn)
}
}
}
}(blockName)
entryID, err := cm.cron.AddFunc(cronExpr, func() {
utils.LogDebug("执行定时任务: %s", blockName)
task()
})
if err != nil {
utils.LogError("重新加载定时任务失败 %s: %v", blockName, err)
} else {
cm.jobs[blockName] = entryID
utils.LogDebug("已重新加载定时任务: %s (%s)", blockName, cronExpr)
}
}
}
utils.LogDebug("定时任务配置重新同步完成,当前活跃任务: %d 个", len(cm.jobs))
return nil
}