package cmd import ( "os" "sort" "time" "github.com/evil7/hostsync/core" "github.com/evil7/hostsync/utils" "github.com/robfig/cron/v3" "github.com/spf13/cobra" ) // cronCmd 定时任务命令 var cronCmd = &cobra.Command{ Use: "cron [blockName] [cronExpression]", Short: "管理定时任务", Long: `管理块的定时任务配置。使用6字段cron表达式格式,支持秒级精度。 cron表达式格式: 秒 分 时 日 月 星期 示例: hostsync cron # 列出所有定时任务(默认) hostsync cron list # 列出所有定时任务 hostsync cron github "0 0 0 * * *" # 设置每日午夜更新 hostsync cron github "*/30 * * * * *" # 每30秒更新一次 hostsync cron github # 清除定时任务 常用cron表达式: 0 0 0 * * * # 每天午夜 0 0 */4 * * * # 每4小时 0 0 9 * * 1 # 每周一上午9点 0 */10 * * * * # 每10分钟 */30 * * * * * # 每30秒 0 */5 * * * * # 每5分钟 0 0 12 * * * # 每天中午12点`, Args: cobra.RangeArgs(0, 2), // 修改为允许0-2个参数 Run: runCron, } func runCron(cmd *cobra.Command, args []string) { // 如果没有参数或第一个参数是"list",显示定时任务列表 if len(args) == 0 || (len(args) == 1 && args[0] == "list") { listCronJobs() return } blockName := args[0] if !utils.ValidateBlockName(blockName) { utils.LogError("无效的块名称: %s", blockName) utils.LogError("块名称只能包含字母、数字、下划线和连字符") os.Exit(1) } // 如果有两个参数,先验证cron表达式(不需要管理员权限) if len(args) == 2 { cronExpr := args[1] cronManager := core.NewCronManager() if err := cronManager.ValidateCronExpression(cronExpr); err != nil { utils.LogError("无效的cron表达式: %v", err) utils.LogInfo("cron表达式格式: 秒 分 时 日 月 星期") utils.LogInfo("常用示例:") utils.LogInfo(" \"0 0 0 * * *\" - 每日午夜") utils.LogInfo(" \"0 */30 * * * *\" - 每30分钟") utils.LogInfo(" \"*/30 * * * * *\" - 每30秒") utils.LogInfo("更多信息: https://pkg.go.dev/github.com/robfig/cron") os.Exit(1) } } // 验证通过后,检查管理员权限 utils.CheckAdmin() hm := core.NewHostsManager() if err := hm.Load(); err != nil { utils.LogError("加载hosts文件失败: %v", err) os.Exit(1) } block := hm.GetBlock(blockName) if block == nil { utils.LogWarning("块不存在: %s", blockName) os.Exit(1) } if len(args) == 1 { // 清除定时任务 clearCronJob(hm, blockName) } else { // 设置定时任务 cronExpr := args[1] setCronJob(hm, blockName, cronExpr) } } func setCronJob(hm *core.HostsManager, blockName, cronExpr string) { // cron表达式已经在上层验证过了,这里直接设置 block := hm.GetBlock(blockName) block.CronJob = cronExpr if err := hm.Save(); err != nil { utils.LogError("保存配置失败: %v", err) os.Exit(1) } utils.LogSuccess("已设置定时任务: %s (%s)", blockName, cronExpr) } func clearCronJob(hm *core.HostsManager, blockName string) { block := hm.GetBlock(blockName) if block.CronJob == "" { utils.LogInfo("块 %s 没有设置定时任务", blockName) return } block.CronJob = "" if err := hm.Save(); err != nil { utils.LogError("保存配置失败: %v", err) os.Exit(1) } utils.LogSuccess("已清除定时任务: %s", blockName) } func listCronJobs() { hm := core.NewHostsManager() if err := hm.Load(); err != nil { utils.LogError("加载hosts文件失败: %v", err) os.Exit(1) } // 收集有定时任务的块名称 var cronBlockNames []string for name, block := range hm.Blocks { if block.CronJob != "" { cronBlockNames = append(cronBlockNames, name) } } if len(cronBlockNames) == 0 { utils.LogInfo("没有找到任何定时任务") return } // 对块名称进行排序,保持与 list 命令一致 sort.Strings(cronBlockNames) utils.LogResult("定时任务列表 (%d 个)\n", len(cronBlockNames)) headers := []string{"块名称", "Cron表达式", "最后更新", "下次执行"} var rows [][]string for _, name := range cronBlockNames { block := hm.Blocks[name] lastUpdate := "从未更新" if !block.UpdateAt.IsZero() { lastUpdate = block.UpdateAt.Format("2006-01-02 15:04") } // 计算下次执行时间(简化版本) nextRun := calculateNextRun(block.CronJob) rows = append(rows, []string{block.Name, block.CronJob, lastUpdate, nextRun}) } // 设置合适的列宽 columnWidths := []int{12, 20, 17, 20} utils.FormatTable(headers, rows, columnWidths) } // calculateNextRun 计算下次执行时间 func calculateNextRun(cronExpr string) string { // 使用robfig/cron解析器解析6字段cron表达式 parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) schedule, err := parser.Parse(cronExpr) if err != nil { return "解析错误" } // 计算下次执行时间 nextTime := schedule.Next(time.Now()) return nextTime.Format("2006-01-02 15:04:05") }