NewLife/AntJob

[feat] 新增QuietTime,免打扰。设置免打扰时间段,该时间段内不生成作业任务,例如09:00-12:00,13:00-18:00
智能大石头 authored at 2025-06-06 16:20:27
8d5a443
Tree
1 Parent(s) 7b09610
Summary: 9 changed files with 131 additions and 1 deletions.
Modified +11 -0
Modified +34 -0
Modified +16 -0
Modified +1 -0
Modified +0 -0
Modified +2 -0
Modified +1 -1
Modified +1 -0
Added +65 -0
Modified +11 -0
diff --git a/AntJob.Data/Ant.htm b/AntJob.Data/Ant.htm
index f27fc8e..4cd51f0 100644
--- a/AntJob.Data/Ant.htm
+++ b/AntJob.Data/Ant.htm
@@ -1047,6 +1047,17 @@
         </tr>
 
         <tr>
+            <td>QuietTime</td>
+            <td>免打扰</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>设置免打扰时间段,例如09:00-12:00,13:00-18:00</td>
+        </tr>
+
+        <tr>
             <td>Data</td>
             <td>附加数据</td>
             <td>String</td>
Modified +34 -0
diff --git "a/AntJob.Data/Entity/\344\275\234\344\270\232.Biz.cs" "b/AntJob.Data/Entity/\344\275\234\344\270\232.Biz.cs"
index 824815d..7f4fff6 100644
--- "a/AntJob.Data/Entity/\344\275\234\344\270\232.Biz.cs"
+++ "b/AntJob.Data/Entity/\344\275\234\344\270\232.Biz.cs"
@@ -355,5 +355,39 @@ public partial class Job : EntityBase<Job>
             Mode = Mode,
         };
     }
+
+    /// <summary>检查是否免打扰模式</summary>
+    /// <returns></returns>
+    public Boolean CheckQuiet(DateTime time)
+    {
+        var qt = QuietTime;
+        if (qt.IsNullOrEmpty()) return false;
+
+        foreach (var item in qt.Split(",", StringSplitOptions.RemoveEmptyEntries))
+        {
+            var ss = item.Split("-");
+            if (ss.Length != 2) continue;
+
+            if (TimeSpan.TryParse(ss[0], out var start) &&
+                TimeSpan.TryParse(ss[1], out var end))
+            {
+                // 如果时间在免打扰时间段内,则返回true。需要判断time.TimeOfDay是否在start和end之间,注意跨天的情况
+                if (start < end)
+                {
+                    // 正常时间段,不跨天
+                    if (time.TimeOfDay >= start && time.TimeOfDay < end)
+                        return true;
+                }
+                else
+                {
+                    // 跨天时间段
+                    if (time.TimeOfDay >= start || time.TimeOfDay < end)
+                        return true;
+                }
+            }
+        }
+
+        return false;
+    }
     #endregion
 }
\ No newline at end of file
Modified +16 -0
diff --git "a/AntJob.Data/Entity/\344\275\234\344\270\232.cs" "b/AntJob.Data/Entity/\344\275\234\344\270\232.cs"
index 9c88fb5..d63a2a8 100644
--- "a/AntJob.Data/Entity/\344\275\234\344\270\232.cs"
+++ "b/AntJob.Data/Entity/\344\275\234\344\270\232.cs"
@@ -285,6 +285,14 @@ public partial class Job
     [BindColumn("LastTime", "最后时间。最后一次时间", "")]
     public DateTime LastTime { get => _LastTime; set { if (OnPropertyChanging("LastTime", value)) { _LastTime = value; OnPropertyChanged("LastTime"); } } }
 
+    private String _QuietTime;
+    /// <summary>免打扰。设置免打扰时间段,例如09:00-12:00,13:00-18:00</summary>
+    [DisplayName("免打扰")]
+    [Description("免打扰。设置免打扰时间段,例如09:00-12:00,13:00-18:00")]
+    [DataObjectField(false, false, true, 50)]
+    [BindColumn("QuietTime", "免打扰。设置免打扰时间段,例如09:00-12:00,13:00-18:00", "")]
+    public String QuietTime { get => _QuietTime; set { if (OnPropertyChanging("QuietTime", value)) { _QuietTime = value; OnPropertyChanged("QuietTime"); } } }
+
     private String _Data;
     /// <summary>附加数据。执行作业任务时附带的数据,可以是Json配置,也可以是Sql模板或C#模板</summary>
     [DisplayName("附加数据")]
@@ -414,6 +422,7 @@ public partial class Job
             "Speed" => _Speed,
             "LastStatus" => _LastStatus,
             "LastTime" => _LastTime,
+            "QuietTime" => _QuietTime,
             "Data" => _Data,
             "CreateUserID" => _CreateUserID,
             "CreateUser" => _CreateUser,
