【项目升级】集成Quartz.Net Job实现(一)

2022-04-11 14:11:49 浏览数 (1)

这两天的新闻也是越来越多了,不仅Github接手了NPM,还有.NET 5也要新鲜出炉了(11月正式发布),当然还有MVP峰会也正在如火如荼的展开,会有哪些好的东西被碰撞出来,也是很期待的。这些天我也简单的开始了学习之路,网路一直不好,直播也就不好展开,但是肯定会有的,应该过不了多久,所以暂时通过文字来讲解吧。

BCVP(也就是Blog.Core和Vue的全家桶)项目开源一年多,我也一直在开发和维护,目标呢,也一直致力于打造一个开箱即用的丰富小框架,目前的核心功能如下:

也算是完成了九层了吧,剩下的10%属于锦上添花的功能,一般小项目可能用不上,但是中型项目是必须要用的,今天的重点就是说说作业调度Quzrtz.net,目前已经集成到了项目里,为了不影响Master分支,目前代码在is4分支上,感兴趣的小伙伴可以自行PULL下来看看,目前的效果是这样的,下篇文章会集成到Blog.Admin项目中。

(任务调度展示,可持久化到数据库)

本文重点参考Kawhi代码,自己做了调整:

【壹起学】1:Uwl.Admin开源框架基于QuartzNet的实现

这个系列我打算写三篇文章和一篇视频的形式,文章分为后端、前端、原理三篇,视频就是总体串一下,今天就是第一篇,简单说说后端的配置和操作,不讲原理。

为什么要使用Quartz.Net

关于Quartz.Net的概念、内容和工作原理UML这都不说了,相信你如果看到了这个文章标题,并点进来了,应该知道这是干啥的,也应该知道他的应用场景——任务调度,白话就是通过一定的简单配置,定时去执行一些任务,多见于统计和同步操作。

这里简单的贴一下它Github的数据,就足可见受欢迎度:

(我一直认为,好的开源项目,要看Closed了多少Issue)

其实本来我的项目中已经有了一套任务执行程序,用的还是微软的自带的HostingService

用起来是特别简单,几乎不用配置,只需要创建一个Service,然后直接写逻辑就行了,它会随着我们的运行的项目一起执行,如果说你的任务调度很简单,就是定时跑一个小方法,我还是比较推荐这个的,当然,这个也是有很多问题,比如不能手动动态配置,不能手动控制任务的启动、暂停、重启等多个操作,所以,应群友的号召,我就把.net中用的较多的Quzrtz给集成到了项目里,当然还有一个Hangfire也很流行,我目前公司老的项目中是用的这个Hangfire,但是我感觉有些臃肿了,不太应景NetCore这么优雅的高效框架。

后端如何配置Quartz.Net

01

创建任务数据库表以及四层服务

既然我们要动态配置到数据库里,那肯定就需要一个数据库表结构了,这个过程就是很简单的了,得益于我们有强大的Seed功能,无论是是CodeFirst生成数据库表结构,还是根据表结构利用FrameSeed生成四层文件,都很简单。

首先是创建实体类,然后生成到数据库中,我已经配置好了:

代码语言:javascript复制
   /// <summary>
   /// 任务计划表
   /// </summary>
   public class TasksQz : RootEntity
    {
        /// <summary>
        /// 任务名称
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string Name { get; set; }
        /// <summary>
        /// 任务分组
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string JobGroup { get; set; }
        /// <summary>
        /// 任务运行时间表达式
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string Cron { get; set; }
        /// <summary>
        /// 任务所在DLL对应的程序集名称
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string AssemblyName { get; set; }
        /// <summary>
        /// 任务所在类
        /// </summary>
        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)]
        public string ClassName { get; set; }
        /// <summary>
        /// 任务描述
        /// </summary>
        public string Remark { get; set; }
        /// <summary>
        /// 执行次数
        /// </summary>
        public int RunTimes { get; set; }
        /// <summary>
        /// 开始时间
        /// </summary>
        public DateTime? BeginTime { get; set; }
        /// <summary>
        /// 结束时间
        /// </summary>
        public DateTime? EndTime { get; set; }
        /// <summary>
        /// 触发器类型(0、simple 1、cron)
        /// </summary>
        public int TriggerType { get; set; }
        /// <summary>
        /// 执行间隔时间, 秒为单位
        /// </summary>
        public int IntervalSecond { get; set; }
        /// <summary>
        /// 是否启动
        /// </summary>
        public bool IsStart { get; set; } = false;
        /// <summary>
        /// 执行传参
        /// </summary>
        public string JobParams { get; set; }
        
        [SugarColumn(IsNullable = true)]
        public bool? IsDeleted { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
        [SugarColumn(IsNullable = true)]
        public DateTime CreateTime { get; set; } = DateTime.Now;
      }

然后SeedData到数据库:

然后配置种子数据:

