EasyVQD/internal/core/vqdtask/clean.go

228 lines
6.6 KiB
Go
Raw Normal View History

2026-01-23 18:05:36 +08:00
package vqdtask
2026-01-17 16:19:36 +08:00
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
}