Quartz.net 动态定时任务

由于到新公司解决老旧系统定时任务基于bat脚本curl定时请求无法统一管理和扩展性太差问题,以及在搭建的过程中遇到的坑,故针对这情况写了一个定时任务的管理模块,支持Get,Post请求方式。

源代码地址:源代码地址

无缝嵌入项目,支持Swagger测试

什么是定时任务?

定时发邮件,定时统计信息等内容,那么如何实现才能使得我们的项目整齐划一呢?本文通过一些简单的小例子,简述在.Net 7+Quartz实现定时任务的一些基本操作,及相关知识介绍,仅供学习分享使用,如有不足之处,还请指正。

什么是Quartz?
Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵 活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB 作业预构 建,JavaMail 及其它,支持 cron-like 表达式等等。虽然Quartz最初是为Java编写的,但是目前已经有.Net版本的Quartz,所以在.Net中应用Quartz已经不再是奢望,而是轻而易举的事情了。

Github上开源网址为:https://github.com/quartznet
关于Quartz的快速入门和API文档,可以参考:

https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html

Quartz安装

为了方便,本项目采用仓储模式,由Api,BizManagement,DataBase,Entity 4个模块组成,本次基于Rider开发,通过Nuget包管理器进行安装,如下所示:

项目模块组成:

依赖组件:

1. redis (StackExchange.Redis: 2.6.122)查询全量数据的持久化

2. Nlog(5.3.5) 日志记录到文件

3. SkyApm(0.9.0) 接口追踪

4. Mysql(Pomelo.EntityFrameworkCore.MySql: 7.0.0) 持久化Scheduler到数据库

5. AutoMapper(12.0.1) 实体映射

6. EFCore(7.0.13) ORM管理

remark:数据库,Redis地址在api模块 appsetting.json修改

 主要功能组件:

QuartzManage: 对Job的管理,支持暂停,启动,更新频率,批量删除等操作

using System.Collections.Specialized;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using org.huage.BizManagement.Job;
using org.huage.BizManagement.Listener;
using org.huage.BizManagement.Proxy;
using org.huage.Entity.common;
using org.huage.Entity.Request;
using Quartz;
using Quartz.Impl;
using Quartz.Impl.Matchers;
using Quartz.Impl.Triggers;
using Quartz.Spi;
using SchedulerException = Quartz.SchedulerException;

namespace org.huage.BizManagement.Manager;

public class QuartzManage : IQuartzManage
{
    private static ISchedulerFactory _schedulerFactory;
    private static IScheduler _scheduler;
    private readonly IJobFactory _jobFactory;
    
    public QuartzManage(IJobFactory jobFactory)
    {
        _jobFactory = jobFactory;
        
        var config = new NameValueCollection();
        config.Add("quartz.jobStore.misfireThreshold", "1000");
        
        _schedulerFactory = new StdSchedulerFactory(config);
        //获得调度器
        _scheduler = _schedulerFactory.GetScheduler().Result;
        
        _scheduler.JobFactory = jobFactory;
        //调度开始
        _scheduler.Start();
    }
    
    
    /// <summary>
    /// 判断corn表达式是否有效
    /// </summary>
    /// <param name="cronExpression"></param>
    /// <returns></returns>
    private bool IsValidExpression(string cronExpression)
    {
        CronTriggerImpl trigger = new CronTriggerImpl();
        try
        {
            trigger.CronExpressionString = cronExpression;
            DateTimeOffset? date = trigger.ComputeFirstFireTimeUtc(null);
            return date != null;
        }
        catch (Exception)
        {
            return false;
        }
    }
    
