| | |
| | | |
| | | namespace VueWebCoreApi.Extensions |
| | | { |
| | | /// <summary> |
| | | /// Quartz.NET 扩展方法类 |
| | | /// 封装Quartz任务的初始化、增删改查、启停、立即执行等核心操作 |
| | | /// 解决了Scheduler启动、内存与数据库状态同步、Cron表达式兼容、Key匹配等核心问题 |
| | | /// </summary> |
| | | public static class QuartzNETExtension |
| | | { |
| | | public static ToMessage mes = new ToMessage(); //定义全局返回信息对象 |
| | | /// <summary> |
| | | /// 全局返回信息对象(统一接口返回格式) |
| | | /// 包含code(状态码)、count(数量)、message(提示信息)、data(数据) |
| | | /// </summary> |
| | | public static ToMessage mes = new ToMessage(); |
| | | /// <summary> |
| | | /// 内存级任务列表缓存 |
| | | /// 用于减少数据库查询,同时保证内存与数据库状态一致 |
| | | /// </summary> |
| | | private static List<TaskOptions> _taskList = new List<TaskOptions>(); |
| | | |
| | | /// <summary> |
| | | /// 初始化作业 |
| | | /// 扩展IApplicationBuilder,初始化Quartz任务调度器并加载所有任务 |
| | | /// 【核心修复】解决Quartz默认不启动Scheduler的问题 |
| | | /// </summary> |
| | | /// <param name="applicationBuilder"></param> |
| | | /// <param name="env"></param> |
| | | /// <returns></returns> |
| | | public static IApplicationBuilder UseQuartz(this IApplicationBuilder applicationBuilder, IWebHostEnvironment env) |
| | | /// <param name="app">ASP.NET Core应用构建器</param> |
| | | /// <param name="env">Web主机环境</param> |
| | | /// <returns>应用构建器(链式调用)</returns> |
| | | public static IApplicationBuilder UseQuartz(this IApplicationBuilder app, IWebHostEnvironment env) |
| | | { |
| | | IServiceProvider services = applicationBuilder.ApplicationServices; |
| | | // 从DI容器获取依赖服务 |
| | | var services = app.ApplicationServices; |
| | | // Quartz调度器工厂 |
| | | var schedulerFactory = services.GetService<ISchedulerFactory>(); |
| | | // 自定义Quartz仓储(数据库操作) |
| | | var quartzRepo = services.GetService<QuartzRepository>(); |
| | | |
| | | ISchedulerFactory _schedulerFactory = services.GetService<ISchedulerFactory>(); |
| | | |
| | | string path = FileQuartz.CreateQuartzRootPath(env); |
| | | //查找任务列表 |
| | | var data = QuartzSearchData.QuartzSearch(); |
| | | if (data.Rows.Count <= 0) |
| | | // 1. 核心修复:Quartz默认不会自动启动Scheduler,必须显式启动 |
| | | var scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult(); |
| | | if (!scheduler.IsStarted) |
| | | { |
| | | FileHelper.WriteFile(FileQuartz.LogPath, "start.txt", $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},没有默认配置任务\r\n", true); |
| | | return applicationBuilder; |
| | | scheduler.Start().GetAwaiter().GetResult(); |
| | | } |
| | | string jobConfig = JsonConvert.SerializeObject(data); |
| | | |
| | | //string jobConfig = FileHelper.ReadFile(path + QuartzFileInfo.JobConfigFileName); |
| | | //if (string.IsNullOrEmpty(jobConfig)) |
| | | //{ |
| | | // FileHelper.WriteFile(FileQuartz.LogPath, "start.txt", $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},没有默认配置任务\r\n", true); |
| | | // return applicationBuilder; |
| | | //} |
| | | |
| | | // 2. 从数据库加载所有配置的任务 |
| | | var taskList = quartzRepo.GetAllTasksAsync().GetAwaiter().GetResult(); |
| | | if (taskList.Count == 0) |
| | | { |
| | | //无任务时记录启动日志 |
| | | quartzRepo.WriteStartLogAsync($"{DateTime.Now:yyyy-MM-dd HH:mm:ss},没有默认配置任务").GetAwaiter().GetResult(); |
| | | return app; |
| | | } |
| | | // 3. 遍历任务并初始化(记录失败数和异常信息) |
| | | int errorCount = 0; |
| | | string errorMsg = ""; |
| | | TaskOptions options = null; |
| | | try |
| | | string errorMsg = string.Empty; |
| | | // 初始化内存任务列表 |
| | | _taskList = taskList; |
| | | |
| | | foreach (var task in taskList) |
| | | { |
| | | _taskList = JsonConvert.DeserializeObject<List<TaskOptions>>(jobConfig); |
| | | _taskList.ForEach(x => |
| | | try |
| | | { |
| | | options = x; |
| | | var result = x.AddJob(_schedulerFactory, true, jobFactory: services.GetService<IJobFactory>()).GetAwaiter().GetResult(); |
| | | }); |
| | | //var result = task.AddJob(schedulerFactory, true, services.GetService<IJobFactory>()).GetAwaiter().GetResult(); |
| | | // 调用TaskOptions的AddJob方法添加任务到调度器 |
| | | // 传入仓储用于数据库操作,传入JobFactory用于自定义Job实例化 |
| | | var result = task.AddJob(schedulerFactory, true, services.GetService<IJobFactory>(), quartzRepo).GetAwaiter().GetResult(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | // 记录单个任务初始化失败信息 |
| | | errorCount++; |
| | | errorMsg += $"作业:{task.TaskName},异常:{ex.Message};"; |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | errorCount = +1; |
| | | errorMsg += $"作业:{options?.TaskName},异常:{ex.Message}"; |
| | | } |
| | | string content = $"成功:{ _taskList.Count - errorCount}个,失败{errorCount}个,异常:{errorMsg}\r\n"; |
| | | FileQuartz.WriteStartLog(content); |
| | | // 4. 记录整体初始化结果日志 |
| | | var content = $"成功:{taskList.Count - errorCount}个,失败{errorCount}个,异常:{errorMsg}"; |
| | | quartzRepo.WriteStartLogAsync($"{DateTime.Now:yyyy-MM-dd HH:mm:ss},{content}").GetAwaiter().GetResult(); |
| | | |
| | | |
| | | return applicationBuilder; |
| | | return app; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取所有的作业 |
| | | /// 获取调度器中所有任务,并同步内存/数据库状态(核心修复:解决内存列表与数据库不一致问题) |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <returns></returns> |
| | | public static async Task<List<TaskOptions>> GetJobs(this ISchedulerFactory schedulerFactory) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>最新的任务列表</returns> |
| | | public static async Task<List<TaskOptions>> GetJobs(this ISchedulerFactory schedulerFactory, QuartzRepository quartzRepo) |
| | | { |
| | | List<TaskOptions> list = new List<TaskOptions>(); |
| | | var list = new List<TaskOptions>(); |
| | | try |
| | | { |
| | | IScheduler _scheduler = await schedulerFactory.GetScheduler(); |
| | | var groups = await _scheduler.GetJobGroupNames(); |
| | | var scheduler = await schedulerFactory.GetScheduler(); |
| | | // 确保Scheduler处于启动状态(防御性编程) |
| | | if (!scheduler.IsStarted) await scheduler.Start(); |
| | | // 1. 获取所有任务分组(Quartz任务按分组管理) |
| | | var groups = await scheduler.GetJobGroupNames(); |
| | | |
| | | foreach (var groupName in groups) |
| | | { |
| | | foreach (var jobKey in await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName))) |
| | | // 2. 获取分组下所有JobKey(任务唯一标识:名称+分组) |
| | | var jobKeys = await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)); |
| | | foreach (var jobKey in jobKeys) |
| | | { |
| | | TaskOptions taskOptions = _taskList.Where(x => x.GroupName == jobKey.Group && x.TaskName == jobKey.Name) |
| | | .FirstOrDefault(); |
| | | if (taskOptions == null) |
| | | continue; |
| | | |
| | | var triggers = await _scheduler.GetTriggersOfJob(jobKey); |
| | | foreach (ITrigger trigger in triggers) |
| | | // 3. 核心修复:优先从数据库获取最新任务(避免内存数据过期) |
| | | var dbTask = await quartzRepo.TaskExists(jobKey.Name, jobKey.Group); |
| | | // 数据库无数据时,降级从内存列表获取 |
| | | var task = dbTask.FirstOrDefault() ?? _taskList.FirstOrDefault(x => x.GroupName == jobKey.Group && x.TaskName == jobKey.Name); |
| | | // 过滤无效任务 |
| | | if (task == null) continue; |
| | | // 4. 获取任务关联的触发器(Quartz任务通过触发器触发执行) |
| | | var triggers = await scheduler.GetTriggersOfJob(jobKey); |
| | | foreach (var trigger in triggers) |
| | | { |
| | | DateTimeOffset? dateTimeOffset = trigger.GetPreviousFireTimeUtc(); |
| | | if (dateTimeOffset != null) |
| | | // 5. 同步触发器实际状态到任务对象,并更新到数据库 |
| | | task.Status = (int)await scheduler.GetTriggerState(trigger.Key); |
| | | await quartzRepo.UpdateTaskAsync(task); // 状态同步到库 |
| | | // 6. 同步任务最后执行时间 |
| | | if (trigger.GetPreviousFireTimeUtc() != null) |
| | | { |
| | | taskOptions.LastRunTime = Convert.ToDateTime(dateTimeOffset.ToString()); |
| | | //更改最后执行时间 |
| | | bool aa = QuartzSearchData.QuartzUpdate(taskOptions); |
| | | // 优先从触发器获取最后执行时间(UTC转本地时间) |
| | | task.LastRunTime = trigger.GetPreviousFireTimeUtc()?.LocalDateTime; |
| | | await quartzRepo.UpdateTaskLastRunTimeAsync(task.TaskName, task.GroupName, task.LastRunTime.Value); |
| | | } |
| | | else |
| | | { |
| | | var runlog = FileQuartz.GetJobRunLog(taskOptions.TaskName, taskOptions.GroupName, 1, 2); |
| | | if (runlog.Count > 0) |
| | | // 触发器无记录时,从执行日志中获取最后执行时间 |
| | | var logs = await quartzRepo.GetJobRunLogAsync(task.TaskName, task.GroupName, 1, 1); |
| | | if (logs.Count > 0 && DateTime.TryParse(logs[0].BeginDate, out var lastRunTime)) |
| | | { |
| | | DateTime.TryParse(runlog[0].BeginDate, out DateTime lastRunTime); |
| | | taskOptions.LastRunTime = lastRunTime; |
| | | //更改最后执行时间 |
| | | bool aa = QuartzSearchData.QuartzUpdate(taskOptions); |
| | | task.LastRunTime = lastRunTime; |
| | | await quartzRepo.UpdateTaskLastRunTimeAsync(task.TaskName, task.GroupName, lastRunTime); |
| | | } |
| | | } |
| | | } |
| | | list.Add(taskOptions); |
| | | list.Add(task); |
| | | } |
| | | } |
| | | // 7. 同步内存列表为最新数据(保证后续操作使用最新状态) |
| | | _taskList = list; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | |
| | | mes.count = 0; |
| | | mes.message = ex.Message + ex.StackTrace; |
| | | mes.data = null; |
| | | FileQuartz.WriteStartLog("获取作业异常:" + ex.Message + ex.StackTrace); |
| | | await quartzRepo.WriteStartLogAsync($"获取作业异常:{ex.Message}{ex.StackTrace}"); |
| | | return new List<TaskOptions>(); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加作业 |
| | | /// 新增Quartz任务(初始化/手动添加通用) |
| | | /// </summary> |
| | | /// <param name="taskOptions"></param> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="init">是否初始化,否=需要重新生成配置文件,是=不重新生成配置文件</param> |
| | | /// <returns></returns> |
| | | public static async Task<object> AddJob(this TaskOptions taskOptions, ISchedulerFactory schedulerFactory, bool init = false, IJobFactory jobFactory = null) |
| | | /// <param name="task">任务配置项</param> |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="init">是否为初始化阶段(初始化时不重复入库)</param> |
| | | /// <param name="jobFactory">Job工厂(自定义Job实例化逻辑)</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>统一返回结果(ToMessage)</returns> |
| | | public static async Task<object> AddJob(this TaskOptions task, ISchedulerFactory schedulerFactory, bool init = false, IJobFactory jobFactory = null, QuartzRepository quartzRepo = null) |
| | | { |
| | | try |
| | | { |
| | | //处理前端Corn表达式最后一个月的星期天表达式缺陷问题 |
| | | string resultString = ""; |
| | | string[] str = taskOptions.Interval.Split(" "); |
| | | if ((str[3].Equals("1L") || str[3].Equals("2L") || str[3].Equals("3L") || str[3].Equals("4L") || str[3].Equals("5L") || str[3].Equals("6L") || str[3].Equals("7L")) && str[5].Equals("?")) |
| | | // 1. 修复前端传入的Cron表达式兼容性问题 |
| | | task.Interval = FixCronExpression(task.Interval); |
| | | // 2. 验证Cron表达式有效性 |
| | | var (isValid, msg) = task.Interval.IsValidExpression(); |
| | | if (!isValid) |
| | | return new ToMessage { code = "300", count = 0, message = msg, data = null }; |
| | | // 3. 非初始化阶段:校验任务是否已存在(避免重复添加) |
| | | if (!init && await quartzRepo.TaskExistsAsync(task.TaskName, task.GroupName)) |
| | | { |
| | | str[5] = str[3]; |
| | | str[3] = "?"; |
| | | for (int i = 0; i < str.Length; i++) |
| | | { |
| | | resultString += str[i] + " "; |
| | | } |
| | | taskOptions.Interval = resultString; |
| | | return new ToMessage { code = "300", count = 0, message = $"作业:{task.TaskName},分组:{task.GroupName}已经存在", data = null }; |
| | | } |
| | | (bool, string) validExpression = taskOptions.Interval.IsValidExpression(); |
| | | if (!validExpression.Item1) |
| | | return new { code = 300,count=0,Message=validExpression.Item2,data="null" }; |
| | | |
| | | (bool, object) result = taskOptions.Exists(init); |
| | | if (!result.Item1) |
| | | return result.Item2; |
| | | // 4. 非初始化阶段:新增任务到内存+数据库+记录操作日志 |
| | | if (!init) |
| | | { |
| | | //将历史任务及新增加任务天加到List集合 |
| | | _taskList.Add(taskOptions); |
| | | //将集合数据添加到json文件(覆盖式) |
| | | FileQuartz.WriteJobConfig(_taskList); |
| | | //将当前新任务添加到数据表 |
| | | bool aa = QuartzSearchData.QuartzCreate(taskOptions); |
| | | _taskList.Add(task);// 内存添加 |
| | | await quartzRepo.CreateTaskAsync(task);// 数据库添加 |
| | | await quartzRepo.AddJobActionLogAsync(JobAction.新增.ToString(), task.TaskName, task.GroupName, "新增任务成功");// 操作日志 |
| | | } |
| | | // 5. 构建Quartz Job(任务执行体) |
| | | var job = JobBuilder.Create<HttpResultfulJob>()// HttpResultfulJob:自定义的HTTP请求型Job |
| | | .WithIdentity(task.TaskName, task.GroupName)// 设置Job唯一标识(名称+分组) |
| | | .Build(); |
| | | |
| | | IJobDetail job = JobBuilder.Create<HttpResultfulJob>().WithIdentity(taskOptions.TaskName, taskOptions.GroupName).Build(); |
| | | ITrigger trigger = TriggerBuilder.Create() |
| | | .WithIdentity(taskOptions.TaskName, taskOptions.GroupName) |
| | | .StartNow() |
| | | .WithDescription(taskOptions.Describe) |
| | | .WithCronSchedule(taskOptions.Interval) |
| | | .Build(); |
| | | // 6. 构建Cron触发器(按Cron表达式触发) |
| | | var trigger = TriggerBuilder.Create() |
| | | .WithIdentity(task.TaskName, task.GroupName)// 触发器唯一标识(与Job保持一致) |
| | | .StartNow()// 立即启动 |
| | | .WithDescription(task.Describe)// 任务描述 |
| | | .WithCronSchedule(task.Interval)// 绑定Cron表达式 |
| | | .Build(); |
| | | |
| | | IScheduler scheduler = await schedulerFactory.GetScheduler(); |
| | | |
| | | if (jobFactory == null) |
| | | { |
| | | try |
| | | { |
| | | jobFactory = HttpContext.Current.RequestServices.GetService<IJobFactory>(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Console.WriteLine($"创建任务[{taskOptions.TaskName}]异常,{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | // 7. 获取调度器并确保启动 |
| | | var scheduler = await schedulerFactory.GetScheduler(); |
| | | if (!scheduler.IsStarted) await scheduler.Start(); // 确保启动 |
| | | // 8. 设置自定义JobFactory(如需控制Job的依赖注入/实例化) |
| | | if (jobFactory != null) |
| | | { |
| | | scheduler.JobFactory = jobFactory; |
| | | } |
| | | |
| | | // 9. 将Job和触发器绑定到调度器 |
| | | await scheduler.ScheduleJob(job, trigger); |
| | | if (taskOptions.Status == (int)TriggerState.Normal) |
| | | |
| | | // 10. 根据任务状态设置触发器启停 |
| | | if (task.Status == (int)TriggerState.Normal) |
| | | { |
| | | await scheduler.Start(); |
| | | await scheduler.ResumeTrigger(trigger.Key);// 显式恢复(确保触发器正常运行) |
| | | } |
| | | else |
| | | { |
| | | await scheduler.PauseJob(job.Key); |
| | | // await schedulerFactory.Pause(taskOptions); |
| | | FileQuartz.WriteStartLog($"作业:{taskOptions.TaskName},分组:{taskOptions.GroupName},新建时未启动原因,状态为:{taskOptions.Status}"); |
| | | await scheduler.PauseJob(job.Key);// 暂停任务 |
| | | await quartzRepo.WriteStartLogAsync($"作业:{task.TaskName},分组:{task.GroupName},新建时未启动原因,状态为:{task.Status}"); |
| | | } |
| | | if (!init) |
| | | FileQuartz.WriteJobAction(JobAction.新增, taskOptions.TaskName, taskOptions.GroupName); |
| | | mes.code = "200"; |
| | | mes.count = 0; |
| | | mes.message ="执行成功!"; |
| | | mes.message = "执行成功!"; |
| | | mes.data = null; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | mes.code = "300"; |
| | | mes.count = 0; |
| | | mes.message = ex.Message; |
| | | mes.data = null; |
| | | return new { status = false, msg = ex.Message }; |
| | | return new ToMessage { code = "300", count = 0, message = ex.Message, data = null }; |
| | | } |
| | | return mes; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 移除作业 |
| | | /// 移除(删除)任务 |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="taskName"></param> |
| | | /// <param name="groupName"></param> |
| | | /// <returns></returns> |
| | | public static Task<object> Remove(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="task">任务配置</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>操作结果</returns> |
| | | public static Task<object> Remove(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo) |
| | | { |
| | | return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.删除, taskOptions); |
| | | return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.删除, task, quartzRepo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 更新作业 |
| | | /// 修改任务配置 |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="taskOptions"></param> |
| | | /// <returns></returns> |
| | | public static Task<object> Update(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="task">新的任务配置</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>操作结果</returns> |
| | | public static Task<object> Update(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo) |
| | | { |
| | | return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.修改, taskOptions); |
| | | return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.修改, task, quartzRepo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 暂停作业 |
| | | /// 暂停任务 |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="taskOptions"></param> |
| | | /// <returns></returns> |
| | | public static Task<object> Pause(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="task">任务配置</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>操作结果</returns> |
| | | public static Task<object> Pause(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo) |
| | | { |
| | | return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.暂停, taskOptions); |
| | | return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.暂停, task, quartzRepo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 启动作业 |
| | | /// 启动(恢复)任务 |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="taskOptions"></param> |
| | | /// <returns></returns> |
| | | public static Task<object> Start(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="task">任务配置</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>操作结果</returns> |
| | | public static Task<object> Start(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo) |
| | | { |
| | | return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.开启, taskOptions); |
| | | return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.开启, task, quartzRepo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 立即执行一次作业 |
| | | /// 立即执行任务(无视Cron表达式) |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="taskOptions"></param> |
| | | /// <returns></returns> |
| | | public static Task<object> Run(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="task">任务配置</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>操作结果</returns> |
| | | public static Task<object> Run(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo) |
| | | { |
| | | return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.立即执行, taskOptions); |
| | | } |
| | | |
| | | public static object ModifyTaskEntity(this TaskOptions taskOptions, ISchedulerFactory schedulerFactory, JobAction action) |
| | | { |
| | | TaskOptions options = null; |
| | | object result = null; |
| | | switch (action) |
| | | { |
| | | case JobAction.删除: |
| | | for (int i = 0; i < _taskList.Count; i++) |
| | | { |
| | | options = _taskList[i]; |
| | | if (options.TaskName == taskOptions.TaskName && options.GroupName == taskOptions.GroupName) |
| | | { |
| | | _taskList.RemoveAt(i); |
| | | //删除任务 |
| | | bool aa = QuartzSearchData.QuartzDelete(options); |
| | | } |
| | | } |
| | | break; |
| | | case JobAction.修改: |
| | | options = _taskList.Where(x => x.TaskName == taskOptions.TaskName && x.GroupName == taskOptions.GroupName).FirstOrDefault(); |
| | | //移除以前的配置 |
| | | if (options != null) |
| | | { |
| | | _taskList.Remove(options); |
| | | //删除任务 |
| | | bool aa = QuartzSearchData.QuartzDelete(options); |
| | | } |
| | | |
| | | //生成任务并添加新配置 |
| | | result = taskOptions.AddJob(schedulerFactory, false).GetAwaiter().GetResult(); |
| | | break; |
| | | case JobAction.暂停: |
| | | case JobAction.开启: |
| | | case JobAction.停止: |
| | | case JobAction.立即执行: |
| | | options = _taskList.Where(x => x.TaskName == taskOptions.TaskName && x.GroupName == taskOptions.GroupName).FirstOrDefault(); |
| | | if (action == JobAction.暂停) |
| | | { |
| | | options.Status = (int)TriggerState.Paused; |
| | | //更新任务状态 |
| | | bool aa= QuartzSearchData.QuartzUpdate(options); |
| | | } |
| | | else if (action == JobAction.停止) |
| | | { |
| | | options.Status = (int)action; |
| | | //更新任务状态 |
| | | bool aa = QuartzSearchData.QuartzUpdate(options); |
| | | } |
| | | else |
| | | { |
| | | options.Status = (int)TriggerState.Normal; |
| | | //更新任务状态 |
| | | bool aa = QuartzSearchData.QuartzUpdate(options); |
| | | } |
| | | break; |
| | | } |
| | | //生成配置文件 |
| | | FileQuartz.WriteJobConfig(_taskList); |
| | | FileQuartz.WriteJobAction(action, taskOptions.TaskName, taskOptions.GroupName, "操作对象:" + JsonConvert.SerializeObject(taskOptions)); |
| | | return result; |
| | | return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.立即执行, task, quartzRepo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发新增、删除、修改、暂停、启用、立即执行事件 |
| | | /// 任务操作核心逻辑(核心修复:Key匹配+状态同步+Scheduler启动校验) |
| | | /// 封装删除/修改/暂停/开启/立即执行的通用逻辑,避免代码冗余 |
| | | /// </summary> |
| | | /// <param name="schedulerFactory"></param> |
| | | /// <param name="taskName"></param> |
| | | /// <param name="groupName"></param> |
| | | /// <param name="action"></param> |
| | | /// <param name="taskOptions"></param> |
| | | /// <returns></returns> |
| | | public static async Task<object> TriggerAction(this ISchedulerFactory schedulerFactory, string taskName, string groupName, JobAction action, TaskOptions taskOptions = null) |
| | | /// <param name="schedulerFactory">调度器工厂</param> |
| | | /// <param name="taskName">任务名称</param> |
| | | /// <param name="groupName">分组名称</param> |
| | | /// <param name="action">操作类型(删除/修改/暂停/开启/立即执行)</param> |
| | | /// <param name="task">任务配置</param> |
| | | /// <param name="quartzRepo">Quartz仓储</param> |
| | | /// <returns>操作结果</returns> |
| | | private static async Task<object> TriggerAction(this ISchedulerFactory schedulerFactory, string taskName, string groupName, JobAction action, TaskOptions task, QuartzRepository quartzRepo) |
| | | { |
| | | string errorMsg = ""; |
| | | try |
| | | { |
| | | //处理前端Corn表达式最后一个月的星期天表达式缺陷问题 |
| | | string resultString = ""; |
| | | string[] str = taskOptions.Interval.Split(" "); |
| | | if ((str[3].Equals("1L") || str[3].Equals("2L") || str[3].Equals("3L") || str[3].Equals("4L") || str[3].Equals("5L") || str[3].Equals("6L") || str[3].Equals("7L")) && str[5].Equals("?")) |
| | | { |
| | | str[5] = str[3]; |
| | | str[3] = "?"; |
| | | for (int i = 0; i < str.Length; i++) |
| | | { |
| | | resultString += str[i] + " "; |
| | | } |
| | | taskOptions.Interval = resultString; |
| | | } |
| | | IScheduler scheduler = await schedulerFactory.GetScheduler(); |
| | | List<JobKey> jobKeys = scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)).Result.ToList(); |
| | | if (jobKeys == null || jobKeys.Count() == 0) |
| | | { |
| | | errorMsg = $"未找到分组[{groupName}]"; |
| | | return new { code = 300, count = 0, Message = errorMsg, data = "null" }; |
| | | //return new { status = false, msg = errorMsg }; |
| | | } |
| | | JobKey jobKey = jobKeys.Where(s => scheduler.GetTriggersOfJob(s).Result.Any(x => (x as CronTriggerImpl).Name == taskName)).FirstOrDefault(); |
| | | if (jobKey == null) |
| | | { |
| | | errorMsg = $"未找到触发器[{taskName}]"; |
| | | return new { code = 300, count = 0, Message = errorMsg, data = "null" }; |
| | | } |
| | | var triggers = await scheduler.GetTriggersOfJob(jobKey); |
| | | ITrigger trigger = triggers?.Where(x => (x as CronTriggerImpl).Name == taskName).FirstOrDefault(); |
| | | // 1. 预处理Cron表达式(修复前端传入的表达式) |
| | | if (task != null) task.Interval = FixCronExpression(task.Interval); |
| | | |
| | | if (trigger == null) |
| | | // 2. 获取调度器并确保启动(核心修复1:全局Scheduler启动校验) |
| | | var scheduler = await schedulerFactory.GetScheduler(); |
| | | // 核心修复1:确保Scheduler全局启动 |
| | | if (!scheduler.IsStarted) await scheduler.Start(); |
| | | |
| | | // 3. 核心修复2:正确构建JobKey(任务名+分组),解决Key匹配失败问题 |
| | | var jobKey = new JobKey(taskName, groupName); |
| | | if (!await scheduler.CheckExists(jobKey)) |
| | | { |
| | | errorMsg = $"未找到触发器[{taskName}]"; |
| | | return new { code = 300, count = 0, Message = errorMsg, data = "null" }; |
| | | return new ToMessage { code = "300", count = 0, message = $"未找到任务[{taskName}-{groupName}]", data = null }; |
| | | } |
| | | |
| | | // 4. 获取任务关联的触发器(无触发器则任务无法执行) |
| | | var triggers = await scheduler.GetTriggersOfJob(jobKey); |
| | | if (triggers == null || !triggers.Any()) |
| | | { |
| | | return new ToMessage { code = "300", count = 0, message = $"任务[{taskName}-{groupName}]无触发器", data = null }; |
| | | } |
| | | // 取第一个触发器(单任务默认绑定一个触发器) |
| | | var trigger = triggers.First(); |
| | | |
| | | object result = null; |
| | | // 5. 根据操作类型执行不同逻辑 |
| | | switch (action) |
| | | { |
| | | case JobAction.删除: |
| | | case JobAction.修改: |
| | | // 暂停触发器 -> 解绑触发器 -> 删除Job -> 同步内存+数据库 -> 记录日志 |
| | | await scheduler.PauseTrigger(trigger.Key); |
| | | await scheduler.UnscheduleJob(trigger.Key);// 移除触发器 |
| | | await scheduler.DeleteJob(trigger.JobKey); |
| | | result = taskOptions.ModifyTaskEntity(schedulerFactory, action); |
| | | await scheduler.UnscheduleJob(trigger.Key); |
| | | await scheduler.DeleteJob(jobKey); |
| | | _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName); |
| | | await quartzRepo.DeleteTaskAsync(taskName, groupName); |
| | | await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "删除任务成功"); |
| | | break; |
| | | |
| | | case JobAction.修改: |
| | | // 修改逻辑:先删除旧任务 -> 重新添加新配置 -> 记录日志 |
| | | await scheduler.PauseTrigger(trigger.Key); |
| | | await scheduler.UnscheduleJob(trigger.Key); |
| | | await scheduler.DeleteJob(jobKey); |
| | | _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName); |
| | | await quartzRepo.DeleteTaskAsync(taskName, groupName); |
| | | result = await task.AddJob(schedulerFactory, false, null, quartzRepo);// 重新添加任务 |
| | | await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, JsonConvert.SerializeObject(task)); |
| | | break; |
| | | |
| | | case JobAction.暂停: |
| | | case JobAction.停止: |
| | | case JobAction.开启: |
| | | result = taskOptions.ModifyTaskEntity(schedulerFactory, action); |
| | | if (action == JobAction.暂停) |
| | | { |
| | | await scheduler.PauseTrigger(trigger.Key); |
| | | } |
| | | else if (action == JobAction.开启) |
| | | { |
| | | await scheduler.ResumeTrigger(trigger.Key); |
| | | // await scheduler.RescheduleJob(trigger.Key, trigger); |
| | | } |
| | | else |
| | | { |
| | | await scheduler.Shutdown(); |
| | | } |
| | | // 暂停触发器 -> 更新任务状态 -> 同步内存+数据库 -> 记录日志 |
| | | await scheduler.PauseTrigger(trigger.Key); |
| | | task.Status = (int)TriggerState.Paused; |
| | | await quartzRepo.UpdateTaskAsync(task); |
| | | _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName); |
| | | _taskList.Add(task); // 同步内存状态 |
| | | await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "暂停任务成功"); |
| | | break; |
| | | |
| | | case JobAction.开启: |
| | | // 恢复触发器 -> 更新任务状态 -> 同步内存+数据库 -> 记录日志 |
| | | await scheduler.ResumeTrigger(trigger.Key); |
| | | task.Status = (int)TriggerState.Normal; |
| | | await quartzRepo.UpdateTaskAsync(task); |
| | | _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName); |
| | | _taskList.Add(task); // 同步内存状态 |
| | | await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "开启任务成功"); |
| | | break; |
| | | |
| | | case JobAction.立即执行: |
| | | if (taskOptions != null && taskOptions.Status != (int)TriggerState.Normal) |
| | | // 立即执行前确保任务处于启动状态 -> 触发Job立即执行 -> 记录日志 |
| | | if (task.Status != (int)TriggerState.Normal) |
| | | { |
| | | result = taskOptions.ModifyTaskEntity(schedulerFactory, JobAction.开启); |
| | | await scheduler.ResumeTrigger(trigger.Key); |
| | | |
| | | task.Status = (int)TriggerState.Normal; |
| | | await quartzRepo.UpdateTaskAsync(task); |
| | | _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName); |
| | | _taskList.Add(task); |
| | | await scheduler.ResumeTrigger(trigger.Key); |
| | | } |
| | | else { |
| | | await scheduler.TriggerJob(jobKey); |
| | | } |
| | | |
| | | await scheduler.TriggerJob(jobKey); // 立即触发任务执行 |
| | | await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "立即执行任务成功"); |
| | | break; |
| | | } |
| | | mes.code = "200"; |
| | |
| | | mes.message = $"作业{action.ToString()}成功"; |
| | | mes.data = null; |
| | | return result ?? mes; |
| | | //return result ?? new { status = true, msg = $"作业{action.ToString()}成功" }; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | errorMsg = ex.Message; |
| | | mes.code = "300"; |
| | | mes.count = 0; |
| | | mes.message = errorMsg; |
| | | mes.data = null; |
| | | return mes; |
| | | //return new { status = false, msg = ex.Message }; |
| | | } |
| | | finally |
| | | { |
| | | FileQuartz.WriteJobAction(action, taskName, groupName, errorMsg); |
| | | await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, $"操作失败:{ex.Message}"); |
| | | return new ToMessage { code = "300", count = 0, message = ex.Message, data = null }; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// |
| | | /// 修复前端传入的Cron表达式缺陷 |
| | | /// 兼容ASP.NET Core 3.1中Cron表达式的Day/Week字段写法问题 |
| | | /// </summary> |
| | | /// <param name="context"></param>通过作业上下文获取作业对应的配置参数 |
| | | /// <returns></returns> |
| | | public static TaskOptions GetTaskOptions(this IJobExecutionContext context) |
| | | /// <param name="cron">原始Cron表达式</param> |
| | | /// <returns>修复后的Cron表达式</returns> |
| | | private static string FixCronExpression(string cron) |
| | | { |
| | | AbstractTrigger trigger = (context as JobExecutionContextImpl).Trigger as AbstractTrigger; |
| | | TaskOptions taskOptions = _taskList.Where(x => x.TaskName == trigger.Name && x.GroupName == trigger.Group).FirstOrDefault(); |
| | | return taskOptions ?? _taskList.Where(x => x.TaskName == trigger.JobName && x.GroupName == trigger.JobGroup).FirstOrDefault(); |
| | | if (string.IsNullOrEmpty(cron)) |
| | | return cron; |
| | | |
| | | // 拆分Cron表达式(标准Cron:秒 分 时 日 月 周 年(可选)) |
| | | var str = cron.Split(" ", StringSplitOptions.RemoveEmptyEntries); |
| | | if (str.Length < 6) |
| | | return cron; |
| | | |
| | | // 兼容 ASP.NET Core 3.1 的写法(替换 or 语法) |
| | | // 修复场景:Day字段为1L/2L...7L 且 周字段为? 时,交换Day和周字段 |
| | | // 原因:前端可能混淆了Day/周字段的写法,Quartz对L(最后一天)的解析有特定规则 |
| | | var dayValue = str[3];// 第4个字段:Day |
| | | if ((dayValue == "1L" || dayValue == "2L" || dayValue == "3L" || |
| | | dayValue == "4L" || dayValue == "5L" || dayValue == "6L" || dayValue == "7L") && str[5] == "?") |
| | | { |
| | | str[5] = str[3]; // 周字段 = 原Day字段 |
| | | str[3] = "?"; // Day字段 = ? |
| | | return string.Join(" ", str); |
| | | } |
| | | |
| | | return cron; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 作业是否存在 |
| | | /// 验证Cron表达式是否有效 |
| | | /// </summary> |
| | | /// <param name="taskOptions"></param> |
| | | /// <param name="init">初始化的不需要判断</param> |
| | | /// <returns></returns> |
| | | public static (bool, object) Exists(this TaskOptions taskOptions, bool init) |
| | | { |
| | | if (!init && _taskList.Any(x => x.TaskName == taskOptions.TaskName && x.GroupName == taskOptions.GroupName)) |
| | | { |
| | | return (false, |
| | | new |
| | | { |
| | | status = false, |
| | | msg = $"作业:{taskOptions.TaskName},分组:{taskOptions.GroupName}已经存在" |
| | | }); |
| | | } |
| | | return (true, null); |
| | | } |
| | | |
| | | /// <param name="cronExpression">Cron表达式</param> |
| | | /// <returns>是否有效 + 错误信息</returns> |
| | | public static (bool, string) IsValidExpression(this string cronExpression) |
| | | { |
| | | try |
| | | { |
| | | CronTriggerImpl trigger = new CronTriggerImpl(); |
| | | // 通过Quartz内置的CronTriggerImpl验证表达式 |
| | | var trigger = new CronTriggerImpl(); |
| | | trigger.CronExpressionString = cronExpression; |
| | | DateTimeOffset? date = trigger.ComputeFirstFireTimeUtc(null); |
| | | return (date != null, date == null ? $"请确认表达式{cronExpression}是否正确!" : ""); |
| | | // 计算第一个触发时间:null表示表达式无效 |
| | | var date = trigger.ComputeFirstFireTimeUtc(null); |
| | | return (date != null, date == null ? $"表达式{cronExpression}无效!" : string.Empty); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | return (false, $"请确认表达式{cronExpression}是否正确!{e.Message}"); |
| | | return (false, $"表达式{cronExpression}无效!{e.Message}"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从Job执行上下文获取任务配置 |
| | | /// 用于Job执行时获取最新的任务参数 |
| | | /// </summary> |
| | | /// <param name="context">Job执行上下文</param> |
| | | /// <returns>任务配置项</returns> |
| | | public static TaskOptions GetTaskOptions(this IJobExecutionContext context) |
| | | { |
| | | var jobKey = context.JobDetail.Key; |
| | | // 从内存列表中获取匹配的任务(Job执行时优先用内存,减少数据库查询) |
| | | return _taskList.FirstOrDefault(x => x.TaskName == jobKey.Name && x.GroupName == jobKey.Group); |
| | | } |
| | | } |
| | | } |