From 4e578fcfc6f4b1a57f3e03efafd5ede03d577783 Mon Sep 17 00:00:00 2001
From: Demo <Demo@DESKTOP-CPA90BF>
Date: 星期四, 20 六月 2024 16:47:58 +0800
Subject: [PATCH] 合并

---
 Wms/Utility/Job/HostedService.cs         |   30 +
 Wms/Utility/Job/SchedulerCenter.cs       |  509 +++++++++++++++++++
 Wms/Wms/Wms.csproj                       |    1 
 Wms/Utility/Enum/RequestTypeEnum.cs      |   15 
 Wms/Utility/Entity/JobInfoEntity.cs      |  115 ++++
 Wms/Utility/Enum/JobTypeEnum.cs          |   23 
 Wms/Utility/Enum/TriggerTypeEnum.cs      |   13 
 Wms/Wms/Startup.cs                       |    4 
 Wms/Utility/Entity/LogModel.cs           |   50 +
 Wms/Utility/Job/HttpJob.cs               |   86 +++
 Wms/Utility/Job/TestJob.cs               |   21 
 Wms/Utility/Job/JobBase.cs               |   93 +++
 Wms/Wms/appsettings.json                 |    4 
 Wms/Utility/Entity/JobBriefInfoEntity.cs |   89 +++
 Wms/Utility/Utility.csproj               |    3 
 Wms/Wms/Controllers/JobController.cs     |  179 ++++++
 Wms/Utility/Job/Constant.cs              |   61 ++
 Wms/Utility/Job/HttpHelper.cs            |  181 ++++++
 Wms/Utility/Entity/ScheduleEntity.cs     |   78 +++
 19 files changed, 1,555 insertions(+), 0 deletions(-)

diff --git a/Wms/Utility/Entity/JobBriefInfoEntity.cs b/Wms/Utility/Entity/JobBriefInfoEntity.cs
new file mode 100644
index 0000000..237f490
--- /dev/null
+++ b/Wms/Utility/Entity/JobBriefInfoEntity.cs
@@ -0,0 +1,89 @@
+锘縰sing 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 = "姝e父";
+                        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; }
+    }
+}
diff --git a/Wms/Utility/Entity/JobInfoEntity.cs b/Wms/Utility/Entity/JobInfoEntity.cs
new file mode 100644
index 0000000..082c403
--- /dev/null
+++ b/Wms/Utility/Entity/JobInfoEntity.cs
@@ -0,0 +1,115 @@
+锘縰sing 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 = "姝e父";
+                        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; }
+    }
+}
diff --git a/Wms/Utility/Entity/LogModel.cs b/Wms/Utility/Entity/LogModel.cs
new file mode 100644
index 0000000..606ed9d
--- /dev/null
+++ b/Wms/Utility/Entity/LogModel.cs
@@ -0,0 +1,50 @@
+锘縰sing 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; }
+    }
+}
diff --git a/Wms/Utility/Entity/ScheduleEntity.cs b/Wms/Utility/Entity/ScheduleEntity.cs
new file mode 100644
index 0000000..cebe9a5
--- /dev/null
+++ b/Wms/Utility/Entity/ScheduleEntity.cs
@@ -0,0 +1,78 @@
+锘縰sing 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>
+        /// 鎵ц闂撮殧鏃堕棿锛屽崟浣嶆绉掞紙濡傛灉鏈塁ron锛屽垯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>
+        /// 璇锋眰鍙傛暟锛圥ost锛孭ut璇锋眰鐢級
+        /// </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; }
+    }
+}
diff --git a/Wms/Utility/Enum/JobTypeEnum.cs b/Wms/Utility/Enum/JobTypeEnum.cs
new file mode 100644
index 0000000..f6c73ae
--- /dev/null
+++ b/Wms/Utility/Enum/JobTypeEnum.cs
@@ -0,0 +1,23 @@
+锘縰sing 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,
+    }
+}
diff --git a/Wms/Utility/Enum/RequestTypeEnum.cs b/Wms/Utility/Enum/RequestTypeEnum.cs
new file mode 100644
index 0000000..6d39210
--- /dev/null
+++ b/Wms/Utility/Enum/RequestTypeEnum.cs
@@ -0,0 +1,15 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Utility
+{
+    public enum RequestTypeEnum
+    {
+        None = 0,
+        Get = 1,
+        Post = 2,
+        Put = 4,
+        Delete = 8
+    }
+}
diff --git a/Wms/Utility/Enum/TriggerTypeEnum.cs b/Wms/Utility/Enum/TriggerTypeEnum.cs
new file mode 100644
index 0000000..5db9f49
--- /dev/null
+++ b/Wms/Utility/Enum/TriggerTypeEnum.cs
@@ -0,0 +1,13 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Utility
+{
+    public enum TriggerTypeEnum
+    {
+        None = 0,
+        Cron = 1,
+        Simple = 2,
+    }
+}
diff --git a/Wms/Utility/Job/Constant.cs b/Wms/Utility/Job/Constant.cs
new file mode 100644
index 0000000..454e8f0
--- /dev/null
+++ b/Wms/Utility/Job/Constant.cs
@@ -0,0 +1,61 @@
+锘縰sing 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";
+
+    }
+}
diff --git a/Wms/Utility/Job/HostedService.cs b/Wms/Utility/Job/HostedService.cs
new file mode 100644
index 0000000..c317706
--- /dev/null
+++ b/Wms/Utility/Job/HostedService.cs
@@ -0,0 +1,30 @@
+锘縰sing 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;
+        }
+    }
+}
diff --git a/Wms/Utility/Job/HttpHelper.cs b/Wms/Utility/Job/HttpHelper.cs
new file mode 100644
index 0000000..3a37432
--- /dev/null
+++ b/Wms/Utility/Job/HttpHelper.cs
@@ -0,0 +1,181 @@
+锘縰sing 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">璇锋眰鍙傛暟锛圝son瀛楃涓诧級</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)
+            {
+                //濡傛灉鏈塰eaders璁よ瘉绛変俊鎭紝鍒欐瘡涓姹傚疄渚嬩竴涓狧ttpClient
+                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)
+            {
+                //濡傛灉鏈塰eaders璁よ瘉绛変俊鎭紝鍒欐瘡涓姹傚疄渚嬩竴涓狧ttpClient
+                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">璇锋眰鍙傛暟锛圝son瀛楃涓诧級</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)
+            {
+                //濡傛灉鏈塰eaders璁よ瘉绛変俊鎭紝鍒欐瘡涓姹傚疄渚嬩竴涓狧ttpClient
+                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)
+            {
+                //濡傛灉鏈塰eaders璁よ瘉绛変俊鎭紝鍒欐瘡涓姹傚疄渚嬩竴涓狧ttpClient
+                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);
+            }
+        }
+    }
+}
diff --git a/Wms/Utility/Job/HttpJob.cs b/Wms/Utility/Job/HttpJob.cs
new file mode 100644
index 0000000..90a8381
--- /dev/null
+++ b/Wms/Utility/Job/HttpJob.cs
@@ -0,0 +1,86 @@
+锘縰sing 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
+            {
+                //杩欓噷闇�瑕佸拰璇锋眰鏂圭害瀹氬ソ杩斿洖缁撴灉绾﹀畾涓篐ttpResultModel妯″瀷
+                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; }
+    }
+}
diff --git a/Wms/Utility/Job/JobBase.cs b/Wms/Utility/Job/JobBase.cs
new file mode 100644
index 0000000..d6ba708
--- /dev/null
+++ b/Wms/Utility/Job/JobBase.cs
@@ -0,0 +1,93 @@
+锘縰sing 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);
+        }
+    }
+}
diff --git a/Wms/Utility/Job/SchedulerCenter.cs b/Wms/Utility/Job/SchedulerCenter.cs
new file mode 100644
index 0000000..73f3642
--- /dev/null
+++ b/Wms/Utility/Job/SchedulerCenter.cs
@@ -0,0 +1,509 @@
+锘縰sing 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>
+        /// 鍒濆鍖朌riverDelegateType
+        /// </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>
+        /// 鍒濆鍖朣cheduler
+        /// </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()
+        {
+            //鍒濆鍖朣cheduler
+            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;
+            //鏍¢獙鏄惁姝g‘鐨勬墽琛屽懆鏈熻〃杈惧紡
+            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;
+            //鏃т唬鐮佹病鏈変繚瀛楯obTypeEnum锛屾墍浠one鍙互榛樿涓篣rl銆�
+            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>
+        /// 鑾峰彇鎵�鏈塉ob锛堣鎯呬俊鎭� - 鍒濆鍖栭〉闈㈣皟鐢級
+        /// </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)
+                    {
+                        //鏃т唬鐮佹病鏈変繚瀛楯obTypeEnum锛屾墍浠one鍙互榛樿涓篣rl銆�
+                        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 鎵�浠ヨ嚜宸盧UNNUMBER璁板綍
+                        });
+                        continue;
+                    }
+                }
+            }
+            return jobInfoList;
+        }
+        /// <summary>
+        /// 绉婚櫎寮傚父淇℃伅
+        /// 鍥犱负鍙兘鍦↖Job鎸佷箙鍖栨搷浣淛obDataMap锛屾墍鏈夎繖閲岀洿鎺ユ毚鍔涙搷浣滄暟鎹簱銆�
+        /// </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>
+        /// 鑾峰彇鎵�鏈塉ob淇℃伅锛堢畝瑕佷俊鎭� - 鍒锋柊鏁版嵁鐨勬椂鍊欎娇鐢級
+        /// </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  娉ㄦ剰锛歋hutdown鍚嶴tart浼氭姤閿欙紝鎵�浠ヨ繖閲屼娇鐢ㄦ殏鍋溿��
+                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();
+        }
+    }
+}
diff --git a/Wms/Utility/Job/TestJob.cs b/Wms/Utility/Job/TestJob.cs
new file mode 100644
index 0000000..8e53707
--- /dev/null
+++ b/Wms/Utility/Job/TestJob.cs
@@ -0,0 +1,21 @@
+锘縰sing 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;
+        }
+
+
+    }
+}
diff --git a/Wms/Utility/Utility.csproj b/Wms/Utility/Utility.csproj
index 9d18915..92ea9bb 100644
--- a/Wms/Utility/Utility.csproj
+++ b/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>
 
diff --git a/Wms/Wms/Controllers/JobController.cs b/Wms/Wms/Controllers/JobController.cs
new file mode 100644
index 0000000..f42da03
--- /dev/null
+++ b/Wms/Wms/Controllers/JobController.cs
@@ -0,0 +1,179 @@
+锘縰sing 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>
+        /// 鑾峰彇鎵�鏈塉ob淇℃伅锛堢畝瑕佷俊鎭� - 鍒锋柊鏁版嵁鐨勬椂鍊欎娇鐢級
+        /// </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);
+        //}
+    }
+}
diff --git a/Wms/Wms/Startup.cs b/Wms/Wms/Startup.cs
index 692d801..e2e8614 100644
--- a/Wms/Wms/Startup.cs
+++ b/Wms/Wms/Startup.cs
@@ -21,6 +21,7 @@
 using Microsoft.Extensions.Options;
 using Microsoft.AspNetCore.Http;
 using SqlSugar;
+using Utility.Job;
 
 namespace Wms
 {
@@ -128,6 +129,9 @@
             services.AddScoped<UnitOfWorkAttribute>();
             services.AddSingleton<ISqlSugarClient>(DataContext.Db); // 单例注册
             services.AddTransient<IUnitOfWork, SqlSugarUnitOfWork>(); // 事务与工作单元注册
+
+            services.AddHostedService<HostedService>();
+            services.AddSingleton<SchedulerCenter>();
         }
         public void ConfigureContainer(ContainerBuilder builder)
         {
diff --git a/Wms/Wms/Wms.csproj b/Wms/Wms/Wms.csproj
index 63bb75b..60a9af4 100644
--- a/Wms/Wms/Wms.csproj
+++ b/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" />
diff --git a/Wms/Wms/appsettings.json b/Wms/Wms/appsettings.json
index 3ae635d..1958cff 100644
--- a/Wms/Wms/appsettings.json
+++ b/Wms/Wms/appsettings.json
@@ -40,6 +40,10 @@
   "SignConfig": {
     "AppKey": "90170307d4184844ac2a26b431f79980", //楠岀
     "Minutes": 5 //楠岀鏃堕棿 5鍒嗛挓
+  },
+  "Quartz": {
+    "dbProviderName": "SqlServer",
+    "connectionString": "Server=192.168.62.200;Database=QRTZ;User ID=sa;Password=sql2019;Integrated Security=False;"
   }
 
 

--
Gitblit v1.8.0