improve(task):定时任务优化,显示中文名称

This commit is contained in:
qian committed 2026-08-02 17:52:21 +08:00
1 parent 84ea7543b1
commit ab463ede83
8 files changed
+57 -67

No files matched your search

+1
View File
@@ -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"`
+21 -21
View File
@@ -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)
})
}
+4 -3
View File
@@ -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 {
+19 -13
View File
@@ -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
+1 -1
View File
@@ -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
})
+5 -4
View File
@@ -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 配置字段定义。
+5 -4
View File
@@ -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,
},
}
}
+1 -21
View File
@@ -15,26 +15,6 @@ const execLoading = ref<Record<string, boolean>>({})
const execData = ref<Record<string, any[]>>({})
const execPagination = reactive<Record<string, { current: number; total: number }>>({})
const jobNameMap: Record<string, string> = {
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)
},
},
{