Files
admin 086c5c6573 perf: 性能优化与错误修复
- 修复 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 响应格式
- 添加数据库索引优化查询性能
- 实现可配置的数据清理定时任务
2026-05-06 19:19:02 +08:00

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