Quartz.Net 组件的封装使用Quartz.AspNetCore

Quartz.Net 组件的封装使用

Quartz.Net是面向.NET的一款功能齐全的开源作业调度组件,你可以把它嵌入你的系统中实现作业调度,也可以基于Quartz.Net开发一套完整的作业调度系统。它既支持简单的timer,也支持灵活强大的corn表达式。本文提供了一种把Quartz.Net嵌入项目中的实现。你可以看到系统其它项目用于作业调度的项目解耦,这样做可以实现一次封装,多系统使用

1、环境

  • 操作系统:Windows 10
  • IDE:Visual Studio 2019-16.8.3
  • .Net Core版本:.NET 5.0

2、创建项目,添加引用

创建一个空解决方案Theo.QuartzDemo,然后开始添加新项目。

2.1、创建名为Theo.Business的类库项目

创建.Net Core类库项目Theo.Business,代表系统的业务项目。添加Microsoft.Extensions.Logging引用。
添加IProviderDemo接口,定义两个方法分别提供发邮件和发短信服务。你可能觉得发邮件和发短信服务不应该在同一个provider中,但是作为示例,我偷了个懒。
IProviderDemo.cs:

using System;

namespace Theo.Business
{
    public interface IProviderDemo
    {

        /// <summary>
        /// 模拟提供发邮件服务
        /// </summary>
        /// <param name="param">参数</param>
        void AutoSendMail(string param);

        /// <summary>
        /// 模拟提供发短信服务
        /// </summary>
        /// <param name="param">参数</param>
        void AutoSendSMS(string param);
    }
}

添加IProviderDemo的实现:ProviderDemo

using Microsoft.Extensions.Logging;
using System;

namespace Theo.Business
{
    ///<summary>
    /// 模拟业务逻辑
    ///</summary>
    public class ProviderDemo : IProviderDemo
    {
        private readonly ILogger<ProviderDemo> _logger;

        /// <summary>
        /// .ctor
        /// </summary>
        /// <param name="logger"></param>
        public ProviderDemo(ILogger<ProviderDemo> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// 模拟提供发邮件服务
        /// </summary>
        /// <param name="param">参数</param>
        public void AutoSendMail(string param)
        {
            _logger.LogError($"[{DateTimeOffset.Now:HH:mm:ss.fff}]\t{nameof(AutoSendMail)}\tparam:{param}");
        }

        /// <summary>
        /// 模拟提供发短信服务
        /// </summary>
        /// <param name="param">参数</param>
        public void AutoSendSMS(string param)
        {
            _logger.LogInformation($"[{DateTimeOffset.Now:HH:mm:ss.fff}]\t{nameof(AutoSendSMS)}\tparam:{param}");
        }
    }
}

2.2、添加名为Theo.TaskSchedulerWorker Service(辅助角色服务)项目

创建Theo.TaskScheduler项目,用于作业调度
创建Worker Service项目
添加引用

Install-Package Microsoft.Extensions.Hosting.Systemd
Install-Package Microsoft.Extensions.Hosting.WindowsServices
Install-Package Quartz.AspNetCore
Install-Package Microsoft.Extensions.Logging.Log4Net.AspNetCore
Install-Package System.Text.Json
  • Microsoft.Extensions.Hosting.Systemd:用以支持Linux守护进程,部署在Linux平台时需要此组件
  • Microsoft.Extensions.Hosting.WindowsServices:用以支持Windows服务,部署在Windows平台时需要此组件
  • Quartz.AspNetCore:.Net Core平台的Quartz组件
  • Microsoft.Extensions.Logging.Log4Net.AspNetCore:Log4Net日志组件,当然也可以是Nlog/Serilog等其他任意组件
  • 添加对Theo.Business项目的引用

3、封装Quartz.Net

Theo.TaskScheduler项目中封装Quartz.Net。封装的主要目的是为了更方便的配置作业调度计划。实现过程中的工作量主要在读取配置并交给Quartz.Net组件,以告诉组件如何根据配置去调用指定方法。

新建Models文件夹,文件夹下新建JobModelJobGroupModelScheduleModel三个类。
* JobModel:计划任务job模型
* JobGroupModel:计划任务job组模型
* ScheduleModel:作业调度模型
新建IOCHelper文件,注入业务代码(IProviderDemo),并暴露IServiceProvider
新建Quartz文件夹,文件夹下新建:

  • QuartzJob:实现Quartz.IJobExecute方法,来调用配置的作业调度。因为Quartz.AspNetCore目前对IOC的支持有限,这里实现一个protected void Init(ILogger logger, IServiceProvider serviceProvider)方法,以便在Execute方法中记录日志以及通过IServiceProvider发现服务。Execute方法的逻辑:
    • IJobExecutionContext读取配置
    • 通过IServiceProvider发现服务
    • Invoke调用
  • BlockedJob:继承QuartzJob,类添加[DisallowConcurrentExecution]属性,指定为阻塞模式job。例如某个job调用频率是每分钟1次,本次调用时发现上次调用还未执行结束,将放弃本次调用且不会抛出任何异常
  • ConcurrentJob:继承QuartzJob,可并发执行job,不管上次执行是否结束,到时间就重新调用执行
    新建QuartzWorker类,继承BackgroundService并重载StopAsyncExecuteAsync方法。完成读取作业调度配置文件并交给Quartz.Net组件的工作。通过appsetting.json文件Quartz:Config配置找到作业调度配置文件路径,读取配置文件。配置文件支持xmljson两种格式二选一。根据Quartz:Watch配置确定是否监控配置文件变化。如果Quartz:Watch配置为true,在服务运行过程中可以通过修改作业调度配置文件来修改作业调度计划。
    新建Configs文件夹,文件夹下新建SchedulerConfig.json或者SchedulerConfig.xml配置文件,用以配置作业调度计划。SchedulerConfig.json配置示例如下,发邮件服务调用频率为从5秒开始每10秒钟一次,发短信服务调用频率为从0秒开始每10秒钟一次
{
  "GroupList": [
    {
      "Name": "JobGroup",
      "JobList": [
        {
          "Disabled": false,
          "Desc": "发邮件服务",
          "Name": "SendMail",
          "Cron": "5/10 * * * * ? *",
          "DllName": "Theo.Business",
          "ClassName": "Theo.Business.IProviderDemo",
          "MethodName": "AutoSendMail"
        },
        {
          "Disabled": false,
          "Desc": "发短信服务",
          "Name": "SendSMS",
          "Cron": "0/10 * * * * ? *",
          "DllName": "Theo.Business",
          "ClassName": "Theo.Business.IProviderDemo",
          "MethodName": "AutoSendSMS"
        }
      ]
    }
  ]
}

appsetting.json添加配置,指定作业调度配置文件:

...
  "Quartz": {
    "Config": "Configs/SchedulerConfig.json",
    "Watch": true
  }
...

4、配置Program并测试运行

Program.cs文件:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using Theo.TaskScheduler.Quartz;

namespace Theo.TaskScheduler
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseWindowsService()   //支持Windows服务,  其他平台自动忽略
                .UseSystemd()          //支持Linux守护进程,其他平台自动忽略
                //使用log4net日志组件,并指定配置文件
                .ConfigureLogging(conf => conf.AddLog4Net("Configs/log4net.config"))
                .ConfigureAppConfiguration((hostContext, config) =>
                {
                    config.SetBasePath(AppContext.BaseDirectory);
                    var env = hostContext.HostingEnvironment;
                    if (env.IsDevelopment())
                    {
                        config.AddJsonFile("appsettings.Development.json");
                    }
                    else
                    {
                        config.AddJsonFile("appsettings.json");
                    }
                })
                .ConfigureServices((hostContext, services) =>
                {
                    IOCHelper.InjectDependencies(services);
                    services.AddHostedService<QuartzWorker>();
                });
    }
}

启动调试之前还需要为Log4Net日志组件添加配置文件。根据Program.cs中指定的配置文件Configs/log4net.config,我们在项目Configs文件夹下添加文件log4net.config。具体的配置规则您可以去 log4net官网 查找,篇幅所限,本文不做说明。

终于可以F5启动调试,结果如下图所示。
我们的作业调度配置:SendMail从5秒开始每10秒钟一次SendSMS从0秒开始每10秒钟一次。从下图可以看出,作业调度运行情况和我们预期的一致。
调试运行结果
在运行中,我们把SendMail作业改为从1秒开始每10秒钟一次,把SendSMS作业禁用,然后新增一个名为SendSMS-1的作业,调用频率为从0秒开始每3秒钟一次。从下图可以看出(``开始),程序检测到了配置文件的变化,并启用的新的作业调度计划。
在这里插入图片描述

5、部署服务

部署到Linux平台的方法,您可以参阅官方文档:
若要部署到Windows平台,您可以发布Theo.TaskScheduler项目,然后通过执行shell命令或者把命令包装成.bat文件并执行的方式安装为Windows服务并启动。参考命令如下:

@echo.服务启动......  
@echo off  
@sc create TheoTaskScheduler binPath= "publish\Theo.TaskScheduler.exe"
@sc description TheoTaskScheduler "TheoDemo-任务调度服务"
@net start TheoTaskScheduler  
@echo off
@echo.启动完毕!  
@pause

6、总结

本文介绍了Quartz.Net组件在DotNetCore平台封装使用的详细步骤。
Quartz.AspNetCore封装到了单独DotNetCore项目中,与其他业务代码解耦。并实现了作业调度计划的配置化,和服务运行中实时监控配置文件功能。可以在运行中动态禁用/启用作业,添加新的作业,修改现有作业的调度计划(cron表达式)。
上文有提到示例中实现了可以指定是否允许并行调用作业的功能(BlockedJob/ConcurrentJob),但因篇幅所限并未贴出测试结果。
展望:

1、在大型项目中,调度服务可能以集群或者分布式架构的形式运行。这就要考虑把作业调度计划配置到数据库或者redis等缓存中。服务启动时候从数据库或者缓存中加载作业调度计划,并订阅修改作业调度计划事件,以便在调度计划发生改变时及时更新。
2、本例仅实现了调度服务运行中实时禁用/启用作业,添加新的作业,修改现有作业的调度计划的功能,以满足基本使用需求。如果您有兴趣/需求,可以自行探索如何实时修改是否允许并行调用作业等功能。

示例代码已上传至资源库,仅需5积分,感谢您的支持:Theo.QuartzDemo源码

posted @ 2021-01-13 14:38  Theo·Chan  阅读(1497)  评论(2编辑  收藏  举报