    /// <summary>
    /// 暂停Job
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    public async Task PauseJob(string jobName, string groupName)
    {
        //查询同一个分组的JobKey列表
        var jobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName));
        if (jobKeys != null && jobKeys.Count > 0)
        {
            //获得指定名称jobName的的jobKey
            var jobKey = jobKeys.FirstOrDefault(p => p.Name.Equals(jobName));
            if (jobKey != null)
            {
                //暂停job的调度
                await _scheduler.PauseJob(jobKey);
            }
            else
            {
                throw new SchedulerException("Can' find this Job.");
            }
        }
        else
        {
            throw new SchedulerException("Can't fin this Job.");
        }
    }
    
    /// <summary>
    /// 重启job
    /// </summary>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    /// <exception cref="Exception"></exception>
    public async Task StartJob(string jobName, string groupName)
    {
        //查询同一个分组的JobKey列表
        var jobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName));
        if (jobKeys != null && jobKeys.Any())
        {
            //获得指定名称jobName的的jobKey
            var jobKey = jobKeys.FirstOrDefault(p => p.Name.Equals(jobName));
            if (jobKey != null)
            {
                //暂停job的调度
                await _scheduler.ResumeJob(jobKey);
            }
            else
            {
                throw new SchedulerException("没有此Job");
            }
        }
        else
        {
            throw new SchedulerException("没有此Job");
        }
    }
    
    /// <summary>
    /// 创建Get请求方式Job
    /// </summary>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    /// <param name="desc"></param>
    /// <param name="cronExpression"></param>
    /// <typeparam name="T"></typeparam>
    /// <exception cref="Exception"></exception>
    public async Task AddJobForGet<T>(string jobName, string groupName, string remark,AddSchedulerRequest request)
        where T : IJob
    {
        //判断 cron 表达式是否有效
        if (!IsValidExpression(request.CronExpression))
        {
            throw new Exception("请输入cron表达式");
        }
        //判断url
        if (string.IsNullOrEmpty(request.Url) || ! Uri.TryCreate(request.Url,UriKind.Absolute,out var success))
        {
            throw new Exception($"当前Url 格式不正确:{request.Url}");
        }
        //创建Job
        var job = JobBuilder.Create<T>().UsingJobData(new JobDataMap()
            {
                new KeyValuePair<string, object>("RequestUrl", request.Url),
                new KeyValuePair<string, object>("RequestParam", request.MethodParams)
            })
            .WithIdentity(jobName, groupName)//键值
            .WithDescription(remark)//描述
            .Build();

        //创建触发器
        var timeUnit = SchedulerUtils.GetTimeUnit(request.StartTime);
        
        ITrigger trigger = TriggerBuilder.Create()
            .WithCronSchedule(request.CronExpression,x=>x.WithMisfireHandlingInstructionFireAndProceed())
            .WithIdentity(jobName, groupName)
            .StartAt(DateBuilder.DateOf(timeUnit[3], timeUnit[4], 
                timeUnit[5],timeUnit[2],timeUnit[1],timeUnit[0]))
            .Build();

        //添加监听器
        //_scheduler.ListenerManager.AddJobListener(new JobListener(),GroupMatcher<JobKey>.AnyGroup());
        //_scheduler.ListenerManager.AddTriggerListener(new TriggerListener(), GroupMatcher<TriggerKey>.AnyGroup());
        //开始以创建的触发器调度创建的Job
        
        await _scheduler.ScheduleJob(job, trigger);
    }
    
    /// <summary>
    /// 创建post请求的参数
    /// </summary>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    /// <param name="request"></param>
    /// <typeparam name="T"></typeparam>
    /// <exception cref="Exception"></exception>
    public async Task AddJobForPost<T>(string jobName, string groupName,AddSchedulerRequest request)
        where T : IJob
    {
        //判断 cron 表达式是否有效
        if (!IsValidExpression(request.CronExpression))
        {
            throw new Exception("请输入cron表达式");
        }
        //创建Job
        var job = JobBuilder.Create<T>()
            .WithIdentity(jobName, groupName).UsingJobData(new JobDataMap()
            {
                new KeyValuePair<string, object>("RequestUrl", request.Url),
                new KeyValuePair<string, object>("RequestParam", request.MethodParams)
            }).RequestRecovery(true).WithDescription(request.Remark ?? "")
            .Build();

        //创建触发器
        var timeUnit = SchedulerUtils.GetTimeUnit(request.StartTime);
        ITrigger trigger = TriggerBuilder.Create()
            .WithCronSchedule(request.CronExpression,x=>x.WithMisfireHandlingInstructionFireAndProceed())
            .WithIdentity(SchedulerKeyGenerator.TriggerKey(request.Id), groupName)
            .StartAt(DateBuilder.DateOf(timeUnit[3], timeUnit[4], 
                timeUnit[5],timeUnit[2],timeUnit[1],timeUnit[0]))
            .Build();

        //添加监听器
        //_scheduler.ListenerManager.AddJobListener(new JobListener(),GroupMatcher<JobKey>.AnyGroup());
        //_scheduler.ListenerManager.AddTriggerListener(new TriggerListener(), GroupMatcher<TriggerKey>.AnyGroup());
        
        await _scheduler.ScheduleJob(job, trigger);
    }
    
    
    /// <summary>
    /// 更新job调度频率
    /// </summary>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    /// <param name="remark"></param>
    /// <param name="cronExpression"></param>
    /// <exception cref="Exception"></exception>
    /// <exception cref="SchedulerException"></exception>
    public async Task UpdateJobRate(string jobName, string groupName, string remark, string cronExpression,long startTime)
    {
        if (!IsValidExpression(cronExpression))
        {
            throw new Exception("cron表达式无效");
        }

        var jobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName));
        if (jobKeys != null && jobKeys.Any())
        {
            var jobKey = jobKeys.FirstOrDefault(p => p.Name.Equals(jobName));
            if (jobKey != null)
            {
                var job = await _scheduler.GetJobDetail(jobKey);
                if (job == null) throw new Exception("job不存在");
                //先删除原有job
                await _scheduler.DeleteJob(jobKey);
                
                var timeUnit = SchedulerUtils.GetTimeUnit(startTime);
        
                var trigger = TriggerBuilder.Create()
                    .WithCronSchedule(cronExpression)
                    .WithIdentity(jobName, groupName)
                    .StartAt(DateBuilder.DateOf(timeUnit[3], timeUnit[4], 
                        timeUnit[5],timeUnit[2],timeUnit[1],timeUnit[0]))
                    .Build();
                await _scheduler.ScheduleJob(job, trigger);
            }
            else
            {
                throw new SchedulerException("job不存在");
            }
        }
        else
        {
            throw new SchedulerException("job不存在");
        }
    }
    
    
    /// <summary>
    /// 删除job
    /// </summary>
    /// <param name="jobName"></param>
    /// <param name="groupName"></param>
    /// <exception cref="Exception"></exception>
    public async Task DeleteJob(string jobName, string groupName)
    {
        var jobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName));
        if (jobKeys != null && jobKeys.Any())
        {
            var jobKey = jobKeys.FirstOrDefault(p => p.Name.Equals(jobName));
            if (jobKey != null)
            {
                await _scheduler.DeleteJob(jobKey);
            }
            else
            {
                throw new SchedulerException("要删除的job不存在");
            }
        }
        else
        {
            throw new SchedulerException("要删除的job不存在");
        }
    }
}
     

