实现Quartz.NET的HTTP作业调度

quartz,net,http · 浏览次数 : 22

小编点评

本文介绍了如何使用Quartz.NET库来实现HTTP作业调度,包括创建、修改、暂停、恢复和删除作业。文章详细阐述了Quartz.NET的工作原理、核心组件以及如何通过ASP.NET Core的Controller提供一系列Web API接口,以便于通过HTTP请求管理作业。 1. **Quartz.NET简介**: - Quartz.NET是一个用于.NET应用程序的开源作业调度库。 - 它允许开发者创建定时任务,以执行外部API或其他任务。 2. **HttpJob类实现**: - `HttpJob`类继承自`IJob`接口,用于执行HTTP请求。 - 使用`RestRequest`构建请求,并通过静态字典`Delegates`存储作业配置信息。 - 实现了`Execute`方法,用于处理HTTP请求并记录日志。 3. **作业信息持久化**: - 定义了`JobInfo`类来存储作业的基本信息。 - 将作业信息保存在本地JSON文件中,以便在程序退出后仍然可以保留数据。 4. **QuartzHelper类**: - `QuartzHelper`类负责管理作业的生命周期。 - 包括加载作业信息、创建作业、调度作业、暂停/恢复作业以及删除作业等功能。 - 提供了Web API接口,用于与外部交互和管理作业。 5. **作业执行日志**: - 设计了`JobLog`类和`JobLogHelper`类来记录和查询作业执行日志。 - 日志记录功能有助于监控作业的执行情况和问题排查。 6. **Web API接口**: - 提供了一系列Web API接口,用于管理作业。 - 接口包括获取作业列表、添加作业、修改作业、删除作业、暂停作业、恢复作业和立即执行作业等。 - 通过ASP.NET Core的Controller提供了这些接口,方便开发者进行远程管理和操作。 总的来说,本文展示了如何利用Quartz.NET库和ASP.NET Core来实现一个高效且灵活的HTTP作业调度系统,以满足各种定时任务的需求。

正文

Quartz.NET作为一个开源的作业调度库,广泛应用于.NET应用程序中,以实现复杂的定时任务,本次记录利用Quartz.NET实现HTTP作业调度,通过自定义HTTP作业,实现对外部API的定时调用和如何管理这些作业,包括创建、修改、暂停、恢复和删除作业。

 

 

 

 

1.首先定义了一个HttpJob类,该类实现了IJob接口,用于执行HTTP请求。利用了RestRequest来构建请求,并通过静态字典Delegates存储每个作业的配置信息,如URL、请求方法和请求头等

public class HttpJob : IJob
{
    public static readonly Dictionary<string, HttpJobInfo> Delegates = new();

    public async Task Execute(IJobExecutionContext context)
    {
        var delegateKey = context.JobDetail.JobDataMap.GetString("delegateKey");
        if (delegateKey != null && Delegates.TryGetValue(delegateKey, out var func))
        {
            var requestBody = new RestRequest();
            if (func.Headers != null)
            {
                foreach (var header in func.Headers)
                {
                    requestBody.AddHeader(header.Key, header.Value);
                }
            }

            var content = HttpHelper.HttpRequest(func.Url, func.Request, requestBody);
            JobLogHelper.AddJobLog(new JobLog() { JobName = context.JobDetail.Key.Name, GroupName = context.JobDetail.Key.Group, RunTime = DateTime.Now, RunResult = content });
            UpdateLastExecutionTime(context.JobDetail.Key.Name, context.JobDetail.Key.Group, DateTime.Now);
        }
        await Task.CompletedTask;
    }
}

 

2.作业信息的持久化:为了持久化作业信息,定义了JobInfo类来存储作业的基本信息,如名称、组名、Cron表达式等,并将这些信息保存在本地的JSON文件中。

public class JobInfo
{
    public required string JobName { get; set; }
    public required string GroupName { get; set; }
    public required string CronExpression { get; set; }
    public DateTime LastExecutionTime { get; set; }
    public JobStatus Status { get; set; }
    public required HttpJobInfo HttpJob { get; set; }
}