@@ -461,6 +470,7 @@ public partial class Job
                 case "Speed": _Speed = value.ToInt(); break;
                 case "LastStatus": _LastStatus = (JobStatus)value.ToInt(); break;
                 case "LastTime": _LastTime = value.ToDateTime(); break;
+                case "QuietTime": _QuietTime = Convert.ToString(value); break;
                 case "Data": _Data = Convert.ToString(value); break;
                 case "CreateUserID": _CreateUserID = value.ToInt(); break;
                 case "CreateUser": _CreateUser = Convert.ToString(value); break;
@@ -588,6 +598,9 @@ public partial class Job
         /// <summary>最后时间。最后一次时间</summary>
         public static readonly Field LastTime = FindByName("LastTime");
 
+        /// <summary>免打扰。设置免打扰时间段,例如09:00-12:00,13:00-18:00</summary>
+        public static readonly Field QuietTime = FindByName("QuietTime");
+
         /// <summary>附加数据。执行作业任务时附带的数据,可以是Json配置,也可以是Sql模板或C#模板</summary>
         public static readonly Field Data = FindByName("Data");
 
@@ -717,6 +730,9 @@ public partial class Job
         /// <summary>最后时间。最后一次时间</summary>
         public const String LastTime = "LastTime";
 
+        /// <summary>免打扰。设置免打扰时间段,例如09:00-12:00,13:00-18:00</summary>
+        public const String QuietTime = "QuietTime";
+
         /// <summary>附加数据。执行作业任务时附带的数据,可以是Json配置,也可以是Sql模板或C#模板</summary>
         public const String Data = "Data";
 
Modified +1 -0
diff --git a/AntJob.Data/Model.xml b/AntJob.Data/Model.xml
index e49ed6a..dc80c08 100644
--- a/AntJob.Data/Model.xml
+++ b/AntJob.Data/Model.xml
@@ -150,6 +150,7 @@
         <Column Name="Speed" DataType="Int32" Description="速度" Category="统计" />
         <Column Name="LastStatus" DataType="Int32" Description="最后状态。最后一次状态" Category="统计" Type="JobStatus" />
         <Column Name="LastTime" DataType="DateTime" Description="最后时间。最后一次时间" Category="统计" />
+        <Column Name="QuietTime" DataType="String" Description="免打扰。设置免打扰时间段,该时间段内不生成作业任务,例如09:00-12:00,13:00-18:00" />
         <Column Name="Data" DataType="String" Length="-1" Description="附加数据。执行作业任务时附带的数据,可以是Json配置,也可以是Sql模板或C#模板" />
         <Column Name="CreateUserID" DataType="Int32" Description="创建人" Category="扩展" />
         <Column Name="CreateUser" DataType="String" Description="创建者" Category="扩展" />
Modified +0 -0
diff --git a/AntJob.Data/xcodetool.exe b/AntJob.Data/xcodetool.exe
index 2a8e300..3092489 100644
Binary files a/AntJob.Data/xcodetool.exe and b/AntJob.Data/xcodetool.exe differ
Modified +2 -0
diff --git a/AntJob.Server/Services/JobService.cs b/AntJob.Server/Services/JobService.cs
index 38b8b3f..9033867 100644
--- a/AntJob.Server/Services/JobService.cs
+++ b/AntJob.Server/Services/JobService.cs
@@ -145,7 +145,9 @@ public class JobService(AppService appService, ICacheProvider cacheProvider, ITr
         app = App.FindByID(app.ID) ?? app;
         if (!app.Enable) return [];
 
+        // 作业是否启用,是否处于免打扰时间
         var job = app.Jobs.FirstOrDefault(e => e.Name == jobName);
+        if (job != null && (!job.Enable || job.CheckQuiet(DateTime.Now))) return [];
 
         // 全局锁,确保单个作业只有一个线程在分配作业
         using var ck = _cacheProvider.AcquireLock($"antjob:lock:{job.ID}", 15_000);
Modified +1 -1
diff --git a/AntJob.Web/Areas/Ant/Controllers/JobController.cs b/AntJob.Web/Areas/Ant/Controllers/JobController.cs
index aa3d2cc..2982f27 100644
--- a/AntJob.Web/Areas/Ant/Controllers/JobController.cs
+++ b/AntJob.Web/Areas/Ant/Controllers/JobController.cs
@@ -23,7 +23,7 @@ public class JobController : AntEntityController<Job>
         LogOnChange = true;
 
         ListFields.RemoveField("ClassName", "Step", "Cron", "Topic", "MessageCount", "Time", "End");
-        ListFields.RemoveField("Times", "Speed");
+        ListFields.RemoveField("Times", "Speed", "QuietTime");
         ListFields.RemoveField("MaxError", "MaxRetry", "MaxTime", "MaxRetain", "MaxIdle", "ErrorDelay", "Deadline");
         ListFields.RemoveCreateField().RemoveUpdateField();
         ListFields.AddListField("UpdateTime");
Modified +1 -0
diff --git a/AntTest/AntTest.csproj b/AntTest/AntTest.csproj
index d1c16f2..5b494b5 100644
--- a/AntTest/AntTest.csproj
+++ b/AntTest/AntTest.csproj
@@ -23,6 +23,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\AntJob.Data\AntJob.Data.csproj" />
     <ProjectReference Include="..\AntJob.Extensions\AntJob.Extensions.csproj" />
     <ProjectReference Include="..\AntJob\AntJob.csproj" />
   </ItemGroup>
Added +65 -0
diff --git a/AntTest/JobTests.cs b/AntTest/JobTests.cs
new file mode 100644
index 0000000..738ac27
--- /dev/null
+++ b/AntTest/JobTests.cs
@@ -0,0 +1,65 @@
+using System;
+using AntJob.Data.Entity;
+using Xunit;
+
+namespace AntTest;
+
+public class JobTests
+{
+    [Theory]
+    [InlineData("09:00-12:00", "2024-01-01 10:00", true)]  // 区间内
+    [InlineData("09:00-12:00", "2024-01-01 08:59", false)] // 区间外
+    [InlineData("09:00-12:00", "2024-01-01 12:00", false)] // 上界不含
+    public void CheckQuiet_SinglePeriod_SameDay(String quietTime, String time, Boolean expected)
+    {
+        var job = new Job { QuietTime = quietTime };
+        var dt = DateTime.Parse(time);
+        Assert.Equal(expected, job.CheckQuiet(dt));
+    }
+
+    [Theory]
+    [InlineData("23:00-02:00", "2024-01-01 23:30", true)]  // 跨天,前一天区间内
+    [InlineData("23:00-02:00", "2024-01-02 01:30", true)]  // 跨天,后一天区间内
+    [InlineData("23:00-02:00", "2024-01-01 22:59", false)] // 区间外
+    [InlineData("23:00-02:00", "2024-01-02 02:00", false)] // 上界不含
+    public void CheckQuiet_SinglePeriod_CrossDay(String quietTime, String time, Boolean expected)
+    {
+        var job = new Job { QuietTime = quietTime };
+        var dt = DateTime.Parse(time);
+        Assert.Equal(expected, job.CheckQuiet(dt));
+    }
+
+    [Theory]
+    [InlineData("09:00-12:00,13:00-18:00", "2024-01-01 10:00", true)]  // 第一个区间内
+    [InlineData("09:00-12:00,13:00-18:00", "2024-01-01 14:00", true)]  // 第二个区间内
+    [InlineData("09:00-12:00,13:00-18:00", "2024-01-01 12:30", false)] // 两区间外
+    public void CheckQuiet_MultiPeriod_SameDay(String quietTime, String time, Boolean expected)
+    {
+        var job = new Job { QuietTime = quietTime };
+        var dt = DateTime.Parse(time);
+        Assert.Equal(expected, job.CheckQuiet(dt));
+    }
+
+    [Theory]
+    [InlineData("23:00-02:00,09:00-12:00", "2024-01-01 23:30", true)]  // 跨天区间内
+    [InlineData("23:00-02:00,09:00-12:00", "2024-01-02 01:30", true)]  // 跨天区间内
+    [InlineData("23:00-02:00,09:00-12:00", "2024-01-01 10:00", true)]  // 当天区间内
+    [InlineData("23:00-02:00,09:00-12:00", "2024-01-01 08:00", false)] // 所有区间外
+    public void CheckQuiet_MultiPeriod_CrossDay(String quietTime, String time, Boolean expected)
+    {
+        var job = new Job { QuietTime = quietTime };
+        var dt = DateTime.Parse(time);
+        Assert.Equal(expected, job.CheckQuiet(dt));
+    }
+
+    [Theory]
+    [InlineData("23:00-02:00", "2024-01-01 01:00", true)]  // 跨天,凌晨,属于前一天的免打扰
+    [InlineData("23:00-02:00", "2024-01-02 01:00", true)]  // 跨天,凌晨,属于前一天的免打扰
+    [InlineData("23:00-02:00", "2024-01-01 02:00", false)] // 上界不含
+    public void CheckQuiet_CrossDay_EdgeCases(String quietTime, String time, Boolean expected)
+    {
+        var job = new Job { QuietTime = quietTime };
+        var dt = DateTime.Parse(time);
+        Assert.Equal(expected, job.CheckQuiet(dt));
+    }
+}