You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.3 KiB
5.3 KiB
定时任务功能设计
概述
为 Nuxt 应用增加定时任务(cron job)功能,支持系统预定义任务和用户可配置任务,通过 REST API 和 Nuxt UI 管理界面控制,处理服务重启恢复。
技术选型
- cron 解析:
croner— 纯 JS,TypeScript 友好,零原生依赖,bun 兼容 - 持久化:Drizzle ORM + SQLite(复用现有技术栈)
- 调度引擎:Nitro 插件 + 内存调度器
核心架构
Nitro Plugin (server/plugins/03.scheduler.ts)
├── Scheduler Engine — 主调度器,管理所有活跃任务
│ ├── croner — cron 表达式解析与触发
│ └── Executor Pool — 并发控制(max N,可配置)
├── Task Store — Drizzle DB 持久化
├── Task Registry — 系统任务名称→处理函数的映射
├── REST API — CRUD + 手动触发 + 执行日志查询
└── Admin UI — Nuxt UI 管理页面
数据模型
scheduled_tasks
| 字段 | 类型 | 说明 |
|---|---|---|
| id | text (PK) | uuid |
| name | text | 任务名称 |
| cron_expression | text | 标准 5 段 cron 表达式 |
| type | text | "function" 或 "http" |
| function_name | text (nullable) | 系统注册的函数名 |
| function_payload | text (nullable) | JSON string,传给函数的参数 |
| http_method | text (nullable) | GET / POST / PUT / DELETE |
| http_url | text (nullable) | 请求 URL |
| http_headers | text (nullable) | JSON string,自定义请求头 |
| http_body | text (nullable) | 请求体 |
| catch_up | integer | 0=跳过, 1=补执行 |
| enabled | integer | 0=暂停, 1=启用 |
| max_retries | integer | 失败重试次数,默认 0 |
| retry_delay_seconds | integer | 重试间隔秒,默认 60 |
| timeout_seconds | integer | 超时秒,默认 300 |
| created_at | text | |
| updated_at | text |
task_execution_logs
| 字段 | 类型 | 说明 |
|---|---|---|
| id | text (PK) | uuid |
| task_id | text (FK) | 关联 scheduled_tasks.id |
| status | text | "running" / "success" / "failed" |
| started_at | text | |
| finished_at | text (nullable) | |
| error_message | text (nullable) | |
| result_summary | text (nullable) | 简要结果描述 |
API 设计
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/scheduler/tasks | 列出任务(分页+筛选) |
| GET | /api/scheduler/tasks/:id | 任务详情+最近执行日志 |
| POST | /api/scheduler/tasks | 创建任务 |
| PUT | /api/scheduler/tasks/:id | 更新任务 |
| DELETE | /api/scheduler/tasks/:id | 删除任务(保留日志) |
| POST | /api/scheduler/tasks/:id/trigger | 手动触发一次 |
| POST | /api/scheduler/tasks/:id/toggle | 启用/暂停 |
| GET | /api/scheduler/executions | 执行日志列表 |
| GET | /api/scheduler/stats | 汇总统计 |
创建/更新任务时 Scheduler Engine 实时热更新,无需重启服务。
Scheduler 生命周期
启动
- 从 DB 加载所有
enabled=1的任务 - 对每个任务解析 cron,计算最近应触发时间
catchUp=1且已错过触发时间 → 补执行一次- 注册到 croner,进入调度循环
热更新
- API 写入 DB 后通知 Scheduler Engine
- 增量同步:新增→注册 / 更新→替换 / 删除→取消
- 同步失败仅记日志,DB 已写入,重启后自行纠正
重启恢复
status=running的执行日志全部标记failed- 按 catchUp 字段处理每个启用任务
并发控制
- Executor Pool 维护信号量
- 槽位满时排队,超时强制标记失败
Task Registry
type TaskHandler = (payload?: Record<string, unknown>) => Promise<{ success: boolean; message: string }>;
registerTask(name: string, handler: TaskHandler): void
executeTask(name: string, payload?: Record<string, unknown>): Promise<{ success: boolean; message: string }>
listRegisteredTasks(): string[]
系统任务在代码中注册,通过 API 创建调度时引用 functionName。HTTP 类型不走 Registry,直接用 fetch()。
前端管理页面
/admin/scheduler— 任务列表 + 统计栏 + 创建/编辑弹窗/admin/scheduler/:id— 任务详情 + 执行历史时间线
创建/编辑弹窗:名称、类型切换、cron 输入+人类可读预览+快捷预设、函数选择下拉、HTTP 配置、高级设置(catchUp/超时/重试)、测试运行按钮。
错误处理
| 场景 | 处理 |
|---|---|
| cron 表达式非法 | 校验拒绝(422),已存在的标记 disabled + ERROR 日志 |
| DB 读取失败 | 服务启动失败(fast fail) |
| Registry 未找到函数 | 执行时跳过 + ERROR 日志 |
| 执行超时 | 强制终止,标记 failed |
| 执行异常 | 按 retryCount 重试,全部失败记日志 |
| HTTP 请求失败 | 同上,记录状态码和响应摘要 |
| 并发池满 | 排队等待,WARN 日志 |
| croner 内部异常 | 全局 catch,不影响其他任务 |
执行日志保留 30 天,定期清理防 SQLite 膨胀。
测试策略
| 层 | 覆盖 | 工具 |
|---|---|---|
| 单元 | cron 解析、Registry、Executor Pool、数据校验 | vitest |
| 集成 | API CRUD 流程、热更新、手动触发 | vitest + 内存 SQLite |
| E2E | 前端管理页面操作流程 | 手动验证 |
核心场景:创建→调度→触发→日志、热更新替换 cron、并发池满排队、重启恢复 catchUp、非法表达式校验拒绝。