3.实现了QuartzHelper类,用于管理作业的生命周期。这包括加载作业信息、创建作业、调度作业、暂停/恢复作业以及删除作业等功能。

 public class QuartzHelper
 {
     private IScheduler scheduler;
     private List<JobInfo> jobInfos;

     private string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "jobs.json");

     /// <summary>
     /// 构造函数,初始化定时任务管理器
     /// </summary>
     public QuartzHelper()
     {
         ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
         scheduler = schedulerFactory.GetScheduler().Result;
         scheduler.Start().Wait();
         LoadJobInfosApi().Wait();

     }
     /// <summary>
     /// 保存作业信息到本地 JSON 文件
     /// </summary>
     private void SaveJobInfos()
     {
         string json = JsonConvert.SerializeObject(jobInfos);
         File.WriteAllText(filePath, json);
     }

     /// <summary>
     /// 加载本地 JSON 文件中的作业信息
     /// </summary>
     private async Task LoadJobInfosApi()
     {
         if (File.Exists(filePath))
         {
             string json = File.ReadAllText(filePath);
             jobInfos = JsonConvert.DeserializeObject<List<JobInfo>>(json) ?? new List<JobInfo>();
             foreach (var jobInfo in jobInfos)
             {
                
                 // 创建委托的唯一键
                 var delegateKey = Guid.NewGuid().ToString();
                 // 将委托存储在静态字典中
                 HttpJob.Delegates[delegateKey] = jobInfo.HttpJob;

                 // 创建并调度作业
                 IJobDetail job = JobBuilder.Create<HttpJob>()
                     .WithIdentity(jobInfo.JobName, jobInfo.GroupName).UsingJobData("delegateKey", delegateKey) // 将委托的键添加到JobDataMap
                     .Build();

                 ITrigger trigger = TriggerBuilder.Create()
                     .WithIdentity(jobInfo.JobName, jobInfo.GroupName)
                     .WithCronSchedule(jobInfo.CronExpression)
                     //.StartNow()
                     .Build();

                 await scheduler.ScheduleJob(job, trigger);

                 // 根据任务状态恢复或暂停任务
                 if (jobInfo.Status == JobStatus.正常运行)
                 {
                     await ResumeJob(jobInfo.JobName, jobInfo.GroupName);
                 }
                 else
                 {
                     await PauseJob(jobInfo.JobName, jobInfo.GroupName);
                 }
             }
         }
         else
         {
             jobInfos = new List<JobInfo>();
         }
     }



     #region 执行普通任务时使用,传委托时可以参考此方法
     ///// <summary>
     ///// 新建任务并立即执行
     ///// </summary>
     //[Obsolete("执行普通任务时使用,可以传委托使用")]
     //public async Task AddJob(string jobName, string groupName, string cronExpression, Func<bool> func, string description = "")
     //{
     //    if (jobInfos.Any(c => c.JobName == jobName && c.GroupName == groupName))
     //    {
     //        return;
     //    }

     //    // 创建委托的唯一键
     //    var delegateKey = Guid.NewGuid().ToString();
     //    // 将委托存储在静态字典中
     //   // MyJobClass.Delegates[delegateKey] = func;

     //    // 创建作业信息并保存到列表  需要将func 加入到jobInfo 中做作业持久化!!!!
     //    var jobInfo = new JobInfo { JobName = jobName, GroupName = groupName, CronExpression = cronExpression, Status = JobStatus.正常运行, Description = description, JobCreateTime = DateTime.Now };
     //    jobInfos.Add(jobInfo);
     //    SaveJobInfos();

     //    // 创建Quartz作业和触发器
     //    IJobDetail job = JobBuilder.Create<MyJobClass>()
     //        .WithIdentity(jobName, groupName)
     //        .UsingJobData("delegateKey", delegateKey) // 将委托的键添加到JobDataMap
     //        .Build();

     //    ITrigger trigger = TriggerBuilder.Create()
     //        .WithIdentity(jobName + "Trigger", groupName)
     //        .StartNow()
     //        .WithCronSchedule(cronExpression).WithDescription(description)
     //        .Build();

     //    await scheduler.ScheduleJob(job, trigger);

     //}

     #endregion

     /// <summary>
     /// 新建任务并立即执行
     /// </summary>       
   
     public async Task AddJobApi(string jobName, string groupName, string cronExpression, HttpJobInfo httpJobInfo, string description = "")
     {
         if (jobInfos.Any(c => c.JobName == jobName && c.GroupName == groupName))
         {
             return;
         }

         // 创建委托的唯一键
         var delegateKey = Guid.NewGuid().ToString();
         // 将委托存储在静态字典中
         HttpJob.Delegates[delegateKey] = httpJobInfo;

         // 创建作业信息并保存到列表  需要将func 加入到jobInfo 中做作业持久化!!!!
         var jobInfo = new JobInfo { JobName = jobName, GroupName = groupName, CronExpression = cronExpression, HttpJob = httpJobInfo, Status = JobStatus.正常运行, Description = description, JobCreateTime = DateTime.Now };
         jobInfos.Add(jobInfo);
         SaveJobInfos();

         // 创建Quartz作业和触发器
         IJobDetail job = JobBuilder.Create<HttpJob>()
             .WithIdentity(jobName, groupName)
             .UsingJobData("delegateKey", delegateKey) // 将委托的键添加到JobDataMap
             .Build();

         ITrigger trigger = TriggerBuilder.Create()
             .WithIdentity(jobName + "Trigger", groupName)
             .StartNow()
             .WithCronSchedule(cronExpression).WithDescription(description)
             .Build();

         await scheduler.ScheduleJob(job, trigger);

     }


     /// <summary>
     /// 暂停任务
     /// </summary>
     public async Task PauseJob(string jobName, string groupName)
     {
         await scheduler.PauseJob(new JobKey(jobName, groupName));
         var job = jobInfos.FirstOrDefault(j => j.JobName == jobName && j.GroupName == groupName);
         if (job != null)
         {
             job.Status = JobStatus.暂停;
             SaveJobInfos();
         }
     }

     /// <summary>
     /// 开启任务
     /// </summary>
     public async Task ResumeJob(string jobName, string groupName)
     {
         await scheduler.ResumeJob(new JobKey(jobName, groupName));
         var job = jobInfos.FirstOrDefault(j => j.JobName == jobName && j.GroupName == groupName);
         if (job != null)
         {
             job.Status = JobStatus.正常运行;
             SaveJobInfos();
         }
     }

     /// <summary>
     /// 立即执行任务
     /// </summary>
     public async Task TriggerJob(string jobName, string groupName)
     {
         await scheduler.TriggerJob(new JobKey(jobName, groupName));
         var job = jobInfos.FirstOrDefault(j => j.JobName == jobName && j.GroupName == groupName);
         if (job != null)
         {
             job.LastExecutionTime = DateTime.Now;
             SaveJobInfos();
         }
     }


     /// <summary>
     /// 修改任务
     /// </summary>
     public async Task ModifyJob(string jobName, string groupName, string cronExpression, HttpJobInfo httpJobInfo, string description = "")
     {
         await DeleteJob(jobName, groupName);
         await AddJobApi(jobName, groupName, cronExpression, httpJobInfo, description);
     }
     /// <summary>
     /// 删除任务
     /// </summary>
     public async Task DeleteJob(string jobName, string groupName)
     {
         await scheduler.DeleteJob(new JobKey(jobName, groupName));
         jobInfos.RemoveAll(j => j.JobName == jobName && j.GroupName == groupName);
         SaveJobInfos();
     }

     /// <summary>
     /// 获取当前所有任务列表
     /// </summary>
     public List<JobInfo> GetAllJobs()
     {
         if (File.Exists(filePath))
         {
             string json = File.ReadAllText(filePath);
             jobInfos = JsonConvert.DeserializeObject<List<JobInfo>>(json) ?? new List<JobInfo>();
             return jobInfos;
         }
         else
             return null;
         
     }


 }
