zoukankan      html  css  js  c++  java
  • Uwl.Admin.Core开源框架(二) 使用QuartzNet

    Uwl.Admin.Core中使用QuartzNet定时任务模块

    本文负责讲解RabbitMQ的使用

    Uwl.Admin.Core使用的技术有:

      *、Async和Await 异步编程

      *、Repository + Service 仓储模式编程;仓储模式支持工作单元

      *、Swagger 前后端文档说明,基于RESTful风格编写接口

      *、Cors 简单的跨域解决方案

      *、JWT自定义策略授权权限验证

      *、依赖注入选择的是官方自带的DI注入,没有使用第三方框架,ORM使用EF Core,数据库使用的是Sql server,(后期会扩展MySql版本);

      *、AutoMapper 自动对象映射、

      *、Linq To Sql lambda表达式树查询;(表达式树查询是个人扩展的,表达式树的使用方法请参考Uwl.Data.Server.MenuServer的多条件查询)

      *、登录认证方式使用JWT认证方式,后台接口使用SwaggerUI展示,角色权限使用  自定义权限处理器PermissionHandler 继承与微软官方 IAuthorizationRequirement;

      *、Excel导入导出使用的是Epplus第三方框架,导入导出只需要配置Attribute特性就好,不需要在自己写列名;导出只支持List导出,暂时不支持Datatable;(Excel使用方法请参考UserController控制器)

      *、Rabbit MQ消息队列(目前暂无业务使用场景后期准备用来记录日志)

      *、Redis 轻量级分布式缓存;(Redis使用方法请参考Uwl.Data.Server.MenuServer类)

      *、QuartzNet第三方任务框架;(使用方法请参考类库Uwl.ScheduledTask.Job.TestJobOne类)

      *、IdentityServer4授权模式已开发完成,未发布演示服务器代码在github(Identityserver4Auth分支)

    Quartz.NET:

    Quartz.NET官网地址:https://www.quartz-scheduler.net/

    Quartz.NET文档地址:https://www.quartz-scheduler.net/documentation/index.html

    Quartz.NET是一个开源的作业调度框架,是OpenSymphonyQuartz API的.NET移植,它用C#写成,可用于winformasp.net应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。

    现在Quartz.NET3.0已支持Asp.Net Core,3.0新功能如下:

    新功能

    • 具有异步/等待支持的基于任务的作业,内部以异步/等待方式工作
    • 支持.NET Core / netstandard 2.0和.NET Framework 4.5.2及更高版本
    • 通过提供程序名称SQLite-Microsoft支持Microsoft.Data.Sqlite,旧的提供程序SQLite也仍然有效
    • 增加了SQL Server内存优化表和Quartz.Impl.AdoJobStore.UpdateLockRowSemaphoreMOT的初步支持
    • Common.Logging从相关性中删除
    • ILMerge进程中删除的C5集合不再需要
    • 在插件启动时添加对作业调度XML文件的急切验证的支持
    • TimeZoneUtil中添加对额外的自定义时区解析器功能的支持

    变化

    • 作业和插件现在位于独立的程序集NuGetQuartz.JobsQuartz.Plugins
    • ADO.NET提供者名称已被简化,提供者名称没有版本,例如SqlServer-20 => SqlServer
    • API方法已被重新使用,主要使用IReadOnlyCollection,这隐藏了两个HashSets和List小号
    • LibLog一直隐藏于内部(ILog等),就像它原本打算的那样
    • SimpleThreadPool消失了,旧的拥有的线程消失了
    • 调度程序方法已更改为基于任务,请记住等待它们
    • IJob接口现在返回一个任务
    • 一些IList属性已更改为IReadOnlyList以正确反映意图
    • SQL Server CE支持已被删除
    • DailyCalendar现在将日期时间用于排除的日期,并具有ISet接口来访问它们
    • IObjectSerializer有新的方法,void Initialize(),必须实现
    • IInterruptableJob取消了上下文的CancellationToken

    Quartz API的关键接口和类是

    • IScheduler - 与调度程序交互的主要API。
    • IJob - 您希望由调度程序执行的组件实现的接口。
    • IJobDetail - 用于定义作业的实例。
    • ITrigger - 定义执行给定Job的时间表的组件。
    • JobBuilder - 用于定义/构建定义作业实例的JobDetail实例。
    • TriggerBuilder - 用于定义/构建触发器实例

    一、Quartz.NET基本使用

      1、新建Uwl.QuartzNet.JobCenter 类库项目,使用NuGet添加Quartz,或使用程序包管理器引用,命令如下:

      Install-Package Quartz

      2、Uwl.QuartzNet.JobCenter 类库的作用:

       Uwl.QuartzNet.JobCenter 类库是计划任务管理中心,这里我就放一段代码了,不放太多,具体的实现可以下载下来Uwl.Admin.Core项目看
        /// <summary>
            /// 开启任务调度
            /// </summary>
            /// <returns></returns>
            public async Task<JobResuleModel> StartScheduleAsync()
            {
                var result = new JobResuleModel();
                try
                {
                    if (!this._scheduler.Result.IsStarted)
                    {
                        //等待任务运行完成
                        await this._scheduler.Result.Start();
                        await Console.Out.WriteLineAsync("任务调度开启!");
                        result.IsSuccess = true;
                        result.Message = $"任务调度开启成功";
                        return result;
                    }
                    else
                    {
                        result.IsSuccess = false;
                        result.Message = $"任务调度已经开启";
                        return result;
                    }
                }
                catch (Exception)
                {
                    throw;
                }
            }

     如果你想添加JSON序列化,只需要以同样的方式添加Quartz.Serialization.Json包。

    2、简单实例,代码如下:
    using Five.QuartzNetJob.ExecuteJobTask.Service;
    using Quartz;
    using Quartz.Impl;
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Five.QuartzNetJob.Web.Controllers
    {
        public class TestTask
        {
            public async Task StartTestAsync() 
            {
                try
                {
                    // 从工厂中获取调度程序实例
                    NameValueCollection props = new NameValueCollection
                    {
                        { "quartz.serializer.type", "binary" }
                    };
                    StdSchedulerFactory factory = new StdSchedulerFactory(props);
                    IScheduler scheduler = await factory.GetScheduler();
                   
                    // 开启调度器
                    await scheduler.Start();
    
                    // 定义这个工作,并将其绑定到我们的IJob实现类
                    IJobDetail job = JobBuilder.Create<HelloJob>()
                        .WithIdentity("job1", "group1")
                        .Build();
    
                    // 触发作业立即运行,然后每10秒重复一次,无限循环
                    ITrigger trigger = TriggerBuilder.Create()
                        .WithIdentity("trigger1", "group1")
                        .StartNow()
                        .WithSimpleSchedule(x => x
                            .WithIntervalInSeconds(10)
                            .RepeatForever())
                        .Build();
    
                    // 告诉Quartz使用我们的触发器来安排作业
                    await scheduler.ScheduleJob(job, trigger);
    
                    // 等待60秒
                    await Task.Delay(TimeSpan.FromSeconds(60));
    
                    // 关闭调度程序
                    await scheduler.Shutdown();
                }
                catch (SchedulerException se)
                {
                    await Console.Error.WriteLineAsync(se.ToString());
                }
            }
        }
    }

     TestJobOne内容如下:

    using Quartz;
    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using Uwl.Common.Cache.RedisCache;
    using Uwl.Common.Subscription;
    using Uwl.Data.Server.MenuServices;

    namespace Uwl.ScheduledTask.Job
    {
        public class TestJobOne : IJob
        {
            private readonly IRedisCacheManager _redisCacheManager;
            private readonly IMenuServer _menuServer;
            public TestJobOne(IRedisCacheManager redisCacheManager,IMenuServer menuServer)
            {
                this._redisCacheManager = redisCacheManager;
                this._menuServer = menuServer;
            }
            public async Task Execute(IJobExecutionContext context)
            {
                //记录Job时间
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                
                await Console.Out.WriteLineAsync("我是有Redis的注入测试任务");
                var list = await _menuServer.GetMenuList();
                await Console.Out.WriteLineAsync("菜单表里总数量" + list.Count.ToString());
                stopwatch.Stop();
                await Console.Out.WriteLineAsync("执行时间" +  stopwatch.Elapsed.TotalMilliseconds);
                //if (stopwatch.Elapsed.TotalMilliseconds > 0)
                //{
                //    //写入日志性能监控表和执行是否出错
                //}
            }
        }
    }

    执行效果:

    二、触发器类型

     1、SimpleTrigger触发器(简单触发器)

    SimpleTrigger的属性包括:开始时间和结束时间,重复计数和重复间隔。重复计数可以是零,一个正整数或常数值SimpleTrigger.RepeatIndefinitely。重复时间间隔属性必须是TimeSpan.Zero或正的TimeSpan值。请注意,重复间隔为0会导致触发器的“重复计数”触发同时发生。SimpleTrigger实例使用TriggerBuilder(用于触发器的主属性)和WithSimpleSchedule扩展方法(用于SimpleTrigger特定的属性)构建。

    在特定的时间内建立触发器,无需重复,代码如下:

       /// <summary>
            /// 创建SimpleTrigger触发器(简单触发器)
            /// </summary>
            /// <param name="sysSchedule"></param>
            /// <param name="starRunTime"></param>
            /// <param name="endRunTime"></param>
            /// <returns></returns>
            private ITrigger CreateSimpleTrigger(SysSchedule sysSchedule)
            {
                if(sysSchedule.RunTimes>0)
                {
                    ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                    .StartAt(sysSchedule.BeginTime.Value)
                    .EndAt(sysSchedule.EndTime.Value)
                    .WithSimpleSchedule(x =>
                    x.WithIntervalInSeconds(sysSchedule.IntervalSecond)
                    .WithRepeatCount(sysSchedule.RunTimes)).ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup).Build();
                    return trigger;
                }
                else
                {
                    ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                    .StartAt(sysSchedule.BeginTime.Value)
                    .EndAt(sysSchedule.EndTime.Value)
                    .WithSimpleSchedule(x =>
                    x.WithIntervalInSeconds(sysSchedule.IntervalSecond)
                    .RepeatForever()).ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup).Build();
                    return trigger;
                }
                // 触发作业立即运行,然后每10秒重复一次,无限循环
                
            }

    因此简单的任务调度使用SimpleTrigger完全够用,如果SimpleTrigger还是不能满足您的需求请往下看。

    2、CronTrigger触发器

    如果你需要一个基于类似日历的概念而不是精确指定的SimpleTrigger时间间隔的工作调度计划,CronTriggers通常比SimpleTrigger更有用。

    使用CronTrigger,您可以在每周一,周三的上午9点至上午10点之间指定开始时间表,例如“每星期五中午”或“每个工作日和上午9点30分”,或者“每5分钟”和星期五”。

    即使如此,就像SimpleTrigger一样,CronTrigger有一个startTime,它指定了时间表的生效时间,还有一个(可选的)endTime,用于指定应该停止时间表的时间。

    这里不在详细介绍Cron

    Cron表达式在线生成器:http://cron.qqe2.com/

    Cron表达式详细介绍:https://www.jianshu.com/p/e9ce1a7e1ed1

       /// <summary>
            /// 创建类型Cron的触发器
            /// </summary>
            /// <param name="m"></param>
            /// <returns></returns>
            private ITrigger CreateCronTrigger(SysSchedule sysSchedule)
            {
                // 作业触发器
                return TriggerBuilder.Create()
                       .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
                       .StartAt(sysSchedule.BeginTime.Value)//开始时间
                       .EndAt(sysSchedule.EndTime.Value)//结束数据
                       .WithCronSchedule(sysSchedule.Cron)//指定cron表达式
                       .ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup)//作业名称
                       .Build();
            }

    三、在Uwl.Admin.Core配置使用方法

       1、在Uwl.ScheduledTask.Job类库下面新建一个类继承于JobBase和IJob接口:

      

      2、在新建的类里面写一个方法,并且把这个方法通过实现的IJob的Execute方法传给JobBase基类:

      

      3、在新建的类里面写一个方法,并且把这个方法通过实现的IJob的Execute方法传给JobBase基类:

      在uwl.admin后台管理的定时任务模块添加一个新的任务,填写对应的名称,这里需要注意的是(DLL程序集是☞你的类库,任务所在类是指你的Job需要执行的Calss,这里有两种触发类型,一个是simple类型,一个是Cron类型可以根据自己的需要去设置对应的类型

      simple类型适合简单任务,开始时间和结束时间非必填,不填的话在你点击开始任务的时候就是默认执行,结束时间取的是最大时间)

      为什么要填程序集和类的名字呢,因为这里我是通过反射来获取程序集和类来进行执行那个Job的

      

      我们把这些配置完成之后点击启动任务就OK啦~~

      

      这里还有一点小问题……就是程序暂停运行了之后不会自动启动在执行的任务,后面我会慢慢修复,暂且各位大佬每次发布之后记得点击一下启动任务嗷~~~

      总结(很重要): Quartz.NET的3.0版本跟之前的版本api接口变化并不大。只是在3.0.7版本中添加了异步调用,并支持.net core。简单的任务调度使用官网中的实例即可满足需求,进行依赖注入的时候应当重写IJobFactory工厂,在IJobFactory工厂内重写         NewJob,ReturnJob方法;

      具体代码实现

       /// <summary>
            /// 注入反射获取依赖对象
            /// </summary>
            private readonly IServiceProvider _serviceProvider;
            public IOCJobFactory(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
            /// <summary>
            /// 实现接口Job
            /// </summary>
            /// <param name="bundle"></param>
            /// <param name="scheduler"></param>
            /// <returns></returns>
            public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
            {
                try
                {

         这个位置需要重新创建_serviceProvider.CreateScope();容器,不然会提示找不到Job/还有一种情况是你也注入了但是Job无法执行,所以这个位置应当重新创建容器实例,
                    var serviceScope = _serviceProvider.CreateScope();
                    var job = serviceScope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
                    return job;
                    //var job = _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
                    //return job;

                }
                catch (Exception e)
                {
                    throw e;
                }
            }

            public void ReturnJob(IJob job)
            {
                var disposable = job as IDisposable;
                if(disposable!=null)
                {
                    disposable.Dispose();
                }
                
            }

    学不完的技术,写不完的代码。QQ群:773595012
    ▼-----------------------------------------▼-----------------------------------------▼
  • 相关阅读:
    js遍历不要使用index 而是使用id(数据唯一表示)
    eureka
    Mybatis-plus自动填充字段的值(如createTime,UpdateTime)
    计算机网络入门
    操作系统入门
    计算机组成原理入门
    《事实》读书笔记
    推荐算法入门
    源码编译安装apache2.4脚本
    Mycat实现读写分离
  • 原文地址:https://www.cnblogs.com/pual13/p/12010047.html
Copyright © 2011-2022 走看看