这两天的新闻也是越来越多了,不仅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的概念、内容和工作原理UML这都不说了,相信你如果看到了这个文章标题,并点进来了,应该知道这是干啥的,也应该知道他的应用场景—— 任务调度,白话就是通过一定的简单配置,定时去执行一些任务 ,多见于统计和同步操作。
这里简单的 贴一下 它Github的数据, 就足可见 受欢迎度 :
(我一直认为,好的开源项目,要看Closed了多少Issue)
其实本来我的项目中已经有了一套任务执行程序,用的还是微软的自带的HostingService
用起来是特别简单,几乎不用配置,只需要创建一个Service,然后直接写逻辑就行了,它会随着我们的运行的项目一起执行, 如果说你的任务调度很简单,就是定时跑一个小方法,我还是比较推荐这个 的,当然,这个也是有很多问题,比如不能手动动态配置,不能手动控制任务的启动、暂停、重启等多个操作,所以,应群友的号召,我就把.net中用的较多的Quzrtz给集成到了项目里,当然还有一个Hangfire也很流行,我目前公司老的项目中是用的这个Hangfire,但是我感觉有些臃肿了,不太应景NetCore这么优雅的高效框架。
创 建任务数据库表以及四层服务
既然我们要动态配置到数据库里,那肯定就需要一个数据库表结构了,这个过程就是很简单的了,得益于我们有强大的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操作,不赘述。核心要说的,还是我们的任务调度中心。
创 建任务调度服务中心
当然,首先我们需要引用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类了。
创 建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);
// 开启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,就暂时说到这里了,下篇简单说下如何在前端配置页面吧,这两天我先设计着。