@elf-express/admin-net-mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +83 -88
- package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
- package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
- package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
- package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
- package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
- package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
- package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
- package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
- package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
- package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
- package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
- package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
- package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
- package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
- package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
- package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
- package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
- package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
- package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
- package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
- package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
- package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
- package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
- package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
- package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
- package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
- package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
- package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
- package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
- package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
- package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
- package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
- package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
- package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
- package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
- package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
- package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
- package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
- package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
- package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
- package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
- package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
- package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
- package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
- package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
- package/knowledge/attributes.md +153 -0
- package/knowledge/config.md +147 -0
- package/knowledge/entity.md +115 -0
- package/knowledge/event.md +124 -0
- package/knowledge/plugin.md +136 -0
- package/knowledge/service.md +146 -0
- package/knowledge/sqlsugar.md +172 -0
- package/knowledge/vue-typescript.md +338 -0
- package/package.json +3 -2
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
# 26.1 排程作業(定時任務)
|
|
2
|
+
|
|
3
|
+
> **版本說明**:僅適用於 Furion 4.8.0+(Sundial)。Furion 已內建此功能;非 Furion 框架可安裝 `Sundial` 或 `Sundial.Dashboard`。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 26.1.1 概述
|
|
8
|
+
|
|
9
|
+
排程作業(定時任務)是指在預設時間點或按設定週期自動觸發執行的任務。應用場景包括:資料備份、定時發送郵件/簡訊、報表生成、資料採集、DevOps 定時發佈等。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 26.1.2 快速入門
|
|
14
|
+
|
|
15
|
+
### 定義作業處理程式
|
|
16
|
+
|
|
17
|
+
```csharp
|
|
18
|
+
public class MyJob : IJob
|
|
19
|
+
{
|
|
20
|
+
private readonly ILogger<MyJob> _logger;
|
|
21
|
+
public MyJob(ILogger<MyJob> logger) => _logger = logger;
|
|
22
|
+
|
|
23
|
+
public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
|
|
24
|
+
{
|
|
25
|
+
_logger.LogInformation($"{context}");
|
|
26
|
+
return Task.CompletedTask;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 註冊 Schedule 服務
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
services.AddSchedule(options =>
|
|
35
|
+
{
|
|
36
|
+
options.AddJob<MyJob>(Triggers.Secondly()); // 每秒執行
|
|
37
|
+
options.AddJob<MyJob>("myjob", Triggers.Secondly()); // 指定作業 Id
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 多個觸發器
|
|
42
|
+
|
|
43
|
+
```csharp
|
|
44
|
+
options.AddJob<MyJob>(
|
|
45
|
+
Triggers.Minutely(),
|
|
46
|
+
Triggers.PeriodSeconds(5),
|
|
47
|
+
Triggers.Cron("3,7,8 * * * * ?", CronStringFormat.WithSeconds));
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 串行執行
|
|
51
|
+
|
|
52
|
+
```csharp
|
|
53
|
+
options.AddJob<MyJob>(concurrent: false, Triggers.Secondly());
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> 串行模式下,若上次未完成則跳過本次觸發。可透過 `options.LogEnabled = false` 關閉跳過警告日誌。
|
|
57
|
+
|
|
58
|
+
### HTTP 請求作業(4.8.7.7+)
|
|
59
|
+
|
|
60
|
+
```csharp
|
|
61
|
+
options.AddHttpJob(request =>
|
|
62
|
+
{
|
|
63
|
+
request.RequestUri = "https://www.chinadot.net";
|
|
64
|
+
request.HttpMethod = HttpMethod.Get;
|
|
65
|
+
// request.Body = "{}";
|
|
66
|
+
// request.Headers.Add("framework", "Furion");
|
|
67
|
+
// request.Timeout = 100_000;
|
|
68
|
+
// request.PrintResponseContent = true;
|
|
69
|
+
}, Triggers.PeriodSeconds(5));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 委託方式(動態)作業
|
|
73
|
+
|
|
74
|
+
```csharp
|
|
75
|
+
options.AddJob((context, stoppingToken) =>
|
|
76
|
+
{
|
|
77
|
+
context.ServiceProvider.GetLogger().LogInformation($"{context}");
|
|
78
|
+
return Task.CompletedTask;
|
|
79
|
+
}, Triggers.PeriodSeconds(5));
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 臨時性作業(4.9.7.67+)
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
options.AddJob<MyJob>(builder => builder.SetTemporary(), Triggers.PeriodMinutes(5));
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 設定作業組名稱(4.9.2.9+)
|
|
89
|
+
|
|
90
|
+
```csharp
|
|
91
|
+
options.GroupSet("group1", () =>
|
|
92
|
+
{
|
|
93
|
+
options.AddJob<MyJob>(Triggers.Secondly());
|
|
94
|
+
options.AddJob<MyJob>(Triggers.Hourly());
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 觸發器特性掃描
|
|
99
|
+
|
|
100
|
+
```csharp
|
|
101
|
+
options.AddJob(JobBuilder.Create<MyJob>().SetIncludeAnnotations(true));
|
|
102
|
+
// 或批次掃描
|
|
103
|
+
options.AddJob(App.EffectiveTypes.ScanToBuilders());
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```csharp
|
|
107
|
+
[Minutely]
|
|
108
|
+
[PeriodSeconds(5)]
|
|
109
|
+
[Cron("*/3 * * * * *", CronStringFormat.WithSeconds)]
|
|
110
|
+
public class MyJob : IJob { }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 執行階段動態操作
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
// 注入 ISchedulerFactory
|
|
117
|
+
_schedulerFactory.AddJob<MyJob>("動態作業 Id", Triggers.Secondly());
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 非 DI 環境使用(4.8.8.5+)
|
|
121
|
+
|
|
122
|
+
```csharp
|
|
123
|
+
await new ServiceCollection()
|
|
124
|
+
.AddSchedule(options => options.AddJob<MyJob>(Triggers.Period(5000)))
|
|
125
|
+
.GetScheduleHostedService()
|
|
126
|
+
.StartAsync(new CancellationTokenSource().Token);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 26.1.3 作業資訊 JobDetail
|
|
132
|
+
|
|
133
|
+
### 屬性
|
|
134
|
+
|
|
135
|
+
| 屬性 | 型別 | 預設值 | 說明 |
|
|
136
|
+
|------|------|--------|------|
|
|
137
|
+
| `JobId` | `string` | — | 作業 Id |
|
|
138
|
+
| `GroupName` | `string` | — | 作業組名稱 |
|
|
139
|
+
| `JobType` | `string` | — | 處理程式型別 FullName |
|
|
140
|
+
| `AssemblyName` | `string` | — | 程式集 Name |
|
|
141
|
+
| `Description` | `string` | — | 描述 |
|
|
142
|
+
| `Concurrent` | `bool` | `true` | 並行(true)/ 串行(false) |
|
|
143
|
+
| `IncludeAnnotations` | `bool` | `false` | 是否掃描觸發器特性 |
|
|
144
|
+
| `Properties` | `string` | `"{}"` | 額外資料 |
|
|
145
|
+
| `UpdatedTime` | `DateTime?` | — | 更新時間 |
|
|
146
|
+
|
|
147
|
+
### JobBuilder 建立方式
|
|
148
|
+
|
|
149
|
+
```csharp
|
|
150
|
+
JobBuilder.Create<MyJob>();
|
|
151
|
+
JobBuilder.Create("job1");
|
|
152
|
+
JobBuilder.Create("YourProject", "YourProject.MyJob");
|
|
153
|
+
JobBuilder.Create((context, stoppingToken) => { return Task.CompletedTask; });
|
|
154
|
+
JobBuilder.From(jobDetail); // 從執行階段建立
|
|
155
|
+
JobBuilder.From(jsonString); // 從 JSON 建立
|
|
156
|
+
JobBuilder.Clone(fromJobBuilder); // 克隆
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### JobBuilder 設定方法
|
|
160
|
+
|
|
161
|
+
`.SetJobId()` / `.SetGroupName()` / `.SetJobType<T>()` / `.SetDescription()` / `.SetConcurrent()` / `.SetIncludeAnnotations()` / `.SetProperties()` / `.SetDynamicExecuteAsync()` / `.SetTemporary()` / `.LoadFrom()`
|
|
162
|
+
|
|
163
|
+
### 額外資料操作
|
|
164
|
+
|
|
165
|
+
```csharp
|
|
166
|
+
jobDetail.GetProperty<int>("count");
|
|
167
|
+
jobDetail.AddOrUpdateProperty("count", count + 1);
|
|
168
|
+
jobDetail.RemoveProperty("key");
|
|
169
|
+
jobDetail.ClearProperties();
|
|
170
|
+
jobDetail.ContainsProperty("key"); // 4.9.2.32+
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
> 額外資料值僅支援 `int32`、`int64`、`string`、`bool`、`null` 或其陣列。
|
|
174
|
+
|
|
175
|
+
### 輸出格式
|
|
176
|
+
|
|
177
|
+
```csharp
|
|
178
|
+
jobDetail.ConvertToJSON();
|
|
179
|
+
jobDetail.ConvertToSQL("tableName", PersistenceBehavior.Appended, NamingConventions.CamelCase);
|
|
180
|
+
jobDetail.ConvertToMonitor();
|
|
181
|
+
jobDetail.ToString();
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 啟用自動日誌(4.8.3.7+)
|
|
185
|
+
|
|
186
|
+
```csharp
|
|
187
|
+
options.JobDetail.LogEnabled = true;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 26.1.4 作業處理程式 IJob
|
|
193
|
+
|
|
194
|
+
### JobExecutingContext 上下文
|
|
195
|
+
|
|
196
|
+
| 屬性 | 說明 |
|
|
197
|
+
|------|------|
|
|
198
|
+
| `JobId` | 作業 Id |
|
|
199
|
+
| `TriggerId` | 觸發器 Id |
|
|
200
|
+
| `JobDetail` | 作業資訊 |
|
|
201
|
+
| `Trigger` | 觸發器 |
|
|
202
|
+
| `OccurrenceTime` | 計畫觸發時間(最準確) |
|
|
203
|
+
| `ExecutingTime` | 實際執行時間 |
|
|
204
|
+
| `RunId` | 本次執行唯一 Id(4.8.5.1+) |
|
|
205
|
+
| `Result` | 設定/讀取執行結果(4.8.7.7+) |
|
|
206
|
+
| `ServiceProvider` | 服務提供器(4.8.7.10+) |
|
|
207
|
+
| `Mode` | 觸發方式:定時/手動(4.9.3.1+) |
|
|
208
|
+
|
|
209
|
+
### 作業處理程式實例(4.8.8.13+)
|
|
210
|
+
|
|
211
|
+
預設每次觸發都建立新實例。推薦高頻作業註冊為**單例**:
|
|
212
|
+
|
|
213
|
+
```csharp
|
|
214
|
+
services.AddSingleton<MyJob>();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
自訂工廠:
|
|
218
|
+
|
|
219
|
+
```csharp
|
|
220
|
+
public class JobFactory : IJobFactory
|
|
221
|
+
{
|
|
222
|
+
public IJob CreateJob(IServiceProvider sp, JobFactoryContext ctx)
|
|
223
|
+
=> ActivatorUtilities.GetServiceOrCreateInstance(sp, ctx.JobType) as IJob;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
options.AddJobFactory<JobFactory>();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 異常處理與重試
|
|
230
|
+
|
|
231
|
+
```csharp
|
|
232
|
+
options.AddJob<MyJob>(Triggers.PeriodSeconds(3).SetNumRetries(3));
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
> 異常僅影響當次觸發,不影響下次執行。推薦使用作業執行器 `IJobExecutor` 設定全域重試策略。
|
|
236
|
+
|
|
237
|
+
### 異常回退策略(4.8.8.6+)
|
|
238
|
+
|
|
239
|
+
```csharp
|
|
240
|
+
public class TestJob : IJob
|
|
241
|
+
{
|
|
242
|
+
public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { throw new Exception(); }
|
|
243
|
+
public Task FallbackAsync(JobExecutedContext context, CancellationToken stoppingToken)
|
|
244
|
+
{
|
|
245
|
+
Console.WriteLine("回退被呼叫");
|
|
246
|
+
return Task.CompletedTask;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 上下文資料(4.9.7.82+)
|
|
252
|
+
|
|
253
|
+
```csharp
|
|
254
|
+
public IDictionary<string, object> GetContextData()
|
|
255
|
+
=> new Dictionary<string, object> { { "db", _dbContext } };
|
|
256
|
+
|
|
257
|
+
// 在監視器/執行器中取得
|
|
258
|
+
var db = context.GetItem<IDbContext>("db");
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Roslyn 動態建立(4.8.8.7+)
|
|
262
|
+
|
|
263
|
+
```csharp
|
|
264
|
+
var jobAssembly = Schedular.CompileCSharpClassCode(@"/* C# 程式碼 */");
|
|
265
|
+
var jobType = jobAssembly.GetType("YourProject.MyJob");
|
|
266
|
+
_schedulerFactory.AddJob(jobType, Triggers.PeriodSeconds(5));
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 26.1.5 作業觸發器 Trigger
|
|
272
|
+
|
|
273
|
+
### 屬性
|
|
274
|
+
|
|
275
|
+
| 屬性 | 型別 | 預設值 | 說明 |
|
|
276
|
+
|------|------|--------|------|
|
|
277
|
+
| `TriggerId` | `string` | — | 觸發器 Id |
|
|
278
|
+
| `JobId` | `string` | — | 作業 Id |
|
|
279
|
+
| `TriggerType` / `AssemblyName` | `string` | — | 觸發器型別 |
|
|
280
|
+
| `Args` | `string` | — | 初始化參數(`object[]` 序列化) |
|
|
281
|
+
| `Status` | `TriggerStatus` | `Ready` | 觸發器狀態 |
|
|
282
|
+
| `StartTime` / `EndTime` | `DateTime?` | — | 起止時間 |
|
|
283
|
+
| `LastRunTime` / `NextRunTime` | `DateTime?` | — | 上次/下次執行時間 |
|
|
284
|
+
| `NumberOfRuns` / `MaxNumberOfRuns` | `long` | `0` | 觸發次數 / 最大次數(0=不限) |
|
|
285
|
+
| `NumberOfErrors` / `MaxNumberOfErrors` | `long` | `0` | 出錯次數 / 最大出錯(0=不限) |
|
|
286
|
+
| `NumRetries` | `int` | `0` | 重試次數 |
|
|
287
|
+
| `RetryTimeout` | `int` | `1000` | 重試間隔(ms) |
|
|
288
|
+
| `StartNow` | `bool` | `true` | 是否立即啟動 |
|
|
289
|
+
| `RunOnStart` | `bool` | `false` | 啟動時執行一次 |
|
|
290
|
+
| `ResetOnlyOnce` | `bool` | `true` | 重啟時重置僅一次的作業 |
|
|
291
|
+
| `Result` | `string` | — | 執行結果(4.8.7.7+) |
|
|
292
|
+
| `ElapsedTime` | `long` | `0` | 執行耗時 ms(4.8.7.7+) |
|
|
293
|
+
|
|
294
|
+
### TriggerStatus 列舉
|
|
295
|
+
|
|
296
|
+
`Backlog(0)` / `Ready(1)` / `Running(2)` / `Pause(3)` / `Blocked(4)` / `ErrorToReady(5)` / `Archived(6)` / `Panic(7)` / `Overrun(8)` / `Unoccupied(9)` / `NotStart(10)` / `Unknown(11)` / `Unhandled(12)`
|
|
297
|
+
|
|
298
|
+
### 內建觸發器速查(Triggers 靜態類)
|
|
299
|
+
|
|
300
|
+
| 方法 | 說明 |
|
|
301
|
+
|------|------|
|
|
302
|
+
| `Period(ms)` / `PeriodSeconds(n)` / `PeriodMinutes(n)` / `PeriodHours(n)` | 間隔觸發 |
|
|
303
|
+
| `Cron(expr, format)` | Cron 表達式 |
|
|
304
|
+
| `Secondly()` / `Minutely()` / `Hourly()` / `Daily()` / `Monthly()` / `Weekly()` / `Yearly()` / `Workday()` | 週期觸發 |
|
|
305
|
+
| `Secondly(n)` / `Minutely(n)` / `Hourly(n)` | 間隔 n 秒/分/時(4.9.7.224+) |
|
|
306
|
+
| `SecondlyAt(3,5,6)` / `MinutelyAt(3)` / `HourlyAt(3)` / `DailyAt(3)` / `MonthlyAt(3)` / `WeeklyAt(3,"FRI")` / `YearlyAt(3,"MAR")` | Macro At 方式 |
|
|
307
|
+
| `At("2025-11-26 13:20:13")` | 指定時間一次性觸發(4.9.7.214+) |
|
|
308
|
+
|
|
309
|
+
### TriggerBuilder 建立方式
|
|
310
|
+
|
|
311
|
+
```csharp
|
|
312
|
+
Triggers.PeriodSeconds(5);
|
|
313
|
+
Triggers.Cron("* * * * *");
|
|
314
|
+
TriggerBuilder.Create<CronTrigger>("* * * * *", CronStringFormat.Default);
|
|
315
|
+
TriggerBuilder.From(trigger); // 從執行階段
|
|
316
|
+
TriggerBuilder.From(jsonString); // 從 JSON
|
|
317
|
+
TriggerBuilder.Clone(from); // 克隆
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### TriggerBuilder 設定方法
|
|
321
|
+
|
|
322
|
+
`.SetTriggerId()` / `.SetDescription()` / `.SetStatus()` / `.SetStartTime()` / `.SetEndTime()` / `.SetMaxNumberOfRuns()` / `.SetNumRetries()` / `.SetRetryTimeout()` / `.SetStartNow()` / `.SetRunOnStart()` / `.SetResult()` 等。
|
|
323
|
+
|
|
324
|
+
### 更改觸發時間(AlterTo)(4.8.7.31+)
|
|
325
|
+
|
|
326
|
+
```csharp
|
|
327
|
+
triggerBuilder.AlterToPeriodSeconds(5);
|
|
328
|
+
triggerBuilder.AlterToCron("* * * * *");
|
|
329
|
+
triggerBuilder.AlterToSecondly();
|
|
330
|
+
triggerBuilder.AlterToMinutelyAt(3, 5, 6);
|
|
331
|
+
triggerBuilder.AlterTo<CustomTrigger>(30);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 自訂觸發器
|
|
335
|
+
|
|
336
|
+
```csharp
|
|
337
|
+
public class CustomTrigger : Trigger
|
|
338
|
+
{
|
|
339
|
+
public CustomTrigger(int seconds) => Seconds = seconds;
|
|
340
|
+
private int Seconds { get; set; }
|
|
341
|
+
public override DateTime? GetNextOccurrence(DateTime startAt) => startAt.AddSeconds(Seconds);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
options.AddJob<MyJob>(Triggers.Create<CustomTrigger>(3));
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 觸發器特性
|
|
348
|
+
|
|
349
|
+
`[Period]` / `[PeriodSeconds]` / `[PeriodMinutes]` / `[PeriodHours]` / `[Cron]` / `[Secondly]` / `[Minutely]` / `[Hourly]` / `[Daily]` / `[Monthly]` / `[Weekly]` / `[Yearly]` / `[Workday]` / `[SecondlyAt]` / `[MinutelyAt]` / `[HourlyAt]` / `[DailyAt]` / `[MonthlyAt]` / `[WeeklyAt]` / `[YearlyAt]` / `[At]`
|
|
350
|
+
|
|
351
|
+
### 查看最近運行記錄(4.8.4.3+)
|
|
352
|
+
|
|
353
|
+
```csharp
|
|
354
|
+
var timelines = trigger.GetTimelines(); // 最近 5 條
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 26.1.6 作業計畫 Scheduler
|
|
360
|
+
|
|
361
|
+
### SchedulerBuilder 建立
|
|
362
|
+
|
|
363
|
+
```csharp
|
|
364
|
+
SchedulerBuilder.Create<MyJob>(Triggers.PeriodSeconds(5), Triggers.Minutely());
|
|
365
|
+
SchedulerBuilder.Create(jobBuilder, triggerBuilder1, triggerBuilder2);
|
|
366
|
+
SchedulerBuilder.From(scheduler); // 從 IScheduler
|
|
367
|
+
SchedulerBuilder.From(jsonString); // 從 JSON
|
|
368
|
+
SchedulerBuilder.Clone(from);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### SchedulerBuilder 操作
|
|
372
|
+
|
|
373
|
+
```csharp
|
|
374
|
+
schedulerBuilder.GetJobBuilder();
|
|
375
|
+
schedulerBuilder.GetTriggerBuilders();
|
|
376
|
+
schedulerBuilder.AddTriggerBuilder(triggerBuilder);
|
|
377
|
+
schedulerBuilder.UpdateTriggerBuilder(triggerBuilder);
|
|
378
|
+
schedulerBuilder.RemoveTriggerBuilder("triggerId");
|
|
379
|
+
schedulerBuilder.ConvertToJSON();
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 持久化標記
|
|
383
|
+
|
|
384
|
+
`.Appended()` → INSERT / `.Updated()` → UPDATE / `.Removed()` → DELETE
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 26.1.7 ScheduleOptionsBuilder 設定
|
|
389
|
+
|
|
390
|
+
```csharp
|
|
391
|
+
services.AddSchedule(options =>
|
|
392
|
+
{
|
|
393
|
+
options.UseUtcTimestamp = false;
|
|
394
|
+
options.LogEnabled = true;
|
|
395
|
+
options.ClusterId = "cluster1";
|
|
396
|
+
options.BuildSqlType = SqlTypes.SqlServer;
|
|
397
|
+
options.JobDetail.LogEnabled = true;
|
|
398
|
+
options.JobDetail.ConvertToSQL = (tableName, cols, detail, behavior, naming) => { };
|
|
399
|
+
options.Trigger.ConvertToSQL = (tableName, cols, trigger, behavior, naming) => { };
|
|
400
|
+
options.UnobservedTaskExceptionHandler = (obj, args) => { };
|
|
401
|
+
options.RunOnStartProvider = (trigger, checkTime) => { };
|
|
402
|
+
|
|
403
|
+
// 作業註冊
|
|
404
|
+
options.AddJob<MyJob>(Triggers.Secondly());
|
|
405
|
+
options.AddHttpJob(request => { }, Triggers.PeriodSeconds(5));
|
|
406
|
+
options.AddMonitor<YourJobMonitor>();
|
|
407
|
+
options.AddExecutor<YourJobExecutor>();
|
|
408
|
+
options.AddPersistence<DbJobPersistence>();
|
|
409
|
+
options.AddClusterServer<JobClusterServer>();
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## 26.1.8 作業監視器 IJobMonitor
|
|
416
|
+
|
|
417
|
+
```csharp
|
|
418
|
+
public class YourJobMonitor : IJobMonitor
|
|
419
|
+
{
|
|
420
|
+
public Task OnExecutingAsync(JobExecutingContext context, CancellationToken ct)
|
|
421
|
+
{
|
|
422
|
+
// 執行之前
|
|
423
|
+
return Task.CompletedTask;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
public Task OnExecutedAsync(JobExecutedContext context, CancellationToken ct)
|
|
427
|
+
{
|
|
428
|
+
// 執行之後(context.Exception 可取得異常)
|
|
429
|
+
return Task.CompletedTask;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
options.AddMonitor<YourJobMonitor>();
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 26.1.9 作業執行器 IJobExecutor
|
|
439
|
+
|
|
440
|
+
### 重試策略
|
|
441
|
+
|
|
442
|
+
```csharp
|
|
443
|
+
public class YourJobExecutor : IJobExecutor
|
|
444
|
+
{
|
|
445
|
+
public async Task ExecuteAsync(JobExecutingContext context, IJob jobHandler, CancellationToken ct)
|
|
446
|
+
{
|
|
447
|
+
await Retry.InvokeAsync(async () => await jobHandler.ExecuteAsync(context, ct), 3, 1000);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 逾時控制
|
|
453
|
+
|
|
454
|
+
```csharp
|
|
455
|
+
public async Task ExecuteAsync(JobExecutingContext context, IJob jobHandler, CancellationToken ct)
|
|
456
|
+
{
|
|
457
|
+
await jobHandler.ExecuteAsync(context, ct).WaitAsync(TimeSpan.FromMilliseconds(3000));
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## 26.1.10 作業計畫工廠 ISchedulerFactory
|
|
464
|
+
|
|
465
|
+
單例服務,提供執行階段操作作業的完整 API。
|
|
466
|
+
|
|
467
|
+
### 常用方法速查
|
|
468
|
+
|
|
469
|
+
| 方法 | 說明 |
|
|
470
|
+
|------|------|
|
|
471
|
+
| `GetJobs()` / `GetJobs("group")` | 查詢所有/分組作業 |
|
|
472
|
+
| `GetNextRunJobs(DateTime.Now)` | 下一批觸發的作業 |
|
|
473
|
+
| `TryGetJob("jobId", out scheduler)` / `GetJob("jobId")` | 取得單個作業 |
|
|
474
|
+
| `TrySaveJob(builder, out scheduler)` | 儲存(新增/更新/刪除) |
|
|
475
|
+
| `TryAddJob<T>(triggers, out scheduler)` | 新增作業 |
|
|
476
|
+
| `TryUpdateJob(builder, out scheduler)` | 更新作業 |
|
|
477
|
+
| `TryRemoveJob("jobId", out scheduler)` | 刪除作業 |
|
|
478
|
+
| `ContainsJob("jobId")` | 檢查是否存在 |
|
|
479
|
+
| `StartAll()` / `PauseAll()` / `RemoveAll()` | 全部啟動/暫停/刪除 |
|
|
480
|
+
| `TryRunJob("jobId", out scheduler)` | 手動執行(4.8.7.11+) |
|
|
481
|
+
| `TryCancelJob("jobId", out scheduler)` | 取消執行(4.9.1.9+) |
|
|
482
|
+
| `TryStartJob("jobId")` / `TryPauseJob("jobId")` | 啟停單個作業(4.9.2.16+) |
|
|
483
|
+
| `CancelSleep()` | 強制喚醒排程器 |
|
|
484
|
+
| `GCCollect()` | 觸發 GC 回收(4.9.1.23+) |
|
|
485
|
+
| `OnChanged` | 作業變更事件(4.8.8.29+) |
|
|
486
|
+
| `OnExecutionRecord` | 觸發記錄通知(4.9.1.16+) |
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## 26.1.11 作業計畫 IScheduler
|
|
491
|
+
|
|
492
|
+
單個作業操作介面。
|
|
493
|
+
|
|
494
|
+
```csharp
|
|
495
|
+
scheduler.GetJobDetail();
|
|
496
|
+
scheduler.GetTriggers();
|
|
497
|
+
scheduler.GetTriggerBuilder("triggerId");
|
|
498
|
+
scheduler.TryAddTrigger(triggerBuilder, out trigger);
|
|
499
|
+
scheduler.TryUpdateTrigger(triggerBuilder, out trigger);
|
|
500
|
+
scheduler.TryRemoveTrigger("triggerId", out trigger);
|
|
501
|
+
scheduler.UpdateDetail(jobBuilder);
|
|
502
|
+
scheduler.Start() / .Pause() / .Remove();
|
|
503
|
+
scheduler.Run() / .Run("triggerId"); // 手動執行
|
|
504
|
+
scheduler.Cancel() / .Cancel("triggerId"); // 取消執行
|
|
505
|
+
scheduler.Reload(); // 強制重新整理
|
|
506
|
+
scheduler.ConvertToJSON();
|
|
507
|
+
scheduler.GetEnumerable(); // 轉可枚舉字典
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## 26.1.12 作業持久化器 IJobPersistence
|
|
513
|
+
|
|
514
|
+
```csharp
|
|
515
|
+
public class DbJobPersistence : IJobPersistence
|
|
516
|
+
{
|
|
517
|
+
public Task<IEnumerable<SchedulerBuilder>> PreloadAsync(CancellationToken ct)
|
|
518
|
+
{
|
|
519
|
+
// 啟動時從資料庫載入作業
|
|
520
|
+
return Task.FromResult(Enumerable.Empty<SchedulerBuilder>());
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
public Task<SchedulerBuilder> OnLoadingAsync(SchedulerBuilder builder, CancellationToken ct)
|
|
524
|
+
{
|
|
525
|
+
// 初始化時進一步修改(可標記 .Updated() / .Appended() / .Removed())
|
|
526
|
+
return Task.FromResult(builder);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public Task OnChangedAsync(PersistenceContext context)
|
|
530
|
+
{
|
|
531
|
+
var sql = context.ConvertToSQL("job_detail"); // 執行 SQL 即可
|
|
532
|
+
return Task.CompletedTask;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
public Task OnTriggerChangedAsync(PersistenceTriggerContext context)
|
|
536
|
+
{
|
|
537
|
+
var sql = context.ConvertToSQL("job_trigger");
|
|
538
|
+
return Task.CompletedTask;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
public Task OnExecutionRecordAsync(PersistenceExecutionRecordContext context)
|
|
542
|
+
{
|
|
543
|
+
// 寫入運行記錄表
|
|
544
|
+
return Task.CompletedTask;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
options.AddPersistence<DbJobPersistence>();
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
> 框架自動生成標準 SQL(支援 `SqlTypes.SqlServer` / `MySQL` / `PostgreSQL` / `Sqlite` / `Oracle` / `Firebird`)。
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## 26.1.13 作業叢集控制
|
|
556
|
+
|
|
557
|
+
提供簡單的**故障轉移**功能(非負載均衡)。
|
|
558
|
+
|
|
559
|
+
```csharp
|
|
560
|
+
public class JobClusterServer : IJobClusterServer
|
|
561
|
+
{
|
|
562
|
+
public void Start(JobClusterContext context) { /* 寫入 Waiting 狀態 */ }
|
|
563
|
+
public async Task WaitingForAsync(JobClusterContext context) { /* 輪詢檢查,條件滿足時呼叫 WorkNow */ }
|
|
564
|
+
public void Stop(JobClusterContext context) { /* 更新為 Crashed */ }
|
|
565
|
+
public void Crash(JobClusterContext context) { /* 更新為 Crashed */ }
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
options.ClusterId = "cluster1";
|
|
569
|
+
options.AddClusterServer<JobClusterServer>();
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
`ClusterStatus` 列舉:`Crashed` / `Working` / `Waiting`(預設)。
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## 26.1.16 Dashboard 看板(4.8.4+)
|
|
577
|
+
|
|
578
|
+
```csharp
|
|
579
|
+
app.UseStaticFiles();
|
|
580
|
+
app.UseScheduleUI(); // 必須在 UseStaticFiles 之後
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
> 預設入口:`/schedule`,預設帳號:`schedule`,密碼為空。
|
|
584
|
+
|
|
585
|
+
### 設定選項
|
|
586
|
+
|
|
587
|
+
```csharp
|
|
588
|
+
app.UseScheduleUI(options =>
|
|
589
|
+
{
|
|
590
|
+
options.RequestPath = "/custom-job";
|
|
591
|
+
options.DisableOnProduction = true;
|
|
592
|
+
options.DisplayEmptyTriggerJobs = true;
|
|
593
|
+
options.DefaultExpandAllJobs = false;
|
|
594
|
+
options.Title = "定時任務看板";
|
|
595
|
+
|
|
596
|
+
options.LoginConfig.OnLoging = async (username, password, httpContext) =>
|
|
597
|
+
await Task.FromResult(username == "furion" && password == "furion");
|
|
598
|
+
options.LoginConfig.DefaultUsername = "";
|
|
599
|
+
options.LoginConfig.DefaultPassword = "";
|
|
600
|
+
});
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Worker Service 中啟用看板
|
|
604
|
+
|
|
605
|
+
```csharp
|
|
606
|
+
IHost host = Host.CreateDefaultBuilder(args)
|
|
607
|
+
.ConfigureServices(services => { services.AddSchedule(); })
|
|
608
|
+
.ConfigureWebHostDefaults(builder => builder.Configure(app =>
|
|
609
|
+
{
|
|
610
|
+
app.UseStaticFiles();
|
|
611
|
+
app.UseScheduleUI();
|
|
612
|
+
}))
|
|
613
|
+
.Build();
|
|
614
|
+
host.Run();
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## 26.1.15 部署建議
|
|
620
|
+
|
|
621
|
+
- 部署到 IIS 需停用應用程式集區回收
|
|
622
|
+
- **強烈建議**採用 Worker Service 獨立部署
|
|
623
|
+
- 確保伺服器不會進入休眠
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
## 26.1.17 常見問題
|
|
628
|
+
|
|
629
|
+
- **時間偏差**:使用 `context.OccurrenceTime` 而非 `DateTime.Now`
|
|
630
|
+
- **參數序列化**:使用 `Schedular.Serialize()` / `Schedular.Deserialize<T>()`
|
|
631
|
+
- **延遲操作**:使用 `Task.Delay` 而非 `Thread.Sleep`
|