外观
定时任务
概述
定时任务模块为系统提供可视化的任务调度能力:在后台配置 Cron 表达式与执行处理器,由调度器在指定时间自动触发,并完整记录每一次执行的日志。典型场景包括数据清理、报表生成、缓存刷新、第三方接口同步等周期性工作。
模块对应的后端代码位于 backend/app/api/v1/module_infra/job,前端页面位于 apps/web-ele/src/views/infra/job。

核心概念
- 任务(Job):一条调度记录,由任务名称、Cron 表达式、处理器名称与参数组成,并带有「启动 / 暂停」状态。
- 处理器(Handler):真正执行任务逻辑的 Python 函数。任务只需保存处理器的引用路径,调度触发时由框架动态导入并执行。
- 执行日志(JobLog):每次任务运行都会落库一条日志,记录起止时间、耗时、状态与结果或错误信息,便于事后排查。
工作原理
系统基于 APScheduler 的 AsyncIOScheduler 实现调度,任务定义持久化在数据库(表 infra_job),而运行时的调度状态则存放在独立的 Redis JobStore(infra_apscheduler_jobs)中,保证多实例重启后调度状态可恢复。
一次任务触发的执行流程如下:
编写任务处理器
处理器是一个普通的 Python 函数(同步或 async 均可),放在任意可被导入的模块中。系统通过「模块路径 + 属性名」的方式定位它:
- 点号分隔:
module_infra.file_config.task.hello_world表示模块module_infra.file_config.task下的hello_world函数。 - 冒号分隔:
module_infra.file_config.task:hello_world,语义相同,更接近 Python 惯例。
若路径未带 app.api.v1. 前缀,框架会自动补全后重试导入,兼容历史短路径配置。
python
# backend/app/api/v1/module_infra/file_config/task.py
"""定时任务示例"""
from datetime import datetime
async def my_clock():
# 无参数处理器
print(f"定时任务[my_clock]被触发啦!当前时间: {datetime.now():%Y-%m-%d %H:%M:%S}")
async def hello_world(name: str, age: int = 18):
# 带参数处理器:参数来自任务的 handler_param
return f"定时任务[hello_world]被触发啦!我的名字是: {name},我的年龄是: {age}"在前端新增任务时:
- 处理器的名字填写
module_infra.file_config.task.hello_world; - 处理器的参数填写 JSON,例如
{"name": "张三", "age": 20}。
Cron 表达式
系统使用 Quartz 风格的 6 或 7 位 Cron 表达式,字段依次为:秒、分、时、日、月、星期、年(年可省略)。为兼容 APScheduler,框架在解析时会做以下归一:
?等价于*(?表示「不指定」);L等价于last(当月最后一天);- 6 位表达式自动补全年字段
*。
校验借助 app.framework.common.cron.CronUtil,非法表达式在保存时会被拒绝。可用「详情页 / 下次执行时间」或 /infra/job/get_next_times 预览未来的触发时刻。
常见示例:
| 表达式 | 含义 |
|---|---|
0 0 2 * * ? | 每天凌晨 2 点 |
0 0/30 * * * ? | 每 30 分钟 |
0 0 9 ? * MON | 每周一上午 9 点 |
0 0 0 L * ? | 每月最后一天零点 |
调度器生命周期
调度器的启停由应用生命周期统一管理,开发者无需手动操作:
- 启动:
app_init.py的lifespan在应用启动时调用init_infra_scheduler(),先start()再remove_all_jobs(),最后sync_jobs()把数据库中所有「启动」状态的任务重新注册进调度器,并在控制台打印初始化结果。 - 关闭:应用退出时调用
shutdown_infra_scheduler(),以wait=False方式立即关闭。
因此,凡是状态为「启动」的任务,都会在应用重启后自动恢复调度,无需人工干预。
后端接口一览
接口前缀为 /infra/job(任务)与 /infra/job-log(日志),标签为「基础设施-定时任务」。核心接口:
| 接口 | 说明 | 权限 |
|---|---|---|
GET /job/page | 分页查询任务列表 | infra:job:query |
GET /job/get | 任务详情(含后续执行时间) | infra:job:query |
POST /job/create | 新增任务(状态为正常时自动注册) | infra:job:create |
PUT /job/update | 修改任务(按状态注册/移除) | infra:job:update |
DELETE /job/delete、/job/delete-list | 删除 / 批量删除 | infra:job:delete |
PUT /job/update-status | 切换启动 / 暂停 | infra:job:update |
PUT /job/trigger | 立即执行一次 | infra:job:trigger |
GET /job/get_next_times | 计算后续若干次执行时间 | infra:job:query |
POST /job/sync | 全量同步任务到调度器 | infra:job:create |
GET /job/export-excel | 导出任务 Excel | infra:job:export |
GET /job-log/page、/get、/export-excel | 日志查询 / 详情 / 导出 | infra:job:query、infra:job:export |
几个关键行为的实现要点:
- 新增 / 修改 / 切换状态:
InfraJobService在对数据库操作后,会调用register_job(状态=正常)或remove_job(状态=暂停)保持调度器与数据库一致。 - 立即执行:
trigger直接调用run_infra_job(id),绕过 Cron 在当次同步写入一条执行日志。 - 同步:
sync触发sync_jobs(),仅注册状态为正常的任务,返回成功/失败数量。
前端使用
前端页面位于「系统管理 / 基础设施 / 定时任务」,提供任务管理列表与执行日志两个视图。
任务列表(apps/web-ele/src/views/infra/job/index.vue)支持:
- 新增、编辑、删除、批量删除任务;
- 行内「开启 / 暂停」状态切换(对应
update-status); - 行内「执行」按钮(对应
trigger)立即跑一次; - 「同步任务」将全部任务重新注册到调度器;
- 「执行日志」跳转到日志页,可按任务筛选。
新增 / 编辑表单(modules/form.vue)中:
- CRON 表达式使用
CronTab组件可视化配置,并实时预览下次执行时间; - 处理器的参数以 JSON 文本输入,提交前由前端校验为合法 JSON 对象,后端
normalize_handler_param也会做兜底归一(空串、null、None统一视为无参数)。
执行日志页(views/infra/job/logger/index.vue)以表格展示每次执行的耗时、状态、结果与错误,详情抽屉可查看完整返回内容与异常堆栈。
