diff --git a/api/admin/v1/cron_job.go b/api/admin/v1/cron_job.go index 4ac81a5..6a3792c 100644 --- a/api/admin/v1/cron_job.go +++ b/api/admin/v1/cron_job.go @@ -10,6 +10,7 @@ type CronJobListReq struct { type CronJobItem struct { Name string `json:"name"` + DisplayName string `json:"display_name"` Schedule string `json:"schedule"` IsRunning bool `json:"is_running"` LastStatus string `json:"last_status"` diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 6edd070..631fc9c 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -315,57 +315,57 @@ func printBanner() { // registerCronJobs 集中注册所有定时任务,避免散落在主启动流程中。 // 新增 cron 任务时在此追加一项即可;任务名(用于分布式锁 key)需保持唯一且稳定。 func registerCronJobs(cs *common.CronScheduler) { - cs.Register("ops_system_collector", "* * * * *", func(ctx context.Context) error { + cs.Register("ops_system_collector", "系统指标采集", "* * * * *", func(ctx context.Context) error { return monitor.CollectSystemMetrics(ctx) }) - cs.Register("ops_alert_detector", "* * * * *", func(ctx context.Context) error { + cs.Register("ops_alert_detector", "告警检测", "* * * * *", func(ctx context.Context) error { return monitor.RunAlertDetection(ctx) }) - cs.Register("ops_metrics_cleanup", "0 3 * * *", func(ctx context.Context) error { + cs.Register("ops_metrics_cleanup", "指标数据清理", "0 3 * * *", func(ctx context.Context) error { return monitor.CleanupOldMetrics(ctx) }) - cs.Register("partition_ensure", "0 2 * * *", func(ctx context.Context) error { + cs.Register("partition_ensure", "分区维护", "0 2 * * *", func(ctx context.Context) error { return common.EnsurePartitions(ctx) }) - cs.Register("health_snapshot", "*/5 * * * *", func(ctx context.Context) error { + cs.Register("health_snapshot", "渠道健康快照", "*/5 * * * *", func(ctx context.Context) error { return task.SnapshotHealthScores(ctx) }) - cs.Register("channel_auto_test", "*/5 * * * *", func(ctx context.Context) error { + cs.Register("channel_auto_test", "渠道自动测试", "*/5 * * * *", func(ctx context.Context) error { if common.Config().GetBool(ctx, "channel_auto_test_enabled") { task.AutoTestChannels(ctx) } return nil }) - cs.Register("model_sunset_check", "0 0 * * *", func(ctx context.Context) error { + cs.Register("model_sunset_check", "模型下线检查", "0 0 * * *", func(ctx context.Context) error { return task.CheckModelSunset(ctx) }) - cs.Register("data_cleanup", "0 3 * * *", func(ctx context.Context) error { + cs.Register("data_cleanup", "过期数据清理", "0 3 * * *", func(ctx context.Context) error { return admin.CleanupExpiredData(ctx) }) - cs.Register("export_file_cleanup", "0 4 * * *", func(ctx context.Context) error { + cs.Register("export_file_cleanup", "导出文件清理", "0 4 * * *", func(ctx context.Context) error { return admin.CleanupExpiredExportFiles(ctx) }) - cs.Register("file_retention_check", "0 5 * * *", func(ctx context.Context) error { + cs.Register("file_retention_check", "文件保留检查", "0 5 * * *", func(ctx context.Context) error { return admin.CheckFileRetention(ctx) }) - cs.Register("task_timeout_check", "*/10 * * * *", func(ctx context.Context) error { + cs.Register("task_timeout_check", "任务超时检查", "*/10 * * * *", func(ctx context.Context) error { return admin.MarkStuckTasksFailed(ctx) }) - cs.Register("task_executor", "*/1 * * * *", func(ctx context.Context) error { + cs.Register("task_executor", "异步任务执行", "*/1 * * * *", func(ctx context.Context) error { task.RunPendingTasks(ctx) return nil }) - cs.Register("project_budget_check", "*/5 * * * *", func(ctx context.Context) error { + cs.Register("project_budget_check", "项目预算检查", "*/5 * * * *", func(ctx context.Context) error { return tenant.CheckBudgetExhausted(ctx) }) - cs.Register("usage_log_cleanup", "0 3 * * *", func(ctx context.Context) error { + cs.Register("usage_log_cleanup", "用量日志清理", "0 3 * * *", func(ctx context.Context) error { retentionDays := common.Config().GetInt(ctx, "usage_log_retention_days") if retentionDays == 0 { retentionDays = 90 } return task.ScheduleAutoCleanup(ctx, retentionDays) }) - cs.Register("usage_daily_aggregate", "0 1 * * *", func(ctx context.Context) error { + cs.Register("usage_daily_aggregate", "用量日报表聚合", "0 1 * * *", func(ctx context.Context) error { // 用量日维度聚合:将 bil_usage_logs 聚合进 bil_usage_daily,供流量桑基图与趋势分析。 // 自愈:每次重算最近 3 个完整天,覆盖短暂宕机;ON CONFLICT 保证幂等。 // end 取今天 00:00(开区间),永不聚合当天进行中的数据。 @@ -373,30 +373,30 @@ func registerCronJobs(cs *common.CronScheduler) { start := time.Now().AddDate(0, 0, -3).Format("2006-01-02") return task.AggregateUsageRange(ctx, start, end) }) - cs.Register("oauth_token_refresh", "*/10 * * * *", func(ctx context.Context) error { + cs.Register("oauth_token_refresh", "OAuth 令牌刷新", "*/10 * * * *", func(ctx context.Context) error { return task.RefreshExpiringOAuthTokens(ctx) }) - cs.Register("prededuct_orphan_cleanup", "*/2 * * * *", func(ctx context.Context) error { + cs.Register("prededuct_orphan_cleanup", "预扣孤儿清理", "*/2 * * * *", func(ctx context.Context) error { billing.CleanExpiredPreDeducts(ctx) return nil }) - cs.Register("prededuct_tracks_cleanup", "0 4 * * *", func(ctx context.Context) error { + cs.Register("prededuct_tracks_cleanup", "预扣轨迹清理", "0 4 * * *", func(ctx context.Context) error { billing.CleanSettledPreDeductTracks(ctx) return nil }) - cs.Register("billing_daily_reconciliation", "20 5 * * *", func(ctx context.Context) error { + cs.Register("billing_daily_reconciliation", "计费日对账", "20 5 * * *", func(ctx context.Context) error { // 日对账:聚合对账(bil_records vs bil_transactions)+ 交叉对账(usage_logs 反连接 // 发现漏结算免单请求)+ 冻结余额一致性校验,结果经日志告警 _, err := billing.RunDailyReconciliation(ctx) return err }) - cs.Register("update_check", "0 */6 * * *", func(ctx context.Context) error { + cs.Register("update_check", "系统更新检查", "0 */6 * * *", func(ctx context.Context) error { if common.Config().GetBool(ctx, "update_auto_check_enabled") { return update.BackgroundCheck(ctx) } return nil }) - cs.Register("order_expiration", "*/5 * * * *", func(ctx context.Context) error { + cs.Register("order_expiration", "订单过期处理", "*/5 * * * *", func(ctx context.Context) error { return task.ExpirePendingOrders(ctx) }) } diff --git a/internal/logic/admin/cron_job.go b/internal/logic/admin/cron_job.go index e43bd00..a0eb863 100644 --- a/internal/logic/admin/cron_job.go +++ b/internal/logic/admin/cron_job.go @@ -40,9 +40,10 @@ func (s *sAdmin) CronJobList(ctx context.Context, _ *v1.CronJobListReq) (*v1.Cro items := make([]v1.CronJobItem, 0, len(jobs)) for _, j := range jobs { item := v1.CronJobItem{ - Name: j.Name, - Schedule: j.Schedule, - IsRunning: j.IsRunning, + Name: j.Name, + DisplayName: j.DisplayName, + Schedule: j.Schedule, + IsRunning: j.IsRunning, } if data, ok := jobMap[j.Name]; ok { diff --git a/internal/logic/common/cron.go b/internal/logic/common/cron.go index ba87ece..acc0d2e 100644 --- a/internal/logic/common/cron.go +++ b/internal/logic/common/cron.go @@ -14,16 +14,18 @@ import ( // CronJob defines a scheduled job. type CronJob struct { - Name string - Schedule string // cron expression, e.g. "0 0 * * *" for daily at midnight - Handler func(ctx context.Context) error + Name string + DisplayName string // 中文展示名,供管理后台列表展示;空则前端回退到 Name + Schedule string // cron expression, e.g. "0 0 * * *" for daily at midnight + Handler func(ctx context.Context) error } // JobInfo contains info about a registered job for listing. type JobInfo struct { - Name string `json:"name"` - Schedule string `json:"schedule"` - IsRunning bool `json:"is_running"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Schedule string `json:"schedule"` + IsRunning bool `json:"is_running"` } // CronScheduler manages scheduled tasks with distributed locking. @@ -64,13 +66,16 @@ func NewCronScheduler() *CronScheduler { } // Register adds a scheduled job. -func (cs *CronScheduler) Register(name, schedule string, handler func(ctx context.Context) error) { +// name 为全局唯一标识(用作分布式锁 key 和 sys_cron_jobs 主键,必须稳定,禁止中文); +// displayName 为管理后台展示用的中文名,与 name 就近声明,避免散落到前端映射表。 +func (cs *CronScheduler) Register(name, displayName, schedule string, handler func(ctx context.Context) error) { cs.mu.Lock() defer cs.mu.Unlock() cs.jobs = append(cs.jobs, &CronJob{ - Name: name, - Schedule: schedule, - Handler: handler, + Name: name, + DisplayName: displayName, + Schedule: schedule, + Handler: handler, }) } @@ -85,9 +90,10 @@ func (cs *CronScheduler) ListJobs() []JobInfo { running := cs.running[j.Name] cs.runMu.Unlock() jobs = append(jobs, JobInfo{ - Name: j.Name, - Schedule: j.Schedule, - IsRunning: running, + Name: j.Name, + DisplayName: j.DisplayName, + Schedule: j.Schedule, + IsRunning: running, }) } return jobs diff --git a/internal/plugin/bootstrap.go b/internal/plugin/bootstrap.go index fdd685d..6a26f44 100644 --- a/internal/plugin/bootstrap.go +++ b/internal/plugin/bootstrap.go @@ -67,7 +67,7 @@ func startPlugin(ctx context.Context, entry *PluginEntry) error { for _, job := range c.CronJobs() { jobFunc := job.Handler jobName := "plugin_" + job.Name - cs.Register(jobName, job.CronExpr, func(ctx context.Context) error { + cs.Register(jobName, job.DisplayName, job.CronExpr, func(ctx context.Context) error { jobFunc(ctx) return nil }) diff --git a/internal/plugin/interfaces.go b/internal/plugin/interfaces.go index 503bb91..ca89dc0 100644 --- a/internal/plugin/interfaces.go +++ b/internal/plugin/interfaces.go @@ -71,10 +71,11 @@ type HookResult struct { // CronJobDef 定时任务定义。 type CronJobDef struct { - Name string // 任务名(全局唯一) - CronExpr string // cron 表达式 - Handler func(ctx context.Context) // 任务处理函数 - Singleton bool // 是否单例执行 + Name string // 任务名(全局唯一,注册时自动加 plugin_ 前缀) + DisplayName string // 中文展示名,供管理后台列表展示 + CronExpr string // cron 表达式 + Handler func(ctx context.Context) // 任务处理函数 + Singleton bool // 是否单例执行 } // ConfigFieldDef 配置字段定义。 diff --git a/plugins/example/plugin.go b/plugins/example/plugin.go index 68f7d62..74ec437 100644 --- a/plugins/example/plugin.go +++ b/plugins/example/plugin.go @@ -99,10 +99,11 @@ func (p *examplePlugin) Routes(ctx context.Context, server *ghttp.Server) error func (p *examplePlugin) CronJobs() []plugin.CronJobDef { return []plugin.CronJobDef{ { - Name: "example_daily_cleanup", - CronExpr: "0 3 * * *", - Handler: p.dailyCleanup, - Singleton: true, + Name: "example_daily_cleanup", + DisplayName: "示例插件数据清理", + CronExpr: "0 3 * * *", + Handler: p.dailyCleanup, + Singleton: true, }, } } diff --git a/web/admin/src/views/monitor/CronJobsPage.vue b/web/admin/src/views/monitor/CronJobsPage.vue index b082337..77604bd 100644 --- a/web/admin/src/views/monitor/CronJobsPage.vue +++ b/web/admin/src/views/monitor/CronJobsPage.vue @@ -15,26 +15,6 @@ const execLoading = ref>({}) const execData = ref>({}) const execPagination = reactive>({}) -const jobNameMap: Record = { - ops_system_collector: '系统指标采集', - ops_alert_detector: '告警检测', - ops_metrics_cleanup: '指标数据清理', - partition_ensure: '分区维护', - health_snapshot: '渠道健康快照', - channel_auto_test: '渠道自动测试', - model_sunset_check: '模型下线检查', - data_cleanup: '过期数据清理', - export_file_cleanup: '导出文件清理', - file_retention_check: '文件保留检查', - task_timeout_check: '任务超时检查', - task_executor: '异步任务执行', - usage_log_cleanup: '用量日志清理', - oauth_token_refresh: 'OAuth 令牌刷新', - cron_execution_cleanup: '执行记录清理', - prededuct_orphan_cleanup: '预扣孤儿清理', - plugin_example_daily_cleanup: '插件示例数据清理', -} - function formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms` return `${(ms / 1000).toFixed(1)}s` @@ -46,7 +26,7 @@ const columns: TableColumnData[] = [ dataIndex: 'name', width: 200, render({ record }: { record: any }) { - return h('span', jobNameMap[record.name] || record.name) + return h('span', record.display_name || record.name) }, }, {