furion定时任务

开发环境

vs2022
.net 6
Furion.Pure 4.8.8.48

创建学习项目

在这里插入图片描述
安装Furion.Pure 4.8.8.48
在这里插入图片描述
Program.cs

var builder = WebApplication.CreateBuilder(args).Inject();

builder.Services.AddControllers().AddInject();
var app = builder.Build();
app.UseInject(string.Empty);
app.MapControllers();
app.Run();

在这里插入图片描述> 学习环境搭建成功,后面都是依赖于这个环境

简单的实现定时任务

var builder = WebApplication.CreateBuilder(args).Inject();

builder.Services.AddControllers().AddInject();
//此时没有任何任务注入
builder.Services.AddSchedule();
var app = builder.Build();
// 还可以配置生产环境关闭
app.UseScheduleUI(options =>
{
    options.RequestPath = "/myjob";
    options.DisableOnProduction = false;
});
app.UseInject(string.Empty);
app.MapControllers();

app.Run();

访问/myjob
在这里插入图片描述
此时没有任何任务显示
新建MyJob01.cs

using Furion.Schedule;

namespace FurionJobStu01
{
    /// <summary>
    /// 最简单的定时任务
    /// </summary>
    public class MyJob01 : IJob
    {
        private readonly ILogger<MyJob01> _logger;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="logger"></param>
        public MyJob01(ILogger<MyJob01> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// 定时任务执行入口
        /// </summary>
        /// <param name="context"></param>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
        {
            _logger.LogInformation($"{context}");
            return Task.CompletedTask;
        }
    }
}

修改builder.Services.AddSchedule();

//静态注入
builder.Services.AddSchedule(options =>
{
    // 表示每秒执行
    //options.AddJob<MyJob01>(Triggers.Secondly());
    //增加id标识
    options.AddJob<MyJob01>("myjob01", Triggers.Secondly());
});

在这里插入图片描述

在这里插入图片描述
一个简单的定时任务就实现了

动态添加定时任务

将静态增加修改为builder.Services.AddSchedule();
新建HomeController.cs

using Furion.Schedule;
using Microsoft.AspNetCore.Mvc;

namespace FurionJobStu01
{
    [DynamicApiController]
    public class HomeController
    {
        private readonly ISchedulerFactory schedulerFactory;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="schedulerFactory"></param>
        public HomeController(ISchedulerFactory schedulerFactory)
        {
            this.schedulerFactory = schedulerFactory;

        }

        /// <summary>
        /// 添加定时任务
        /// </summary>
        [HttpGet]
        public long AddJob()
        {
            TimeSpan mTimeSpan = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0);
            //得到精确到秒的时间戳(长度10位)
            long time = (long)mTimeSpan.TotalSeconds;
            schedulerFactory.AddJob<MyJob01>($"{time}", Triggers.Secondly());
            return time;
        }
    }
}

访问http://localhost:5058/api/home/job
在这里插入图片描述
执行几次就动态增加了几个定时任务
在这里插入图片描述
增删改查操作都有,查看furion官方文档

//schedulerFactory.RemoveJob 移除作业
//schedulerFactory.UpdateJob 更新作业
//schedulerFactory.GetJobs 查找所有作业
//schedulerFactory.GetCurrentRunJobs 查找即将触发的作业

持久化存储

一般定时任务需要持久化存储到存储中(数据库、缓存、文件等),下面讲讲如何实现,需要实现作业持久化器 IJobPersistence接口,先熟悉一下IJobPersistence

using Furion;
using Furion.Schedule;

namespace FurionJobStu01
{
    public class DatabaseJobPersistence : IJobPersistence
    {
        /// <summary>
        /// 作业信息更改通知
        /// </summary>
        /// <param name="context"></param>
        public void OnChanged(PersistenceContext context)
        {
            Console.WriteLine("作业信息更改通知");
        }

        /// <summary>
        /// 作业计划初始化通知,比如修改定时任务的名称等
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public SchedulerBuilder OnLoading(SchedulerBuilder builder)
        {
            // 如果是更新操作,则 return builder.Updated(); 将生成 UPDATE 语句
            // 如果是新增操作,则 return builder.Appended(); 将生成 INSERT 语句
            // 如果是删除操作,则 return builder.Removed(); 将生成 DELETE 语句
            // 如果无需标记操作,返回 builder 默认值即可
            Console.WriteLine("作业计划初始化通知");
            return builder;
        }

