开发环境
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.Pure
和SqlSugarCore
新建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;
}
}
}