使用Quartz.Net之前先了解一些基本概念、接口以及类。

基本概念

  • Scheduler: 用来管理调度,实现IScheduler接口。
  • Job: 具体的作业,实现IJob接口,通过IJobExecute方法来做具体的事,比如使用HttpClient向一个业务接口发送请求。
  • Trigger: 用来触发Job开始工作,实现ITrigger接口。

基本接口和类

  • IScheduler: 包含了调度的主要API
  • IJob: 需要IScheduler执行的具体工作
  • IJobDetail: 用来生成Job的实例
  • ITrigger: 触发器,决定了在什么时间以何种方式触发
  • JobBuilder: 用来生成IJobDetail实例
  • TriggerBuilder: 用来生成ITrigger实例
  • ISchedulerFactory:用来生成IScheduler实例
  • IJobListener: 侦听IJob

在项目中使用的大致步骤是: 在mysql数据库运行sql语句生成Quartz.Net相关的、专属的数据库,从NuGet下载相关组件,然后在Startup.cs的DI容器和请求管道中分别配置,写一个围绕IScheduler的帮助类,定义IJob如何实现,最后在控制器中调用IScheduler的帮助类。

在mysql数据库运行sql语句

# By: Ron Cordell - roncordell
#  I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.
# make sure you have UTF-8 collaction for best .NET interoperability
# CREATE DATABASE quartznet CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(60) NOT NULL,
JOB_NAME VARCHAR(60) NOT NULL,
JOB_GROUP VARCHAR(60) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE BOOLEAN NOT NULL,
IS_NONCONCURRENT BOOLEAN NOT NULL,
IS_UPDATE_DATA BOOLEAN NOT NULL,
REQUESTS_RECOVERY BOOLEAN NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(60) NOT NULL,
TRIGGER_NAME VARCHAR(60) NOT NULL,
TRIGGER_GROUP VARCHAR(60) NOT NULL,
JOB_NAME VARCHAR(60) NOT NULL,
JOB_GROUP VARCHAR(60) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(19) NULL,
PREV_FIRE_TIME BIGINT(19) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(19) NOT NULL,
END_TIME BIGINT(19) NULL,
CALENDAR_NAME VARCHAR(60) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(60) NOT NULL,
TRIGGER_NAME VARCHAR(60) NOT NULL,
TRIGGER_GROUP VARCHAR(60) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(60) NOT NULL,
TRIGGER_NAME VARCHAR(60) NOT NULL,
TRIGGER_GROUP VARCHAR(60) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(60) NOT NULL,
    TRIGGER_NAME VARCHAR(60) NOT NULL,
    TRIGGER_GROUP VARCHAR(60) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 BOOLEAN NULL,
    BOOL_PROP_2 BOOLEAN NULL,
    TIME_ZONE_ID VARCHAR(80) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(60) NOT NULL,
TRIGGER_NAME VARCHAR(60) NOT NULL,
TRIGGER_GROUP VARCHAR(60) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(60) NOT NULL,
CALENDAR_NAME VARCHAR(60) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(60) NOT NULL,
TRIGGER_GROUP VARCHAR(60) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(60) NOT NULL,
ENTRY_ID VARCHAR(140) NOT NULL,
TRIGGER_NAME VARCHAR(60) NOT NULL,
TRIGGER_GROUP VARCHAR(60) NOT NULL,
INSTANCE_NAME VARCHAR(60) NOT NULL,
FIRED_TIME BIGINT(19) NOT NULL,
SCHED_TIME BIGINT(19) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(60) NULL,
JOB_GROUP VARCHAR(60) NULL,
IS_NONCONCURRENT BOOLEAN NULL,
REQUESTS_RECOVERY BOOLEAN NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;
CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(60) NOT NULL,
INSTANCE_NAME VARCHAR(60) NOT NULL,
LAST_CHECKIN_TIME BIGINT(19) NOT NULL,
CHECKIN_INTERVAL BIGINT(19) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(60) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
commit; 

