hwh
2024-06-20 e8620c74a1153b448d2af5821d0555206aa18d62
quartz.net定时任务
4个文件已修改
15个文件已添加
1555 ■■■■■ 已修改文件
Wms/Utility/Entity/JobBriefInfoEntity.cs 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Entity/JobInfoEntity.cs 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Entity/LogModel.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Entity/ScheduleEntity.cs 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Enum/JobTypeEnum.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Enum/RequestTypeEnum.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Enum/TriggerTypeEnum.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/Constant.cs 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/HostedService.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/HttpHelper.cs 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/HttpJob.cs 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/JobBase.cs 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/SchedulerCenter.cs 509 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Job/TestJob.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Utility.csproj 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Controllers/JobController.cs 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Startup.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/Wms.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Wms/appsettings.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Wms/Utility/Entity/JobBriefInfoEntity.cs
New file
@@ -0,0 +1,89 @@
using Quartz;
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility.Entity
{
    public class JobBriefInfoEntity
    {
        /// <summary>
        /// 任务组名
        /// </summary>
        public string GroupName { get; set; }
        /// <summary>
        /// 任务信息
        /// </summary>
        public List<JobBriefInfo> JobInfoList { get; set; } = new List<JobBriefInfo>();
    }
    public class JobBriefInfo
    {
        /// <summary>
        /// 任务名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 下次执行时间
        /// </summary>
        public DateTime? NextFireTime { get; set; }
        /// <summary>
        /// 上次执行时间
        /// </summary>
        public DateTime? PreviousFireTime { get; set; }
        /// <summary>
        /// 上次执行的异常信息
        /// </summary>
        public string LastErrMsg { get; set; }
        /// <summary>
        /// 任务状态
        /// </summary>
        public TriggerState TriggerState { get; set; }
        /// <summary>
        /// 显示状态
        /// </summary>
        public string DisplayState
        {
            get
            {
                var state = string.Empty;
                switch (TriggerState)
                {
                    case TriggerState.Normal:
                        state = "正常";
                        break;
                    case TriggerState.Paused:
                        state = "暂停";
                        break;
                    case TriggerState.Complete:
                        state = "完成";
                        break;
                    case TriggerState.Error:
                        state = "异常";
                        break;
                    case TriggerState.Blocked:
                        state = "阻塞";
                        break;
                    case TriggerState.None:
                        state = "不存在";
                        break;
                    default:
                        state = "未知";
                        break;
                }
                return state;
            }
        }
        /// <summary>
        /// 已经执行次数
        /// </summary>
        public long RunNumber { get; set; }
    }
}
Wms/Utility/Entity/JobInfoEntity.cs
New file
@@ -0,0 +1,115 @@
using Quartz;
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility.Entity
{
    public class JobInfoEntity
    {
        /// <summary>
        /// 任务组名
        /// </summary>
        public string GroupName { get; set; }
        /// <summary>
        /// 任务信息
        /// </summary>
        public List<JobInfo> JobInfoList { get; set; } = new List<JobInfo>();
    }
    public class JobInfo
    {
        /// <summary>
        /// 任务名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 下次执行时间
        /// </summary>
        public DateTime? NextFireTime { get; set; }
        /// <summary>
        /// 上次执行时间
        /// </summary>
        public DateTime? PreviousFireTime { get; set; }
        /// <summary>
        /// 开始时间
        /// </summary>
        public DateTime BeginTime { get; set; }
        /// <summary>
        /// 结束时间
        /// </summary>
        public DateTime? EndTime { get; set; }
        /// <summary>
        /// 上次执行的异常信息
        /// </summary>
        public string LastErrMsg { get; set; }
        /// <summary>
        /// 任务状态
        /// </summary>
        public TriggerState TriggerState { get; set; }
        /// <summary>
        /// 描述
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// 显示状态
        /// </summary>
        public string DisplayState
        {
            get
            {
                var state = string.Empty;
                switch (TriggerState)
                {
                    case TriggerState.Normal:
                        state = "正常";
                        break;
                    case TriggerState.Paused:
                        state = "暂停";
                        break;
                    case TriggerState.Complete:
                        state = "完成";
                        break;
                    case TriggerState.Error:
                        state = "异常";
                        break;
                    case TriggerState.Blocked:
                        state = "阻塞";
                        break;
                    case TriggerState.None:
                        state = "不存在";
                        break;
                    default:
                        state = "未知";
                        break;
                }
                return state;
            }
        }
        /// <summary>
        /// 时间间隔
        /// </summary>
        public string Interval { get; set; }
        /// <summary>
        /// 触发地址
        /// </summary>
        public string TriggerAddress { get; set; }
        public string RequestType { get; set; }
        /// <summary>
        /// 已经执行的次数
        /// </summary>
        public long RunNumber { get; set; }
        public long JobType { get; set; }
    }
}
Wms/Utility/Entity/LogModel.cs
New file
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility.Entity
{
    public abstract class LogModel
    {
        /// <summary>
        /// 开始执行时间
        /// </summary>
        public string BeginTime { get; set; }
        /// <summary>
        /// 结束时间
        /// </summary>
        public string EndTime { get; set; }
        /// <summary>
        /// 耗时(秒)
        /// </summary>
        public string ExecuteTime { get; set; }
        /// <summary>
        /// 任务名称
        /// </summary>
        public string JobName { get; set; }
        /// <summary>
        /// 结果
        /// </summary>
        public string Result { get; set; }
        /// <summary>
        /// 异常消息
        /// </summary>
        public string ErrorMsg { get; set; }
    }
    public class LogUrlModel : LogModel
    {
        /// <summary>
        /// 请求地址
        /// </summary>
        public string Url { get; set; }
        /// <summary>
        /// 请求类型
        /// </summary>
        public string RequestType { get; set; }
        /// <summary>
        /// 请求参数
        /// </summary>
        public string Parameters { get; set; }
    }
}
Wms/Utility/Entity/ScheduleEntity.cs
New file
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public class ScheduleEntity
    {
        /// <summary>
        /// 任务名称
        /// </summary>
        public string JobName { get; set; }
        /// <summary>
        /// 任务分组
        /// </summary>
        public string JobGroup { get; set; }
        /// <summary>
        /// 任务类型
        /// </summary>
        public JobTypeEnum JobType { get; set; } = JobTypeEnum.Url;
        /// <summary>
        /// 开始时间
        /// </summary>
        public DateTimeOffset BeginTime { get; set; } = DateTime.Now;
        /// <summary>
        /// 结束时间
        /// </summary>
        public DateTimeOffset? EndTime { get; set; }
        /// <summary>
        /// Cron表达式
        /// </summary>
        public string Cron { get; set; }
        /// <summary>
        /// 执行次数(默认无限循环)
        /// </summary>
        public int? RunTimes { get; set; }
        /// <summary>
        /// 执行间隔时间,单位毫秒(如果有Cron,则IntervalSecond失效)
        /// </summary>
        public int? IntervalMilliseconds { get; set; }
        /// <summary>
        /// 触发器类型
        /// </summary>
        public TriggerTypeEnum TriggerType { get; set; }
        /// <summary>
        /// 描述
        /// </summary>
        public string Description { get; set; }
        #region Url
        /// <summary>
        /// 请求url
        /// </summary>
        public string RequestUrl { get; set; }
        /// <summary>
        /// 请求参数(Post,Put请求用)
        /// </summary>
        public string RequestParameters { get; set; }
        /// <summary>
        /// Headers(可以包含如:Authorization授权认证)
        /// 格式:{"Authorization":"userpassword.."}
        /// </summary>
        public string Headers { get; set; }
        /// <summary>
        /// 请求类型
        /// </summary>
        public RequestTypeEnum RequestType { get; set; } = RequestTypeEnum.Post;
        #endregion
    }
    public class ModifyJobInput
    {
        public ScheduleEntity NewScheduleEntity { get; set; }
        public ScheduleEntity OldScheduleEntity { get; set; }
    }
}
Wms/Utility/Enum/JobTypeEnum.cs
New file
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public enum JobTypeEnum
    {
        None = 0,
        /// <summary>
        /// 网络请求
        /// </summary>
        Url = 1,
        /// <summary>
        /// 内置
        /// </summary>
        BuiltIn = 2,
        /// <summary>
        /// 热加载
        /// </summary>
        Hotreload = 3,
    }
}
Wms/Utility/Enum/RequestTypeEnum.cs
New file
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public enum RequestTypeEnum
    {
        None = 0,
        Get = 1,
        Post = 2,
        Put = 4,
        Delete = 8
    }
}
Wms/Utility/Enum/TriggerTypeEnum.cs
New file
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility
{
    public enum TriggerTypeEnum
    {
        None = 0,
        Cron = 1,
        Simple = 2,
    }
}
Wms/Utility/Job/Constant.cs
New file
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Utility.Job
{
    public class Constant
    {
        /// <summary>
        /// 请求url RequestUrl
        /// </summary>
        public const string REQUESTURL = "RequestUrl";
        /// <summary>
        /// 请求参数 RequestParameters
        /// </summary>
        public const string REQUESTPARAMETERS = "RequestParameters";
        /// <summary>
        /// Headers(可以包含:Authorization授权认证)
        /// </summary>
        public const string HEADERS = "Headers";
        /// <summary>
        /// 是否发送邮件
        /// </summary>
        public const string MAILMESSAGE = "MailMessage";
        /// <summary>
        /// 请求类型 RequestType
        /// </summary>
        public const string REQUESTTYPE = "RequestType";
        /// <summary>
        /// 日志 LogList
        /// </summary>
        public const string LOGLIST = "LogList";
        /// <summary>
        /// 异常 Exception
        /// </summary>
        public const string EXCEPTION = "Exception";
        /// <summary>
        /// 执行次数
        /// </summary>
        public const string RUNNUMBER = "RunNumber";
        public const string MailTitle = "MailTitle";
        public const string MailContent = "MailContent";
        public const string MailTo = "MailTo";
        public const string JobTypeEnum = "JobTypeEnum";
        public const string EndAt = "EndAt";
        public const string ClientID = "ClientID";
        public const string Host = "Host";
        public const string Password = "Password";
        public const string Port = "Port";
        public const string UserName = "UserName";
        public static string Topic = "Topic";
        public static string Payload = "Payload";
        public static string BuiltIn = "BuiltIn";
    }
}
Wms/Utility/Job/HostedService.cs
New file
@@ -0,0 +1,30 @@
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace Utility.Job
{
    public class HostedService : IHostedService
    {
        private SchedulerCenter schedulerCenter;
        public HostedService(SchedulerCenter schedulerCenter)
        {
            this.schedulerCenter = schedulerCenter;
        }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            //开启调度器
            await schedulerCenter.StartScheduleAsync();
        }
        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
}
Wms/Utility/Job/HttpHelper.cs
New file
@@ -0,0 +1,181 @@
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Headers;
namespace Utility.Job
{
    /// <summary>
    /// 请求帮助类
    /// </summary>
    public class HttpHelper
    {
        public static readonly HttpHelper Instance;
        static HttpHelper()
        {
            Instance = new HttpHelper();
        }
        /// <summary>
        /// 不同url分配不同HttpClient
        /// </summary>
        public static ConcurrentDictionary<string, HttpClient> dictionary = new ConcurrentDictionary<string, HttpClient>();
        private HttpClient GetHttpClient(string url)
        {
            var uri = new Uri(url);
            var key = uri.Scheme + uri.Host;
            //if (!dictionary.Keys.Contains(key))
            return dictionary.GetOrAdd(key, new HttpClient());
            //return dictionary[key];
        }
        /// <summary>
        /// Post请求
        /// </summary>
        /// <param name="url">url地址</param>
        /// <param name="jsonString">请求参数(Json字符串)</param>
        /// <param name="headers">webapi做用户认证</param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> PostAsync(string url, string jsonString, Dictionary<string, string> headers = null)
        {
            if (string.IsNullOrWhiteSpace(jsonString))
                jsonString = "{}";
            StringContent content = new StringContent(jsonString);
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            if (headers != null && headers.Count > 0)
            {
                //如果有headers认证等信息,则每个请求实例一个HttpClient
                using (HttpClient http = new HttpClient())
                {
                    foreach (var item in headers)
                    {
                        http.DefaultRequestHeaders.Remove(item.Key);
                        http.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                    }
                    return await http.PostAsync(new Uri(url), content);
                }
            }
            else
            {
                return await GetHttpClient(url).PostAsync(new Uri(url), content);
            }
        }
        /// <summary>
        /// Post请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url">url地址</param>
        /// <param name="content">请求参数</param>
        /// <param name="headers">webapi做用户认证</param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> PostAsync<T>(string url, T content, Dictionary<string, string> headers = null) where T : class
        {
            return await PostAsync(url, JsonConvert.SerializeObject(content), headers);
        }
        /// <summary>
        /// Get请求
        /// </summary>
        /// <param name="url">url地址</param>
        /// <param name="headers">webapi做用户认证</param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> GetAsync(string url, Dictionary<string, string> headers = null)
        {
            if (headers != null && headers.Count > 0)
            {
                //如果有headers认证等信息,则每个请求实例一个HttpClient
                using (HttpClient http = new HttpClient())
                {
                    foreach (var item in headers)
                    {
                        http.DefaultRequestHeaders.Remove(item.Key);
                        http.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                    }
                    return await http.GetAsync(url);
                }
            }
            else
            {
                return await GetHttpClient(url).GetAsync(url);
            }
        }
        /// <summary>
        /// Put请求
        /// </summary>
        /// <param name="url">url地址</param>
        /// <param name="jsonString">请求参数(Json字符串)</param>
        /// <param name="headers">webapi做用户认证</param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> PutAsync(string url, string jsonString, Dictionary<string, string> headers = null)
        {
            if (string.IsNullOrWhiteSpace(jsonString))
                jsonString = "{}";
            StringContent content = new StringContent(jsonString);
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            if (headers != null && headers.Count > 0)
            {
                //如果有headers认证等信息,则每个请求实例一个HttpClient
                using (HttpClient http = new HttpClient())
                {
                    foreach (var item in headers)
                    {
                        http.DefaultRequestHeaders.Remove(item.Key);
                        http.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                    }
                    return await http.PutAsync(url, content);
                }
            }
            else
            {
                return await GetHttpClient(url).PutAsync(url, content);
            }
        }
        /// <summary>
        /// Put请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url">url地址</param>
        /// <param name="content">请求参数</param>
        /// <param name="headers">webapi做用户认证</param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> PutAsync<T>(string url, T content, Dictionary<string, string> headers = null)
        {
            return await PutAsync(url, JsonConvert.SerializeObject(content), headers);
        }
        /// <summary>
        /// Delete请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="headers">webapi做用户认证</param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> DeleteAsync(string url, Dictionary<string, string> headers = null)
        {
            if (headers != null && headers.Count > 0)
            {
                //如果有headers认证等信息,则每个请求实例一个HttpClient
                using (HttpClient http = new HttpClient())
                {
                    foreach (var item in headers)
                    {
                        http.DefaultRequestHeaders.Remove(item.Key);
                        http.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
                    }
                    return await http.DeleteAsync(url);
                }
            }
            else
            {
                return await GetHttpClient(url).DeleteAsync(url);
            }
        }
    }
}
Wms/Utility/Job/HttpJob.cs
New file
@@ -0,0 +1,86 @@
using Newtonsoft.Json;
using Quartz;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Utility.Tools;
using Utility.Entity;
using Serilog;
using Talk.Extensions;
namespace Utility.Job
{
    public class HttpJob : JobBase<LogUrlModel>, IJob
    {
        public HttpJob(ILogger logger) : base(new LogUrlModel(), logger)
        { }
        public override async Task NextExecute(IJobExecutionContext context)
        {
            //获取相关参数
            var requestUrl = context.JobDetail.JobDataMap.GetString(Constant.REQUESTURL)?.Trim();
            requestUrl = requestUrl?.IndexOf("http") == 0 ? requestUrl : "http://" + requestUrl;
            var requestParameters = context.JobDetail.JobDataMap.GetString(Constant.REQUESTPARAMETERS);
            var headersString = context.JobDetail.JobDataMap.GetString(Constant.HEADERS);
            var headers = headersString != null ? JsonConvert.DeserializeObject<Dictionary<string, string>>(headersString?.Trim()) : null;
            var requestType = (RequestTypeEnum)int.Parse(context.JobDetail.JobDataMap.GetString(Constant.REQUESTTYPE));
            LogInfo.Url = requestUrl;
            LogInfo.RequestType = requestType.ToString();
            LogInfo.Parameters = requestParameters;
            HttpResponseMessage response = new HttpResponseMessage();
            var http = HttpHelper.Instance;
            switch (requestType)
            {
                case RequestTypeEnum.Get:
                    response = await http.GetAsync(requestUrl, headers);
                    break;
                case RequestTypeEnum.Post:
                    response = await http.PostAsync(requestUrl, requestParameters, headers);
                    break;
                case RequestTypeEnum.Put:
                    response = await http.PutAsync(requestUrl, requestParameters, headers);
                    break;
                case RequestTypeEnum.Delete:
                    response = await http.DeleteAsync(requestUrl, headers);
                    break;
            }
            var result = HttpUtility.HtmlEncode(await response.Content.ReadAsStringAsync());
            LogInfo.Result = $"<span class='result'>{result.MaxLeft(1000)}</span>";
            if (!response.IsSuccessStatusCode)
            {
                LogInfo.ErrorMsg = $"<span class='error'>{result.MaxLeft(3000)}</span>";
                context.JobDetail.JobDataMap[Constant.EXCEPTION] = $"<div class='err-time'>{LogInfo.BeginTime}</div>{JsonConvert.SerializeObject(LogInfo)}";
            }
            else
            {
                //这里需要和请求方约定好返回结果约定为HttpResultModel模型
                var httpResult = JsonConvert.DeserializeObject<HttpResultModel>(HttpUtility.HtmlDecode(result));
                if (!httpResult.IsSuccess)
                {
                    LogInfo.ErrorMsg = $"<span class='error'>{httpResult.ErrorMsg}</span>";
                    context.JobDetail.JobDataMap[Constant.EXCEPTION] = $"<div class='err-time'>{LogInfo.BeginTime}</div>{JsonConvert.SerializeObject(LogInfo)}";
                }
            }
        }
    }
    /// <summary>
    /// Job任务结果
    /// </summary>
    public class HttpResultModel
    {
        /// <summary>
        /// 请求是否成功
        /// </summary>
        public bool IsSuccess { get; set; } = true;
        /// <summary>
        /// 异常消息
        /// </summary>
        public string ErrorMsg { get; set; }
    }
}
Wms/Utility/Job/JobBase.cs
New file
@@ -0,0 +1,93 @@
using Newtonsoft.Json;
using Quartz;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using Utility.Entity;
namespace Utility.Job
{
    [DisallowConcurrentExecution]
    [PersistJobDataAfterExecution]
    public abstract class JobBase<T> where T : LogModel, new()
    {
        protected readonly int maxLogCount = 20;//最多保存日志数量
        protected readonly int warnTime = 20;//接口请求超过多少秒记录警告日志
        protected Stopwatch stopwatch = new Stopwatch();
        protected T LogInfo { get; private set; }
        private ILogger _logger;
        public JobBase(T logInfo,ILogger logger)
        {
            LogInfo = logInfo;
            _logger = logger;
        }
        public async Task Execute(IJobExecutionContext context)
        {
            //如果结束时间超过当前时间,则暂停当前任务。
            var endTime = context.JobDetail.JobDataMap.GetString("EndAt");
            if (!string.IsNullOrWhiteSpace(endTime) && DateTime.Parse(endTime) <= DateTime.Now)
            {
                await context.Scheduler.PauseJob(new JobKey(context.JobDetail.Key.Name, context.JobDetail.Key.Group));
                return;
            }
            //记录执行次数
            var runNumber = context.JobDetail.JobDataMap.GetLong(Constant.RUNNUMBER);
            context.JobDetail.JobDataMap[Constant.RUNNUMBER] = ++runNumber;
            var logs = context.JobDetail.JobDataMap[Constant.LOGLIST] as List<string> ?? new List<string>();
            if (logs.Count >= maxLogCount)
                logs.RemoveRange(0, logs.Count - maxLogCount);
            stopwatch.Restart(); //  开始监视代码运行时间
            try
            {
                LogInfo.BeginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                LogInfo.JobName = $"{context.JobDetail.Key.Group}.{context.JobDetail.Key.Name}";
                await NextExecute(context);
            }
            catch (Exception ex)
            {
                LogInfo.ErrorMsg = $"<span class='error'>{ex.Message}</span>";
                context.JobDetail.JobDataMap[Constant.EXCEPTION] = $"<div class='err-time'>{LogInfo.BeginTime}</div>{JsonConvert.SerializeObject(LogInfo)}";
            }
            finally
            {
                stopwatch.Stop(); //  停止监视
                double seconds = stopwatch.Elapsed.TotalSeconds;  //总秒数
                LogInfo.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                if (seconds >= 1)
                    LogInfo.ExecuteTime = seconds + "秒";
                else
                    LogInfo.ExecuteTime = stopwatch.Elapsed.TotalMilliseconds + "毫秒";
                var classErr = string.IsNullOrWhiteSpace(LogInfo.ErrorMsg) ? "" : "error";
                logs.Add($"<p class='msgList {classErr}'><span class='time'>{LogInfo.BeginTime} 至 {LogInfo.EndTime}  【耗时】{LogInfo.ExecuteTime}</span>{JsonConvert.SerializeObject(LogInfo)}</p>");
                context.JobDetail.JobDataMap[Constant.LOGLIST] = logs;
            }
        }
        public abstract Task NextExecute(IJobExecutionContext context);
        public async Task WarningAsync(string title, string msg)
        {
            _logger.Warning(msg);
        }
        public async Task InformationAsync(string title, string msg)
        {
            _logger.Information(msg);
        }
        public async Task ErrorAsync(string title, Exception ex, string msg)
        {
            _logger.Error(ex, msg);
        }
    }
}
Wms/Utility/Job/SchedulerCenter.cs
New file
@@ -0,0 +1,509 @@
using Quartz;
using Quartz.Impl;
using Quartz.Impl.AdoJobStore;
using Quartz.Impl.AdoJobStore.Common;
using Quartz.Impl.Matchers;
using Quartz.Impl.Triggers;
using Quartz.Simpl;
using Quartz.Util;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Utility.Job;
using System.Linq;
using Utility.Entity;
using Talk.Extensions;
namespace Utility
{
    public class SchedulerCenter
    {
        /// <summary>
        /// 数据连接
        /// </summary>
        private IDbProvider dbProvider;
        /// <summary>
        /// ADO 数据类型
        /// </summary>
        private string driverDelegateType;
        /// <summary>
        /// 调度器
        /// </summary>
        private static IScheduler scheduler;
        public SchedulerCenter()
        {
            InitDriverDelegateType();
            dbProvider = new DbProvider(ConfigurationManager.GetTryConfig("Quartz:dbProviderName"), ConfigurationManager.GetTryConfig("Quartz:connectionString"));
        }
        /// <summary>
        /// 初始化DriverDelegateType
        /// </summary>
        private void InitDriverDelegateType()
        {
            switch (ConfigurationManager.GetTryConfig("Quartz:dbProviderName"))
            {
                case "SQLite-Microsoft":
                case "SQLite":
                    driverDelegateType = typeof(SQLiteDelegate).AssemblyQualifiedName;
                    break;
                case "MySql":
                    driverDelegateType = typeof(MySQLDelegate).AssemblyQualifiedName;
                    break;
                case "OracleODPManaged":
                    driverDelegateType = typeof(OracleDelegate).AssemblyQualifiedName;
                    break;
                case "SqlServer":
                case "SQLServerMOT":
                    driverDelegateType = typeof(SqlServerDelegate).AssemblyQualifiedName;
                    break;
                case "Npgsql":
                    driverDelegateType = typeof(PostgreSQLDelegate).AssemblyQualifiedName;
                    break;
                case "Firebird":
                    driverDelegateType = typeof(FirebirdDelegate).AssemblyQualifiedName;
                    break;
                default:
                    throw new Exception("错误数据库类型");
            }
        }
        /// <summary>
        /// 初始化Scheduler
        /// </summary>
        private async Task InitSchedulerAsync()
        {
            if (scheduler == null)
            {
                DBConnectionManager.Instance.AddConnectionProvider("default", dbProvider);
                var serializer = new JsonObjectSerializer();
                serializer.Initialize();
                var jobStore = new JobStoreTX
                {
                    DataSource = "default",
                    TablePrefix = "QRTZ_",
                    InstanceId = "AUTO",
                    DriverDelegateType = driverDelegateType,
                    ObjectSerializer = serializer,
                };
                DirectSchedulerFactory.Instance.CreateScheduler("bennyScheduler", "AUTO", new DefaultThreadPool(), jobStore);
                scheduler = await SchedulerRepository.Instance.Lookup("bennyScheduler");
            }
        }
        /// <summary>
        /// 开启调度器
        /// </summary>
        /// <returns></returns>
        public async Task<bool> StartScheduleAsync()
        {
            //初始化Scheduler
            await InitSchedulerAsync();
            //开启调度器
            if (scheduler.InStandbyMode)
            {
                await scheduler.Start();
                Log.Information("任务调度启动!");
            }
            return scheduler.InStandbyMode;
        }
        /// <summary>
        /// 添加一个工作调度
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="runNumber"></param>
        /// <returns></returns>
        public async Task<string> AddScheduleJobAsync(ScheduleEntity entity, long? runNumber = null)
        {
            //检查任务是否已存在
            var jobKey = new JobKey(entity.JobName, entity.JobGroup);
            if (await scheduler.CheckExists(jobKey))
            {
                throw Oops.Bah("任务已存在");
            }
            //http请求配置
            var httpDir = new Dictionary<string, string>()
                {
                    { Constant.EndAt, entity.EndTime.ToString()},
                    { Constant.JobTypeEnum, ((int)entity.JobType).ToString()},
                };
            if (runNumber.HasValue)
                httpDir.Add(Constant.RUNNUMBER, runNumber.ToString());
            IJobConfigurator jobConfigurator = null;
            if (entity.JobType == JobTypeEnum.Url)
            {
                jobConfigurator = JobBuilder.Create<HttpJob>();
                httpDir.Add(Constant.REQUESTURL, entity.RequestUrl);
                httpDir.Add(Constant.HEADERS, entity.Headers);
                httpDir.Add(Constant.REQUESTPARAMETERS, entity.RequestParameters);
                httpDir.Add(Constant.REQUESTTYPE, ((int)entity.RequestType).ToString());
            }
            // 定义这个工作,并将其绑定到我们的IJob实现类
            IJobDetail job = jobConfigurator
                .SetJobData(new JobDataMap(httpDir))
                .WithDescription(entity.Description)
                .WithIdentity(entity.JobName, entity.JobGroup)
                .Build();
            // 创建触发器
            ITrigger trigger;
            //校验是否正确的执行周期表达式
            if (entity.TriggerType == TriggerTypeEnum.Cron)//CronExpression.IsValidExpression(entity.Cron))
            {
                trigger = CreateCronTrigger(entity);
            }
            else
            {
                trigger = CreateSimpleTrigger(entity);
            }
            // 告诉Quartz使用我们的触发器来安排作业
            await scheduler.ScheduleJob(job, trigger);
            return "添加成功";
        }
        /// <summary>
        /// 暂停/删除 指定的计划
        /// </summary>
        /// <param name="jobGroup">任务分组</param>
        /// <param name="jobName">任务名称</param>
        /// <param name="isDelete">停止并删除任务</param>
        /// <returns></returns>
        public async Task<string> StopOrDelScheduleJobAsync(string jobGroup, string jobName, bool isDelete = false)
        {
            try
            {
                await scheduler.PauseJob(new JobKey(jobName, jobGroup));
                if (isDelete)
                {
                    await scheduler.DeleteJob(new JobKey(jobName, jobGroup));
                    return "删除任务计划成功!";
                }
                else
                {
                    return "停止任务计划成功!";
                }
            }
            catch (Exception ex)
            {
                Log.Error(string.Format("停止任务计划失败!{0}", ex));
                return "停止任务计划失败" + ex.Message;
            }
        }
        /// <summary>
        /// 恢复运行暂停的任务
        /// </summary>
        /// <param name="jobName">任务名称</param>
        /// <param name="jobGroup">任务分组</param>
        public async Task<string> ResumeJobAsync(string jobGroup, string jobName)
        {
            try
            {
                //检查任务是否存在
                var jobKey = new JobKey(jobName, jobGroup);
                if (await scheduler.CheckExists(jobKey))
                {
                    var jobDetail = await scheduler.GetJobDetail(jobKey);
                    var endTime = jobDetail.JobDataMap.GetString("EndAt");
                    if (!string.IsNullOrWhiteSpace(endTime) && DateTime.Parse(endTime) <= DateTime.Now)
                    {
                        return "Job的结束时间已过期。";
                    }
                    else
                    {
                        //任务已经存在则暂停任务
                        await scheduler.ResumeJob(jobKey);
                        Log.Information(string.Format("任务“{0}”恢复运行", jobName));
                        return "恢复任务计划成功!";
                    }
                }
                else
                {
                    return "任务不存在";
                }
            }
            catch (Exception ex)
            {
                Log.Error(string.Format("恢复任务失败!{0}", ex));
                return "恢复任务计划失败!";
            }
        }
        /// <summary>
        /// 查询任务
        /// </summary>
        /// <param name="jobGroup"></param>
        /// <param name="jobName"></param>
        /// <returns></returns>
        public async Task<ScheduleEntity> QueryJobAsync(string jobGroup, string jobName)
        {
            var entity = new ScheduleEntity();
            var jobKey = new JobKey(jobName, jobGroup);
            var jobDetail = await scheduler.GetJobDetail(jobKey);
            var triggersList = await scheduler.GetTriggersOfJob(jobKey);
            var triggers = triggersList.AsEnumerable().FirstOrDefault();
            var intervalSeconds = (triggers as SimpleTriggerImpl)?.RepeatInterval.TotalSeconds;
            var endTime = jobDetail.JobDataMap.GetString("EndAt");
            entity.BeginTime = triggers.StartTimeUtc.LocalDateTime;
            if (!string.IsNullOrWhiteSpace(endTime)) entity.EndTime = DateTime.Parse(endTime);
            if (intervalSeconds.HasValue) entity.IntervalMilliseconds = Convert.ToInt32(intervalSeconds.Value);
            entity.JobGroup = jobGroup;
            entity.JobName = jobName;
            entity.Cron = (triggers as CronTriggerImpl)?.CronExpressionString;
            entity.RunTimes = (triggers as SimpleTriggerImpl)?.RepeatCount;
            entity.TriggerType = triggers is SimpleTriggerImpl ? TriggerTypeEnum.Simple : TriggerTypeEnum.Cron;
            entity.Description = jobDetail.Description;
            //旧代码没有保存JobTypeEnum,所以None可以默认为Url。
            entity.JobType = (JobTypeEnum)int.Parse(jobDetail.JobDataMap.GetString(Constant.JobTypeEnum) ?? "1");
            switch (entity.JobType)
            {
                case JobTypeEnum.None:
                    break;
                case JobTypeEnum.Url:
                    entity.RequestUrl = jobDetail.JobDataMap.GetString(Constant.REQUESTURL);
                    entity.RequestType = (RequestTypeEnum)int.Parse(jobDetail.JobDataMap.GetString(Constant.REQUESTTYPE));
                    entity.RequestParameters = jobDetail.JobDataMap.GetString(Constant.REQUESTPARAMETERS);
                    entity.Headers = jobDetail.JobDataMap.GetString(Constant.HEADERS);
                    break;
                case JobTypeEnum.BuiltIn:
                    break;
                case JobTypeEnum.Hotreload:
                    break;
                default:
                    break;
            }
            return entity;
        }
        /// <summary>
        /// 立即执行
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        public async Task<bool> TriggerJobAsync(JobKey jobKey)
        {
            await scheduler.TriggerJob(jobKey);
            return true;
        }
        /// <summary>
        /// 获取job日志
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        public async Task<List<string>> GetJobLogsAsync(JobKey jobKey)
        {
            var jobDetail = await scheduler.GetJobDetail(jobKey);
            return jobDetail.JobDataMap[Constant.LOGLIST] as List<string>;
        }
        /// <summary>
        /// 获取运行次数
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        public async Task<long> GetRunNumberAsync(JobKey jobKey)
        {
            var jobDetail = await scheduler.GetJobDetail(jobKey);
            return jobDetail.JobDataMap.GetLong(Constant.RUNNUMBER);
        }
        /// <summary>
        /// 获取所有Job(详情信息 - 初始化页面调用)
        /// </summary>
        /// <returns></returns>
        public async Task<List<JobInfoEntity>> GetAllJobAsync()
        {
            List<JobKey> jboKeyList = new List<JobKey>();
            List<JobInfoEntity> jobInfoList = new List<JobInfoEntity>();
            var groupNames = await scheduler.GetJobGroupNames();
            foreach (var groupName in groupNames.OrderBy(t => t))
            {
                jboKeyList.AddRange(await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)));
                jobInfoList.Add(new JobInfoEntity() { GroupName = groupName });
            }
            foreach (var jobKey in jboKeyList.OrderBy(t => t.Name))
            {
                var jobDetail = await scheduler.GetJobDetail(jobKey);
                var triggersList = await scheduler.GetTriggersOfJob(jobKey);
                var triggers = triggersList.AsEnumerable().FirstOrDefault();
                var interval = string.Empty;
                if (triggers is SimpleTriggerImpl)
                    interval = (triggers as SimpleTriggerImpl)?.RepeatInterval.ToString();
                else
                    interval = (triggers as CronTriggerImpl)?.CronExpressionString;
                foreach (var jobInfo in jobInfoList)
                {
                    if (jobInfo.GroupName == jobKey.Group)
                    {
                        //旧代码没有保存JobTypeEnum,所以None可以默认为Url。
                        var jobType = (JobTypeEnum)jobDetail.JobDataMap.GetLong(Constant.JobTypeEnum);
                        jobType = jobType == JobTypeEnum.None ? JobTypeEnum.Url : jobType;
                        var triggerAddress = string.Empty;
                        if (jobType == JobTypeEnum.Url)
                            triggerAddress = jobDetail.JobDataMap.GetString(Constant.REQUESTURL);
                        else if (jobType == JobTypeEnum.BuiltIn)
                            triggerAddress = jobDetail.JobDataMap.GetString(Constant.BuiltIn);
                        jobInfo.JobInfoList.Add(new JobInfo()
                        {
                            Name = jobKey.Name,
                            LastErrMsg = jobDetail.JobDataMap.GetString(Constant.EXCEPTION),
                            TriggerAddress = triggerAddress,
                            TriggerState = await scheduler.GetTriggerState(triggers.Key),
                            PreviousFireTime = triggers.GetPreviousFireTimeUtc()?.LocalDateTime,
                            NextFireTime = triggers.GetNextFireTimeUtc()?.LocalDateTime,
                            BeginTime = triggers.StartTimeUtc.LocalDateTime,
                            Interval = interval,
                            EndTime = triggers.EndTimeUtc?.LocalDateTime,
                            Description = jobDetail.Description,
                            RequestType = jobDetail.JobDataMap.GetString(Constant.REQUESTTYPE),
                            RunNumber = jobDetail.JobDataMap.GetLong(Constant.RUNNUMBER),
                            JobType = (long)jobType
                            //(triggers as SimpleTriggerImpl)?.TimesTriggered
                            //CronTriggerImpl 中没有 TimesTriggered 所以自己RUNNUMBER记录
                        });
                        continue;
                    }
                }
            }
            return jobInfoList;
        }
        /// <summary>
        /// 移除异常信息
        /// 因为只能在IJob持久化操作JobDataMap,所有这里直接暴力操作数据库。
        /// </summary>
        /// <param name="jobGroup"></param>
        /// <param name="jobName"></param>
        /// <returns></returns>
        //public async Task<bool> RemoveErrLog(string jobGroup, string jobName)
        //{
        //    IRepositorie logRepositorie = RepositorieFactory.CreateRepositorie(driverDelegateType, dbProvider);
        //    if (logRepositorie == null) return false;
        //    await logRepositorie.RemoveErrLogAsync(jobGroup, jobName);
        //    var jobKey = new JobKey(jobName, jobGroup);
        //    var jobDetail = await scheduler.GetJobDetail(jobKey);
        //    jobDetail.JobDataMap[Constant.EXCEPTION] = string.Empty;
        //    return true;
        //}
        /// <summary>
        /// 获取所有Job信息(简要信息 - 刷新数据的时候使用)
        /// </summary>
        /// <returns></returns>
        public async Task<List<JobBriefInfoEntity>> GetAllJobBriefInfoAsync()
        {
            List<JobKey> jboKeyList = new List<JobKey>();
            List<JobBriefInfoEntity> jobInfoList = new List<JobBriefInfoEntity>();
            var groupNames = await scheduler.GetJobGroupNames();
            foreach (var groupName in groupNames.OrderBy(t => t))
            {
                jboKeyList.AddRange(await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)));
                jobInfoList.Add(new JobBriefInfoEntity() { GroupName = groupName });
            }
            foreach (var jobKey in jboKeyList.OrderBy(t => t.Name))
            {
                var jobDetail = await scheduler.GetJobDetail(jobKey);
                var triggersList = await scheduler.GetTriggersOfJob(jobKey);
                var triggers = triggersList.AsEnumerable().FirstOrDefault();
                foreach (var jobInfo in jobInfoList)
                {
                    if (jobInfo.GroupName == jobKey.Group)
                    {
                        jobInfo.JobInfoList.Add(new JobBriefInfo()
                        {
                            Name = jobKey.Name,
                            LastErrMsg = jobDetail?.JobDataMap.GetString(Constant.EXCEPTION),
                            TriggerState = await scheduler.GetTriggerState(triggers.Key),
                            PreviousFireTime = triggers.GetPreviousFireTimeUtc()?.LocalDateTime,
                            NextFireTime = triggers.GetNextFireTimeUtc()?.LocalDateTime,
                            RunNumber = jobDetail?.JobDataMap.GetLong(Constant.RUNNUMBER) ?? 0
                        });
                        continue;
                    }
                }
            }
            return jobInfoList;
        }
        /// <summary>
        /// 停止任务调度
        /// </summary>
        public async Task<bool> StopScheduleAsync()
        {
            //判断调度是否已经关闭
            if (!scheduler.InStandbyMode)
            {
                //等待任务运行完成
                await scheduler.Standby(); //TODO  注意:Shutdown后Start会报错,所以这里使用暂停。
                Log.Information("任务调度暂停!");
            }
            return !scheduler.InStandbyMode;
        }
        /// <summary>
        /// 创建类型Simple的触发器
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        private ITrigger CreateSimpleTrigger(ScheduleEntity entity)
        {
            //作业触发器
            if (entity.RunTimes.HasValue && entity.RunTimes > 0)
            {
                return TriggerBuilder.Create()
               .WithIdentity(entity.JobName, entity.JobGroup)
               .StartAt(entity.BeginTime)//开始时间
                                         //.EndAt(entity.EndTime)//结束数据
               .WithSimpleSchedule(x =>
               {
                   x.WithInterval(TimeSpan.FromMilliseconds(entity.IntervalMilliseconds.Value)) // 执行时间间隔,单位毫秒
                        .WithRepeatCount(entity.RunTimes.Value)//执行次数、默认从0开始
                        .WithMisfireHandlingInstructionFireNow();
               })
               .ForJob(entity.JobName, entity.JobGroup)//作业名称
               .Build();
            }
            else
            {
                return TriggerBuilder.Create()
                .WithIdentity(entity.JobName, entity.JobGroup)
                .StartAt(entity.BeginTime)//开始时间
                                          //.EndAt(entity.EndTime)//结束数据
                .WithSimpleSchedule(x =>
                {
                    x.WithInterval(TimeSpan.FromMilliseconds(entity.IntervalMilliseconds.Value)) // 执行时间间隔,单位毫秒
                        .RepeatForever() // 无限循环
                        .WithMisfireHandlingInstructionFireNow();
                })
                .ForJob(entity.JobName, entity.JobGroup) // 作业名称
                .Build();
            }
        }
        /// <summary>
        /// 创建类型Cron的触发器
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        private ITrigger CreateCronTrigger(ScheduleEntity entity)
        {
            // 作业触发器
            return TriggerBuilder.Create()
                   .WithIdentity(entity.JobName, entity.JobGroup)
                   .StartAt(entity.BeginTime)//开始时间
                                             //.EndAt(entity.EndTime)//结束时间
                   .WithCronSchedule(entity.Cron, cronScheduleBuilder => cronScheduleBuilder.WithMisfireHandlingInstructionFireAndProceed())//指定cron表达式
                   .ForJob(entity.JobName, entity.JobGroup)//作业名称
                   .Build();
        }
    }
}
Wms/Utility/Job/TestJob.cs
New file
@@ -0,0 +1,21 @@
using Quartz;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Utility
{
    public class TestJob : IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            Console.WriteLine($"【任务执行】:{DateTime.Now}");
            Console.WriteLine($"【触发时间】:{context.ScheduledFireTimeUtc?.LocalDateTime}");
            Console.WriteLine($"【下次触发时间】:{context.NextFireTimeUtc?.LocalDateTime}");
            await Task.CompletedTask;
        }
    }
}
Wms/Utility/Utility.csproj
@@ -19,6 +19,8 @@
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageReference Include="Quartz" Version="3.9.0" />
    <PackageReference Include="Quartz.Serialization.Json" Version="3.9.0" />
    <PackageReference Include="Serilog" Version="4.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
    <PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
