// 内置系统定时任务注册表 — 告警引擎 / 数据清理等 // Logan.Li package systask import ( "fmt" "sync" "time" "ezops/model" "ezops/pkg/auditcleaner" "ezops/pkg/billingsync" "ezops/pkg/database" snmppkg "ezops/pkg/snmp" "gorm.io/gorm" ) // Def 系统任务元数据 // Logan.Li type Def struct { Code string `json:"code"` Name string `json:"name"` Description string `json:"description"` Schedule string `json:"schedule"` Category string `json:"category"` // monitor / cleanup Runnable bool `json:"runnable"` Enabled bool `json:"enabled"` } // Item API 列表项(元数据 + 运行状态) // Logan.Li type Item struct { Def LastRunAt *time.Time `json:"last_run_at,omitempty"` LastStatus string `json:"last_status,omitempty"` LastMessage string `json:"last_message,omitempty"` RunCount int64 `json:"run_count"` LastDurationMs int `json:"last_duration_ms"` Extra any `json:"extra,omitempty"` } var ( mu sync.RWMutex db *gorm.DB catalog = []Def{ { Code: "alert_engine", Name: "告警引擎扫描", Description: "扫描 Agent 指标阈值、主机离线、端口策略与日志告警规则,触发通知与自动恢复", Schedule: "每 30 秒", Category: "monitor", Runnable: false, Enabled: true, }, { Code: "middleware_monitor", Name: "中间件监控采集", Description: "直连 Redis/MySQL 等中间件目标采集指标并写入监控库", Schedule: "每 30 秒", Category: "monitor", Runnable: false, Enabled: true, }, { Code: "agent_report_cleanup", Name: "Agent 上报清理", Description: "清理 7 天前的 Agent 指标上报原始数据,防止表膨胀", Schedule: "每 24 小时", Category: "cleanup", Runnable: true, Enabled: true, }, { Code: "audit_log_cleanup", Name: "审计日志清理", Description: "按系统设置中的保留天数清理操作审计、登录日志、终端会话与命令记录", Schedule: "每 24 小时", Category: "cleanup", Runnable: true, Enabled: true, }, { Code: "snmp_metrics_cleanup", Name: "SNMP 指标清理", Description: "清理 7 天前的 SNMP 采集指标历史,防止表膨胀", Schedule: "每 24 小时", Category: "cleanup", Runnable: true, Enabled: true, }, { Code: "cloud_billing_sync", Name: "云账单同步", Description: "从云账号 AK 拉取昨日账单(按产品+地域),写入云账单管理;当前支持阿里云", Schedule: "每天 10:30", Category: "finops", Runnable: true, Enabled: true, }, } ) // Init 注入数据库连接,并确保状态行存在 // Logan.Li func Init(gdb *gorm.DB) { mu.Lock() db = gdb mu.Unlock() ensureStatusRows(gdb) } func ensureStatusRows(gdb *gorm.DB) { for _, d := range catalog { var row model.SystemTaskStatus if gdb.Where("code = ?", d.Code).First(&row).Error == gorm.ErrRecordNotFound { gdb.Create(&model.SystemTaskStatus{Code: d.Code, LastStatus: "pending"}) } } } // Catalog 返回任务定义列表 // Logan.Li func Catalog() []Def { out := make([]Def, len(catalog)) copy(out, catalog) return out } // Record 记录任务执行结果 // Logan.Li func Record(code, status, message string, duration time.Duration) { mu.RLock() gdb := db mu.RUnlock() if gdb == nil { return } now := time.Now() updates := map[string]interface{}{ "last_run_at": now, "last_status": status, "last_message": truncate(message, 480), "last_duration_ms": int(duration.Milliseconds()), "run_count": gorm.Expr("run_count + 1"), } gdb.Model(&model.SystemTaskStatus{}).Where("code = ?", code).Updates(updates) } func truncate(s string, max int) string { if len(s) <= max { return s } return s[:max] } // List 合并元数据与库中状态 // Logan.Li func List(gdb *gorm.DB, extras map[string]any) []Item { ensureStatusRows(gdb) var rows []model.SystemTaskStatus gdb.Find(&rows) statusMap := make(map[string]model.SystemTaskStatus, len(rows)) for _, r := range rows { statusMap[r.Code] = r } items := make([]Item, 0, len(catalog)) for _, d := range catalog { item := Item{Def: d} if st, ok := statusMap[d.Code]; ok { item.LastRunAt = st.LastRunAt item.LastStatus = st.LastStatus item.LastMessage = st.LastMessage item.RunCount = st.RunCount item.LastDurationMs = st.LastDurationMs } if extras != nil { if ex, ok := extras[d.Code]; ok { item.Extra = ex } } // 审计清理任务补充保留天数 // Logan.Li if d.Code == "audit_log_cleanup" { days := auditcleaner.GetRetentionDays(gdb) if item.Extra == nil { item.Extra = map[string]any{"retention_days": days} } } if d.Code == "snmp_metrics_cleanup" && item.Extra == nil { item.Extra = map[string]any{"retention_days": snmppkg.MetricsRetentionDays} } items = append(items, item) } return items } // RunManual 手动触发可运行的系统任务 // Logan.Li func RunManual(gdb *gorm.DB, driver, code string) (Item, error) { def, ok := findDef(code) if !ok { return Item{}, fmt.Errorf("未知系统任务: %s", code) } if !def.Runnable { return Item{}, fmt.Errorf("任务 %s 不支持手动执行", def.Name) } start := time.Now() var msg string var err error switch code { case "agent_report_cleanup": sql := database.CleanupAgentReportsSQL(driver) r := gdb.Exec(sql) err = r.Error msg = fmt.Sprintf("清理 %d 条 Agent 上报记录", r.RowsAffected) case "audit_log_cleanup": var result auditcleaner.Result result, err = auditcleaner.RunFromSettings(gdb) if err == nil { msg = fmt.Sprintf("清理 %d 条超期记录(保留 %d 天)", result.Total, result.RetentionDays) now := time.Now() gdb.Model(&model.AuditSettings{}).Where("id > 0").Updates(map[string]interface{}{ "last_cleanup_at": now, "last_cleanup_summary": result.SummaryJSON(), }) } case "snmp_metrics_cleanup": sql := database.CleanupSNMPMetricsSQL(driver, snmppkg.MetricsRetentionDays) r := gdb.Exec(sql) err = r.Error msg = fmt.Sprintf("清理 %d 条 SNMP 指标(保留 %d 天)", r.RowsAffected, snmppkg.MetricsRetentionDays) case "cloud_billing_sync": result := billingsync.SyncDate(gdb, billingsync.Yesterday(), nil) msg = billingsync.SummaryMessage(result) if result.Accounts == 0 { err = fmt.Errorf("没有可同步的活跃云账号") } else if result.Created == 0 && result.Updated == 0 && len(result.Errors) > 0 { err = fmt.Errorf("%s", result.Errors[0]) } default: return Item{}, fmt.Errorf("任务 %s 未实现手动执行", code) } duration := time.Since(start) status := "success" if err != nil { status = "failed" msg = err.Error() } Record(code, status, msg, duration) items := List(gdb, nil) for _, it := range items { if it.Code == code { return it, err } } return Item{Def: def}, err } func findDef(code string) (Def, bool) { for _, d := range catalog { if d.Code == code { return d, true } } return Def{}, false }