从NuGet下载相关组件

Quartz
Quartz.Serialization.Json

Startup.cs的DI容器和请求管道中分别配置

using Quartz;
using Quartz.Impl;
using Quartz.Spi;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    //基本配置
    NameValueCollection props = new NameValueCollection {
        { "quartz.threadPool.threadCount","100"},
        { "quartz.jobStore.type","Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"},
        { "quartz.jobStore.driverDelegateType","Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz"},
        { "quartz.jobStore.dataSource","myDS"},
        { "quartz.dataSource.myDS.connectionString","Server=your_ip;Port=3306;Database=quartznet;Uid=your_username;Pwd=your_password"},
        { "quartz.dataSource.myDS.provider","MySql"},
        { "quartz.serializer.type","json"}
        //{ "quartz.jobStore.useProperties","true"}
    };
    var schedulerFactory = new StdSchedulerFactory(props);
    var scheduler = schedulerFactory.GetScheduler().Result;
    //这里定义了一个IJobListener,用来处理当Job运行失败
    var yourContext = services.BuildServiceProvider().GetService<YourContext>();
    IJobListener jobListener = new JobFailureHandler(yourContext);
    scheduler.ListenerManager.AddJobListener(jobListener);
    services.AddSingleton<ISchedulerFactory>(schedulerFactory);
    services.AddSingleton(scheduler);
    //SimpleInjectorJobFactory,下面详细写
    services.AddSingleton<IJobFactory, SimpleInjectorJobFactory>();
    services.AddSingleton<IJobListener, JobFailureHandler>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
{
    //JobScheduler,是围绕`IScheduler`的帮助类,下面详细写
    var scheduler = serviceProvider.GetService<JobScheduler>();
    scheduler.Init();
}

围绕IScheduler的帮助类

