在上篇文章中Quartz.Net(1) 已经介绍了Quartz.Net的基本运用,该篇文章中将主要介绍NetCore3.1如何整合Quartz.Net,在后台运行定时job,并运用到上篇文章讲到的介绍点。
1 导入Nuget包
xml
<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表达式。
在实际运行的任务中将依赖于其他服务 如数据库服务、配置服务等,这里可以通过构造函数注入的方式引入其他的服务
csharp
/// <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;
}
}
csharp
/// <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();
}
}
csharp
/// <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。具体的业务逻辑请忽略,这里只是给出一个思路。
csharp
/// <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配置,触发器的配置等配置服务抽离到一个单独的配置类中,运用到了反射的思想。
csharp
/// <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);
//配置Job
configure.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配置
csharp
public void ConfigureServices(IServiceCollection services)
{
//忽略其他服务配置
#region Quartz服务配置
services.AddMyQuartz();
#endregion
}