diff --git a/Src/Dlhis.ScheduledTask/Attributes/RecurringJobAttribute.cs b/Src/Dlhis.ScheduledTask/Attributes/RecurringJobAttribute.cs index cb97f98a..9db34804 100644 --- a/Src/Dlhis.ScheduledTask/Attributes/RecurringJobAttribute.cs +++ b/Src/Dlhis.ScheduledTask/Attributes/RecurringJobAttribute.cs @@ -1,18 +1,46 @@ namespace Dlhis.ScheduledTask.Attributes; +/// +/// 用于标记一个类为定时任务的特性。 +/// [AttributeUsage(AttributeTargets.Class)] public class RecurringJobAttribute : Attribute { - public RecurringJobAttribute(string uniqueJobKey, string cron) + /// + /// 初始化 类的新实例。 + /// + /// 任务的唯一标识符。 + /// Cron 表达式,定义任务的执行计划。 + /// 是否在开发环境中运行。 + public RecurringJobAttribute(string uniqueJobKey, string cron, bool runOnDevelopment = false) { UniqueJobKey = uniqueJobKey; Cron = cron; + RunOnDevelopment = runOnDevelopment; } + /// + /// 任务名称(可选)。 + /// public string Name { get; set; } + /// + /// 任务的唯一标识符。 + /// public string UniqueJobKey { get; } + + /// + /// Cron 表达式,定义任务的执行计划。 + /// public string Cron { get; } + /// + /// 是否在开发环境中运行。 + /// + public bool RunOnDevelopment { get; } + + /// + /// 任务所属队列,默认为 "default"。 + /// public string Queue { get; set; } = "default"; -} +} \ No newline at end of file diff --git a/Src/Dlhis.ScheduledTask/Packs/JobPack.cs b/Src/Dlhis.ScheduledTask/Packs/JobPack.cs index 8dfdd034..c95386dd 100644 --- a/Src/Dlhis.ScheduledTask/Packs/JobPack.cs +++ b/Src/Dlhis.ScheduledTask/Packs/JobPack.cs @@ -8,40 +8,71 @@ using Kurisu.AspNetCore.Utils.Extensions; namespace Dlhis.ScheduledTask.Packs; +/// +/// 表示一个任务包,用于初始化并使用 Hangfire 调度定时任务。 +/// public class JobPack : BaseAppPack { + /// + /// 获取任务包的执行顺序。此值决定任务包的执行顺序。 + /// public override int Order => 999; + /// + /// 调用任务包以发现并调度定时任务。 + /// + /// 用于解析依赖项的服务提供器。 public override void Invoke(IServiceProvider serviceProvider) { + // 获取日志记录器和主机环境服务 var logger = serviceProvider.GetService>(); + var env = serviceProvider.GetService(); - var jobs = Assembly.GetEntryAssembly().GetTypes() + // 发现所有继承自 BaseJob 且具有 RecurringJobAttribute 的类型 + var jobs = Assembly.GetEntryAssembly()!.GetTypes() .Where(x => x.IsAssignableTo(typeof(BaseJob)) && x != typeof(BaseJob)) .Where(x => x.IsDefined(typeof(RecurringJobAttribute))) + // 如果当前环境为开发环境,仅筛选出 RunOnDevelopment 为 true 的任务类型 + .WhereIf(env.IsDevelopment(), x => x.GetCustomAttribute()!.RunOnDevelopment) .ToList(); + // 如果未发现任何任务,则直接返回 + if (jobs.IsEmpty()) + { + return; + } + + // 使用反射获取 Job.FromExpression 私有方法 var setJob = typeof(Job).GetMethod(nameof(Job.FromExpression), BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(LambdaExpression), typeof(Type), typeof(string) }); - var m = (Lazy)typeof(RecurringJob).GetField("Instance", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); + // 获取 RecurringJobManager 的单例实例 + var m = (Lazy)typeof(RecurringJob).GetField("Instance", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!; - (jobs.Select(job => job.GetCustomAttribute().UniqueJobKey).Distinct().Count() != jobs.Count).ThrowIfTrue("任务key重复"); + // 确保所有任务的唯一键不重复 + (jobs.Select(job => job.GetCustomAttribute()!.UniqueJobKey).Distinct().Count() != jobs.Count).ThrowIfTrue("任务key重复"); + // 记录发现的任务数量 logger.LogDebug("一共获取{count}个调度任务", jobs.Count); + // 遍历每个发现的任务并进行调度 foreach (var job in jobs) { + // 获取任务的 RecurringJobAttribute var recurringSetting = job.GetCustomAttribute()!; + // 确保 Cron 表达式已定义 recurringSetting.Cron.IsEmpty().ThrowIfTrue(job.Name + "尚未定义Cron"); + // 创建调用任务 TodoAsync 方法的表达式 var parameterExpression = Expression.Parameter(job); - var todo = job.GetMethod(nameof(BaseJob.TodoAsync)); + var todo = job.GetMethod(nameof(BaseJob.TodoAsync))!; var todoExp = Expression.Call(parameterExpression, todo); - var nJob = (Job)setJob.Invoke(null, new object[] { Expression.Lambda(todoExp), job, recurringSetting.Queue }); + // 使用私有方法 Job.FromExpression 创建一个新的 Hangfire 任务 + var nJob = (Job)setJob!.Invoke(null, new object[] { Expression.Lambda(todoExp), job, recurringSetting.Queue }); + // 在 RecurringJobManager 中添加或更新定时任务 m.Value.AddOrUpdate(recurringSetting.UniqueJobKey, nJob, recurringSetting.Cron, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); } } -} +} \ No newline at end of file