        /// <summary>
        /// 作业触发器更改通知,比如修改定时任务的时间
        /// </summary>
        /// <param name="context"></param>
        public void OnTriggerChanged(PersistenceTriggerContext context)
        {
            Console.WriteLine("作业触发器更改通知");
        }

        /// <summary>
        /// 作业调度器预加载服务
        /// </summary>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public IEnumerable<SchedulerBuilder> Preload()
        {
            Console.WriteLine("作业调度器预加载服务");
            // 获取所有定义的作业
            var allJobs = App.EffectiveTypes.ScanToBuilders();
            foreach (var schedulerBuilder in allJobs)
            {
                // 标记更新
                schedulerBuilder.Updated();
            }
            return allJobs;
        }
    }
}

builder.Services.AddSchedule();修改为

builder.Services.AddSchedule(options =>
{
    options.AddPersistence<DatabaseJobPersistence>(); // 添加作业持久化器
});

在这里插入图片描述
新增作业
在这里插入图片描述
暂停作业
在这里插入图片描述
删除作业
在这里插入图片描述

总结无论什么操作都走作业信息通知更改接口

但是定时任务的状态改变会走触发器
新建三个MyJob01、MyJob02、MyJob03

using Furion.Schedule;

namespace FurionJobStu02
{
    /// <summary>
    /// 最简单的定时任务
    /// </summary>
    [JobDetail("job_log", Description = "清理操作日志", GroupName = "default", Concurrent = false)]
    [Daily(TriggerId = "trigger_log", Description = "清理操作日志")]
    public class MyJob01 : IJob
    {
        private readonly ILogger<MyJob01> _logger;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="logger"></param>
        public MyJob01(ILogger<MyJob01> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// 定时任务执行入口
        /// </summary>
        /// <param name="context"></param>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
        {
            _logger.LogInformation($"{context}");
            return Task.CompletedTask;
        }
    }
}

除了id不一样其他一样,这里只放一个,通过分类处理不同类型的作业和作业触发器

using Furion;
using Furion.Schedule;
using SqlSugar;

namespace FurionJobStu02
{
    public class DatabaseJobPersistence : IJobPersistence
    {
        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="sqlSugarClient"></param>
        public DatabaseJobPersistence22()
        {
        }

        public void OnChanged(PersistenceContext context)
        {
            switch (context.Behavior)
            {
                case PersistenceBehavior.Appended:
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine($"新增作业\r\n{context.ConvertToJSON()}");
                    break;
                case PersistenceBehavior.Updated:
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine($"更新作业\r\n{context.ConvertToJSON()}");
                    break;
                case PersistenceBehavior.Removed:
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"删除作业\r\n{context.ConvertToJSON()}");
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public SchedulerBuilder OnLoading(SchedulerBuilder builder)
        {
            return builder;
        }

        public void OnTriggerChanged(PersistenceTriggerContext context)
        {
            switch (context.Behavior)
            {
                case PersistenceBehavior.Appended:
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine($"新增触发器\r\n{context.ConvertToJSON()}");
                    break;
                case PersistenceBehavior.Updated:
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine($"更新触发器\r\n{context.ConvertToJSON()}");
                    break;
                case PersistenceBehavior.Removed:
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"删除触发器\r\n{context.ConvertToJSON()}");
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public IEnumerable<SchedulerBuilder> Preload()
        {
            // 获取所有定义的作业
            var allJobs = App.EffectiveTypes.ScanToBuilders().ToList();
            foreach (var item in allJobs)
            {
                var job = item.GetJobBuilder();
                if (job.JobId == "job_log")
                {
                    var jobDetails = item.GetEnumerable();
                    foreach (var dic in jobDetails)
                    {
                        dic.Value.Updated();
                    }
                    item.Updated();
                }
                if (job.JobId == "job_log3")
                {
                    var jobDetails = item.GetEnumerable();
                    foreach (var dic in jobDetails)
                    {
                        dic.Value.Removed();
                    }
                    item.Removed();
                }
            }
            return allJobs;
        }
    }
}

可以运行查看结果

最终结果:

安装Furion.PureSqlSugarCore
在这里插入图片描述
新建JobModel和JobTriggerModel
JobModel

using SqlSugar;

namespace FurionJobStu02
{
    /// <summary>
    /// 作业表
    /// </summary>
    [SugarTable("Jobs", "作业表")]
    public class JobModel
    {
        /// <summary>
        /// 作业 Id
        /// </summary>
        [SugarColumn(ColumnName = "JobId", ColumnDescription = "作业 Id", IsPrimaryKey = true, IsIdentity = false, Length = 255)]
        public string? JobId { get; set; }

