package vqdtask import ( "fmt" "io/fs" "log/slog" "os" "path/filepath" "time" ) // 配置参数 const ( // 要清理的目标目录(请替换为你实际的目录路径) cleanDir = "/snap" // 定时任务执行间隔(每天执行一次) interval = 24 * time.Hour // 批量删除大小(避免单次删除过多锁表) batchSize = 1000 // 日期目录的格式(如 20260117) dateDirLayout = "20060102" // 定时任务首次执行时间(每天凌晨1点) executeHour = 1 ) // scheduleCleanFile 定时执行清理任务 func (c Core) scheduleCleanTask() { // 计算首次执行时间(今天/明天的凌晨1点) now := time.Now() nextExec := time.Date(now.Year(), now.Month(), now.Day(), executeHour, 0, 0, 0, now.Location()) if nextExec.Before(now) { nextExec = nextExec.Add(24 * time.Hour) } // 计算首次执行的等待时间 initialDelay := nextExec.Sub(now) slog.Info(fmt.Sprintf("定时任务已启动,首次执行时间:%s(等待 %v)", nextExec.Format(time.RFC3339), initialDelay)) // 首次执行等待 time.Sleep(initialDelay) // 执行首次清理 if err := c.cleanExpiredFiles(); err != nil { slog.Error("首次清理任务执行失败 Files", "err", err) } time.Sleep(20 * time.Minute) if err := c.cleanExpiredDbs(); err != nil { slog.Error("首次清理任务执行失败 Dbs", "err", err) } // 循环执行定时任务 ticker := time.NewTicker(interval) defer ticker.Stop() for range ticker.C { if err := c.cleanExpiredFiles(); err != nil { slog.Error("定时清理任务执行失败 Files", "err", err) } time.Sleep(20 * time.Minute) if err := c.cleanExpiredDbs(); err != nil { slog.Error("定时清理任务执行失败 Dbs", "err", err) } } } // cleanExpiredDbs 清理超过expireDays天的数据 func (c Core) cleanExpiredDbs() error { // 计算过期时间阈值 expireDays := time.Duration(c.Cfg.VqdConfig.SaveDay) if expireDays < 1 { expireDays = 1 } expireTime := time.Now().Add(-expireDays * 24 * time.Hour) slog.Info(fmt.Sprintf("开始清理中超过 %d 天的数据,过期时间阈值:%s", expireDays, expireTime.Format(time.RFC3339))) totalDeleted := 0 for { deletedCount, err := c.VqdTaskCore.DelVqdTaskAlarmAll(expireTime, batchSize) if err != nil { slog.Error("数据清理失败", "err", err) return err } // 获取本次删除的行数 totalDeleted += deletedCount // 无更多数据则退出循环 if deletedCount < batchSize { break } // 批量删除间隔,降低数据库压力 time.Sleep(100 * time.Millisecond) } slog.Info("本次数据清理任务执行完成") return nil } // deleteDirContents 删除目录下的所有文件(保留目录结构,仅删文件) func deleteTaskDirContents(dir string, expireTime time.Time) error { // 遍历目录 err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { // 处理遍历过程中的错误(如权限问题) if err != nil { slog.Error("访问路径失败", "path", path, "err", err) return nil } // 只处理目录(跳过文件) if !d.IsDir() { return nil } // 获取当前目录的名称(如 20260117) dirName := filepath.Base(path) dirDate, err := time.Parse(dateDirLayout, dirName) if err != nil { slog.Error("解析目录日期失败", "path", path, "err", err) return nil } // 判断日期目录是否早于阈值(即过期) isExpired := dirDate.Before(expireTime) // 非日期目录,继续遍历子目录 if dirDate.IsZero() { return nil } // 日期目录未过期,跳过 if !isExpired { slog.Error("目录日期未过期跳过", "path", path, "dirDate", dirDate.Format(dateDirLayout)) return nil } // 过期日期目录:先删除目录内所有文件 slog.Error("目录已过期开始清理其中文件", "path", path, "dirDate", dirDate.Format(dateDirLayout)) if err := deleteDirContents(path, expireTime); err != nil { slog.Error("清理目录内容失败", "path", path, "err", err) return nil } // 删除空的日期目录 if err := os.Remove(path); err != nil { slog.Error("删除空目录失败(可能非空)", "path", path, "err", err) } else { slog.Info("成功删除过期目录", "path", path) } // 跳过已删除目录的子目录遍历(避免无效操作) return fs.SkipDir }) if err != nil { slog.Error("遍历目录失败", "dir", dir, "err", err) return fmt.Errorf("遍历目录 [%s] 失败:%w", dir, err) } return nil } func deleteDirContents(dir string, expireTime time.Time) error { err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { slog.Error("访问路径失败", "path", path, "err", err) return nil // 跳过错误路径,继续处理 } // 只删除文件,跳过目录 if !d.IsDir() { // 获取文件信息(包含修改时间) fileInfo, err := d.Info() if err != nil { slog.Error("获取文件失败", "path", path, "err", err) return nil } // 判断文件是否过期 if fileInfo.ModTime().Before(expireTime) { // 删除过期文件 if errs := os.Remove(path); errs != nil { slog.Error("删除文件失败", "path", path, "err", err) } else { slog.Info("成功删除文件", "path", path) } } } return nil }) if err != nil { return fmt.Errorf("遍历目录 [%s] 失败:%w", dir, err) } return nil } // cleanExpiredFiles 清理指定目录下超过expireDays天未修改的文件 func (c Core) cleanExpiredFiles() error { // 计算过期时间阈值 expireDays := time.Duration(c.Cfg.VqdConfig.SaveDay) if expireDays < 1 { expireDays = 1 } expireTime := time.Now().Add(-expireDays * 24 * time.Hour) slog.Info(fmt.Sprintf("开始清理目录 [%s] 中超过 %d 天的文件,过期时间阈值:%s", cleanDir, expireDays, expireTime.Format(time.RFC3339))) dir, _ := os.Getwd() rootDir := filepath.Join(dir, cleanDir) dateDirs, err := os.ReadDir(rootDir) if err != nil { slog.Error("访问根目录路径失败", "path", rootDir, "err", err) return nil } for _, d := range dateDirs { // 只处理目录(跳过文件) if !d.IsDir() { return nil } path := filepath.Join(rootDir, d.Name()) if err := deleteTaskDirContents(path, expireTime); err != nil { slog.Error("清理目录内容失败", "path", path, "err", err) return nil } // 删除空的日期目录 if err := os.Remove(path); err != nil { slog.Error("删除空目录失败(可能非空)", "path", path, "err", err) } else { slog.Info("成功删除过期目录", "path", path) } } slog.Info("本次文件清理任务执行完成") return nil }