在.NET Core中可以通过BackgroundService
来实现简单的定时任务服务,一旦涉及到生产环境下的具体业务,就显得力不从心了。这时,我们可以考虑集成作业调度框架Quartz来实现定时服务管理。
Quartz.NET官网:https://www.quartz-scheduler.net/
本文基于.NET Core SDK5.0 和 Quartz3.3.2,最新版本可能有差异,仅供参考。
目标
- 任务基本管理:包括新增、编辑和删除任务。
- 任务操作管理:包括自启动、启动、停止任务。
Quartz
Quartz 由三个部分组成,分别为Schedule(调度器)、Trigger(触发器)、Job(任务)。其中,Job是指被调度的任务;Trigger是控制任务运行的触发器;Schedule是负责协调Job和Trigger的独立容器。
IJob
继承与实现IJob接口,执行任务并记录任务日志。
public class HttpJob : IJob
{
private readonly ILogger<HttpJob> logger;
private readonly IHttpClientFactory httpClientFactory;
public HttpJob(ILogger<HttpJob> logger, IHttpClientFactory httpClientFactory)
{
this.logger = logger;
this.httpClientFactory = httpClientFactory;
}
public async Task Execute(IJobExecutionContext context)
{
// 任务: 执行
var flag = false;
var model = JsonConvert.DeserializeObject<TaskDto>(context.JobDetail.JobDataMap.GetString("model"));
string httpMessage = string.Empty;
if (model != null)
{
try
{
Dictionary<string, string> header = new();
if (!string.IsNullOrEmpty(model.AuthKey) && !string.IsNullOrEmpty(model.AuthValue))
{
header.Add(model.AuthKey.Trim(), model.AuthValue.Trim());
}
httpMessage = await httpClientFactory.HttpSendAsync(
model.ApiMethod?.ToLower() == "get" ? HttpMethod.Get : HttpMethod.Post,
model.ApiUrl,
header);
logger.LogInformation($"任务: {context.JobDetail.Key.Group}.{context.JobDetail.Key.Name} 执行成功");
logger.LogInformation(httpMessage);
flag = true;
}
catch (Exception ex)
{
logger.LogError($"任务: {context.JobDetail.Key.Group}.{context.JobDetail.Key.Name} 执行失败");
logger.LogError(ex.Message);
}
// 任务: 执行日志
try
{
using (var efDbContext = (EfDbContext)ServiceLocator.Instance.CreateScope().ServiceProvider.GetService(typeof(EfDbContext)))
{
var guid = ZnResponse.GuId();
var remark = flag ? httpMessage : "执行失败";
// 日志
efDbContext.Database.ExecuteSqlRaw("INSERT INTO QTZLogger (ID,TASKID,EXECUTEDDATE,REMARKS) VALUES ({0},{1},{2},{3})", guid, model?.TaskId, DateTime.Now, remark);
// 更新
efDbContext.Database.ExecuteSqlRaw("UPDATE QTZTask SET LASTRUNTIME={0} WHERE TASKID={1}", DateTime.Now, model?.TaskId);
}
logger.LogInformation("任务: 日志入库成功");
}
catch (Exception ex)
{
logger.LogError($"任务: 日志入库失败");
logger.LogError(ex.Message);
}
}
else
{
logger.LogError($"任务: {context.JobDetail.Key.Group}.{context.JobDetail.Key.Name} 执行异常(JobDataMap is null)");
}
}
}
IJobFactory
继承与实现IJobFactory,调度执行任务。
public class JobFactory : IJobFactory
{
private readonly IServiceProvider serviceProvider;
public JobFactory(IServiceProvider serviceProvider)
{
serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
Trigger
封装创建触发器与创建作业实例静态方法。
public static class QuartzExtension
{
/// <summary>
/// 创建触发器
/// </summary>
public static ITrigger CreateCronTrigger(TaskDto task)
{
DateTimeOffset beginTime = DateBuilder.NextGivenSecondDate(task.BeginTime, 1);
DateTimeOffset endTime = DateBuilder.NextGivenSecondDate(task.EndTime, 1);
return TriggerBuilder.Create()
.WithIdentity(task.TaskName, task.GroupName)
.StartAt(beginTime)
.EndAt(endTime)
.WithCronSchedule(task.Interval)
.ForJob(task.TaskName, task.GroupName)
.Build();
}
/// <summary>
/// 创建作业实例
/// </summary>
public static IJobDetail CreateJob(TaskDto task)
{
return JobBuilder
.Create<HttpJob>()
.WithIdentity(task.TaskName, task.GroupName)
.WithDescription(task.TaskDesc)
.UsingJobData("model", JsonConvert.SerializeObject(task))
.Build();
}
}
注入集成
在startup中注入集成Quartz。
services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
services.AddSingleton<IJobFactory, JobFactory>();
services.AddSingleton<HttpJob>();
然后在Configure
实现任务自启动。
app.UseQuartzInit();
/// <summary>
/// 程序启动将任务调度表里所有状态为 执行中 任务启动起来
/// </summary>
public static IApplicationBuilder UseQuartzInit(this IApplicationBuilder app)
{
if (app == null) throw new ArgumentNullException(nameof(app));
var flag = Appsettings.Get(new string[] { "QTZConfig", "Enabled" }).ObjToBool();
if (flag)
{
IServiceProvider services = app.ApplicationServices;
EfDbContext efDbContext = services.GetService<EfDbContext>();
ISchedulerFactory schedulerFactory = services.GetService<ISchedulerFactory>();
IJobFactory jobFactory = services.GetService<IJobFactory>();
if (efDbContext != null)
{
NLogHelp.Info($"任务管理: 初始化");
int i = 0;
var taskList = efDbContext.QTZTask.Where(x => x.Status == 1).ToList();
// 通过工场类获得调度器
IScheduler scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult();
scheduler.JobFactory = jobFactory;
// 装载作业任务
taskList.ForEach(async x =>
{
try
{
var jk = new JobKey(x.TaskName, x.GroupName);
if (await scheduler.CheckExists(jk))
{
await scheduler.PauseJob(jk);
await scheduler.DeleteJob(jk);
}
if (!string.IsNullOrEmpty(x.Interval) && CronExpression.IsValidExpression(x.Interval))
{
IJobDetail job = QuartzExtension.CreateJob(x); ;
ITrigger trigger = QuartzExtension.CreateCronTrigger(x);
// 设置监听器
JobListener listener = new();
scheduler.ListenerManager.AddJobListener(listener, GroupMatcher<JobKey>.AnyGroup());
// 将触发器和作业任务绑定到调度器中
scheduler.ScheduleJob(job, trigger).GetAwaiter().GetResult();
i++;
NLogHelp.Info($"任务管理: 运行成功 - {x.GroupName}.{x.TaskName}");
}
}
catch (Exception ex)
{
NLogHelp.Error($"任务管理: 运行失败 - {x.GroupName}.{x.TaskName}");
NLogHelp.Error(ex.ToString());
}
});
// 开启调度器
scheduler.Start().GetAwaiter().GetResult();
NLogHelp.Info($"任务管理: 初始化完成 - 共成功运行 {i} 个任务");
}
}
return app;
}