SchedulerManage: 实际调用的服务,暴露给controller调用。

using System.Linq.Expressions;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using org.huage.BizManagement.Job;
using org.huage.BizManagement.Redis;
using org.huage.BizManagement.Wrapper;
using org.huage.Entity.common;
using org.huage.Entity.Enum;
using org.huage.Entity.Model;
using org.huage.Entity.Request;
using org.huage.Entity.Response;
using org.huage.EntityFramewok.Database;
using org.huage.EntityFramewok.Database.Table;
using SchedulerException = org.huage.Entity.common.SchedulerException;

namespace org.huage.BizManagement.Manager;

public class SchedulerManager : ISchedulerManager
{
    private readonly IRepositoryWrapper _wrapper;
    private readonly ILogger<SchedulerManager> _logger;

    private readonly IRedisHelper _redisHelper;
    private IMapper _mapper;

    private readonly IQuartzManage _quartzManage;
    
    
    public SchedulerManager(IRepositoryWrapper wrapper, ILogger<SchedulerManager> logger, IMapper mapper,
        IRedisHelper redisHelper, IQuartzManage quartzManage)
    {
        _wrapper = wrapper;
        _logger = logger;
        _mapper = mapper;
        _redisHelper = redisHelper;
        _quartzManage = quartzManage;
    }
    