QuartzHelper

4.为了跟踪作业的执行情况,设计了JobLog类和JobLogHelper类,用于记录和查询作业执行日志。

public class JobLogHelper
{
    private static string _filePath;

    /// <summary>
    /// 根据作业名称和组名称获取当日的作业执行日志
    /// </summary>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    /// <returns></returns>
    public static List<JobLog> GetJobLog(string jobName, string groupName)
    {
        _filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"jobsLog-{DateTime.Now:yyyyMMdd}.json");

        // 检查文件是否存在
        if (!File.Exists(_filePath))
        {
            return new List<JobLog>();
        }
        var jsonText = $"[{File.ReadAllText(_filePath)}]";
        var list = JsonConvert.DeserializeObject<List<JobLog>>(jsonText);
        if (list != null)
        {
            var result = list.Where(c => c.JobName == jobName && groupName == c.GroupName).OrderByDescending(c => c.RunTime).ToList();
            return result;
        }

        return null;
    }
    /// <summary>
    ///获取所有的 作业执行日志  //可以从这里拓展其他查询条件
    /// </summary>
    /// <returns></returns>
    public static List<JobLog> GetAllLogs()
    {
        List<JobLog> jobLogs = new List<JobLog>();
        var logFilePaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "jobsLog-*.json");
        logFilePaths.ToList().ForEach(c =>
        {
            var jsonText = $"[{File.ReadAllText(_filePath)}]";
            var list = JsonConvert.DeserializeObject<List<JobLog>>(jsonText);
            if (list != null) jobLogs.AddRange(list);
        });
        return jobLogs;
    }
    /// <summary>
    /// 添加作业执行日志
    /// </summary>
    /// <param name="jobLog"></param>
    public static void AddJobLog(JobLog jobLog)
    {
        _filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"jobsLog-{DateTime.Now:yyyyMMdd}.json");
        string json = JsonConvert.SerializeObject(jobLog) + ",\n";
        File.AppendAllText(_filePath, json);
    }
}
作业执行日志