        /// <summary>
        /// 作业组名称
        /// </summary>
        [SugarColumn(ColumnDescription = "作业组名称", Length = 255, IsNullable = true)]
        public string? GroupName { get; set; }

        /// <summary>
        /// 作业处理程序类型,存储的是类型的 FullName
        /// </summary>
        [SugarColumn(ColumnDescription = "作业处理程序类型,存储的是类型的 FullName", Length = 255, IsNullable = true)]
        public string? JobType { get; set; }

        /// <summary>
        /// 作业处理程序类型所在程序集,存储的是程序集 Name
        /// </summary>
        [SugarColumn(ColumnDescription = "作业处理程序类型所在程序集,存储的是程序集 Name", Length = 255, IsNullable = true)]
        public string? AssemblyName { get; set; }

        /// <summary>
        /// 描述信息
        /// </summary>
        [SugarColumn(ColumnDescription = "描述信息", Length = 255, IsNullable = true)]
        public string? Description { get; set; }

        /// <summary>
        /// 作业执行方式,如果设置为 false,那么使用 串行 执行,否则 并行 执行
        /// </summary>
        [SugarColumn(ColumnDescription = "作业执行方式,如果设置为 false,那么使用 串行 执行,否则 并行 执行", IsNullable = true)]
        public bool? Concurrent { get; set; } = true;

        /// <summary>
        /// 是否扫描 IJob 实现类 [Trigger] 特性触发器
        /// </summary>
        [SugarColumn(ColumnDescription = "是否扫描 IJob 实现类 [Trigger] 特性触发器", IsNullable = true)]
        public bool? IncludeAnnotations { get; set; } = false;

        /// <summary>
        /// 作业信息额外数据,由 Dictionary<string, object> 序列化成字符串存储
        /// </summary>
        [SugarColumn(ColumnDescription = "作业信息额外数据", Length = 2000, IsNullable = true)]
        public string? Properties { get; set; } = "{}";

        /// <summary>
        /// 作业更新时间
        /// </summary>
        [SugarColumn(ColumnDescription = "作业更新时间", IsNullable = true)]
        public DateTime? UpdatedTime { get; set; }

        /// <summary>
        /// 是否被删除
        /// </summary>
        [SugarColumn(ColumnDescription = "是否被删除", IsNullable = true)]
        public bool IsDelete { get; set; } = false;
    }
}

JobTriggerModel

using Furion.Schedule;
using SqlSugar;

namespace FurionJobStu02
{
    /// <summary>
    /// 作业触发器表
    /// </summary>
    [SugarTable("JobTriggers", "作业触发器表")]
    public class JobTriggerModel
    {
        /// <summary>
        /// 触发器id
        /// </summary>
        [SugarColumn(ColumnName = "TriggerId", ColumnDescription = "主键Id", IsPrimaryKey = true, IsIdentity = false, Length = 255)]
        public string? TriggerId { get; set; }

        /// <summary>
        /// 作业id
        /// </summary>
        [SugarColumn(ColumnDescription = "作业id", Length = 255, IsNullable = true)]
        public string? JobId { get; set; }

        /// <summary>
        /// 触发器类型
        /// </summary>
        [SugarColumn(ColumnDescription = "触发器类型", Length = 255, IsNullable = true)]
        public string? TriggerType { get; set; }

        /// <summary>
        /// 程序集名称
        /// </summary>
        [SugarColumn(ColumnDescription = "程序集名称", Length = 255, IsNullable = true)]
        public string? AssemblyName { get; set; }