    public async Task<AddSchedulerResponse> AddSchedulerAsync(AddSchedulerRequest request)
    {
        //先保存到数据库,后续丢失可以重新跑
        AddSchedulerResponse response = new AddSchedulerResponse();
        try
        {
            if (request is null ||  (request.RequestType != RequestType.REQUEST_TYPE_GET && request.RequestType != RequestType.REQUEST_TYPE_POST))
                throw new SchedulerException("目前只支持Get,Post,请传入正确的RequestType.");
            //首先判断传进来的是get/post,然后在IJob定义向指定的接口发送请求。
            
            switch (request.RequestType)
            {
                case RequestType.REQUEST_TYPE_GET:
                    await _quartzManage.AddJobForGet<GetJob>(SchedulerKeyGenerator.JobKey(request.Id), SchedulerKeyGenerator.GroupKey(request.Id)
                        , "",request);
                    break;
                case RequestType.REQUEST_TYPE_POST:
                    await _quartzManage.AddJobForPost<PostJob>(SchedulerKeyGenerator.JobKey(request.Id), SchedulerKeyGenerator.GroupKey(request.Id), request);
                    break;
            }
            
            //删除缓存
            await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());
            
            //插入数据库
            var scheduler1 = _mapper.Map<Scheduler>(request);
            _wrapper.Scheduler.Create(scheduler1);
            await _wrapper.SaveChangeAsync();
            await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());
        }
        catch (Exception e)
        {
            _logger.LogError("AddSchedulerAsync occur error:{Message}", e.Message);
            throw;
        }

        return response;
    }

    /// <summary>
    /// 更新Scheduler Rate(调度速率)
    /// </summary>
    /// <param name="rateRequest"></param>
    /// <returns></returns>
    public async Task<UpdateSchedulerRateResponse> UpdateSchedulerRateAsync(UpdateSchedulerRateRequest rateRequest)
    {
        //先删除缓存
        await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());
        var schedulerUpdate = _mapper.Map<Scheduler>(rateRequest);

        schedulerUpdate.UpdateBy = "test";
        _wrapper.Scheduler.Update(schedulerUpdate);

        //1.移除原来的job; 2.修改trigger; 3.重新调度jobDetail

        await _quartzManage.UpdateJobRate(SchedulerKeyGenerator.JobKey(rateRequest.Id), SchedulerKeyGenerator.GroupKey(rateRequest.Id), rateRequest.Remark, rateRequest.CronExpression, rateRequest.StartTime);

        return new UpdateSchedulerRateResponse();
    }


    /// <summary>
    ///更新Scheduler状态,暂停,启用。
    /// </summary>
    /// <param name="statusRequest"></param>
    /// <returns></returns>
    /// <exception cref="SchedulerException"></exception>
    public async Task<bool> UpdateSchedulerStatusAsync(UpdateSchedulerStatusRequest statusRequest)
    {
        if (statusRequest is null || (statusRequest.Status != 0 && statusRequest.Status != 1))
            throw new SchedulerException("请传入正确的参数: Status.");

        switch (statusRequest.Status)
        {
            case 0:
                //执行关闭;
                await _quartzManage.PauseJob(SchedulerKeyGenerator.JobKey(statusRequest.Id), SchedulerKeyGenerator.GroupKey(statusRequest.Id));
                break;
            case 1:
                //执行启动
                await _quartzManage.StartJob(SchedulerKeyGenerator.JobKey(statusRequest.Id), SchedulerKeyGenerator.GroupKey(statusRequest.Id));
                break;
        }

        //删除缓存
        await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());

        //更新数据库
        var scheduler = _wrapper.Scheduler.FindByCondition(data => data.Id == statusRequest.Id && data.IsDeleted == false)
            .FirstOrDefault();
        if (scheduler != null)
        {
            scheduler.JobStatus = statusRequest.Status;
            _wrapper.Scheduler.Update(scheduler);
            return true;
        }

        return false;
    }
    
    /// <summary>
    /// 更新整个JobDetail内容
    /// </summary>
    /// <param name="statusRequest"></param>
    /// <returns></returns>
    /// <exception cref="SchedulerException"></exception>
    public async Task<UpdateSchedulerResponse> UpdateSchedulerAsync(UpdateSchedulerRequest request)
    {
        
        //Delay double delete.
        await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());
        
        //更新数据库
        var updateScheduler = _mapper.Map<Scheduler>(request);
        _wrapper.Scheduler.Update(updateScheduler);
        
        await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());
        
        var response = new UpdateSchedulerResponse();
        //思路就是把trigger jobdetail全部替换成新的;1.先移除,2.新建(同时考虑会不会数据库重复,筛选条件加isDel)
        if (request is null ||  !"Get".Equals(request.RequestType,StringComparison.OrdinalIgnoreCase) || !"Post".Equals(request.RequestType,StringComparison.OrdinalIgnoreCase))
            throw new SchedulerException("目前只支持Get,Post,请传入正确的RequestType.");

        await _quartzManage.DeleteJob(SchedulerKeyGenerator.JobKey(request.Id),SchedulerKeyGenerator.GroupKey(request.Id));
        switch (request.RequestType)
        {
            case RequestType.REQUEST_TYPE_GET:
                await _quartzManage.AddJobForGet<GetJob>(SchedulerKeyGenerator.JobKey(request.Id), SchedulerKeyGenerator.GroupKey(request.Id)
                    , "", new AddSchedulerRequest());
                break;
            case RequestType.REQUEST_TYPE_POST:
                var map = _mapper.Map<AddSchedulerRequest>(request);
                await _quartzManage.AddJobForPost<PostJob>(SchedulerKeyGenerator.JobKey(request.Id), SchedulerKeyGenerator.GroupKey(request.Id),map);
                break;
        }
        
        
        return response;
    }

    /// <summary>
    /// 批量删除Schedulers
    /// </summary>
    /// <param name="ids"></param>
    /// <returns></returns>
    /// <exception cref="SchedulerException"></exception>
    public async Task<bool> BatchDelSchedulerAsync(List<Guid> ids)
    {
        if (!ids.Any())
            throw new SchedulerException("请传入正确的参数: ids.");
        //删除缓存
        await _redisHelper.DelKey(RedisKeyGenerator.AllSchedulersRedisKey());
        
        //更新数据库,逻辑删除
        var list = await _wrapper.Scheduler.FindByCondition(x => ids.Contains(x.Id)).ToListAsync();
        foreach (var scheduler in list)
        {
            scheduler.IsDeleted = true;
            scheduler.JobStatus = 0;
            _wrapper.Scheduler.Update(scheduler);
            //删除
            await _quartzManage.DeleteJob(SchedulerKeyGenerator.JobKey(scheduler.Id), SchedulerKeyGenerator.GroupKey(scheduler.Id));
        }
        
        return true;
    }


    /// <summary>
    /// 查询所有的Scheduler列表
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public async Task<QuerySchedulerListResponse> QueryAllSchedulersAsync(QuerySchedulerListRequest request)
    {
        var response = new QuerySchedulerListResponse();
        try
        {
            //先去查redis
            var allSchedulers = await _redisHelper.HGetAllValue<Scheduler>(RedisKeyGenerator.AllSchedulersRedisKey());
            if (allSchedulers.Any())
            {
                var map = _mapper.Map<List<SchedulerModel>>(allSchedulers);
                response.Schedulers = map;
                return response;
            }

            //查数据库,并设置redis;
            var schedulers = _wrapper.Scheduler.FindAll().Where(_=>_.IsDeleted==false).ToList();
            var data = _mapper.Map<List<SchedulerModel>>(schedulers);
            response.Schedulers = data;
            var schedulersDic = schedulers.ToDictionary(_ => _.Id);

            await schedulersDic.ParallelForEachAsync(async scheduler =>
            {
                var redisKey = RedisKeyGenerator.AllSchedulersRedisKey();
                try
                {
                    foreach (var s in schedulers)
                    {
                        await _redisHelper.HashSet(redisKey, scheduler.Key.ToString(),
                            JsonConvert.SerializeObject(scheduler.Value));
                    }
                }
                catch (Exception e)
                {
                    _logger.LogError(e.Message);
                }
            });
        }
        catch (Exception e)
        {
            _logger.LogError($"QueryAllSchedulersAsync error: {e.Message}");
            throw;
        }

        return response;
    }


    /// <summary>
    /// 根据条件查找schedulers.
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public async Task<QuerySchedulerListByConditionsResponse> QuerySchedulerListByConditionsAsync(QuerySchedulerListByConditionsRequest request)
    {
        var response = new QuerySchedulerListByConditionsResponse();
        try
        {
            //Param Check
            Expression<Func<Scheduler, bool>> expression = ExpressionExtension.True<Scheduler>();
            if (!string.IsNullOrEmpty(request.MethodName))
            {
                expression = expression.And(p => p.MethodName.Contains(request.MethodName));
            }
            if (!string.IsNullOrEmpty(request.Url))
            {
                expression = expression.And(p => p.Url.Contains(request.Url));
            }
            if (!string.IsNullOrEmpty(request.RequesType))
            {
                expression = expression.And(p => p.RequestType.Contains(request.RequesType));
            }
            //查数据库
            var schedulers = await _wrapper.Scheduler.FindByCondition(expression)
                .OrderByDescending(_=>_.UpdateTime).ToListAsync();
            if (schedulers.Any())
            {
                var map = _mapper.Map<List<SchedulerModel>>(schedulers);
                response.SchedulerModels = map;
            }
            
            return response;
        }
        catch (Exception e)
        {
            _logger.LogError($"QuerySchedulerListByConditions error:{e.Message}");
            throw;
        }
    }

    private void DelayedDoubleDeletion(string key)
    {
        _ = Task.Factory.StartNew(async () =>
        {
            await Task.Delay(3000);
            //删除key
            _redisHelper.Remove(key);
        });
    }
    
}

