[feat] 新增QuietTime,免打扰。设置免打扰时间段,该时间段内不生成作业任务,例如09:00-12:00,13:00-18:00智能大石头 authored at 2025-06-06 16:20:27
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>
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
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";
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="扩展" />
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
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);
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");
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>
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));
+ }
+}