        /// <summary>
        /// 参数
        /// </summary>
        [SugarColumn(ColumnDescription = "参数", Length = 255, IsNullable = true)]
        public string? Args { get; set; }

        /// <summary>
        /// 描述
        /// </summary>
        [SugarColumn(ColumnDescription = "描述", Length = 255, IsNullable = true)]
        public string? Description { get; set; }

        /// <summary>
        /// 状态
        /// 0 积压
        /// 1 就绪
        /// 2 正在运行
        /// 3 暂停
        /// 4 阻塞
        /// 5 由失败进入就绪,运行错误当并未超出最大错误数,进入下一轮就绪
        /// 6 归档,结束时间小于当前时间
        /// 7 崩溃,错误次数超出了最大错误数
        /// 8 超限,运行次数超出了最大限制
        /// 9 无触发时间,下一次执行时间为 null
        /// 10 初始化时未启动
        /// 11 未知作业触发器,作业触发器运行时类型为 null
        /// 12 未知作业处理程序,作业处理程序类型运行时类型为 null
        /// </summary>
        [SugarColumn(ColumnDescription = "状态", IsNullable = true)]
        public TriggerStatus? Status { get; set; }

        /// <summary>
        /// 开始执行时间
        /// </summary>
        [SugarColumn(ColumnDescription = "开始执行时间", IsNullable = true)]
        public DateTime? StartTime { get; set; }

        /// <summary>
        /// 结束执行时间
        /// </summary>
        [SugarColumn(ColumnDescription = "结束执行时间", IsNullable = true)]
        public DateTime? EndTime { get; set; }

        /// <summary>
        /// 最近运行时间
        /// </summary>
        [SugarColumn(ColumnDescription = "最近运行时间", IsNullable = true)]
        public DateTime? LastRunTime { get; set; }

        /// <summary>
        /// 下一次执行时间
        /// </summary>
        [SugarColumn(ColumnDescription = "下一次执行时间", IsNullable = true)]
        public DateTime? NextRunTime { get; set; }

        /// <summary>
        /// 触发次数
        /// </summary>
        [SugarColumn(ColumnDescription = "触发次数", IsNullable = true)]
        public long? NumberOfRuns { get; set; }

        /// <summary>
        /// 最大触发次数,0:不限制,n:N 次
        /// </summary>
        [SugarColumn(ColumnDescription = "最大触发次数,0:不限制,n:N 次", IsNullable = true)]
        public long? MaxNumberOfRuns { get; set; }

        /// <summary>
        /// 错误次数
        /// </summary>
        [SugarColumn(ColumnDescription = "错误次数", IsNullable = true)]
        public int? NumberOfErrors { get; set; }

        /// <summary>
        /// 最大出错次数,0:不限制,n:N 次
        /// </summary>
        [SugarColumn(ColumnDescription = "最大出错次数,0:不限制,n:N 次", IsNullable = true)]
        public int? MaxNumberOfErrors { get; set; }

        /// <summary>
        /// 重试次数
        /// </summary>
        [SugarColumn(ColumnDescription = "重试次数", IsNullable = true)]
        public int? NumRetries { get; set; }

        /// <summary>
        /// 重试间隔时间,毫秒单位
        /// </summary>
        [SugarColumn(ColumnDescription = "重试间隔时间,毫秒单位", IsNullable = true)]
        public int? RetryTimeout { get; set; }

        /// <summary>
        /// 是否立即启动
        /// </summary>
        [SugarColumn(ColumnDescription = "是否立即启动", IsNullable = true)]
        public bool? StartNow { get; set; }

        /// <summary>
        /// 是否启动时执行一次
        /// </summary>
        [SugarColumn(ColumnDescription = "是否启动时执行一次", IsNullable = true)]
        public bool? RunOnStart { get; set; }

        /// <summary>
        /// 是否在启动时重置最大触发次数等于一次的作业
        /// </summary>
        [SugarColumn(ColumnDescription = "是否在启动时重置最大触发次数等于一次的作业", IsNullable = true)]
        public bool? ResetOnlyOnce { get; set; }

