Quartz.NET | 佳佳的博客
Quartz.NET 是一个定时计划任务的框架,支持 .NET Core。
本文示例代码大部分来自于官方教程:Quartz.NET - Quartz.NET 3.x Tutorial
Target Framework:.NET Core 2.2
Quartz:3.0.7
使用前首先要使用 NuGet 安装 Quartz.NET。
Install-Package Quartz
标准的定时任务写法
// construct a scheduler factory
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" }
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);
// get a scheduler
IScheduler sched = await factory.GetScheduler();
await sched.Start();
// define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob", "group1")
.Build();
// Trigger the job to run now, and then every 40 seconds
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(40)
.RepeatForever())
.Build();
await sched.ScheduleJob(job, trigger);
SchedulerFactory
StdSchedulerFactory
StdSchedulerFactory
继承 ISchedulerFactory
接口。
调用无参构造函数时自动加载根目录的 quartz.config 配置文件;也可以调用带有 NameValueCollection 参数的构造函数通过代码的方式配置。
实例化 StdSchedulerFactory
后调用 getScheduler()
方法生产 Scheduler,初始化 线程池、JobStore、数据源等。
DirectSchedulerFactory
需要使用更多自定义的 Scheduler 时可以使用 DirectSchedulerFactory
,该类同样继承 ISchedulerFactory
接口。
官方文档不建议在不了解时使用该 Factory 。
IJob
Job
具体的 Job 继承 IJob
接口,这个接口只有一个 Execute
方法。
namespace Quartz
{
public interface IJob
{
Task Execute(JobExecutionContext context);
}
}
HelloJob.cs
class HelloJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Console.Out.WriteLineAsync("Greetings from HelloJob!");
}
}
可通过 WithIdentity()
扩展方法指定 Job 的名称(name)和组(group),这两个值组成了这个 Job 的 Key({group}.{name}),这个 key 在整个 Scheduler 必须是唯一的。
这些信息储存在 context.JobDetail
中。
JobDataMap
可通过 UsingJobData
扩展方法 JobDataMap
的值。
// define the job and tie it to our DumbJob class
IJobDetail job = JobBuilder.Create<DumbJob>()
.WithIdentity("myJob", "group1") // name "myJob", group "group1"
.UsingJobData("jobSays", "Hello World!")
.UsingJobData("myFloatValue", 3.141f)
.Build();
可通过 context.JobDetail.JobDataMap
获取。
JobDataMap dataMap = context.JobDetail.JobDataMap;
string jobSays = dataMap.GetString("jobSays");
float myFloatValue = dataMap.GetFloat("myFloatValue");
JobDataMap
中值的获取支持注入:
public class DumbJob : IJob
{
public string JobSays { private get; set; }
public float MyFloatValue { private get; set; }
public async Task Execute(IJobExecutionContext context)
{
JobKey key = context.JobDetail.Key;
JobDataMap dataMap = context.MergedJobDataMap; // Note the difference from the previous example
IList<DateTimeOffset> state = (IList<DateTimeOffset>)dataMap["myStateData"];
state.Add(DateTimeOffset.UtcNow);
await Console.Error.WriteLineAsync("Instance " + key + " of DumbJob says: " + JobSays + ", and val is: " + MyFloatValue);
}
}
Job 的状态和并发
DisallowConcurrentExecution
特性在 Job class 上添加该特性可以禁止同一个 JobDetail 中同一时间运行多个 Job 实例(但可以定义多个 JobDetail 使用同一个 Job class);
PersistJobDataAfterExecution
特性在 Job class 上添加该特性可以保留上一次运行结束后的 JobDataMap 到下一次运行。考虑到并发运行的可能最好和
DisallowConcurrentExecution
一起使用。
Job class 属性使用注入时,修改属性的值并不会自动修改 JobDataMap 中的值,需要JobDataMap.Put
方法更新其中的值。context.JobDetail.JobDataMap.Put("count", Count);
Job 的其它属性
Durability
:是否持久化;为 false 时如果没有任意一个激活的 Trigger ,则将其从 Scheduler 中删除;默认值为 false;RequestsRecovery
:进程意外结束(如PC意外关机)时,被中断的任务是否重新执行;为 true 时会再次运行;默认值为 false;
ITrigger
ITrigger 的 Schedule 扩展方法:
WithCalendarIntervalSchedule
:以指定的时间间隔(年、月、周、日、时、分、秒、毫秒)重复执行;WithCronSchedule
:使用 Cron 表达式指定何时执行;WithSimpleSchedule
:以指定时间间隔重复执行指定的次数或永久执行;WithDailyTimeIntervalSchedule
:可指定每天在何时开始运行、何时停止运行、每天运行多少次;
属性
- JobKey :{group}.{name}
- StartTimeUtc
- EndTimeUtc
- Priority:优先级(默认值为5)
Misfire Instructions 计划失败时的指令
在 Schedule 中通过 WithMisfireHandlingInstructionFireNow()
等方法指定。默认使用 smart policy。
By default they use a ‘smart policy’ instruction - which has dynamic behavior based on trigger type and configuration.
ITrigger myTrigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever()
.WithMisfireHandlingInstructionFireNow()
)
.Build();
可通过 myTrigger.MisfireInstruction
属性获取该设置,默认值为 MisfireInstruction.InstructionNotSet
。
namespace Quartz
{
//
// 摘要:
// Misfire instructions.
public struct MisfireInstruction
{
//
// 摘要:
// Instruction not set (yet).
public const int InstructionNotSet = 0;
//
// 摘要:
// Use smart policy.
public const int SmartPolicy = 0;
//
// 摘要:
// Instructs the Quartz.IScheduler that the Quartz.ITrigger will never be evaluated
// for a misfire situation, and that the scheduler will simply try to fire it as
// soon as it can, and then update the Trigger as if it had fired at the proper
// time.
//
// 备注:
// NOTE: if a trigger uses this instruction, and it has missed several of its scheduled
// firings, then several rapid firings may occur as the trigger attempt to catch
// back up to where it would have been. For example, a SimpleTrigger that fires
// every 15 seconds which has misfired for 5 minutes will fire 20 times once it
// gets the chance to fire.
public const int IgnoreMisfirePolicy = -1;
//
// 摘要:
// Misfire policy settings for SimpleTrigger.
public struct SimpleTrigger
{
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
// wants to be fired now by Quartz.IScheduler.
// NOTE: This instruction should typically only be used for 'one-shot' (non-repeating)
// Triggers. If it is used on a trigger with a repeat count > 0 then it is equivalent
// to the instruction Quartz.MisfireInstruction.SimpleTrigger.RescheduleNowWithRemainingRepeatCount.
public const int FireNow = 1;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
// wants to be re-scheduled to 'now' (even if the associated Quartz.ICalendar excludes
// 'now') with the repeat count left as-is. This does obey the Quartz.ITrigger end-time
// however, so if 'now' is after the end-time the Quartz.ITrigger will not fire
// again.
//
// 备注:
// NOTE: Use of this instruction causes the trigger to 'forget' the start-time and
// repeat-count that it was originally setup with (this is only an issue if you
// for some reason wanted to be able to tell what the original values were at some
// later time).
public const int RescheduleNowWithExistingRepeatCount = 2;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
// wants to be re-scheduled to 'now' (even if the associated Quartz.ICalendar excludes
// 'now') with the repeat count set to what it would be, if it had not missed any
// firings. This does obey the Quartz.ITrigger end-time however, so if 'now' is
// after the end-time the Quartz.ITrigger will not fire again.
// NOTE: Use of this instruction causes the trigger to 'forget' the start-time and
// repeat-count that it was originally setup with. Instead, the repeat count on
// the trigger will be changed to whatever the remaining repeat count is (this is
// only an issue if you for some reason wanted to be able to tell what the original
// values were at some later time).
// NOTE: This instruction could cause the Quartz.ITrigger to go to the 'COMPLETE'
// state after firing 'now', if all the repeat-fire-times where missed.
public const int RescheduleNowWithRemainingRepeatCount = 3;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
// wants to be re-scheduled to the next scheduled time after 'now' - taking into
// account any associated Quartz.ICalendar, and with the repeat count set to what
// it would be, if it had not missed any firings.
//
// 备注:
// NOTE/WARNING: This instruction could cause the Quartz.ITrigger to go directly
// to the 'COMPLETE' state if all fire-times where missed.
public const int RescheduleNextWithRemainingCount = 4;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
// wants to be re-scheduled to the next scheduled time after 'now' - taking into
// account any associated Quartz.ICalendar, and with the repeat count left unchanged.
//
// 备注:
// NOTE/WARNING: This instruction could cause the Quartz.ITrigger to go directly
// to the 'COMPLETE' state if all the end-time of the trigger has arrived.
public const int RescheduleNextWithExistingCount = 5;
}
//
// 摘要:
// misfire instructions for CronTrigger
public struct CronTrigger
{
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICronTrigger
// wants to be fired now by Quartz.IScheduler.
public const int FireOnceNow = 1;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICronTrigger
// wants to have it's next-fire-time updated to the next time in the schedule after
// the current time (taking into account any associated Quartz.ICalendar), but it
// does not want to be fired now.
public const int DoNothing = 2;
}
//
// 摘要:
// Misfire instructions for DateIntervalTrigger
public struct CalendarIntervalTrigger
{
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICalendarIntervalTrigger
// wants to be fired now by Quartz.IScheduler.
public const int FireOnceNow = 1;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICalendarIntervalTrigger
// wants to have it's next-fire-time updated to the next time in the schedule after
// the current time (taking into account any associated Quartz.ICalendar), but it
// does not want to be fired now.
public const int DoNothing = 2;
}
//
// 摘要:
// Misfire instructions for DailyTimeIntervalTrigger
public struct DailyTimeIntervalTrigger
{
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.IDailyTimeIntervalTrigger
// wants to be fired now by Quartz.IScheduler.
public const int FireOnceNow = 1;
//
// 摘要:
// Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.MisfireInstruction.DailyTimeIntervalTrigger
// wants to have it's next-fire-time updated to the next time in the schedule after
// the current time (taking into account any associated Quartz.ICalendar), but it
// does not want to be fired now.
public const int DoNothing = 2;
}
}
}
Calendars 日历
可以通过指定日历来排除执行的日期(如假日不执行)。
同一个日历可以用于多个 Trigger 。
HolidayCalendar cal = new HolidayCalendar();
cal.AddExcludedDate(someDate);
await sched.AddCalendar("myHolidays", cal, false);
ITrigger t = TriggerBuilder.Create()
.WithIdentity("myTrigger")
.ForJob("myJob")
.WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
.ModifiedByCalendar("myHolidays") // but not on holidays
.Build();
// .. schedule job with trigger
ITrigger t2 = TriggerBuilder.Create()
.WithIdentity("myTrigger2")
.ForJob("myJob2")
.WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
.ModifiedByCalendar("myHolidays") // but not on holidays
.Build();
// .. schedule job with trigger2
自带的 Calendar 实现
位于 Quartz.Impl.Calendar 命名空间下。
- AnnualCalendar:排除每年的某一天;
- BaseCalendar:基础实现;
- CronCalendar:基于 Cron 表达式排除日期;
- DailyCalendar:排除每天的某个时间段;
- HolidayCalendar:排除固定的某天;
- MonthlyCalendar:排除每月的某天;
- WeeklyCalendar:排除每周的某天;
自定义 Calendar
自定义 Calendar 时需实现 ICalendar
接口。
namespace Quartz
{
public interface ICalendar
{
string Description { get; set; }
ICalendar CalendarBase { set; get; }
bool IsTimeIncluded(DateTimeOffset timeUtc);
DateTime GetNextIncludedTimeUtc(DateTimeOffset timeUtc);
ICalendar Clone();
}
}
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();从指定的时间开始,每隔10秒执行一次,共执行10次
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();从现在开始每5分钟执行一次,直到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();
SimpleTrigger Misfire Instructions
对于 SimpleTrigger 有以下几种 Misfire Instructions,每个都要一个对应的 SimpleScheduleBuilder
方法。
MisfireInstruction.IgnoreMisfirePolicy
MisfirePolicy.SimpleTrigger.FireNow
MisfirePolicy.SimpleTrigger.RescheduleNowWithExistingRepeatCount
MisfirePolicy.SimpleTrigger.RescheduleNowWithRemainingRepeatCount
MisfirePolicy.SimpleTrigger.RescheduleNextWithRemainingCount
MisfirePolicy.SimpleTrigger.RescheduleNextWithExistingCount
trigger = TriggerBuilder.Create()
.WithIdentity("trigger7", "group1")
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(5)
.RepeatForever()
.WithMisfireHandlingInstructionNextWithExistingCount())
.Build();
public SimpleScheduleBuilder WithMisfireHandlingInstructionFireNow();
public SimpleScheduleBuilder WithMisfireHandlingInstructionIgnoreMisfires();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNextWithExistingCount();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNextWithRemainingCount();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNowWithExistingCount();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNowWithRemainingCount();
CronTrigger
可以通过 ITrigger 的 WithCronSchedule
扩展方法创建 CronTrigger,或者使用 WithSchedule
扩展方法 + CronScheduleBuilder
实现同样的效果。
下面的两个示例均表示在每周三的10点42分(美国时区)执行。
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 42 10 ? * WED", x => x
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithSchedule(CronScheduleBuilder
.WeeklyOnDayAndHourAndMinute(DayOfWeek.Wednesday, 10, 42)
.InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
.ForJob(myJobKey)
.Build();
Cron Expressions (Cron 表达式)
Cron 表达式有7个组成部分,每个部分之间以空格分隔。
详细说明 => Jenkins – Poll SCM – 日程表 中的表达式写法 中的 Cron表达式 部分。
CronTrigger Misfire Instructions
MisfireInstruction.IgnoreMisfirePolicy
MisfireInstruction.CronTrigger.DoNothing
MisfireInstruction.CronTrigger.FireOnceNow
trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.WithCronSchedule("0 0/2 8-17 * * ?", x => x
.WithMisfireHandlingInstructionFireAndProceed())
.ForJob("myJob", "group1")
.Build();
public CronScheduleBuilder WithMisfireHandlingInstructionDoNothing();
public CronScheduleBuilder WithMisfireHandlingInstructionFireAndProceed();
public CronScheduleBuilder WithMisfireHandlingInstructionIgnoreMisfires();
TriggerListeners and JobListeners
监听 Trigger 和 Job 中的事件。
TriggerListeners
TriggerListener 可以监听 Trigger 执行(TriggerFired、VetoJobExecution)、Trigger 未执行(TriggerMisfired)、Trigger 完成(TriggerComplete);
TriggerFired 方法最先执行,结束后调用 VetoJobExecution 方法。
如果 VetoJobExecution 方法返回 true,则 Job 不会被执行,并且不会触发 Trigger 完成事件。
ITriggerListener
//
// 摘要:
// The interface to be implemented by classes that want to be informed when a Quartz.ITrigger
// fires. In general, applications that use a Quartz.IScheduler will not have use
// for this mechanism.
public interface ITriggerListener
{
//
// 摘要:
// Get the name of the Quartz.ITriggerListener.
string Name { get; }
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.ITrigger has fired, it's associated
// Quartz.IJobDetail has been executed, and it's Quartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar)
// method has been called.
//
// 参数:
// trigger:
// The Quartz.ITrigger that was fired.
//
// context:
// The Quartz.IJobExecutionContext that was passed to the Quartz.IJob'sQuartz.IJob.Execute(Quartz.IJobExecutionContext)
// method.
//
// triggerInstructionCode:
// The result of the call on the Quartz.ITrigger'sQuartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar)
// method.
//
// cancellationToken:
// The cancellation instruction.
Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.ITrigger has fired, and it's associated
// Quartz.IJobDetail is about to be executed.
// It is called before the Quartz.ITriggerListener.VetoJobExecution(Quartz.ITrigger,Quartz.IJobExecutionContext,System.Threading.CancellationToken)
// method of this interface.
//
// 参数:
// trigger:
// The Quartz.ITrigger that has fired.
//
// context:
// The Quartz.IJobExecutionContext that will be passed to the Quartz.IJob'sQuartz.IJob.Execute(Quartz.IJobExecutionContext)
// method.
//
// cancellationToken:
// The cancellation instruction.
Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.ITrigger has misfired.
// Consideration should be given to how much time is spent in this method, as it
// will affect all triggers that are misfiring. If you have lots of triggers misfiring
// at once, it could be an issue it this method does a lot.
//
// 参数:
// trigger:
// The Quartz.ITrigger that has misfired.
//
// cancellationToken:
// The cancellation instruction.
Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.ITrigger has fired, and it's associated
// Quartz.IJobDetail is about to be executed.
// It is called after the Quartz.ITriggerListener.TriggerFired(Quartz.ITrigger,Quartz.IJobExecutionContext,System.Threading.CancellationToken)
// method of this interface. If the implementation vetoes the execution (via returning
// true), the job's execute method will not be called.
//
// 参数:
// trigger:
// The Quartz.ITrigger that has fired.
//
// context:
// The Quartz.IJobExecutionContext that will be passed to the Quartz.IJob'sQuartz.IJob.Execute(Quartz.IJobExecutionContext)
// method.
//
// cancellationToken:
// The cancellation instruction.
//
// 返回结果:
// Returns true if job execution should be vetoed, false otherwise.
Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
}
MyTriggerListener.cs
class MyTriggerListener : ITriggerListener
{
public string Name { get; set; }
public async Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).TriggerComplete was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
}
public async Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).TriggerFired was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
}
public async Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).TriggerMisfired was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
}
public async Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).VetoJobExecution was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
return false;
}
}
JobListeners
JobListener 可以监听 Job 即将被执行(JobToBeExecuted)、Job 执行结束(JobWasExecuted);
JobExecutionVetoed 方法则会在 TriggerListener.VetoJobExecution 返回值为 true 时执行。
IJobListener
//
// 摘要:
// The interface to be implemented by classes that want to be informed when a Quartz.IJobDetail
// executes. In general, applications that use a Quartz.IScheduler will not have
// use for this mechanism.
public interface IJobListener
{
//
// 摘要:
// Get the name of the Quartz.IJobListener.
string Name { get; }
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail was about to be executed
// (an associated Quartz.ITrigger has occurred), but a Quartz.ITriggerListener vetoed
// it's execution.
Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail is about to be executed
// (an associated Quartz.ITrigger has occurred).
// This method will not be invoked if the execution of the Job was vetoed by a Quartz.ITriggerListener.
Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler after a Quartz.IJobDetail has been executed,
// and be for the associated Quartz.Spi.IOperableTrigger's Quartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar)
// method has been called.
Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken));
}
MyJobListener.cs
class MyJobListener : IJobListener
{
public string Name { get; set; }
public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyJobListener({Name}.{context.JobDetail.Key}).JobExecutionVetoed was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
}
public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyJobListener({Name}.{context.JobDetail.Key}).JobToBeExecuted was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
}
public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Out.WriteLineAsync($"MyJobListener({Name}.{context.JobDetail.Key}).JobWasExecuted was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
}
}
使用 Listener
监听某个特定的 Job
scheduler.ListenerManager.AddJobListener(myJobListener, KeyMatcher<JobKey>.KeyEquals(new JobKey("myJobName", "myJobGroup")));
监听某个特定组的 Job
scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.GroupEquals("myJobGroup"));
监听两个特定组的 Job
scheduler.ListenerManager.AddJobListener(myJobListener,
OrMatcher<JobKey>.Or(GroupMatcher<JobKey>.GroupEquals("myJobGroup"), GroupMatcher<JobKey>.GroupEquals("yourGroup")));
监听所有的 Job
scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.AnyGroup());
TriggerListener 用法类似。
监听所有的 Trigger
scheduler.ListenerManager.AddTriggerListener(myTriggerListener, GroupMatcher<TriggerKey>.AnyGroup());
运行结果
从下面打印的结果可以看出 TriggerListener 和 JobListener 中方法的调用顺序。
TriggerListener.TriggerFired => TriggerListener.VetoJobExecution => JobListener.JobToBeExecuted => Job 执行 => JobListener.JobWasExecuted => TriggerListener.TriggerComplete
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerFired was executed at 2019-02-12 13:55:29.758.
MyTriggerListener(trigger-listener-1.group1.trigger1).VetoJobExecution was executed at 2019-02-12 13:55:29.759.
MyJobListener(job-listener-1.group1.job1).JobToBeExecuted was executed at 2019-02-12 13:55:29.764.
Greetings from HelloJob(group1.job1 - 636855477297106001)!
MyJobListener(job-listener-1.group1.job1).JobWasExecuted was executed at 2019-02-12 13:55:29.770.
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerComplete was executed at 2019-02-12 13:55:29.776.
如果将 MyTriggerListener.VetoJobExecution 方法的返回值改为 true,则会执行 MyJobListener.JobExecutionVetoed 方法,并且 Job 不会被执行。
TriggerListener.TriggerFired => TriggerListener.VetoJobExecution => JobListener.JobExecutionVetoed
输出结果如下:
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerFired was executed at 2019-02-12 14:03:48.986.
MyTriggerListener(trigger-listener-1.group1.trigger1).VetoJobExecution was executed at 2019-02-12 14:03:48.987.
MyJobListener(job-listener-1.group1.job1).JobExecutionVetoed was executed at 2019-02-12 14:03:48.991.
SchedulerListeners
效果和用法同上面的两个 Listener 类似,只是监听的事件不一样。
SchedulerListener 监听的是 Scheduler 关联的事件,如 添加/删除 Trigger 或 Job、Scheduler 的异常、Scheduler 的关闭等。
ISchedulerListener
//
// 摘要:
// The interface to be implemented by classes that want to be informed of major
// Quartz.IScheduler events.
public interface ISchedulerListener
{
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail has been added.
Task JobAdded(IJobDetail jobDetail, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail has been deleted.
Task JobDeleted(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail has been interrupted.
Task JobInterrupted(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail has been paused.
Task JobPaused(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail has been un-paused.
Task JobResumed(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail is scheduled.
Task JobScheduled(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a group of Quartz.IJobDetails has been paused.
// If all groups were paused, then the jobName parameter will be null. If all jobs
// were paused, then both parameters will be null.
//
// 参数:
// jobGroup:
// The job group.
//
// cancellationToken:
// The cancellation instruction.
Task JobsPaused(string jobGroup, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail has been un-paused.
//
// 参数:
// jobGroup:
// The job group.
//
// cancellationToken:
// The cancellation instruction.
Task JobsResumed(string jobGroup, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.IJobDetail is unscheduled.
Task JobUnscheduled(TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a serious error has occurred within the
// scheduler - such as repeated failures in the Quartz.Spi.IJobStore, or the inability
// to instantiate a Quartz.IJob instance when its Quartz.ITrigger has fired.
Task SchedulerError(string msg, SchedulerException cause, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler to inform the listener that it has move to standby
// mode.
Task SchedulerInStandbyMode(CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler to inform the listener that it has Shutdown.
Task SchedulerShutdown(CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler to inform the listener that it has begun the
// shutdown sequence.
Task SchedulerShuttingdown(CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler to inform the listener that it has started.
Task SchedulerStarted(CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler to inform the listener that it is starting.
Task SchedulerStarting(CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler to inform the listener that all jobs, triggers
// and calendars were deleted.
Task SchedulingDataCleared(CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.ITrigger has reached the condition
// in which it will never fire again.
Task TriggerFinalized(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler a Quartz.ITriggers has been paused.
Task TriggerPaused(TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler when a Quartz.ITrigger has been un-paused.
Task TriggerResumed(TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken));
//
// 摘要:
// Called by the Quartz.IScheduler a group of Quartz.ITriggers has been paused.
//
// 参数:
// triggerGroup:
// The trigger group.
//
// cancellationToken:
// The cancellation instruction.
//
// 备注:
// If a all groups were paused, then the triggerName parameter will be null.
Task TriggersPaused(string triggerGroup,CancellationToken cancellationToken =default(CancellationToken));//// 摘要:// Called by the Quartz.IScheduler when a group of Quartz.ITriggers has been un-paused.//// 参数:// triggerGroup:// The trigger group.//// cancellationToken:// The cancellation instruction.//// 备注:// If all groups were resumed, then the triggerName parameter will be null.TaskTriggersResumed(string triggerGroup,CancellationToken cancellationToken =default(CancellationToken));}
添加 SchedulerListener
scheduler.ListenerManager.AddSchedulerListener(mySchedListener);
删除 SchedulerListener
scheduler.ListenerManager.RemoveSchedulerListener(mySchedListener);
执行输出结果(这里仅有部分方法被调用到):
[14:31:37] [Info] Quartz scheduler 'QuartzScheduler' initialized
[14:31:37] [Info] Quartz scheduler version: 3.0.7.0
[14:31:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED started.
MySchedulerListener(group1.job1).JobAdded was executed at 2019-02-12 14:31:37.395.
MySchedulerListener(group1.trigger1).JobScheduled was executed at 2019-02-12 14:31:37.399.
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerFired was executed at 2019-02-12 14:31:37.453.
MyTriggerListener(trigger-listener-1.group1.trigger1).VetoJobExecution was executed at 2019-02-12 14:31:37.454.
MyJobListener(job-listener-1.group1.job1).JobToBeExecuted was executed at 2019-02-12 14:31:37.458.
Greetings from HelloJob(group1.job1 - 636855498974031303)!
MyJobListener(job-listener-1.group1.job1).JobWasExecuted was executed at 2019-02-12 14:31:37.464.
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerComplete was executed at 2019-02-12 14:31:37.471.
MySchedulerListener(group1.trigger1).TriggerFinalized was executed at 2019-02-12 14:31:37.472.
MySchedulerListener(group1.job1).JobDeleted was executed at 2019-02-12 14:31:37.478.
[14:32:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED shutting down.
[14:32:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED paused.
MySchedulerListener.SchedulerInStandbyMode was executed at 2019-02-12 14:32:37.417.
MySchedulerListener.SchedulerShuttingdown was executed at 2019-02-12 14:32:37.419.
MySchedulerListener.SchedulerShutdown was executed at 2019-02-12 14:32:37.424.
[14:32:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED Shutdown complete.
JobStores (Job存储)
RAMJobStore
默认使用的是 RAMJobStore,Job 保存在内存中。进程关闭后所有 Job 信息都会丢失。
quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz
AdoJobStore
AdoJobStore 将 Job、Trigger 设置经由 ADO.NET 保存到数据库。相比 RAMJobStore 没有那么快,但是可以持久化。另外可以通过对 Table 添加索引来提高数据库的效率。
配置项
quartz.jobStore.type
现在 Quartz.Impl.AdoJobStore.JobStoreTX 是唯一的 AdoJobStore 实现。
quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
quartz.jobStore.driverDelegateType
Quartz.Impl.AdoJobStore.StdAdoDelegate 是个抽象的基类,具体应该设置成对应数据库的代理,如 MySQL 时应设置为 Quartz.Impl.AdoJobStore.MySQLDelegate, Quartz 。
quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz
quartz.jobStore.tablePrefix
定义 Quartz 数据库表名的前缀。当在同一个数据库保存多个 Quartz 实例的数据时使用。
QRTZ_ 是官方提供的建表SQL文中的表前缀。quartz.jobStore.tablePrefix = QRTZ_
quartz.jobStore.dataSource
定义数据源的名称,值可以自定义。
quartz.jobStore.dataSource = myDS
quartz.dataSource.myDS
配置上面定义的数据源 myDS 的类型和连接字符串。
quartz.dataSource.myDS.connectionString = Server=localhost;Database=quartz;Uid=quartznet;Pwd=quartznet
quartz.dataSource.myDS.provider = MySql支持如下数据库 Provider:
- SqlServer - SQL Server driver for .NET Framework 2.0
- OracleODP - Oracle’s Oracle Driver
- OracleODPManaged - Oracle’s managed driver for Oracle 11
- MySql - MySQL Connector/.NET
- SQLite - SQLite ADO.NET Provider
- SQLite-Microsoft - Microsoft SQLite ADO.NET Provider
- Firebird - Firebird ADO.NET Provider
- Npgsql - PostgreSQL Npgsql
示例
这里使用的 SqlServer 数据库 。
执行 tables_sqlServer.sql,创建数据库;
其它的数据库结构参照 quartznet/database/tables/ 中的其它文件。
初始化
StdSchedulerFactory
配置 AdoJobStore;使用代码配置时,使用
StdSchedulerFactory(NameValueCollection props)
构造函数,示例如下:// Grab the Scheduler instance from the Factory
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" },
{ "quartz.scheduler.instanceName", "MyScheduler" },
{ "quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" },
{ "quartz.jobStore.driverDelegateType", "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" },
{ "quartz.jobStore.tablePrefix", "QRTZ_" },
{ "quartz.jobStore.dataSource", "myDS" },
{ "quartz.dataSource.myDS.connectionString", "Data Source=LIUJIAJIA\\LJJ2008;Initial Catalog=Quartz;User ID=sa;Password=ABC123;" },
{ "quartz.dataSource.myDS.provider", "SqlServer" },
{ "quartz.threadPool.threadCount", "3" },
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);
IScheduler scheduler = await factory.GetScheduler(); // and start it off
await scheduler.Start(); // define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("job1", "group1")
.UsingJobData("count", 2)
.Build(); // Trigger the job to run now, and then repeat every 10 seconds
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever()
)
.Build(); // Tell quartz to schedule the job using our trigger
await scheduler.ScheduleJob(job, trigger); // and last shut down the scheduler when you are ready to close your program
await scheduler.Shutdown();使用 quartz.config 配置文件时使用
StdSchedulerFactory()
构造函数。
注意:需要将该配置文件的 复制到输出目录 属性设置为 如果较新则复制 或 始终复制 。
示例如下:quartz.config
quartz.serializer.type = binary
quartz.scheduler.instanceName = MyScheduler
quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz
quartz.jobStore.tablePrefix = QRTZ_
quartz.jobStore.dataSource = myDS
quartz.dataSource.myDS.connectionString = Data Source=LIUJIAJIA\LJJ2008;Initial Catalog=Quartz;User ID=sa;Password=Aa123456;
quartz.dataSource.myDS.provider = SqlServer
quartz.threadPool.threadCount = 3// 加载当前目录下的 quartz.config 配置文件,若找不到则加载默认配置
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler(); // and start it off
await scheduler.Start(); // define some jobs/triggers // and last shut down the scheduler when you are ready to close your program
await scheduler.Shutdown();
集群
集群时仅能使用 AdoJobstore (JobStoreTX). 包含 负载均衡 和 故障转移(仅在 JobDetail
的 request recovery 设置为 true 时)功能。
通过将 quartz.jobStore.clustered
设置为 true
来启动集群功能。
集群中的每个实例应该使用相同的配置。
其中 quartz.threadPool.threadCount
和 quartz.scheduler.instanceId
是例外。quartz.threadPool.threadCount
可以不一致。quartz.scheduler.instanceId
在集群中则必须是唯一的。可以通过将 quartz.scheduler.instanceId
属性设置为 AUTO 来实现。该属性默认值为 NON_CLUSTERED 。
注意:禁止在不同的机器上开启集群,除非机器之间使用某种时间同步服务(误差必须在1秒以内)。
注意:不要在不是集群的实例间使用同一组数据表,否则会导致数据污染等问题。
插件
Quartz 提供了 ISchedulerPlugin
接口来加载附加功能。
具体可以参考 Quartz.Plugins
命名空间中的文档。
Quartz.Plugins 不包含在 Quartz 包中,需要单独安装。
Install-Package Quartz.Plugins
当前最新版是 3.0.7,提供了如下插件:
LoggingJobHistoryPlugin
Logs a history of all job executions (and execution vetoes) via common logging.
LoggingTriggerHistoryPlugin
Logs a history of all trigger firings via the Jakarta Commons-Logging framework.
ShutdownHookPlugin
This plugin catches the event of the VM terminating (such as upon a CRTL-C) and tells the scheduler to Shutdown.
XMLSchedulingDataProcessorPlugin
This plugin loads XML file(s) to add jobs and schedule them with triggers as the scheduler is initialized, and can optionally periodically scan the file for changes.
JobFactory
当 Trigger 被触发时,通过 Scheduler 中配置的 JobFactory 来实例化 Job class。默认的 JobFactory 只是简单的创建一个新的 Job class 实例。
如果需要在创建/初始化 Job 实例时实现额外的处理(如使用 IoC 或 DI)可以通过实现 IJobFactory
接口定义自己的 JobFactory 来实现。
通过 Scheduler.SetJobFactory(fact)
方法来设置自定义的 JobFactory。
Factory-Shipped’ Jobs
Quartz 提供了一下公用的 Job (如发送电子邮件或调用远程作业)。你可以在 Quartz.Jobs
命名空间下发现这些开箱即用的功能。
Quartz.Jobs 不包含在 Quartz 包中,需要单独安装。
Install-Package Quartz.Jobs
当前最新版是 3.0.7,提供了如下 Job:
DirectoryScanJob
Inspects a directory and compares whether any files’ “last modified dates” have changed since the last time it was inspected.
If one or more files have been updated (or created), the job invokes a “call-back” method on an identifiedQuartz.Job.IDirectoryScanListener
that can be found in theQuartz.SchedulerContext
.FileScanJob
Inspects a file and compares whether it’s “last modified date” has changed since the last time it was inspected.
If the file has been updated, the job invokes a “call-back” method on an identifiedQuartz.Job.IFileScanListener
that can be found in theQuartz.SchedulerContext
.NativeJob
Built in job for executing native executables in a separate process.
JobDetail job = new JobDetail("dumbJob", null, typeof(Quartz.Jobs.NativeJob));
job.JobDataMap.Put(Quartz.Jobs.NativeJob.PropertyCommand, "echo \"hi\" >> foobar.txt");
Trigger trigger = TriggerUtils.MakeSecondlyTrigger(5);
trigger.Name = "dumbTrigger";
sched.ScheduleJob(job, trigger);If PropertyWaitForProcess is true, then the integer exit value of the process will be saved as the job execution result in the JobExecutionContext.
NoOpJob
An implementation of Job, that does absolutely nothing - useful for system which only wish to use
Quartz.ITriggerListeners
andQuartz.IJobListeners
, rather than writing Jobs that perform work.SendMailJob
A Job which sends an e-mail with the configured content to the configured recipient.
quartz.serializer.type
这个配置项指定 Job/Trigger 中的数据保存到数据库时的序列化及反序列类型。
在使用非 RAMJobStore 时必须设置该配置,否则会报如下错误。可以选择的值为 binary 和 json。
Quartz.SchedulerException: You must define object serializer using configuration key 'quartz.serializer.type' when using other than RAMJobStore. Out of the box supported values are 'json' and 'binary'. JSON doesn't suffer from versioning as much as binary serialization but you cannot use it if you already have binary serialized data.
at Quartz.Impl.StdSchedulerFactory.Instantiate() in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 352
at Quartz.Impl.StdSchedulerFactory.GetScheduler(CancellationToken cancellationToken) in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 1114
at FirstQuartzNet.Program.RunProgram() in C:\Users\liujiajia\source\repos\FirstQuartzNet\FirstQuartzNet\Program.cs:line 45
如果设置为 json 需要安装 Quartz.Serialization.Json 包。
Install-Package Quartz.Serialization.Json
https://www.liujiajia.me/2019/02/13/quartz-net/
Quartz.NET | 佳佳的博客的更多相关文章
- Python爬取CSDN博客文章
0 url :http://blog.csdn.net/youyou1543724847/article/details/52818339Redis一点基础的东西目录 1.基础底层数据结构 2.win ...
- python网络爬虫 新浪博客篇
上次写了一个爬世纪佳缘的爬虫之后,今天再接再厉又写了一个新浪博客的爬虫.写完之后,我想了一会儿,要不要在博客园里面写个帖子记录一下,因为我觉得这份代码的含金量确实太低,有点炒冷饭的嫌疑,就是把上次的代 ...
- twobin博客样式—“蓝白之风”
自暑假以来,囫囵吞枣一般蒙头栽入前端自学中,且不说是否窥探其道,却不自觉中提高了对网页版面设计的要求,乃至挑剔.一个设计清爽美观的网页能让读者心旷神怡,甚至没有了阅读疲劳:而一个设计粗劣嘈杂的网页实在 ...
- 2016-2017-2 《Java程序设计》课程学生博客和代码托管链接
2016-2017-2 <Java程序设计>课程学生博客和代码托管链接 博客 1552 20155201 李卓雯 20155202 张 旭 20155203 杜可欣 20155204 王 ...
- 2016-2017-1 《信息安全系统设计基础》 学生博客及Git@OSC 链接
2016-2017-1 <信息安全系统设计基础> 学生博客及Git@OSC 链接 博客 1452 20145201李子璇 20145202马 超 20145203盖泽双 20145204张 ...
- (转)[BetterExplained]为什么你应该(从现在开始就)写博客
(一)为什么你应该(从现在开始就)写博客 用一句话来说就是,写一个博客有很多好处,却没有任何明显的坏处.(阿灵顿的情况属于例外,而非常态,就像不能拿抽烟活到一百岁的英国老太太的个例来反驳抽烟对健康的极 ...
- 给博客添加Flash时钟
依稀记得在cnblogs很多大牛的博客里见到过大牛的新闻公告栏里那种动感十足的Flash时钟控件,先上图: 作为一名刚的接触博客菜鸟,自然免不了一番好奇.博客设置选项里翻来覆去找(以为是cnblogs ...
- 2015-2016-2 《Java程序设计》 学生博客及Git@OSC 链接
2015-2016-2 <Java程序设计> 学生博客及Git@OSC 链接 博客 1451 20145101王闰开 20145102周正一 20145103冯文华 20145104张家明 ...
- 1415-2 计科&计高 软件工程博客&Github地址汇总-修正版
序号 标识 博客 代码 1 1121袁颖 joanyy joanyy 2 1122崔琪 chitty ChittyCui 3 1123吕志浩 lucy123 715lvzhihao 4 1124张静 ...
随机推荐
- CSS无图片三角形
border:6px solid #f2f2f2; border-color:#999 transparent transparent transparent; border-style:solid ...
- json 的简单应用
今天做爬虫时的一点盲区 :字符串, 字典,写到同一个文件中.难以利用!比如这样的数据:str = “hi,budy. i like 52pojie!”dirt = {“陈墨”:["男&quo ...
- (63)通信协议之一json
1.什么是JSON JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于JavaScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使 ...
- 值不能为空。参数名viewinfo(microsoft.sqlserver.management.sqlstudio.explorer)
打开MSSQL 2008 R2的时候,展开数据库都显示以下的错误提示: 值不能为空.参数名viewinfo(microsoft.sqlserver.management.sqlstudio.explo ...
- 大哥带的Orchel数据库时间盲注
0X01Oracle基于延时的盲注总结 0x00 前言 oracle注入中可以通过页面响应的状态,这里指的是响应时间,通过这种方式判断SQL是否被执行的方式,便是时间盲注: oracle的时间盲注通常 ...
- yum install ntp 报错:Error: Package: ntp-4.2.6p5-25.el7.centos.2.x86_64 (base)
redhat7 在安装ntp时报如下错误 Error: Package: ntp-4.2.6p5-25.el7.centos.2.x86_64 (base) Requires: ntpdate = 4 ...
- LibUsbDotNet使用方法
最近在用C#调试USB程序,libusb源码是C语言的,C#用起来不方便,偶然在网上看到了LibUsbDotNet,这是开源的项目,下载后参考Example,用起来非常方便. LibUsbDotNet ...
- imu tool使用
安装imu tool sudo apt-get install ros-melodic-imu-tools launch文件: <!-- imu_node launch file--> & ...
- RAC_单实例_DG 关于两端创建表空间数据文件路径不一致的问题注意点
RAC_单实例_DG 关于两端创建表空间数据文件路径不一致的问题注意点 主库SYS@orcl1>show parameter db_file_name_convert NAME TYPE VAL ...
- 2、electron进程
electron核心我们可以分成2个部分,主进程和渲染进程. 主进程: 主进程连接着操作系统和渲染进程,可以把她看做页面和计算机沟通的桥梁. Electron 运行 package.json 的 ma ...