GetJob和PostJob的定义:

注意这里需要解决Quartz.net的Job定义的时候默认不支持依赖注入,我们需要自定义JobFactory去实现IJobFactory接口,本项目中为DefaultSchedulerServiceFactory,注入starup的时候注意要使用AddSingleton方式,然后在QuartzManage构造函数里面主动修改它的JobFactory:

builder.Services.AddTransient<ISchedulerManager, SchedulerManager>();
builder.Services.AddTransient<IRedisHelper, RedisHelper>();
builder.Services.AddSingleton<IJobFactory, DefaultScheduleServiceFactory>();
//这里注意注入方式,以及上下这些注入都不可以缺少
builder.Services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
builder.Services.AddSingleton<GetJob>();
builder.Services.AddSingleton<PostJob>();
builder.Services.AddTransient<IQuartzManage, QuartzManage>();
//主动修改其默认JobFactory
_scheduler.JobFactory = jobFactory;

GetJob定义:

using Microsoft.Extensions.Logging;
using Quartz;

namespace org.huage.BizManagement.Job;

public class GetJob : IJob
{
    private readonly ILogger<GetJob> _logger;
    public GetJob(ILogger<GetJob> logger)
    {
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        var url = context.MergedJobDataMap["RequestUrl"].ToString();
        var param = context.MergedJobDataMap["RequestParam"].ToString();
        try
        {
            _logger.LogInformation($"Current execute url: {url}, param is {param}");
            var client = new HttpClient();
            using var httpResponse = await client.GetAsync(url);
            var result = await httpResponse.Content.ReadAsStringAsync();
            _logger.LogInformation("Result is: "+ result);
        }
        catch (Exception e)
        {
            _logger.LogError("执行请求地址:"+url+" 发生异常");
            _logger.LogError("ERROR:"+e.Message);
        }
    }
}

