086c5c6573
- 修复 N+1 查询: users/devices/agents 批量 GROUP BY 替代循环查询 - 添加分页: cards/finance/agents/devices API - Redis 初始化根据安装配置 redis.enabled 决定是否连接 - 修复 DefaultVal 解析错误: 使用 sql.NullString 处理 NULL 值 - Dashboard 优化: 替换 ECharts 世界地图为 Chart.js 环形饼图 - 适配前端 cards 页面新 API 响应格式 - 添加数据库索引优化查询性能 - 实现可配置的数据清理定时任务
146 lines
4.2 KiB
Go
146 lines
4.2 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"verification-platform-backend/internal/database"
|
|
"verification-platform-backend/internal/model"
|
|
)
|
|
|
|
type CleanupConfig struct {
|
|
EnableAutoCleanup bool
|
|
CleanupIntervalHours int
|
|
CaptchaRetentionDays int
|
|
VerifyCodeRetentionDays int
|
|
ApiUsageRetentionDays int
|
|
WebhookLogRetentionDays int
|
|
DeviceSessionRetentionDays int
|
|
}
|
|
|
|
var defaultConfig = CleanupConfig{
|
|
EnableAutoCleanup: false,
|
|
CleanupIntervalHours: 24,
|
|
CaptchaRetentionDays: 1,
|
|
VerifyCodeRetentionDays: 7,
|
|
ApiUsageRetentionDays: 30,
|
|
WebhookLogRetentionDays: 30,
|
|
DeviceSessionRetentionDays: 7,
|
|
}
|
|
|
|
func GetCleanupConfig() CleanupConfig {
|
|
cfg := defaultConfig
|
|
|
|
var settings []model.Setting
|
|
database.DB.Where("category = ?", "cleanup").Find(&settings)
|
|
|
|
for _, s := range settings {
|
|
switch s.Key {
|
|
case "enable_auto_cleanup":
|
|
cfg.EnableAutoCleanup = s.Value == "true"
|
|
case "cleanup_interval_hours":
|
|
cfg.CleanupIntervalHours = parseVal(s.Value, defaultConfig.CleanupIntervalHours)
|
|
case "captcha_retention_days":
|
|
cfg.CaptchaRetentionDays = parseVal(s.Value, defaultConfig.CaptchaRetentionDays)
|
|
case "verify_code_retention_days":
|
|
cfg.VerifyCodeRetentionDays = parseVal(s.Value, defaultConfig.VerifyCodeRetentionDays)
|
|
case "api_usage_retention_days":
|
|
cfg.ApiUsageRetentionDays = parseVal(s.Value, defaultConfig.ApiUsageRetentionDays)
|
|
case "webhook_log_retention_days":
|
|
cfg.WebhookLogRetentionDays = parseVal(s.Value, defaultConfig.WebhookLogRetentionDays)
|
|
case "device_session_retention_days":
|
|
cfg.DeviceSessionRetentionDays = parseVal(s.Value, defaultConfig.DeviceSessionRetentionDays)
|
|
}
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
func parseVal(value string, defaultVal int) int {
|
|
if value == "" {
|
|
return defaultVal
|
|
}
|
|
var result int
|
|
if _, err := fmt.Sscanf(value, "%d", &result); err != nil || result <= 0 {
|
|
return defaultVal
|
|
}
|
|
return result
|
|
}
|
|
|
|
func StartCleanupScheduler() {
|
|
go func() {
|
|
for {
|
|
cfg := GetCleanupConfig()
|
|
interval := time.Duration(cfg.CleanupIntervalHours) * time.Hour
|
|
if interval < time.Hour {
|
|
interval = time.Hour
|
|
}
|
|
time.Sleep(interval)
|
|
|
|
if !cfg.EnableAutoCleanup {
|
|
continue
|
|
}
|
|
|
|
RunCleanup(cfg)
|
|
}
|
|
}()
|
|
log.Println("[Scheduler] Cleanup scheduler started")
|
|
}
|
|
|
|
func RunCleanup(cfg CleanupConfig) {
|
|
now := time.Now()
|
|
totalCleaned := 0
|
|
|
|
if cfg.CaptchaRetentionDays > 0 {
|
|
cutoff := now.AddDate(0, 0, -cfg.CaptchaRetentionDays)
|
|
result := database.DB.Where("expires_at < ?", cutoff).Delete(&model.Captcha{})
|
|
if result.RowsAffected > 0 {
|
|
log.Printf("[Cleanup] Deleted %d expired captchas", result.RowsAffected)
|
|
totalCleaned += int(result.RowsAffected)
|
|
}
|
|
}
|
|
|
|
if cfg.VerifyCodeRetentionDays > 0 {
|
|
cutoff := now.AddDate(0, 0, -cfg.VerifyCodeRetentionDays)
|
|
emailResult := database.DB.Where("expires_at < ? AND used = ?", cutoff, true).Delete(&model.EmailVerifyCode{})
|
|
smsResult := database.DB.Where("expires_at < ? AND used = ?", cutoff, true).Delete(&model.SmsVerifyCode{})
|
|
count := emailResult.RowsAffected + smsResult.RowsAffected
|
|
if count > 0 {
|
|
log.Printf("[Cleanup] Deleted %d expired verify codes", count)
|
|
totalCleaned += int(count)
|
|
}
|
|
}
|
|
|
|
if cfg.ApiUsageRetentionDays > 0 {
|
|
cutoff := now.AddDate(0, 0, -cfg.ApiUsageRetentionDays)
|
|
result := database.DB.Where("created_at < ?", cutoff).Delete(&model.ApiUsage{})
|
|
if result.RowsAffected > 0 {
|
|
log.Printf("[Cleanup] Deleted %d old api usage records", result.RowsAffected)
|
|
totalCleaned += int(result.RowsAffected)
|
|
}
|
|
}
|
|
|
|
if cfg.WebhookLogRetentionDays > 0 {
|
|
cutoff := now.AddDate(0, 0, -cfg.WebhookLogRetentionDays)
|
|
result := database.DB.Where("created_at < ?", cutoff).Delete(&model.WebhookLog{})
|
|
if result.RowsAffected > 0 {
|
|
log.Printf("[Cleanup] Deleted %d old webhook logs", result.RowsAffected)
|
|
totalCleaned += int(result.RowsAffected)
|
|
}
|
|
}
|
|
|
|
if cfg.DeviceSessionRetentionDays > 0 {
|
|
cutoff := now.AddDate(0, 0, -cfg.DeviceSessionRetentionDays)
|
|
result := database.DB.Where("last_heartbeat < ? AND last_heartbeat IS NOT NULL", cutoff).Delete(&model.DeviceSession{})
|
|
if result.RowsAffected > 0 {
|
|
log.Printf("[Cleanup] Deleted %d expired device sessions", result.RowsAffected)
|
|
totalCleaned += int(result.RowsAffected)
|
|
}
|
|
}
|
|
|
|
if totalCleaned > 0 {
|
|
log.Printf("[Cleanup] Total cleaned: %d records", totalCleaned)
|
|
}
|
|
}
|