0%

[DotnetCore]排程神器-Hangfire:動態排程篇3

前情提要

前篇[DotnetCore]排程神器-Hangfire:動態排程篇1有提到共用類Job的設計,這篇主要講解各個Job種類的實作細節,再複習一下上篇內容,筆者這邊動態排程主要是著墨在排程的資訊設定於資料庫中,透過一支Job Initial的API,可以重新AddOrUpdate來達成動態排程的效果。若所有的Job是宣告於程式端中,要新增類似的Job時著實麻煩,失去其原有的設計理念。因此筆者這邊統計歸納出四種類型:

  • SP執行類: 可以進階設定是否要將SP執行結果打包成檔案上傳至指定路徑
  • Bat檔案執行類: 主要是因為與其他系統串接,主要的前台部分,需要由執行期bat檔案做驅動

  • 下載類: 主要與其他系統交換檔案,由檔案內容塞到固定的資料表中,透過一支SP做後置處理
  • Normal類: 有一些排程無法歸納為上述三種,即便前面執行其中一項,但後續有額外的處理,筆者這邊就不另外規劃做處理,保留其彈性

內容

筆者這篇主要會用到的,已經於另一篇[DotnetCore]Batch檔案執行及追縱說明過,這篇主要是利用該篇的BatchLog的設計,主要邏輯處理的BatJobService中

  • 新增一筆BatchLog資料
  • 接著使用Pooling的方式等待其Result被壓為Y或F,Timeout時間
  • 若第二步驟為Y,進一步判斷是否有後續的SP要執行

設計JobService

照著上述的執行步驟,寫一個JobService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Interface
public interface IBatJobService : ISyncJobService
{
void SyncProcess(CommCode jobSetting);
}
// Service
public class BatJobService : IBatJobService
{
private readonly string _connStr;
private readonly IBusinessDateService _businessDateService;
public NXBatJobService(IConfiguration config
, IBusinessDateService businessDateService)
{
_connStr = Encoding.UTF8.GetString(Convert.FromBase64String(config.GetConnectionString("ConnStrKeyword")));
_businessDateService = businessDateService;
}

public void SyncProcess(CommCode jobSetting)
{
using (var _conn = new SqlConnection(_connStr))
{
// Step0: 取得識別碼,筆者就不列出公司這邊的做法
var batchId = getAutoId();
// Step1: Config解析,這邊使用CodeVal2
var batSettings = jobSetting.CodeVal2.Split(";");
// Step2: 主要是取得外部系統的實際路徑,由資料庫設定
var dirPath = _conn.Query<CommCode>(@"SELECT *
FROM CommCode
WHERE CodeType = 'DirName'")
.FirstOrDefault();
var batFilePath = batSettings[0].Replace("@DirName", dirPath.CodeName);
var batLog = new BatchLog()
{
BatchId = batchId,
BatFilePath = batFilePath,//batSettings[0],
BatFileName = batSettings[1],
ResultFilePath = batSettings[2],
ResultFileName = batSettings[3],
DataStartRow = string.IsNullOrEmpty(batSettings[4]) ? default(int) : int.Parse(batSettings[4]),
CapPublishName = "",
CapPublishParaFname = batSettings[5],
// 主要是沿用上篇提到的日期置換Service
CapPublishParaDataDate = _businessDateService.GetBusinessDateWithParameter(
(batSettings[6], "yyyy/MM/dd")),
Result = "N",
ErrorMessage = "",
AuditUser = "SYS",
AuditTime = DateTime.Now
};
var insertCount = _conn.Execute(@"INSERT INTO BatchLog
Values(@BatchId, @BatFilePath, @BatFileName, @ResultFilePath, @ResultFileName
, @DataStartRow, @CapPublishName, @CapPublishParaFname, @CapPublishParaDataDate, @Result
, @ErrorMessage, @AuditUser, @AuditTime)"
, batLog);

// Pooling
// Timeout時間預設為10秒
var timeoutSecs = 10;
// 由設定讀出Timeout時間,畢竟每個batch檔案執行時間不一定,由外部去設定
int.TryParse(batSettings[7], out timeoutSecs);
if (PoolingService.RetryUntilSuccessOrTimeout(() =>
{
var currBatLog = _conn.Query<BatchLog>(
@"SELECT * FROM BatchLog WHERE BatchId = @BatchId AND Result = @Result"
, new
{
BatchId = batchId,
Result = "Y"
}).FirstOrDefault();
return currBatLog != null;
}, TimeSpan.FromSeconds(timeoutSecs)))
{
var spOrNot = batSettings.Length > 8 && string.IsNullOrEmpty(batSettings[8]) == false;
if (spOrNot)
{
var spSql = _businessDateService.GetBusinessDateWithParameter(
(batSettings[8], "yyyy/MM/dd"));
_conn.Execute(spSql);
}
}
else
{
throw new Exception("Bat執行未成功或等待時間已過!");
}
}
}
}

設計Job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BatJob : TimerJob<BatJob, CommCode>
, ITimerJob<CommCode>
{
private readonly IBatJobService _batJobService;
public BatJob(IConfiguration config
, IBatJobService batJobService
, List<SchedulerLog> schedulerLogs
, SchedulerLog schedulerLog
, ILoggerManager loggerManager)
: base(config, schedulerLogs, schedulerLog, loggerManager)
{
_batJobService = batJobService;
}
public override string JobId => "BatJob";

protected override void PerformJobTasks(List<CommCode> parameters)
{
_batJobService.SyncProcess(parameters.FirstOrDefault());
}
}

註冊相關Service

1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
// 以上省略
services.AddTransient<BatJob>();
services.AddTransient<IBatJobService, BatJobService>();
services.AddTransient<IBusinessDateService, BusinessDateService>();
// 以下省略
}

結論

筆者這邊環境大量與外部系統做介接,剛好有一個外部系統介接方式都是執行對方的batch檔案來達到,因為很多都需要於排程系統中設定使用,因此把它設計為一個共用類,就不用重複宣告了,盡量將執行過程整理過,把它設計進去,就一勞永逸了。

參考