        /// <summary>
        /// 本次执行返回结果,Furion 4.8.7.7+
        /// </summary>
        [SugarColumn(ColumnDescription = "本次执行返回结果", IsNullable = true)]
        public string? Result { get; set; }

        /// <summary>
        /// 本次执行耗时,单位 ms,Furion 4.8.7.7+
        /// </summary>
        [SugarColumn(ColumnDescription = "本次执行耗时,单位 ms", IsNullable = true)]
        public long? ElapsedTime { get; set; }

        /// <summary>
        /// 作业触发器更新时间
        /// </summary>
        [SugarColumn(ColumnDescription = "作业触发器更新时间", IsNullable = true)]
        public DateTime? UpdatedTime { get; set; }

        /// <summary>
        /// 是否被删除
        /// </summary>
        [SugarColumn(ColumnDescription = "是否被删除", IsNullable = true)]
        public bool IsDelete { get; set; } = false;
    }
}

持久化核心代码DatabaseJobPersistence

using Furion;
using Furion.Schedule;
using Newtonsoft.Json;
using SqlSugar;

namespace FurionJobStu02
{
    public class DatabaseJobPersistence : IJobPersistence
    {
        private readonly SqlSugarClient sqlSugarClient;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="sqlSugarClient"></param>
        public DatabaseJobPersistence(SqlSugarClient sqlSugarClient)
        {
            this.sqlSugarClient = sqlSugarClient;
        }

