在上篇文章中Quartz.Net(1) 已经介绍了Quartz.Net的基本运用,该篇文章中将主要介绍NetCore3.1如何整合Quartz.Net,在后台运行定时job,并运用到上篇文章讲到的介绍点。
1 导入Nuget包
<PackageReference Include="Quartz" Version="3.8.1" /><PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.8.1" /><PackageReference Include="Quartz.Extensions.Hosting" Version="3.8.1" />
Quartz 定时框架
Quartz.Extensions.DependencyInjection 用于在任务中导入其他的服务,整合使用NetCore的依赖注入框架
Quartz.Extensions.Hosting 使得Job在后台运行
.NetCore3.1 整合的版本均是3.8.1
2 定义Job
在定义Job时,用到了一个自定义特性JobAttribute,标注在实现了 I J o b \textcolor{red}{IJob} IJob类上,标明该任务的分组,名称,描述 ,触发器触发时的Corn表达式。
在实际运行的任务中将依赖于其他服务 如数据库服务、配置服务等,这里可以通过构造函数注入的方式引入其他的服务
/// <summary>/// 自定义特性,用来标注Job运行信息/// </summary>[AttributeUsage(AttributeTargets.Class)]public class JobAttribute: Attribute{/// <summary>/// Job运行的Corn表达式/// </summary>public string Corn { get; set; }/// <summary>/// Job组/// </summary>public string JobGroup { get; set; }/// <summary>/// Job名称/// </summary>public string JobName { get; set; }/// <summary>/// Job描述/// </summary>public string JobDesc { get; set; }public JobAttribute(string Corn) {this.Corn = Corn;}}
/// <summary>/// 新进员工创建用户刷脸照片Job /// 每天6点、14点执行/// </summary>[Job("0 0 6,14 * * ? *", JobDesc = "新进员工创建用户刷脸照片Job", JobGroup = "group2", JobName = "AddJob")]public class CreatePhotoJob : IJob{private FaceContext faceContext;private FaceWebService faceWebService;private ILogger<DelLeftEmpPhotoJob> logger;private IConfiguration configuration;public CreatePhotoJob(FaceContext faceContext, FaceWebService faceWebService, ILogger<DelLeftEmpPhotoJob> logger, IConfiguration configuration){this.faceContext = faceContext;this.faceWebService = faceWebService;this.logger = logger;this.configuration = configuration;}public async Task Execute(IJobExecutionContext context){//忽略任务的具体执行逻辑await RunJob();}}
/// <summary>/// 删除离职人员刷脸照片Job/// 凌晨2点钟执行/// </summary>[Job("0 0 2 * * ? *", JobDesc = "删除离职人员刷脸照片Job",JobGroup ="group1",JobName ="DelJob")]public class DelLeftEmpPhotoJob : IJob{private FaceContext faceContext;private FaceWebService faceWebService;private ILogger<DelLeftEmpPhotoJob> logger;public DelLeftEmpPhotoJob(FaceContext faceContext, FaceWebService faceWebService, ILogger<DelLeftEmpPhotoJob> logger){this.faceContext = faceContext;this.faceWebService = faceWebService;this.logger = logger;}public async Task Execute(IJobExecutionContext context){//忽略任务的具体执行逻辑await RunJob();}
}
3 定义JobListener
为了更好的监听任务的运行状态,比如说在任务运行结束时向开发人员推送邮件,这里用到了任务监听器。实现方式时实现一个IJobListener。具体的业务逻辑请忽略,这里只是给出一个思路。
/// <summary>/// 任务监听器,使用AOP的思想监听任务[IJob]运行前后的动作/// </summary>public class MyJobListener : IJobListener{public string Name => "JobListener";public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default){//throw new NotImplementedException();}/*** 监听Job运行前*/public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default){// throw new NotImplementedException();}/*** 在这里执行任务结束后的操作* 根据JobDetail所在的分组与名称分别发送不同内容的邮件信息*/public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default){try{HRWebService webService = new HRWebService();var jobkey = context.JobDetail.Key;MailBody body = null ;if (jobkey.Group.Equals("group2") && jobkey.Name.Equals("AddJob")){body = MailBody.AddJobMail();}else if (jobkey.Group.Equals("group1") && jobkey.Name.Equals("DelJob")){body = MailBody.DelJobMail();}await webService.SendMail(body);}catch (Exception e) { }}}
3 配置服务
为了运行定义的IJob 示例,需要注册服务,触发器,监听器等信息,通常是在startup中进行配置。为了避免在startup中进行关于Quartz复杂的配置,这里将Job配置,触发器的配置等配置服务抽离到一个单独的配置类中,运用到了反射的思想。
/// <summary>/// Quartz.Net 定时Job配置类/// </summary>public static class QuartzConfiguration{public static IServiceCollection AddMyQuartz(this IServiceCollection services) {//读取所有实现IJob接口的类,类上标注了JobAttribute类,调度器使用corn触发器去触发JOB任务//FaceJob是我定义Job的模块,这里只做参考,实际读取IJob的反射Type可能各不相同AssemblyName assemblyName = new AssemblyName("FaceJob");Assembly anotherModuleAssembly = Assembly.Load(assemblyName);var types = anotherModuleAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IJob))).Where(t => t.GetCustomAttribute<JobAttribute>() != null).ToArray();services.AddQuartz(configure =>{//Quartz3.8.1版本 默认是使用NetCore的依赖注入框架的,因此可以不用显示的定义此信息,但是在3.3.2及之前版本,不显示定义此配置是无法在Job中引入其他服务的//configure.UseMicrosoftDependencyInjectionJobFactory();foreach (Type type in types){//读取IJob上定jobAttribute(标注Job运行的信息) JobAttribute jobAttribute = type.GetCustomAttribute<JobAttribute>();var jobKey = new JobKey(jobAttribute.JobName, jobAttribute.JobGroup);//配置Jobconfigure.AddJob(type, jobKey, config =>{config.WithDescription(jobAttribute.JobDesc);});//配置触发器,统一使用Corn触发器configure.AddTrigger(config =>{config.ForJob(jobKey).WithCronSchedule(jobAttribute.Corn);});}//配置job监听器[监听任意组下的Job]configure.AddJobListener<MyJobListener>(GroupMatcher<JobKey>.AnyGroup());});//后台Job,任务在后台运行services.AddQuartzHostedService(options =>{options.WaitForJobsToComplete = true;});return services;} }
在startup中引入Quartz配置
public void ConfigureServices(IServiceCollection services){//忽略其他服务配置#region Quartz服务配置services.AddMyQuartz();#endregion}