ABP+AdminLTE+Bootstrap Table权限管理系统一期
Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS
Quartz简介
Quartz.NET是一个开源的作业调度框架,是 OpenSymphony
的 Quartz API
的.NET移植,它用C#写成,可用于winform
和asp.net
应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like
表达式等等。非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等.
参考
对Quartz.NET不熟悉的可以先看下
官方学习文档:http://www.quartz-scheduler.net/documentation/index.html
使用实例介绍:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html
官方的源代码下载:http://sourceforge.net/projects/quartznet/files/quartznet/
特性
它一些很好的特性:
- 支持集群,作业分组,作业远程管理。
- 自定义精细的时间触发器,使用简单,作业和触发分离。
- 数据库支持,可以寄宿
Windows
服务,WebSite
,winform
等。
Quartz框架的一些基础概念解释:
名称 | 描述 |
---|---|
Scheduler | 作业调度器。 |
IJob | 作业接口,继承并实现Execute, 编写执行的具体作业逻辑。 |
JobBuilder | 根据设置,生成一个详细作业信息(JobDetail)。 |
TriggerBuilder | 根据规则,生产对应的Trig |
实战
- Web.config配置
基础概念我就懒得讲了,你也懒得听了,直接上代码吧。
首先配置一下Web.config
,其实这步可有可无,可以直接跳过,只是为了配置一些常量,获取固定的时间,但是不是必要的。
<sectionGroup name="JobList">
<section name="Job" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</sectionGroup>
<JobList>
<Job>
<!--这里是一个任务节点-->
<add key="Url" value="http://www.baidu.com" />
<!--需要访问的Url-->
<add key="Hour" value="10" />
<!--开始时间小时-->
<!--开始时间小时,注意:这里的小时为0-23,如果是1点的话就是1,而不是01-->
<add key="Minute" value="30" />
<!--开始时间分钟-->
<!--开始时间分钟,注意:同上0-59-->
</Job>
</JobList>
-
创建service
创建ISystemSchedulerService
以及SystemSchedulerService
,代码上面都有详细的注释,我就不重复了。
接口:
public interface ISystemSchedulerService: IApplicationService
{
void StartScheduler();
}
service: SystemSchedulerService
public class SystemSchedulerService : ISystemSchedulerService
{
private IScheduler _scheduler;
public ILogger _Logger { get; set; }
public SystemSchedulerService()
{
_Logger = NullLogger.Instance;
}
public void StopScheduler()
{
_scheduler.Shutdown();
}
public void StartScheduler()
{
try
{
//这里读取配置文件中的任务开始时间
int hour = int.Parse(((NameValueCollection)ConfigurationManager.GetSection("JobList/Job"))["Hour"]);
int minute = int.Parse(((NameValueCollection)ConfigurationManager.GetSection("JobList/Job"))["Minute"]);
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();//内存调度
_scheduler = schedulerFactory.GetScheduler();
//创建一个Job来执行特定的任务
IJobDetail myLogJob = new JobDetailImpl("myLogJob", typeof(MyLogJob));
//创建并定义触发器的规则(每天执行一次时间为:时:分)
ITrigger trigger =
TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule(
a => a.WithIntervalInHours(24).OnEveryDay().StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(hour, minute))).Build();
_scheduler.Clear();
//将创建好的任务和触发规则加入到Quartz中
_scheduler.ScheduleJob(myLogJob, trigger);
//开始
_scheduler.Start();
}
catch (Exception ex)
{
_Logger.Info(ex.Message);
}
}
}
Quartz API
Quartz API的关键接口和类是:
-
IScheduler
- 与调度程序交互的主要API。
-IJob
- 您希望由调度程序执行的组件实现的接口。 -
IJobDetail
- 用于定义作业的实例。 -
ITrigger
- 定义执行给定Job的时间表的组件。 -
JobBuilder
- 用于定义/构建obDetail
实例,它定义了Jobs
的实例。 -
TriggerBuilder
- 用于定义/构建触发器实例。
一个调度程序的生命周期是由它为界的创作,通过SchedulerFactory
和其有关方法的调用。一旦创建了IScheduler
接口,就可以使用添加,删除和列出作业和触发器,并执行其他与调度相关的操作(例如暂停触发器)。但是,调度程序不会实际上对任何触发器(执行作业)执行操作,直到使用Start()
方法启动它。
构建作业定义的代码块使用使用流畅接口的JobBuilder
创建产品IJobDetail
。同样,构建触发器的代码块使用TriggerBuilder
流畅的接口和特定于触发器类型的扩展方法。可能的时间延长方法是:
- WithCalendarIntervalSchedule
- WithCronSchedule
- WithDailyTimeIntervalSchedule
- WithSimpleSchedule
示例中的ITrigger
其实可以不读取配置信息的。这就是我说的不用配置Web.config
也可以。效果如下,每隔10秒执行一次。
ITrigger trigger =
TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule(
a => a.WithIntervalInSeconds(10)).Build();
工作和触发器
代码:
public class MyLogJob : JobBase, ITransientDependency
{
public ILogger _Logger { get; set; }
public MyLogJob()
{
_Logger = NullLogger.Instance;
}
public override void Execute(IJobExecutionContext context)
{
try
{
_Logger.Info("QuartzJob 任务开始运行");
for (int i = 0; i < 10; i++)
{
_Logger.InfoFormat("QuartzJob 正在运行{0}", i);
}
_Logger.Info("QuartzJob任务运行结束");
}
catch (Exception ex)
{
_Logger.Error("运行异常:"+ex.Message, ex);
}
}
}
当作业的触发器触发,Execute(..)
方法由调度程序的工作线程之一调用。传递给此方法的JobExecutionContext
对象为作业实例提供有关其“运行时”环境的信息 ( 调度程序执行的逻辑),触发执行的触发器的逻辑。JobDetail
对象是在Job
添加到调度器时由Quartz.NET
客户端创建的。它包含Job的各种属性设置,以及一个JobDataMap
,它可以用来存储作业类的给定实例的状态信息
触发器对象用于触发作业的执行(或“触发”)。当你想安排一个工作,你实例化一个触发器并“调整”它的属性来提供你想要的调度。触发器也可能有一个与它们相关的JobDataMap
- 这对于传递参数到一个特定于触发器的触发的Job
是很有用的。Quartz
提供了一些不同的触发器类型,但最常用的类型是SimpleTrigger
(接口ISimpleTrigger
)和CronTrigger
(接口ICronTrigger
)。
如果您需要“一次性”执行(在某个特定时间只执行一项作业),或者您需要在给定时间开始工作,并且重复执行N次,SimpleTrigger
会很方便的T之间执行。如果您希望基于类似日历的时间表(例如“每个星期五,中午”或“每个月的第10天的10:15”)触发,则CronTrigger
非常有用。
public class UserInfoController : ABPCMSControllerBase
{
private readonly ISystemSchedulerService _iSystemSchedulerService;
public UserInfoController(ISystemSchedulerService iSystemSchedulerService)
{
_iSystemSchedulerService = iSystemSchedulerService;
}
[HttpGet]
[DontWrapResult]
public async Task<ActionResult> GetUserInfo()
{
_iSystemSchedulerService.StartScheduler();
}
}
然后我们在前台控制器里面调用ISystemSchedulerService
的StartScheduler()
方法。然后运行项目。
然后我们打开日志Web\App_Data\Logs\Logs.txt
,看下效果:
每隔十秒钟执行一次。
Abp.Quartz
ABP有內建的持久化后台job队列和后台worker
系统。如果对于后台workers你有更高级的计划安排需求,Quartz
会是一个更好的选择。对于持久化后台job队列,Hangfire
也是一个好的选择。
ABP中Quartz应用其实蛮简单的。
- 首先Abp.Quartz nuget包
- 然后加上引入到项目中。
[DependsOn(
typeof(AbpQuartzModule)
)]
public class ABPCMSWebModule : AbpModule
{
}
- 创建Jobs
这里我们就用上面例子中的job,当然创建一个新job,你可以实现Quartz
的IJob
接口,或者继承JobBase
类(定义在Abp.Quartz包),这个类包含一些帮助属性和方法(例如日志和本地化)。
public class MyLogJob : JobBase, ITransientDependency
{
public override void Execute(IJobExecutionContext context)
{
Logger.Info("Executed MyLogJob :)");
}
}
4.创建调度作业IQuartzScheduleJobManager
接口被用来创建调度作业。你可以在类中注入该接口(或者你可以在你的模块的PostInitialize
方法中解析和使用它)来调度作业。这里我们把之前创建的MyLogJob引用进来。
效果如下:
public async Task<ActionResult> ScheduleJob()
{
await _jobManager.ScheduleAsync<MyLogJob>(
job =>
{
job.WithIdentity("MyLogJobIdentity", "MyGroup")
.WithDescription("A job to simply write logs.");
},
trigger =>
{
trigger.StartNow()
.WithSimpleSchedule(schedule =>
{
schedule.RepeatForever()
.WithIntervalInSeconds(5)
.Build();
});
});
_jobManager.Start();
return Content("OK, scheduled!");
}
效果一样。
经过上面四步,就完成Abp.Quartz运用,是挺简单的。
扩展
SimpleTrigger
说一下SimpleTrigger
,WithCronSchedule()
这里有三种方式,一种就是上面说到的Web.config
配置,一种是WithCronSchedule
设置时间参数。还有一种是WithCronSchedule("")
拥有强大的Cron
时间表达式。
SimpleTrigge
r应满足你的日程安排需求,如果你需要在某个特定时刻及时执行一次作业,或者在特定时间执行一次,然后在特定时间间隔重复执行。如果你想让触发器在2018年1月13日上午11点23分54秒执行,然后再执行5次,每10秒执行一次。
通过这个描述,你可能不会觉得奇怪的是,SimpleTrigger
的属性包括:开始时间和结束时间,重复计数和重复间隔。所有这些属性都与您所期望的完全相同,只有一些与结束时间属性相关的特殊注释。
SimpleTrigger
实例是使用TriggerBuilder
(用于触发器的主属性)和WithSimpleSchedule
扩展方法(用于SimpleTrigger
特有的属性)构建的。
在特定的时刻建立一个触发器,不要重复:
// trigger builder creates simple trigger by default, actually an ITrigger is returned
ISimpleTrigger trigger = (ISimpleTrigger) TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartAt(myStartTime) // some Date
.ForJob("job1", "group1") // identify job with name, group strings
.Build();
建立一个特定时刻的触发器,然后每十秒钟重复十次:
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.StartAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.WithRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.ForJob(myJob) // identify job with handle to its JobDetail itself
.Build();
建立一个触发器,将在未来五分钟内触发一次:
trigger = (ISimpleTrigger) TriggerBuilder.Create()
.WithIdentity("trigger5", "group1")
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute)) // use DateBuilder to create a date in the future
.ForJob(myJobKey) // identify job with its JobKey
.Build();
建立一个触发器,现在会触发,然后每隔五分钟重复一次,直到22:00:
trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever())
.EndAt(DateBuilder.DateOf(22, 0, 0))
.Build();
建立一个触发器,将在下一个小时的顶部执行,然后每2小时重复一次:
trigger = TriggerBuilder.Create()
.WithIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
.StartAt(DateBuilder.EvenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
.WithSimpleSchedule(x => x
.WithIntervalInHours(2)
.RepeatForever())
// note that in this example, 'forJob(..)' is not called
// - which is valid if the trigger is passed to the scheduler along with the job
.Build();
await scheduler.scheduleJob(trigger, job);
另外一些常用的停止作业的指令常量
- MisfireInstruction.IgnoreMisfirePolicy
- MisfirePolicy.SimpleTrigger.FireNow
- MisfirePolicy.SimpleTrigger.RescheduleNowWithExistingRepeatCount
- MisfirePolicy.SimpleTrigger.RescheduleNowWithRemainingRepeatCount
- MisfirePolicy.SimpleTrigger.RescheduleNextWithRemainingCount
- MisfirePolicy.SimpleTrigger.RescheduleNextWithExistingCount
在构建SimpleTriggers时,可以将简单的时间表(通过SimpleSchedulerBuilder)指定为停止作业指令:
trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever()
.WithMisfireHandlingInstructionNextWithExistingCount())
.Build();
Cron时间表达式。
Cron-Expressions
用于配置CronTrigger
的实例。Cron-Expressions
是由七个子表达式组成的字符串,它们描述了计划的各个细节。这些子表达式用空格分隔,表示:
- Seconds
- Minutes
- Hours
- Day-of-Month
- Month
- Day-of-Week
- Year (optional field)
示例Cron表达式
下面是一些表达式及其含义的例子 - 你可以在CronTrigger
的API文档中找到更多的例子
CronTrigger示例1 - 创建一个触发器的表达式,每5分钟触发一次"0 0/5 * * * ?"
CronTrigger示例2 - 一个表达式,用于创建在分钟后10秒(即上午10:00:10,上午10:05:10等)每5分钟触发一次的触发器。"10 0/5 * * * ?"
CronTrigger示例3 - 一个表达式,用于在每个星期三和星期五的10:30,11:30,12:30和13:30创建一个触发器。"0 30 10-13 ? * WED,FRI"
CronTrigger示例4 - 一个表达式,用于创建一个触发器,在每个月的第5天和第20天的上午8点到上午10点之间每隔半小时触发一次。请注意,触发器不会在上午10点,仅在8点,8点,9点和9点30分"0 0/30 8-9 5,20 * ?"
请注意,一些调度要求过于复杂,无法用一个触发器来表示 - 例如“上午9点至上午10点之间每5分钟一次,下午1点至10点之间每20分钟一次”。在这种情况下解决方案是简单地创建两个触发器,并注册他们两个运行相同的工作。
CronTrigger
实例使用TriggerBuilder
(用于触发器的主属性)和WithCronSchedule
扩展方法(用于CronTrigger
特定的属性)构建。
您也可以使用CronScheduleBuilder
的静态方法来创建计划。
建立一个触发器,每隔上午8点到下午5点,每隔一分钟一次:
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?")
.ForJob("myJob", "group1")
.Build();
建立一个触发器,每天在上午10:42开始:
// we use CronScheduleBuilder's static helper methods here
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(10, 42))
.ForJob(myJobKey)
.Build();
要么 -
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 * * ?")
.ForJob("myJob", "group1")
.Build();
建立一个触发器,将在星期三上午10:42,在系统默认的时区以外的其他时间触发:
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder
.WeeklyOnDayAndHourAndMinute(DayOfWeek.Wednesday, 10, 42)
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();
要么 -
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 ? * WED", x => x
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();
以下指令可用于通知Quartz
在CronTrigger
发生停止作业时应该执行的操作。(本教程的“更多关于触发器”部分介绍了停止作业情况)。这些指令被定义为常量(并且API文档具有对其行为的描述)。说明包括:
- MisfireInstruction.IgnoreMisfirePolicy
- MisfireInstruction.CronTrigger.DoNothing
- MisfireInstruction.CronTrigger.FireOnceNow
所有触发器都有MisfireInstrution.SmartPolicy
指令可供使用,并且此指令也是所有触发器类型的默认值。CronTrigger
将“智能策略”指令解释为MisfireInstruction.CronTrigger.FireOnceNow。CronTrigger.UpdateAfterMisfire()
方法的API文档解释了此行为的确切详细信息。
在构建CronTriggers时,您可以将缺火指令指定为cron时间表的一部分(通过WithCronSchedule扩展方法):
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?", x => x
.WithMisfireHandlingInstructionFireAndProceed())
.ForJob("myJob", "group1")
.Build();
其他
我们可以看一下,源码中对IQuartzScheduleJobManager
的ScheduleAsync
方法的封装,其实就是Scheduler.ScheduleJob
处理了一下。
如果要使用模块的PostInitialize
方法中解析和使用它来调度作业,也是可以的。
using System.Reflection;
using Abp.Dependency;
using Abp.Modules;
using Abp.Quartz.Configuration;
using Abp.Threading.BackgroundWorkers;
using Quartz;
namespace Abp.Quartz
{
[DependsOn(typeof (AbpKernelModule))]
public class AbpQuartzModule : AbpModule
{
public override void PreInitialize()
{
IocManager.Register<IAbpQuartzConfiguration, AbpQuartzConfiguration>();
Configuration.Modules.AbpQuartz().Scheduler.JobFactory = new AbpQuartzJobFactory(IocManager);
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
public override void PostInitialize()
{
IocManager.RegisterIfNot<IJobListener, AbpQuartzJobListener>();
Configuration.Modules.AbpQuartz().Scheduler.ListenerManager.AddJobListener(IocManager.Resolve<IJobListener>());
if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
IocManager.Resolve<IBackgroundWorkerManager>().Add(IocManager.Resolve<IQuartzScheduleJobManager>());
}
}
}
}
JobBase
封装的一些方法,继承自IJob
ABP+AdminLTE+Bootstrap Table权限管理系统一期
Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS