VueWebCoreApi/Quartz/QuartzNETExtension.cs
@@ -17,110 +17,142 @@
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;
            string errorMsg = string.Empty;
            // 初始化内存任务列表
            _taskList = taskList;
            foreach (var task in taskList)
            {
            try
            {
                _taskList = JsonConvert.DeserializeObject<List<TaskOptions>>(jobConfig);
                _taskList.ForEach(x =>
                {
                    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 = +1;
                errorMsg += $"作业:{options?.TaskName},异常:{ex.Message}";
                    // 记录单个任务初始化失败信息
                    errorCount++;
                    errorMsg += $"作业:{task.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)
            {
@@ -128,92 +160,78 @@
                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] + " ";
                    return new ToMessage { code = "300", count = 0, message = $"作业:{task.TaskName},分组:{task.GroupName}已经存在", data = null };
                    }
                    taskOptions.Interval = resultString;
                }
                (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, "新增任务成功");// 操作日志
                }
                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)
                // 5. 构建Quartz Job(任务执行体)
                var job = JobBuilder.Create<HttpResultfulJob>()// HttpResultfulJob:自定义的HTTP请求型Job
                    .WithIdentity(task.TaskName, task.GroupName)// 设置Job唯一标识(名称+分组)
                   .Build();
                IScheduler scheduler = await schedulerFactory.GetScheduler();
                // 6. 构建Cron触发器(按Cron表达式触发)
                var trigger = TriggerBuilder.Create()
                    .WithIdentity(task.TaskName, task.GroupName)// 触发器唯一标识(与Job保持一致)
                    .StartNow()// 立即启动
                    .WithDescription(task.Describe)// 任务描述
                    .WithCronSchedule(task.Interval)// 绑定Cron表达式
                    .Build();
                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 ="执行成功!";
@@ -221,221 +239,167 @@
            }
            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.删除:
                        // 暂停触发器 -> 解绑触发器 -> 删除Job -> 同步内存+数据库 -> 记录日志
                        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);
                        await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "删除任务成功");
                        break;
                    case JobAction.修改:
                        // 修改逻辑:先删除旧任务 -> 重新添加新配置 -> 记录日志
                        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);
                        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();
                        }
                        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.立即执行:
                        if (taskOptions != null && taskOptions.Status != (int)TriggerState.Normal)
                        {
                            result = taskOptions.ModifyTaskEntity(schedulerFactory, JobAction.开启);
                    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.立即执行:
                        // 立即执行前确保任务处于启动状态 -> 触发Job立即执行 -> 记录日志
                        if (task.Status != (int)TriggerState.Normal)
                        {
                            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";
@@ -443,70 +407,78 @@
                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);
        }
    }
}