5.最后,通过ASP.NET Core的Controller提供了一系列Web API接口,以便于通过HTTP请求管理作业。这些接口包括获取作业列表、添加作业、修改作业、删除作业、暂停作业、恢复作业和立即执行作业等。

 [Route("api/[controller]")]
 [ApiController]
 public class QuartzController : ControllerBase
 {
     private readonly QuartzHelper _quartzHelper;
     public QuartzController(QuartzHelper quartzHelper)
     {
         _quartzHelper = quartzHelper;
     }

     [HttpGet]
     [Route("job/GetJobs")]
     public object GetJobs()
     {
         return Ok(new {code=200,data = _quartzHelper.GetAllJobs() });
     }

     [HttpGet]
     [Route("job/GetJobLog")]
     public object GetJobLog(string jobName, string groupName)
     {
         return Ok(new { code = 200, data = JobLogHelper.GetJobLog(jobName, groupName) });         
     }
     [HttpGet]
     [Route("job/GetJobLogs")]
     public object GetJobLogs()
     {
         return Ok(new { code = 200, data = JobLogHelper.GetAllLogs() });
     }


     [HttpPost]
     [Route("job/AddJob")]
     public async Task<object> Add(JobInfo jobInfo)
     {
         try
         {
             await _quartzHelper.AddJobApi(jobInfo.JobName, jobInfo.GroupName, jobInfo.CronExpression, jobInfo.HttpJob, jobInfo.Description);
             return Ok(new { code = 200, msg = "创建成功!" });
         }
         catch (Exception ex)
         {
             return Ok(new { code = 500, msg = ex.Message });
         }
     }

     [HttpPost]
     [Route("job/ModifyJob")]
     public async Task<object> Edit(JobInfo jobInfo)
     {
         try
         {
             await _quartzHelper.ModifyJob(jobInfo.JobName, jobInfo.GroupName, jobInfo.CronExpression, jobInfo.HttpJob, jobInfo.Description);
             return Ok(new { code = 200, msg = "修改成功!" });
         }
         catch (Exception ex)
         {
             return Ok(new { code = 500, msg = ex.Message });
         }
     }

     [HttpGet]
     [Route("job/DeleteJob")]
     public async Task<object> Delete(string jobName, string groupName)
     {
         try
         {
             await _quartzHelper.DeleteJob(jobName, groupName);
             return Ok(new { code = 200, msg = "删除成功!" });
         }
         catch (Exception ex)
         {
             return Ok(new { code = 500, msg = ex.Message });
         }
     }

     [HttpGet]
     [Route("job/PauseJob")]
     public async Task<object> PauseJob(string jobName, string groupName)
     {
         try
         {
             await _quartzHelper.PauseJob(jobName, groupName);
             return Ok(new { code = 200, msg = "暂停成功!" });
         }
         catch (Exception ex)
         {
             return Ok(new { code = 500, msg = ex.Message });
         }
     }

     [HttpGet]
     [Route("job/ResumeJob")]
     public async Task<object> ResumeJob(string jobName, string groupName)
     {
         try
         {
             await _quartzHelper.ResumeJob(jobName, groupName);
             return Ok(new { code = 200, msg = "开启任务成功!" });
         }
         catch (Exception ex)
         {
             return Ok(new { code = 500, msg = ex.Message });
         }
     }
     [HttpGet]
     [Route("job/TriggerJob")]
     public async Task<object> TriggerJob(string jobName, string groupName)
     {
         try
         {
             await _quartzHelper.TriggerJob(jobName, groupName);
             return Ok(new { code = 200, msg = "立即执行任务命令已执行!" });
         }
         catch (Exception ex)
         {
             return Ok(new { code = 500, msg = ex.Message });
         }
     }
 }
