首页 > 解决方案 > Quartz.net RFC 2445 或 RFC 5545 而不是 CRON

问题描述

我们有一个在 .NET 中运行的 Web 服务器,它使用 Quartz 来安排作业。作业的触发器以RFC 2445格式提供,但 Quartz 使用CRON 格式。我现在想要么

在后一种情况下,我发现了一些 Java 库,但没有用于 .NET。

我也尝试编写自己的库,但我遇到了间隔。一个 RFC2445 规则可以定义一个双周(或三周或n-周)作业

频率=每周;每天=月;间隔=2

即每隔一个星期一。然而 CRON 似乎没有这个功能。

标签: cronquartz-schedulericalendarrfc2445

解决方案


I have a similar requirement and I couldn't find a RFC 5545 compliant library to work with Quartz scheduler and ended up implementing a custom trigger myself following this suggestion

In my case we are using Telerik Scheduler control to populate the RRULE but you could probably do the same with iCal.Net library as well. This is not the full implementation here but it will get you started and the code is UNTESTED.

Another note: "FREQ=WEEKLY;BYDAY=MO;INTERVAL=2" will probably fail if you try to parse it using Telerik RecurrenceRule, since it's missing DTSTART, DTEND etc. This is an example of a recurrence rule string that will not fail: "DTSTART:20210309T050000Z\r\nDTEND:20210309T060000Z\r\nRRULE:FREQ=WEEKLY;BYDAY=TU;INTERVAL=1".

You need to implement ITrigger interface. A lot of it can be copied from CroneTriggerImpl class and modified.

public interface IRRuleTrigger : ITrigger
{
    string RecurrenceRuleString { get; set; }
} 

Then you need an implementation class inherited from AbstractTrigger

public class MyTriggerImpl: AbstractTrigger, IRRuleTrigger
{
   //implement all members here. Look at CronTriggerImpl class in Quartz.Net source. I'm pasting some of the implementation code but not all.
   //...

    private RecurrenceRule rRule;

    /// <summary>
    /// Gets or sets the RRULE expression string.
    /// </summary>
    /// <value>The expression string.</value>
    public string RecurrenceRuleString
    {
        set
        {
            TimeZoneInfo originalTimeZone = TimeZone;
            var success = RecurrenceRule.TryParse(value, out var parsedRule);
            if(success) rRule = parsedRule ;// RecurrenceRule(value!);
        }
        get => rRule?.ToString();
    }


    /// <summary>
    /// Gets or sets the RRULE expression string.
    /// </summary>
    /// <value>The expression string like RRULE:FREQ=WEEKLY;BYDAY=MO;INTERVAL=2.</value>
    public string RecurrenceRuleString
    {
        set
        {
            TimeZoneInfo originalTimeZone = TimeZone;
            var success = RecurrenceRule.TryParse(value, out var parsedRule);
            if(success) rRule = parsedRule ;// RecurrenceRule(value!);
        }
        get => rRule?.ToString();
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Computation Functions
    //
    ////////////////////////////////////////////////////////////////////////////

    /// <summary>
    /// Gets the next time to fire after the given time.
    /// </summary>
    /// <param name="afterTime">The time to compute from.</param>
    /// <returns></returns>
    protected DateTimeOffset? GetTimeAfter(DateTimeOffset afterTime)
    {
        return rRule?.HasOccurrences == true ?
            rRule?.Occurrences.Where(o => o > afterTime).Min()
            : null;
    }

    /// <summary>
    /// Returns the time before the given time
    /// that this <see cref="IRRuleTrigger" /> will fire.
    /// </summary>
    /// <param name="date">The date.</param>
    /// <returns></returns>
    protected DateTimeOffset? GetTimeBefore(DateTimeOffset? date)
    {
        return rRule?.HasOccurrences == true ? 
            rRule?.Occurrences.Where(o=> o < date).Max()
            : null;
    }
}

public class RRuleScheduleBuilder : ScheduleBuilder<IRRuleTrigger>
{
    private int misfireInstruction = MisfireInstruction.SmartPolicy;
    private RecurrenceRule recurrenceRule;

    public override IMutableTrigger Build()
    {
        MyTriggerImpl myTriggerImpl = new MyTriggerImpl();


        myTriggerImpl.MisfireInstruction = misfireInstruction;
        myTriggerImpl.RecurrenceRuleString = this.recurrenceRule.ToString();
        return myTriggerImpl;
    }
    /// <summary>
    /// Create a RRuleScheduleBuilder with the given string expression - which
    /// is presumed to be valid expression (and hence only a RuntimeException
    /// will be thrown if it is not).
    /// </summary>
    /// <remarks>
    /// </remarks>
    /// <param name="recurrenceRuleString">the RRule expression to base the schedule on.</param>
    /// <returns>the new RRuleScheduleBuilder</returns>
    public static RRuleScheduleBuilder RecurrenceRuleSchedule(string recurrenceRuleString)
    {
        var success = RecurrenceRule.TryParse(recurrenceRuleString, out var rRule);
        if(!success) throw new ArgumentException($"Recurrence Rule String ({recurrenceRuleString}) is invalid.");
        return new RRuleScheduleBuilder(rRule);
    }

    protected RRuleScheduleBuilder(RecurrenceRule rule)
    {
        this.recurrenceRule = rule ?? throw new ArgumentNullException(nameof(rule), "recurrenceRule cannot be null");
    }
}
/// <summary>
/// Extension methods that attach <see cref="RRuleScheduleBuilder" /> to <see cref="TriggerBuilder" />.
/// </summary>
public static class RRuleScheduleTriggerBuilderExtensions
{
    public static TriggerBuilder WithRRuleSchedule(this TriggerBuilder triggerBuilder, string recurrenceRuleString)
    {
        RRuleScheduleBuilder builder = RRuleScheduleBuilder.RecurrenceRuleSchedule(recurrenceRuleString);
        return triggerBuilder.WithSchedule(builder);
    }

    public static TriggerBuilder WithRRuleSchedule(this TriggerBuilder triggerBuilder, string recurrenceRuleString, Action<RRuleScheduleBuilder> action)
    {
        RRuleScheduleBuilder builder = RRuleScheduleBuilder.RecurrenceRuleSchedule(recurrenceRuleString);
        action(builder);
        return triggerBuilder.WithSchedule(builder);
    }
}

After implementing that, you can create and use your trigger like this:

        // Grab the Scheduler instance from the Factory
        StdSchedulerFactory factory = new StdSchedulerFactory();
        var scheduler = await factory.GetScheduler();
        await scheduler.Start();

        var job = JobBuilder.Create<MyBusinessClassThatImplementsIJobInterface>()
            .WithIdentity("someIdentity", "someGroupName")
            .Build();

        var trigger = (IRRuleTrigger)TriggerBuilder.Create()
            .WithIdentity("someName", "myGroup")
            .WithRRuleSchedule(rule.ToString())
            .Build();

        await scheduler.ScheduleJob(job, trigger);

推荐阅读