Skip to content

定时任务 ​

概述 ​

定时任务模块为系统提供可视化的任务调度能力:在后台配置 Cron 表达式与执行处理器,由调度器在指定时间自动触发,并完整记录每一次执行的日志。典型场景包括数据清理、报表生成、缓存刷新、第三方接口同步等周期性工作。

模块对应的后端代码位于 backend/app/api/v1/module_infra/job,前端页面位于 apps/web-ele/src/views/infra/job。

image-20260930152354623

核心概念 ​

  • 任务(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导出任务 Excelinfra: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)以表格展示每次执行的耗时、状态、结果与错误,详情抽屉可查看完整返回内容与异常堆栈。

image-20260930153043418