Web API接口

源码地址:https://github.com/yycb1994/Quartz.Net

与实现Quartz.NET的HTTP作业调度相似的内容:

实现Quartz.NET的HTTP作业调度

Quartz.NET作为一个开源的作业调度库,广泛应用于.NET应用程序中,以实现复杂的定时任务,本次记录利用Quartz.NET实现HTTP作业调度,通过自定义HTTP作业,实现对外部API的定时调用和如何管理这些作业,包括创建、修改、暂停、恢复和删除作业。 1.首先定义了一个HttpJob类,该

React + Springboot + Quartz,从0实现Excel报表自动化

一、项目背景 企业日常工作中需要制作大量的报表,比如商品的销量、销售额、库存详情、员工打卡信息、保险报销、办公用品采购、差旅报销、项目进度等等,都需要制作统计图表以更直观地查阅。但是报表的制作往往需要耗费大量的时间,即使复用制作好的报表模版,一次次周期性对数据的复制粘贴操作也很耗人,同时模版在此过程

自己动手实现一个轻量无负担的任务调度ScheduleTask

至于任务调度这个基础功能,重要性不言而喻,大多数业务系统都会用到,世面上有很多成熟的三方库比如Quartz,Hangfire,Coravel 这里我们不讨论三方的库如何使用 而是从0开始自己制作一个简易的任务调度 技术栈用到了:BackgroundService和NCrontab库 第一步我们定义一

不懂任务调度系统,快来看这篇

摘要:本文讲解如何实现一个任务调度系统的核心逻辑。 本文分享自华为云社区《实现一个任务调度系统,看这篇文章就够了》,作者:勇哥java实战分享 。 1 Quartz Quartz是一款Java开源任务调度框架,也是很多Java工程师接触任务调度的起点。 下图显示了任务调度的整体流程: Quartz的

一种异步延迟队列的实现方式

目前系统中有很多需要用到延时处理的功能:支付超时取消、排队超时、短信、微信等提醒延迟发送、token刷新、会员卡过期等等。通过延时处理,极大的节省系统的资源,不必轮询数据库处理任务。 目前大部分功能通过定时任务完成,定时任务还分使用quartz及xxljob两种类型轮询时间短,每秒执行一次,对数据库造成一定的压力,并且会有1秒的误差。轮询时间久,如30分钟一次,03:01插入一条数据,正常3:3

Quartz 简单使用

Scheduler 每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题(jobDetail的实例也是新的) Quzrtz 定时任务默认都是并发执行,不会等待上一次任务执行完毕,只要间隔时间到就会执行,如果定时任务执行太长,会长时间占用资源,导致其它任务堵塞 @D

Simple WPF: WPF 实现按钮的长按,短按功能

实现了一个支持长短按得按钮组件,单击可以触发Click事件,长按可以触发LongPressed事件,长按松开时触发LongClick事件。还可以和自定义外观相结合,实现自定义的按钮外形。

实现并发新高度:23ai的无锁列值保留

Oracle Database 23ai支持Lock-Free Reservation,中文通常译为“无锁列值保留”。 本文将通过3个部分来阐述Lock-Free Reservation的这个特性: 1.应用场景 2.实现原理 3.使用限制 1.应用场景 Lock-Free Reservation这

[Unity] 实现AssetBundle资源加载管理器

实现Unity AssetBundle资源加载管理器 AssetBundle是实现资源热更新的重要功能,但Unity为其提供的API却十分基(jian)础(lou)。像是自动加载依赖包、重复加载缓存、解决同步/异步加载冲突,等基础功能都必须由使用者自行实现。 因此,本篇博客将会介绍如何实现一个Ass

从零实现的Chrome扩展

# 从零实现的Chrome扩展 `Chrome`扩展是一种可以在`Chrome`浏览器中添加新功能和修改浏览器行为的软件程序,例如我们常用的`TamperMonkey`、`Proxy SwitchyOmega`、`AdGuard`等等,这些拓展都是可以通过`WebExtensions API`来修改