PostJob定义:

using System.Text;
using Microsoft.Extensions.Logging;
using Quartz;

namespace org.huage.BizManagement.Job;

public class PostJob : IJob
{
    private readonly ILogger<PostJob> _logger;

    public PostJob(ILogger<PostJob> logger)
    {
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        //httpClient.DefaultRequestHeaders.Add("Portfolio", Portfolio);
        var url = context.MergedJobDataMap["RequestUrl"].ToString();
        var param = context.MergedJobDataMap["RequestParam"].ToString();
        
        var content = new StringContent(param ?? "{}", Encoding.UTF8, "application/json");

        try
        {
            _logger.LogInformation($"Current execute url: {url}, param is {param}");
            var client = new HttpClient();
            using var httpResponse = await client.PostAsync(url, content);
            var result = await httpResponse.Content.ReadAsStringAsync();
            _logger.LogInformation("Result is:" + result);
        }
        catch (Exception e)
        {
            _logger.LogError("执行请求地址:"+url+"发生异常");
            _logger.LogError("ERROR:"+e.Message);
        }
    }
}

Swagger界面:

 remark: 测试AddScheduler RequestType为Post请求的时候需要把methodParams参数压缩转义传递。

Json压缩转义工具

2023-12-15更新:

原因:为了满足在项目崩溃重启后能够执行之前的scheduler,我们在项目启动时基于IHostService注册一个后台任务run定时任务。

