这两天的新闻也是越来越多了,不仅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生成四层文件,都很简单。
首先是创建实体类,然后生成到数据库中,我已经配置好了:
/// <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到数据库:
然后配置种子数据:
[{"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包:
// 在Blog.Core.Tasks 层安装
<PackageReference Include="Quartz" Version="3.0.7" />
新建QuartzNet文件夹,创建调度服务接口和实现类,具体的原理我会在第三篇简单说下:
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作为示例:
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());}}
}
通过接口调用
这个就很简单了,毕竟我们前后端分离,要通过接口的形式来对我们的任务进行调度,这里简单的列举一个就行了:
/// <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);// 开启jobvar 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,就暂时说到这里了,下篇简单说下如何在前端配置页面吧,这两天我先设计着。