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
|
|
|
|
|
|
}
|