代码实现:
1. Api项目新增:   RestartRegisterScheduler
using AutoMapper;
using org.huage.BizManagement.Job;
using org.huage.BizManagement.Manager;
using org.huage.Entity.common;
using org.huage.Entity.Enum;
using org.huage.Entity.Model;
using org.huage.Entity.Request;

namespace org.huage.Api.Extension;

public class RestartRegisterScheduler : IHostedService
{
    private readonly ILogger<RestartRegisterScheduler> _logger;
    private readonly IServiceProvider _serviceProvider;

    public RestartRegisterScheduler(ILogger<RestartRegisterScheduler> logger, IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider ?? throw new ArgumentNullException("error");
    }

    /// <summary>
    /// 查詢所有的數據庫狀態為enable的,然後重新注冊。
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Register the Scheduler to task container.");

        using var scope = _serviceProvider.CreateScope();
        var manager = scope.ServiceProvider.GetRequiredService<ISchedulerManager>();
        var quartzManage = scope.ServiceProvider.GetRequiredService<IQuartzManage>();
        var mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
        var request = new QuerySchedulerListByConditionsRequest()
        {
            Status = 0
        };
        var schedulerList = await manager.QuerySchedulerListByConditionsAsync(request);
        foreach (var scheduler in schedulerList.SchedulerModels)
        {
            if (request is null ||  (scheduler.RequestType != RequestType.REQUEST_TYPE_GET && scheduler.RequestType != RequestType.REQUEST_TYPE_POST))
                continue;
            var addSchedulerRequest = mapper.Map<SchedulerModel,AddSchedulerRequest>(scheduler);
            //注冊服務
            switch (scheduler.RequestType)
            { 
                case RequestType.REQUEST_TYPE_GET:
                    await quartzManage.AddJobForGet<GetJob>(SchedulerKeyGenerator.JobKey(scheduler.Id), SchedulerKeyGenerator.GroupKey(scheduler.Id)
                        , "",addSchedulerRequest);
                    break;
                case RequestType.REQUEST_TYPE_POST:
                    await quartzManage.AddJobForPost<PostJob>(SchedulerKeyGenerator.JobKey(scheduler.Id), SchedulerKeyGenerator.GroupKey(scheduler.Id), addSchedulerRequest);
                    break;
            }
        }
        _logger.LogInformation("BackGround Service has all register.");
        
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Schedulers Program is shut down,please check out.");
        return Task.CompletedTask;
    }
}
2. Program.cs注册后台服务
builder.Services.AddHostedService<RestartRegisterScheduler>();

以上就是.Net 7.0+Quartz项目定时任务调度的全部内容,后期会增加前端页面进行可视化界面进行管理。有任何问题欢迎留言,看到会第一时间回复。

源代码地址,吴彦祖刘亦菲们动动小手点star

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
.NET Core 中可以使用多种方式来实现定时任务,其中比较常用的是使用 Quartz.NET 和 Hangfire。以下是简单的介绍: 1. Quartz.NETQuartz.NET 是一个功能强大、开源的定时任务框架,可以支持复杂的调度需求。使用 Quartz.NET 可以通过简单的配置和编程实现定时任务的调度和管理。 2. Hangfire:Hangfire 是一个开源的 .NET 库,可以实现在 ASP.NET.NET Core 应用程序中运行后台任务和定时任务。Hangfire 支持多种存储方式,包括内存、Redis、SQL Server 等,可以非常方便地进行任务调度和管理。 无论使用哪种方式,我们都需要定义任务,例如: ```csharp public class MyJob { public void Run() { // 执行具体的业务逻辑 } } ``` 然后根据具体的框架和需求,配置任务调度和管理,例如: ```csharp // 使用 Quartz.NET var scheduler = new StdSchedulerFactory().GetScheduler(); var job = JobBuilder.Create<MyJob>().Build(); var trigger = TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(10) .RepeatForever()) .Build(); scheduler.ScheduleJob(job, trigger); scheduler.Start(); // 使用 Hangfire RecurringJob.AddOrUpdate<MyJob>("job1", job => job.Run(), Cron.Minutely); ``` 其中,Quartz.NET 中的 `Job` 和 `Trigger` 分别表示任务和触发器,Hangfire 中的 `RecurringJob` 表示定时任务。以上只是简单的示例,具体实现还需要根据实际需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值