如何通过Quartz的ITriggerListener与IJobListener实现任务执行历史入库?
问题解答
1. 能否完全通过Quartz实现任务执行历史存储?
可以,但Quartz默认不提供任务执行历史的自动存储逻辑和专用表。你需要:
- 自行定义任务执行历史的数据库表结构
- 通过实现
ITriggerListener和IJobListener接口,监听任务执行的关键事件(如任务开始、结束、成功、失败) - 在监听器中编写数据库写入逻辑,将执行数据存入自定义表
Quartz自身的内置表(如QRTZ_JOB_DETAILS、QRTZ_TRIGGERS等)仅用于存储任务、触发器的元数据和运行状态,不记录每次任务的执行历史,因此必须自定义存储逻辑。
2. IJobListener 具体实现示例
IJobListener用于监听任务的执行生命周期事件,以下是一个写入数据库的实现示例(以EF Core操作为例,也可替换为ADO.NET等方式):
public class JobExecutionHistoryListener : IJobListener { private readonly YourDbContext _dbContext; // 通过依赖注入注入数据库上下文 public JobExecutionHistoryListener(YourDbContext dbContext) { _dbContext = dbContext; } // 监听器唯一标识名称 public string Name => "JobExecutionHistoryListener"; // 任务执行前触发(可记录启动信息) public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) { return Task.CompletedTask; } // 任务被否决(如触发器中断)时触发 public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default) { var history = new JobExecutionHistory { JobName = context.JobDetail.Key.Name, JobGroup = context.JobDetail.Key.Group, TriggerName = context.Trigger.Key.Name, TriggerGroup = context.Trigger.Key.Group, ExecutionStartTime = context.FireTimeUtc?.LocalDateTime, ExecutionEndTime = DateTime.Now, Status = "Vetoed", Message = "任务执行被否决" }; _dbContext.JobExecutionHistories.Add(history); return _dbContext.SaveChangesAsync(cancellationToken); } // 任务执行完成后触发(无论成功/失败) public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken = default) { var history = new JobExecutionHistory { JobName = context.JobDetail.Key.Name, JobGroup = context.JobDetail.Key.Group, TriggerName = context.Trigger.Key.Name, TriggerGroup = context.Trigger.Key.Group, ExecutionStartTime = context.FireTimeUtc?.LocalDateTime, ExecutionEndTime = DateTime.Now, Duration = context.JobRunTime.TotalMilliseconds, Status = jobException == null ? "Success" : "Failed" }; if (jobException != null) { history.Message = jobException.Message; history.StackTrace = jobException.StackTrace; } _dbContext.JobExecutionHistories.Add(history); return _dbContext.SaveChangesAsync(cancellationToken); } }
3. ITriggerListener 具体实现示例
ITriggerListener用于监听触发器的状态变化(如触发、错过触发),可补充记录触发器相关的执行历史:
public class TriggerExecutionHistoryListener : ITriggerListener { private readonly YourDbContext _dbContext; public TriggerExecutionHistoryListener(YourDbContext dbContext) { _dbContext = dbContext; } public string Name => "TriggerExecutionHistoryListener"; // 触发器触发后、任务即将执行前触发 public Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default) { return Task.CompletedTask; } // 决定是否否决任务执行(返回true则任务不执行) public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default) { // 示例返回false,允许任务执行 return Task.FromResult(false); } // 触发器错过触发时间时触发 public Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default) { var history = new TriggerExecutionHistory { TriggerName = trigger.Key.Name, TriggerGroup = trigger.Key.Group, MisfireTime = DateTime.Now, Message = "触发器错过触发时间" }; _dbContext.TriggerExecutionHistories.Add(history); return _dbContext.SaveChangesAsync(cancellationToken); } // 触发器完成触发(任务执行结束后)触发 public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default) { return Task.CompletedTask; } }
4. 注册监听器到Quartz调度器
实现监听器后,需将其注册到Scheduler中才能生效:
// 假设已创建Scheduler实例 var scheduler = await SchedulerBuilder.Create() // 你的Quartz配置项... .Build(); // 从DI容器获取数据库上下文(根据你的DI方式调整) var dbContext = serviceProvider.GetRequiredService<YourDbContext>(); // 注册Job监听器(监听所有分组的任务) scheduler.ListenerManager.AddJobListener(new JobExecutionHistoryListener(dbContext), GroupMatcher<JobKey>.AnyGroup()); // 注册Trigger监听器(监听所有分组的触发器) scheduler.ListenerManager.AddTriggerListener(new TriggerExecutionHistoryListener(dbContext), GroupMatcher<TriggerKey>.AnyGroup()); await scheduler.Start();
5. 数据库表创建说明
Quartz不会自动创建自定义的执行历史表,你需要:
- 手动编写SQL脚本创建表,或者
- 使用EF Core的Code First迁移,通过实体类(如上述示例中的
JobExecutionHistory)自动生成表结构
确保历史表使用的数据库与Quartz配置的数据库一致(或按需配置不同数据库),即可统一存储执行数据。
6. 验证监听器是否写入数据库
可通过以下方式验证:
- 运行测试任务,执行完成后查询自定义历史表,检查是否有数据插入
- 在监听器方法中添加日志输出,确认方法是否被触发
- 捕获
SaveChangesAsync的异常,排查数据库写入失败问题
内容的提问来源于stack exchange,提问作者Гожев Артур
相关产品推荐
相关产品推荐