@@ -26,6 +28,7 @@
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
    <PackageReference Include="SqlSugarCore" Version="5.1.3.43" />
    <PackageReference Include="System.Drawing.Common" Version="6.0.0" />
    <PackageReference Include="Talk.Extensions" Version="1.0.1.84" />
    <PackageReference Include="ZXing.Net" Version="0.16.7" />
  </ItemGroup>
Wms/Wms/Controllers/JobController.cs
New file
@@ -0,0 +1,179 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Quartz;
using System.Collections.Generic;
using System.Threading.Tasks;
using Utility.Entity;
using Utility;
namespace Wms.Controllers
{
    /// <summary>
    /// 任务调度
    /// </summary>
    [Route("api/[controller]/[Action]")]
    [EnableCors("AllowSameDomain")] //允许跨域
    [ServiceFilter(typeof(ApiResponseActionFilter))]
    public class JobController : ControllerBase
    {
        private SchedulerCenter scheduler;
        /// <summary>
        /// 任务调度对象
        /// </summary>
        /// <param name="schedulerCenter"></param>
        public JobController(SchedulerCenter schedulerCenter)
        {
            scheduler = schedulerCenter;
        }
        /// <summary>
        /// 添加任务
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> AddJob([FromBody] ScheduleEntity entity)
        {
            if (entity.TriggerType == TriggerTypeEnum.Cron &&
                     entity.Cron == "* * * * * ?")
            {
                return "不允许过频繁执行任务!";
            }
            return await scheduler.AddScheduleJobAsync(entity);
        }
        /// <summary>
        /// 暂停任务
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> StopJob([FromBody] JobKey job)
        {
            return await scheduler.StopOrDelScheduleJobAsync(job.Group, job.Name);
        }
        /// <summary>
        /// 删除任务
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> RemoveJob([FromBody] JobKey job)
        {
            return await scheduler.StopOrDelScheduleJobAsync(job.Group, job.Name, true);
        }
        /// <summary>
        /// 恢复运行暂停的任务
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> ResumeJob([FromBody] JobKey job)
        {
            return await scheduler.ResumeJobAsync(job.Group, job.Name);
        }
        /// <summary>
        /// 查询任务
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<ScheduleEntity> QueryJob([FromBody] JobKey job)
        {
            return await scheduler.QueryJobAsync(job.Group, job.Name);
        }
        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> ModifyJob([FromBody] ModifyJobInput entity)
        {
            var jobKey = new JobKey(entity.OldScheduleEntity.JobName, entity.OldScheduleEntity.JobGroup);
            var runNumber = await scheduler.GetRunNumberAsync(jobKey);
            await scheduler.StopOrDelScheduleJobAsync(entity.OldScheduleEntity.JobGroup, entity.OldScheduleEntity.JobName, true);
            await scheduler.AddScheduleJobAsync(entity.NewScheduleEntity, runNumber);
            return "修改计划任务成功!";
        }
        /// <summary>
        /// 立即执行
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<bool> TriggerJob([FromBody] JobKey job)
        {
            await scheduler.TriggerJobAsync(job);
            return true;
        }
        /// <summary>
        /// 获取job日志
        /// </summary>
        /// <param name="jobKey"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<List<string>> GetJobLogs([FromBody] JobKey jobKey)
        {
            var logs = await scheduler.GetJobLogsAsync(jobKey);
            logs?.Reverse();
            return logs;
        }
        /// <summary>
        /// 启动调度
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<bool> StartSchedule()
        {
            return await scheduler.StartScheduleAsync();
        }
        /// <summary>
        /// 停止调度
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<bool> StopSchedule()
        {
            return await scheduler.StopScheduleAsync();
        }
        /// <summary>
        /// 获取所有任务
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<List<JobInfoEntity>> GetAllJob()
        {
            return await scheduler.GetAllJobAsync();
        }
        /// <summary>
        /// 获取所有Job信息(简要信息 - 刷新数据的时候使用)
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<List<JobBriefInfoEntity>> GetAllJobBriefInfo()
        {
            return await scheduler.GetAllJobBriefInfoAsync();
        }
        ///// <summary>
        ///// 移除异常信息
        ///// </summary>
        ///// <param name="jobKey"></param>
        ///// <returns></returns>
        //[HttpPost]
        //public async Task<bool> RemoveErrLog([FromBody] JobKey jobKey)
        //{
        //    return await scheduler.RemoveErrLog(jobKey.Group, jobKey.Name);
        //}
    }
}
Wms/Wms/Startup.cs
@@ -21,6 +21,7 @@
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Http;
using SqlSugar;
using Utility.Job;
namespace Wms
{
@@ -125,6 +126,9 @@
            services.AddScoped<UnitOfWorkAttribute>();
            services.AddSingleton<ISqlSugarClient>(DataContext.Db); // 单例注册
            services.AddTransient<IUnitOfWork, SqlSugarUnitOfWork>(); // 事务与工作单元注册
            services.AddHostedService<HostedService>();
            services.AddSingleton<SchedulerCenter>();
        }
        public void ConfigureContainer(ContainerBuilder builder)
        {
Wms/Wms/Wms.csproj
@@ -28,6 +28,7 @@
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.32" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Quartz" Version="3.9.0" />
    <PackageReference Include="Serilog" Version="4.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
    <PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
Wms/Wms/appsettings.json
@@ -36,5 +36,9 @@
    "EditLocateUrl": "/api/WCSApi/EditLocatStatus" //同步修改储位信息
  },
  "Quartz": {
    "dbProviderName": "SqlServer",
    "connectionString": "Server=192.168.62.200;Database=QRTZ;User ID=sa;Password=sql2019;Integrated Security=False;"
  }
}