代码语言:javascript复制
[
  {
    "Name": "博客管理",
    "JobGroup": "博客测试组",
    "Cron": "",
    "AssemblyName": "Blog.Core.Tasks",
    "ClassName": "Job_Blogs_Quartz",
    "Remark": "",
    "RunTimes": 0,
    "BeginTime": "",
    "EndTime": "",
    "TriggerType": 0,//0是simple模式,1的cron模式
    "IntervalSecond": 120,//2分钟执行一次
    "IsStart": true,
    "JobParams": "1",
    "IsDeleted": false,
    "CreateTime": "/Date(1546272000000 0800)/",
    "Id": 1
  }
]

(启动项目,自动SeedData)

生成到数据库后,然后我们就需要生成四层服务文件,因为我们的Blog.Core项目已经封装了代码生成器,还是两个,你可以用T4,也可以用DbFirstController.cs这个控制器方法,只需要FrameSeed.cs文件中,配置上表名就行了:

最后可以创建一个控制器,对这个表进行CURD操作,不赘述。核心要说的,还是我们的任务调度中心。

02

创建任务调度服务中心

当然,首先我们需要引用Nuget包:

代码语言:javascript复制
// 在Blog.Core.Tasks 层安装
<PackageReference Include="Quartz" Version="3.0.7" />

新建QuartzNet文件夹,创建调度服务接口和实现类,具体的原理我会在第三篇简单说下:

代码语言:javascript复制
namespace Blog.Core.Tasks
{
    /// <summary>
    /// 服务调度接口
    /// </summary>
    public interface ISchedulerCenter
    {

        /// <summary>
        /// 开启任务调度
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> StartScheduleAsync();
        /// <summary>
        /// 停止任务调度
        /// </summary>
        /// <returns></returns>
        Task<MessageModel<string>> StopScheduleAsync();
        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        Task<MessageModel<string>> AddScheduleJobAsync(TasksQz sysSchedule);
        /// <summary>
        /// 停止一个任务
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        Task<MessageModel<string>> StopScheduleJobAsync(TasksQz sysSchedule);
        /// <summary>
        /// 恢复一个任务
        /// </summary>
        /// <param name="sysSchedule"></param>
        /// <returns></returns>
        Task<MessageModel<string>> ResumeJob(TasksQz sysSchedule);
    }
}

主要就是利用IScheduler对Job进行处理,核心的逻辑和代码都在实现类类,今天暂时先不进行讲解,具体的可以查看SchedulerCenterServer.cs

配置好了服务以及调度中心,接下来就是创建一个个Job类了。

03

创建Job工作

顾名思义,我们要想实现任务调度,就需要创建很多个Job工作类,让调度中心自己根据相应的逻辑机制来去调度,我这里创建了一个简单的Job作为示例:

代码语言:javascript复制
namespace Blog.Core.Tasks
{
    public class Job_Blogs_Quartz : JobBase, IJob
    {
        private readonly IBlogArticleServices _blogArticleServices;
        private readonly ITasksQzServices _tasksQzServices;

        public Job_Blogs_Quartz(IBlogArticleServices blogArticleServices, ITasksQzServices tasksQzServices)
        {
            _blogArticleServices = blogArticleServices;
            _tasksQzServices = tasksQzServices;
        }
        public async Task Execute(IJobExecutionContext context)
        {
            var executeLog = await ExecuteJob(context, async () => await Run(context));

            //var param = context.MergedJobDataMap;
            // 可以直接获取 JobDetail 的值
            var jobKey = context.JobDetail.Key;
            var jobId = jobKey.Name;

            // 也可以通过数据库配置,获取传递过来的参数
            JobDataMap data = context.JobDetail.JobDataMap;
            //int jobId = data.GetInt("JobParam");

            var model = await _tasksQzServices.QueryById(jobId);
            if (model != null)
            {
                model.RunTimes  = 1;
                model.Remark  = $"{executeLog}<br />";
                await _tasksQzServices.Update(model);
            }
        }
        public async Task Run(IJobExecutionContext context)
        {
            var list = await _blogArticleServices.Query();
            await Console.Out.WriteLineAsync("博客总数量"   list.Count.ToString());
        }
    }
}

通过接口调用

这个就很简单了,毕竟我们前后端分离,要通过接口的形式来对我们的任务进行调度,这里简单的列举一个就行了:

代码语言:javascript复制
 /// <summary>
 /// 启动计划任务
 /// </summary>
 /// <param name="jobId"></param>
 /// <returns></returns>
 [HttpGet]
 public async Task<MessageModel<string>> StartJob(int jobId)
 {
     var data = new MessageModel<string>();
     // 获取任务服务
     var model = await _tasksQzServices.QueryById(jobId);
     // 开启job
     var ResuleModel = await _schedulerCenter.AddScheduleJobAsync(model);
     if (ResuleModel.success)
     {
         model.IsStart = true;
         data.success = await _tasksQzServices.Update(model);
     }
     if (data.success)
     {
         data.msg = "启动成功";
         data.response = jobId.ObjToString();
     }
     return data;
 }

最后的最后,不要忘记把相应的服务和接口进行注册:

好啦,关于后端如何配置任务调度Quzrtz.Net,就暂时说到这里了,下篇简单说下如何在前端配置页面吧,这两天我先设计着。

0 人点赞