hostSync/cmd/cron.go

181 lines
5.0 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 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")
}