public class JobScheduler
{
    private static IScheduler _scheduler;
    public JobScheduler(IScheduler scheduler)
    {
        _scheduler = scheduler;
    }
    public void Start()
    {
        if (!_scheduler.IsStarted)
        {
            _scheduler.Start();
        }
    }
    public void Shutdown(bool waitForJobsToComplete)
    {
        if (!_scheduler.IsShutdown)
        {
            _scheduler.Shutdown(waitForJobsToComplete);
        }
    }
    public void StandBy()
    {
        if (!_scheduler.InStandbyMode)
        {
            _scheduler.Standby();
        }
    }
    public void PauseAll()
    {
        _scheduler.PauseAll();
    }
    public void ResumeAll()
    {
        _scheduler.ResumeAll();
    }
    public async Task<bool> UnscheduleJob(string triggerName, string triggerGroup)
    {
        return await _scheduler.UnscheduleJob(new TriggerKey(triggerName, triggerGroup));
    }
    public Task<bool> DeleteJob(string jobName, string jobGroup)
    {
        return _scheduler.DeleteJob(new JobKey(jobName, jobGroup));
    }
    public void PauseJob(string jobName, string groupName)
    {
        _scheduler.PauseJob(new JobKey(jobName, groupName));
    }
    public void ResumeJob(string jobName, string groupName)
    {
        _scheduler.ResumeJob(new JobKey(jobName, groupName));
    }
    public void TriggerJob(string jobName, string groupName)
    {
        _scheduler.TriggerJob(new JobKey(jobName, groupName));
    }
    public Task<bool> IsJobExist(string jobName, string groupName)
    {
        return _scheduler.CheckExists(new JobKey(jobName, groupName));
    }
    public async Task AddJob(string your_string, string group, string cron)
    {
        //JobUtil帮助类下面详细说明
        if (!JobUtil.IsValidCronExpression(cron.Trim()))
            throw new ArgumentException(string.Format("invalid expression{0}", cron));
        if(await IsJobExist(your_string, group))
        {
            await DeleteJob(your_string, group);
        }
        var jobDetail = JobBuilder.Create()
               .OfType(typeof(HttpRequestJob)) //这里的HttpRequestJob就是实现IJob的类,定义具体做什么,下面详细说
               .WithIdentity(your_string, group)
               .StoreDurably(true)
               .RequestRecovery(false)
               .UsingJobData("your_key", your_string) //这里可以放一些key, value键值对,在IJob中可以调用
               .Build();
         var triggerBuilder = TriggerBuilder.Create()
                .WithIdentity(your_string, group)
                .WithDailyTimeIntervalSchedule(s =>
                { //解决在IIS上不生效的问题
                    s.WithIntervalInHours(24)
                        .OnEveryDay()
                        .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
                        .InTimeZone(TimeZoneInfo.Local);
                })
                .WithCronSchedule(cron.Trim(), x => x.WithMisfireHandlingInstructionDoNothing());//注意WithMisfireHandlingInstructionDoNothing,这里关系这执行错过用什么策略,是枚举值,根据业务场景来设置
        var trigger = triggerBuilder.Build();
        await _scheduler.ScheduleJob(jobDetail, trigger);
        
    }
}
<!--JobUtil帮助类-->
public static class JobUtil
{
    //判断cron表达式是否有效
    public static bool IsValidCronExpression(string expression)
    {
        return CronExpression.IsValidExpression(expression);
    }
    //根据星期、小时、分钟、秒生成cron表达式
    public static string GetCronByTime(int hour, int minute, int second, TimingWeekEnum week) 
    {
        string cron = string.Empty;
        //string cron = "0 0 12 ? * SUN *";
        if (week == TimingWeekEnum.Monday)
        {
            cron = $"{second} {minute} {hour} ? * MON *"; 
        }
        else if (week == TimingWeekEnum.Tuesday)
        {
            cron = $"{second} {minute} {hour} ? * TUE *";
        }
        else if (week == TimingWeekEnum.Wednesday)
        {
            cron = $"{second} {minute} {hour} ? * WED *";
        }
        else if (week == TimingWeekEnum.Thursday)
        {
            cron = $"{second} {minute} {hour} ? * THU *";
        }
        else if (week == TimingWeekEnum.Friday)
        {
            cron = $"{second} {minute} {hour} ? * FRI *";
        }
        else if (week == TimingWeekEnum.Saturday)
        {
            cron = $"{second} {minute} {hour} ? * SAT *";
        }
        else
        {
            cron = $"{second} {minute} {hour} ? * SUN *";
        }
        return cron;
    }
}

定义IJob如何实现

public class HttpRequestJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        //这个就是在JobScheduler帮助类的AddJob方法中UsingJobData("your_key", your_string)这里的设置
        string some_str = context.JobDetail.JobDataMap.GetString("your_key"); 
        using (HttpClient client = new HttpClient())
        {
            client.Timeout = TimeSpan.FromSeconds(some_seconds);
            string url = your_api_url + "/" + some_str;
            var message = await client.GetAsync(url);
        }
    }
}

然后在your_api_url这个api中定义如何实现

在控制器中调用IScheduler的帮助类进行Quartz.Net相关设置

public class SomeController : Controller
{
    private readonly JobScheduler _scheduler;
    public SomeController(JobScheduler scheduler)
    {
        _scheduler = scheduler;
    }
}

SimpleInjectorJobFactory这个类

这个类可能最终没用上。

 public class SimpleInjectorJobFactory : IJobFactory
{
    private readonly IServiceProvider service;
    public SimpleInjectorJobFactory(IServiceProvider serviceProvider)
    {
        this.service = serviceProvider;
    }
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return (IJob)service.GetService(bundle.JobDetail.JobType);
    }
    public void ReturnJob(IJob job)
    {
        var disposable = job as IDisposable;
        disposable?.Dispose();
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