You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
13 KiB
279 lines
13 KiB
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<SqlSugarRepository<JobTriggerEntity>>();
|
|
var jobDetailRepository = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<JobDetailEntity>>();
|
|
var eventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();
|
|
|
|
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<JobDetailAttribute>();
|
|
if (jobDetailAttr != null)
|
|
{
|
|
var detail = jobDetailAttr.Adapt<JobDetailEntity>();
|
|
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<PeriodSecondsAttribute>();
|
|
JobTriggerEntity? entity = null;
|
|
if (secondsAtAttr != null && entity == null)
|
|
{
|
|
entity = secondsAtAttr.Adapt<JobTriggerEntity>();
|
|
entity.TriggerType = TriggerTypeEnum.PeriodSeconds;
|
|
}
|
|
var minutesAttr = item.GetCustomAttribute<PeriodMinutesAttribute>();
|
|
if (minutesAttr != null && entity == null)
|
|
{
|
|
entity = minutesAttr.Adapt<JobTriggerEntity>();
|
|
entity.TriggerType = TriggerTypeEnum.PeriodMinutes;
|
|
}
|
|
var dailyAtAttr = item.GetCustomAttribute<DailyAtAttribute>();
|
|
if (dailyAtAttr != null && entity == null)
|
|
{
|
|
entity = dailyAtAttr.Adapt<JobTriggerEntity>();
|
|
entity.TriggerType = TriggerTypeEnum.DailyAt;
|
|
}
|
|
var cronAttr = item.GetCustomAttribute<CronAttribute>();
|
|
if (cronAttr != null && entity == null)
|
|
{
|
|
entity = cronAttr.Adapt<JobTriggerEntity>();
|
|
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<ISchedulerFactory>();
|
|
var jobTriggerRepository = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<JobTriggerEntity>>();
|
|
var jobDetailRepository = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<JobDetailEntity>>();
|
|
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建trigger
|
|
/// </summary>
|
|
/// <param name="jobTrigger"></param>
|
|
/// <param name="jobDetail"></param>
|
|
/// <returns></returns>
|
|
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
|
|
}
|
|
}
|
|
|