using Mapster; using Microsoft.Extensions.DependencyInjection; using Quartz; using System.Reflection; using Wood.Data.Repository; using Wood.Entity; using Wood.Entity.SystemManage; using Wood.EventBus; using Wood.Util; namespace Wood.AutoJob { public class AutoJobCenter { #region 扫描任务计划 public async void ScanJob() { using var scope = GlobalContext.ServiceProvider!.CreateScope(); var jobTriggerRepository = scope.ServiceProvider.GetRequiredService>(); var jobDetailRepository = scope.ServiceProvider.GetRequiredService>(); var eventBus = scope.ServiceProvider.GetRequiredService(); var types = Assembly.GetAssembly(typeof(AutoJobCenter))!.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.GetCustomAttributes(typeof(JobDetailAttribute), false).Any()) .ToList(); foreach (var item in types) { string jobId = ""; var jobDetailAttr = item.GetCustomAttribute(); if (jobDetailAttr != null) { var detail = jobDetailAttr.Adapt(); detail.JobType = item.FullName; detail.AssemblyName = item.Assembly.FullName; jobId = detail.JobId!; var dbDetail = await jobDetailRepository.GetFirstAsync(it => it.JobId == detail.JobId); if (!jobDetailRepository.IsAny(it => it.JobId == detail.JobId)) jobDetailRepository.Insert(detail); else { if (dbDetail.Description != detail.Description || dbDetail.GroupName != detail.GroupName) { dbDetail.GroupName = detail.GroupName; dbDetail.Description = detail.Description; await jobDetailRepository.UpdateAsync(dbDetail); } } } else continue; var secondsAtAttr = item.GetCustomAttribute(); JobTriggerEntity? entity = null; if (secondsAtAttr != null && entity == null) { entity = secondsAtAttr.Adapt(); entity.TriggerType = TriggerTypeEnum.PeriodSeconds; } var minutesAttr = item.GetCustomAttribute(); if (minutesAttr != null && entity == null) { entity = minutesAttr.Adapt(); entity.TriggerType = TriggerTypeEnum.PeriodMinutes; } var dailyAtAttr = item.GetCustomAttribute(); if (dailyAtAttr != null && entity == null) { entity = dailyAtAttr.Adapt(); entity.TriggerType = TriggerTypeEnum.DailyAt; } var cronAttr = item.GetCustomAttribute(); if (cronAttr != null && entity == null) { entity = cronAttr.Adapt(); entity.TriggerType = TriggerTypeEnum.Cron; } if (entity != null) { entity.JobId = jobId; entity.Status = TriggerStatusEnum.Ready; var trigger = jobTriggerRepository.GetFirst(it => it.TriggerId == entity.TriggerId); if (trigger == null) jobTriggerRepository.Insert(entity); else { //只有触发器类型 或者 参数 或者 说明 变更时需要更新触发器数据 if (trigger.TriggerType != entity.TriggerType || trigger.Args != entity.Args || trigger.Description != entity.Description) { entity.Id = trigger.Id; entity.CreateTime = trigger.CreateTime; entity.CreateUserId = trigger.CreateUserId; jobTriggerRepository.Update(entity.Adapt(trigger)); } } } } } #endregion #region 添加任务计划 public async Task AddScheduleJob() { using var scope = GlobalContext.ServiceProvider!.CreateScope(); var schedulerFactory = scope.ServiceProvider.GetRequiredService(); var jobTriggerRepository = scope.ServiceProvider.GetRequiredService>(); var jobDetailRepository = scope.ServiceProvider.GetRequiredService>(); var jobDetailList = jobDetailRepository.AsQueryable().ClearFilter().ToList(); var scheduler = await schedulerFactory.GetScheduler(); foreach (var detail in jobDetailList) { var jobTrigger = await jobTriggerRepository.GetFirstAsync(it => it.JobId == detail.JobId); var jobType = this.GetType().Assembly.GetType(detail.JobType!); if (jobType != null) { IJobDetail job = JobBuilder.Create(jobType!).WithIdentity(detail.JobId!, detail.GroupName!).Build(); job.JobDataMap.Add("DetailId", detail.Id); job.JobDataMap.Add("TriggerId", jobTrigger.Id); var trigger = CreateTrigger(jobTrigger, detail); //更新下次执行时间 jobTrigger.NextRunTime = trigger.GetNextFireTimeUtc()?.DateTime.AddHours(8); jobTrigger.Status = TriggerStatusEnum.Ready; await jobTriggerRepository.UpdateAsync(jobTrigger); await scheduler.ScheduleJob(job, CreateTrigger(jobTrigger, detail)); } else { jobTrigger.Status = TriggerStatusEnum.Ineffective; await jobTriggerRepository.UpdateAsync(jobTrigger); } } await scheduler.Start(); } /// /// 创建trigger /// /// /// /// public static ITrigger CreateTrigger(JobTriggerEntity jobTrigger, JobDetailEntity jobDetail) { /* * Cron withMisfireHandlingInstructionFireAndProceed [MISFIRE_INSTRUCTION_FIRE_ONCE_NOW](默认) ——以当前时间为触发频率立刻触发一次执行 ——然后按照Cron频率依次执行 withMisfireHandlingInstructionDoNothing [MISFIRE_INSTRUCTION_DO_NOTHING ] ——不触发立即执行 ——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行 withMisfireHandlingInstructionIgnoreMisfires [MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY] ——以错过的第一个频率时间立刻开始执行 ——重做错过的所有频率周期后 ——当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行 即: 忽略所有的超时状态,按照触发器的策略执行。 WithSimpleSchedule withMisfireHandlingInstructionNowWithExistingCount(默认) ——以当前时间为触发频率立即触发执行 ——执行至FinalTIme的剩余周期次数 ——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到 ——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值 withMisfireHandlingInstructionFireNow ——以当前时间为触发频率立即触发执行 ——执行至FinalTIme的剩余周期次数 ——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到 ——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值 withMisfireHandlingInstructionIgnoreMisfires ——以错过的第一个频率时间立刻开始执行 ——重做错过的所有频率周期 ——当下一次触发频率发生时间大于当前时间以后,按照Interval的依次执行剩下的频率 ——共执行RepeatCount+1次 withMisfireHandlingInstructionNextWithExistingCount ——不触发立即执行 ——等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数 ——以startTime为基准计算周期频率,并得到FinalTime ——即使中间出现pause,resume以后保持FinalTime时间不变 withMisfireHandlingInstructionNextWithRemainingCount ——不触发立即执行 ——等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数 ——以startTime为基准计算周期频率,并得到FinalTime ——即使中间出现pause,resume以后保持FinalTime时间不变 withMisfireHandlingInstructionNowWithRemainingCount ——以当前时间为触发频率立即触发执行 ——执行至FinalTIme的剩余周期次数 ——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到 ——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT ——此指令导致trigger忘记原始设置的starttime和repeat-count ——触发器的repeat-count将被设置为剩余的次数 ——这样会导致后面无法获得原始设定的starttime和repeat-count值 */ if (jobTrigger.StartTime == null) jobTrigger.StartTime = DateTime.Now; DateTimeOffset starRunTime = DateBuilder.NextGivenSecondDate(jobTrigger.StartTime, 1); TriggerBuilder triggerBuilder = TriggerBuilder.Create() .WithIdentity(jobTrigger.TriggerId!, jobDetail.GroupName!); if (jobTrigger.TriggerType == Entity.TriggerTypeEnum.DailyAt) { string[] times = jobTrigger.Args!.Split(":"); if (times.Length == 1) { if (!jobTrigger.RunOnStart) triggerBuilder = triggerBuilder.StartAt(starRunTime).WithCronSchedule($"0 0 {times[0]} * * ?", x => x.WithMisfireHandlingInstructionDoNothing()); else triggerBuilder = triggerBuilder.StartAt(starRunTime).WithCronSchedule($"0 0 {times[0]} * * ?", x => x.WithMisfireHandlingInstructionFireAndProceed()); } else { if (!jobTrigger.RunOnStart) triggerBuilder = triggerBuilder.StartAt(starRunTime).WithCronSchedule($"0 {times[1]} {times[0]} * * ?", x => x.WithMisfireHandlingInstructionDoNothing()); else triggerBuilder = triggerBuilder.StartAt(starRunTime).WithCronSchedule($"0 {times[1]} {times[0]} * * ?", x => x.WithMisfireHandlingInstructionFireAndProceed()); } } else if (jobTrigger.TriggerType == Entity.TriggerTypeEnum.PeriodMinutes) { int interval = jobTrigger.Args!.ToInt(); if (!jobTrigger.RunOnStart) starRunTime = DateBuilder.NextGivenSecondDate(DateTime.Now.AddMinutes(interval), 1); triggerBuilder = triggerBuilder.StartAt(starRunTime) .WithSimpleSchedule(x => x .WithIntervalInMinutes(interval) // 每xm分钟重复一次 .RepeatForever()); // 无限重复; } else if (jobTrigger.TriggerType == Entity.TriggerTypeEnum.PeriodSeconds) { int interval = jobTrigger.Args!.ToInt(); if (!jobTrigger.RunOnStart) starRunTime = DateBuilder.NextGivenSecondDate(DateTime.Now.AddSeconds(interval), 1); triggerBuilder = triggerBuilder.WithSimpleSchedule(x => x .WithIntervalInSeconds(interval) // 每x秒重复一次 .RepeatForever()); // 无限重复; } else if (jobTrigger.TriggerType == Entity.TriggerTypeEnum.Cron) { if (!jobTrigger.RunOnStart) triggerBuilder = triggerBuilder.StartAt(starRunTime).WithCronSchedule(jobTrigger.Args!, x => x.WithMisfireHandlingInstructionDoNothing()); else triggerBuilder = triggerBuilder.StartAt(starRunTime).WithCronSchedule(jobTrigger.Args!, x => x.WithMisfireHandlingInstructionFireAndProceed()); } else throw Oops.Oh("AutoJob不支持的触发器类型!"); return triggerBuilder!.Build(); } #endregion } }