        /// <summary>
        /// 作业被改变了
        /// </summary>
        /// <param name="context"></param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void OnChanged(PersistenceContext context)
        {
            switch (context.Behavior)
            {
                case PersistenceBehavior.Appended:
                    //Console.ForegroundColor = ConsoleColor.Green;
                    //Console.WriteLine($"新增作业\r\n{context.ConvertToJSON()}");
                    var appendModel = JsonConvert.DeserializeObject<JobModel>(context.ConvertToJSON());
                    appendModel.IsDelete = false;
                    sqlSugarClient.Insertable(appendModel).ExecuteCommand();
                    break;
                case PersistenceBehavior.Updated:
                    //Console.ForegroundColor = ConsoleColor.Yellow;
                    //Console.WriteLine($"更新作业\r\n{context.ConvertToJSON()}");
                    var updateModel = JsonConvert.DeserializeObject<JobModel>(context.ConvertToJSON());
                    updateModel.IsDelete = false;
                    sqlSugarClient.Updateable(updateModel).ExecuteCommand();
                    break;
                case PersistenceBehavior.Removed:
                    //Console.ForegroundColor = ConsoleColor.Red;
                    //Console.WriteLine($"删除作业\r\n{context.ConvertToJSON()}");
                    var deleteModel = JsonConvert.DeserializeObject<JobModel>(context.ConvertToJSON());
                    deleteModel.IsDelete = true;
                    sqlSugarClient.Updateable(deleteModel).ExecuteCommand();
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// 加载定时任务
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public SchedulerBuilder OnLoading(SchedulerBuilder builder)
        {
            return builder;
        }

        /// <summary>
        /// 作业触发器被改变了
        /// </summary>
        /// <param name="context"></param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void OnTriggerChanged(PersistenceTriggerContext context)
        {
            switch (context.Behavior)
            {
                case PersistenceBehavior.Appended:
                    //Console.ForegroundColor = ConsoleColor.Green;
                    //Console.WriteLine($"新增触发器\r\n{context.ConvertToJSON()}");
                    var appendModel = JsonConvert.DeserializeObject<JobTriggerModel>(context.ConvertToJSON());
                    appendModel.IsDelete = false;
                    sqlSugarClient.Insertable(appendModel).ExecuteCommand();
                    break;
                case PersistenceBehavior.Updated:
                    //Console.ForegroundColor = ConsoleColor.Yellow;
                    //Console.WriteLine($"更新触发器\r\n{context.ConvertToJSON()}");
                    var updateModel = JsonConvert.DeserializeObject<JobTriggerModel>(context.ConvertToJSON());
                    updateModel.IsDelete = false;
                    sqlSugarClient.Updateable(updateModel).ExecuteCommand();
                    break;
                case PersistenceBehavior.Removed:
                    //Console.ForegroundColor = ConsoleColor.Red;
                    //Console.WriteLine($"删除触发器\r\n{context.ConvertToJSON()}");
                    var deleteModel = JsonConvert.DeserializeObject<JobTriggerModel>(context.ConvertToJSON());
                    deleteModel.IsDelete = true;
                    sqlSugarClient.Updateable(deleteModel).ExecuteCommand();
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// 加载之前的处理
        /// </summary>
        /// <returns></returns>
        public IEnumerable<SchedulerBuilder> Preload()
        {
            //查询数据库中所有的作业
            var allDbJobs = sqlSugarClient.Queryable<JobModel>().ToList();
            //查询数据库中所有的作业触发器
            var allDbJobTriggers = sqlSugarClient.Queryable<JobTriggerModel>().ToList();
            // 获取所有定义的作业
            var allJobs = App.EffectiveTypes.ScanToBuilders().ToList();
            foreach (var item in allJobs)
            {
                //不标记默认新增
                var job = item.GetJobBuilder();
                var jobDetail = allDbJobs.Where(p => p.JobId == job.JobId).FirstOrDefault();
                if (jobDetail != null)
                {
                    bool isDelete = false;
                    if (jobDetail.IsDelete)
                    {
                        isDelete = true;
                        item.Removed();
                    }
                    else
                    {
                        job.LoadFrom(jobDetail);
                        item.Updated();
                    }
                    var jobDetails = item.GetEnumerable();
                    foreach (var dic in jobDetails)
                    {
                        //如果作业都被删除了则下面的触发器肯定删除
                        if (isDelete)
                        {
                            dic.Value.Removed();
                        }
                        else
                        {
                            //查找对应的触发器
                            var jobTriggerDetail = allDbJobTriggers
                                .Where(p => p.JobId == job.JobId && p.TriggerId == dic.Value.TriggerId)
                                .FirstOrDefault();
                            if (jobTriggerDetail != null)
                            {
                                if (jobTriggerDetail.IsDelete)
                                {
                                    dic.Value.Removed();
                                }
                                else
                                {
                                    dic.Value.LoadFrom(jobTriggerDetail);
                                    dic.Value.Updated();
                                }
                            }
                        }
                    }
                }
            }
            return allJobs;
        }
    }
}

Program.cs

using FurionJobStu02;
using SqlSugar;

var builder = WebApplication.CreateBuilder(args).Inject();

builder.Services.AddControllers().AddInject();
builder.Services.AddSchedule(options =>
{
    options.AddPersistence<DatabaseJobPersistence>(); // 添加作业持久化器
});

//创建数据库对象 (用法和EF Dappper一样通过new保证线程安全)
SqlSugarClient DbClient = new SqlSugarClient(new ConnectionConfig()
{
    ConnectionString = "SQLServer链接字符串",
    DbType = DbType.SqlServer,
    IsAutoCloseConnection = true,
    MoreSettings = new ConnMoreSettings()
    {
        //SQLServer转化为nvarchar
        SqlServerCodeFirstNvarchar = true,
    }
});

DbClient.CodeFirst.InitTables<JobModel>();
DbClient.CodeFirst.InitTables<JobTriggerModel>();
builder.Services.AddSingleton(DbClient);

var app = builder.Build();

// 还可以配置生产环境关闭
app.UseScheduleUI(options =>
{
    options.RequestPath = "/myjob";
    options.DisableOnProduction = false;
});

app.UseInject(string.Empty);
app.MapControllers();

app.Run();

无论做了什么操作都可以保存下来
在这里插入图片描述
目前还不支持动态的定时任务,下面将解决这个问题HomeController

using Furion.Schedule;
using Microsoft.AspNetCore.Mvc;

namespace FurionJobStu02
{
    [DynamicApiController]
    public class HomeController
    {
        private readonly ISchedulerFactory schedulerFactory;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="schedulerFactory"></param>
        public HomeController(ISchedulerFactory schedulerFactory)
        {
            this.schedulerFactory = schedulerFactory;

        }

        /// <summary>
        /// 添加定时任务
        /// </summary>
        [HttpGet]
        public long AddJob()
        {
            TimeSpan mTimeSpan = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0);
            //得到精确到秒的时间戳(长度10位)
            long time = (long)mTimeSpan.TotalSeconds;
            schedulerFactory.AddJob<MyJob01>($"{time}", Triggers.Secondly());
            return time;
        }
    }
}

如果通过接口新增定时任务再次重启是没有的
在这里插入图片描述
重启之后
在这里插入图片描述
修改Preload方法添加如下方法即可

//解决动态添加的作业和作业触发器显示
foreach (var item in allDbJobs)
{
    var isStaticJob = allJobs
        .Where(p => p.GetJobBuilder().JobId == item.JobId).Any();
    //如果是被删除或者静态定时任务(已经添加了),就不需要再添加了
    if (item.IsDelete || isStaticJob)
    {
        continue;
    }
    var newJobBuilder = JobBuilder.Create(item.AssemblyName, item.JobType).LoadFrom(item);
    //或者下面方法
    // var newJobBuilder = JobBuilder.Create(item.JobId).LoadFrom(item);
    // 强行设置为不扫描 IJob 实现类 [Trigger] 特性触发器,否则 SchedulerBuilder.Create 会再次扫描,导致重复添加同名触发器
    newJobBuilder.SetIncludeAnnotations(false);

    // 获取作业的所有数据库的触发器加入到作业中
    var dbTriggers = allDbJobTriggers.Where(u => u.JobId == newJobBuilder.JobId && !u.IsDelete).ToArray();
    var newTriggerBuilders = dbTriggers.Select(u => TriggerBuilder.Create(u.TriggerId).LoadFrom(u).Updated());
    var schedulerBuilder = SchedulerBuilder.Create(newJobBuilder, newTriggerBuilders.ToArray());
    schedulerBuilder.Updated();
    allJobs.Add(schedulerBuilder);
}

在这里插入图片描述
这样就实现了动态加载定时任务也能存储展示了

异常处理

正常情况下,程序员应该保证作业执行程序总是稳定运行,但有时候会出现一些不可避免的意外导致出现异常,如网络异常等,出现异常可以重试

//静态加载
services.AddSchedule(options =>
{
    options.AddJob<MyJob>(Triggers.PeriodSeconds(3)
                                  .SetNumRetries(3)); // 重试三次
});
//动态创建的时候
var trig = TriggerBuilder.Create(u.TriggerId).LoadFrom(u);
trig .NumRetries = 3;
trig.Updated();

更新

将插入和更新合并为一个方法

using Furion;
using Furion.Schedule;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using SqlSugar;
using System.Data;

namespace FurionJobStu02
{
    public class DatabaseJobPersistence : IJobPersistence
    {
        private readonly IServiceScopeFactory serviceScopeFactory;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="sqlSugarClient"></param>
        public DatabaseJobPersistence(IServiceScopeFactory serviceScopeFactory)
        {
            this.serviceScopeFactory = serviceScopeFactory;
        }

        /// <summary>
        /// 作业被改变了
        /// </summary>
        /// <param name="context"></param>
        public void OnChanged(PersistenceContext context)
        {
            using var scope = serviceScopeFactory.CreateScope();
            var sqlSugarClient = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
            switch (context.Behavior)
            {
                case PersistenceBehavior.Appended:
                case PersistenceBehavior.Updated:
                    var addOrUpdateModel = JsonConvert.DeserializeObject<JobModel>(context.ConvertToJSON());
                    if (addOrUpdateModel != null)
                    {
                        addOrUpdateModel.IsDelete = false;
                        sqlSugarClient.Storageable(addOrUpdateModel).ExecuteCommand();
                    }
                    break;
                case PersistenceBehavior.Removed:
                    var deleteModel = JsonConvert.DeserializeObject<JobModel>(context.ConvertToJSON());
                    if (deleteModel != null)
                    {
                        deleteModel.IsDelete = true;
                        sqlSugarClient.Updateable(deleteModel).ExecuteCommand();
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// 加载定时任务
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public SchedulerBuilder OnLoading(SchedulerBuilder builder)
        {
            return builder;
        }

        /// <summary>
        /// 作业触发器被改变了
        /// </summary>
        /// <param name="context"></param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void OnTriggerChanged(PersistenceTriggerContext context)
        {
            using var scope = serviceScopeFactory.CreateScope();
            var sqlSugarClient = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
            switch (context.Behavior)
            {
                case PersistenceBehavior.Appended:
                case PersistenceBehavior.Updated:
                    var addOrUpdateModel = JsonConvert.DeserializeObject<JobTriggerModel>(context.ConvertToJSON());
                    if (addOrUpdateModel != null)
                    {
                        addOrUpdateModel.IsDelete = false;
                        sqlSugarClient.Storageable(addOrUpdateModel).ExecuteCommand();
                    }
                    break;
                case PersistenceBehavior.Removed:
                    var deleteModel = JsonConvert.DeserializeObject<JobTriggerModel>(context.ConvertToJSON());
                    if (deleteModel != null)
                    {
                        deleteModel.IsDelete = true;
                        sqlSugarClient.Updateable(deleteModel).ExecuteCommand();
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// 加载之前的处理
        /// </summary>
        /// <returns></returns>
        public IEnumerable<SchedulerBuilder> Preload()
        {
            using var scope = serviceScopeFactory.CreateScope();
            var sqlSugarClient = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
            //查询数据库中所有的作业
            var allDbJobs = sqlSugarClient.Queryable<JobModel>().ToList();
            //查询数据库中所有的作业触发器
            var allDbJobTriggers = sqlSugarClient.Queryable<JobTriggerModel>().ToList();
            // 获取所有定义的作业
            var allJobs = App.EffectiveTypes.ScanToBuilders().ToList();
            foreach (var item in allJobs)
            {
                //不标记默认新增
                var job = item.GetJobBuilder();
                var jobDetail = allDbJobs.Where(p => p.JobId == job.JobId).FirstOrDefault();
                if (jobDetail != null)
                {
                    bool isDelete = false;
                    if (jobDetail.IsDelete)
                    {
                        isDelete = true;
                        item.Removed();
                    }
                    else
                    {
                        job.LoadFrom(jobDetail);
                        item.Updated();
                    }
                    var jobDetails = item.GetEnumerable();
                    foreach (var dic in jobDetails)
                    {
                        //如果作业都被删除了则下面的触发器肯定删除
                        if (isDelete)
                        {
                            dic.Value.Removed();
                        }
                        else
                        {
                            //查找对应的触发器
                            var jobTriggerDetail = allDbJobTriggers
                                .Where(p => p.JobId == job.JobId && p.TriggerId == dic.Value.TriggerId)
                                .FirstOrDefault();
                            if (jobTriggerDetail != null)
                            {
                                if (jobTriggerDetail.IsDelete)
                                {
                                    dic.Value.Removed();
                                }
                                else
                                {
                                    dic.Value.LoadFrom(jobTriggerDetail);
                                    dic.Value.Updated();
                                }
                            }
                        }
                    }
                }
            }
            //解决动态添加的作业和作业触发器显示
            foreach (var item in allDbJobs)
            {
                var isStaticJob = allJobs
                    .Where(p => p.GetJobBuilder().JobId == item.JobId).Any();
                //如果是被删除或者静态定时任务(已经添加了),就不需要再添加了
                if (item.IsDelete || isStaticJob)
                {
                    continue;
                }
                //var newJobBuilder = JobBuilder.Create(item.AssemblyName, item.JobType).LoadFrom(item);
                //或者下面方法
                var newJobBuilder = JobBuilder.Create(item.JobId).LoadFrom(item);
                // 强行设置为不扫描 IJob 实现类 [Trigger] 特性触发器,否则 SchedulerBuilder.Create 会再次扫描,导致重复添加同名触发器
                newJobBuilder.SetIncludeAnnotations(false);
                
                // 获取作业的所有数据库的触发器加入到作业中
                var dbTriggers = allDbJobTriggers.Where(u => u.JobId == newJobBuilder.JobId && !u.IsDelete).ToArray();
                var newTriggerBuilders = dbTriggers.Select(u => TriggerBuilder.Create(u.TriggerId).LoadFrom(u).Updated());
                var schedulerBuilder = SchedulerBuilder.Create(newJobBuilder, newTriggerBuilders.ToArray());
                schedulerBuilder.Updated();
                allJobs.Add(schedulerBuilder);
            }
            return allJobs;
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假装我不帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值