前情提要
前篇[DotnetCore]排程神器-Hangfire:動態排程篇1有提到共用類Job的設計,這篇主要講解各個Job種類
的實作細節,再複習一下上篇內容,筆者這邊動態排程
主要是著墨在排程的資訊設定於資料庫中,透過一支Job Initial
的API,可以重新AddOrUpdate
來達成動態排程的效果。若所有的Job是宣告於程式端中,要新增類似的Job時著實麻煩,失去其原有的設計理念。因此筆者這邊統計歸納出四種類型:
SP執行類
: 可以進階設定是否要將SP執行結果打包成檔案上傳至指定路徑
Bat檔案執行類
: 主要是因為與其他系統串接,主要的前台部分,需要由執行期bat檔案做驅動
下載類
: 主要與其他系統交換檔案,由檔案內容塞到固定的資料表中,透過一支SP
做後置處理
Normal類
: 有一些排程無法歸納為上述三種,即便前面執行其中一項,但後續有額外的處理,筆者這邊就不另外規劃做處理,保留其彈性
內容
接下來就以四種類型中的SP執行類介紹其實作內容,大致上分為SPExecuteJobService以及SPExecuteJob,其中日期參數取得比較複雜,獨立設計其作法。
日期參數置換Service
以上篇介紹的CommCode
結構來說,筆者就拿某一個Column
當作設定SP的語法,會牽涉到參數置換的問題,共同參數大概就是營業日了,因為各個作業可能還是會有不同的需求,可能要減一天,或者要用實際日期,或者不同時區的日期等等,筆者這邊也因應這些需求有撰寫一隻置換日期的Service。
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
| public interface IBusinessDateService { string GetBusinessDateWithParameter((string source, string format) conditions); }
public class BusinessDateService : IBusinessDateService { private readonly ILoggerManager _logger; private readonly IConfiguration _config; private readonly SqlConnectionFactory _factory; public BusinessDateService(IConfiguration config, SqlConnectionFactory factory, ILoggerManager logger) { _config = config; _factory = factory; _logger = logger; } public string GetBusinessDateWithParameter((string source, string format) conditions) { if (string.IsNullOrEmpty(conditions.source)) return conditions.source;
var result = conditions.source; var regex = new Regex(@"(\{-?[0-9a-zA-Z]+\})|(\{([0-9a-zA-Z]+):([0-9a-zA-Z\s]+)\}){1}"); foreach (Match match in regex.Matches(result)) { var parameter = match.Value.Trim('{').Trim('}'); if (match.Value.Contains("DateTime")) { var datetimeResult = new DateTime(); var keywordList = parameter.Split(":"); if (keywordList.Length > 1) { TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(keywordList[1]); datetimeResult = TimeZoneInfo.ConvertTime(DateTime.Now, timeZoneInfo); } else { datetimeResult = DateTime.Now; } result = result.Replace(match.Value, datetimeResult.ToString(conditions.format)); } else { if (double.TryParse(parameter, out var period)) { var datePara = ""; result = result.Replace(match.Value, datePara); } } } return result; } }
|
SPExecuteJobService
筆者這邊都會使用Service的形式,不直接把執行邏輯寫在Job中,於Job中注入Job對應的Service做事
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
| public class SPExecuteJobService : ISPExecuteJobService { private readonly ILoggerManager _logger; private readonly FtpServiceResolver _ftpServiceResolver; private IFtpService _ftpService; private readonly HttpClient _client; private readonly IBusinessDateService _businessDateService; private readonly SqlConnectionFactory _factory; public SPExecuteJobService(FtpServiceResolver ftpServiceResolver , HttpClient client , IBusinessDateService businessDateService, ILoggerManager logger, SqlConnectionFactory factory) { _client = client; _businessDateService = businessDateService; _ftpServiceResolver = ftpServiceResolver; _logger = logger; _factory = factory; } public void SyncProcess(CommCode jobSetting) { var spExecuteSql = _businessDateService.GetBusinessDateWithParameter( (jobSetting.CodeVal2, "yyyy/MM/dd")); if (string.IsNullOrEmpty(jobSetting.CodeVal4) && string.IsNullOrEmpty(jobSetting.CodeVal6)) { using (var _conn = _factory.GetConnection()) _conn.Execute(spExecuteSql); } else { if (string.IsNullOrEmpty(jobSetting.CodeVal4) == false) { var ftpConfigSetting = jobSetting.CodeVal4.Split(";"); _ftpService = _ftpServiceResolver(ftpConfigSetting[0]); var result = new List<FileUploadResponseModel>(); using (var _conn = _factory.GetConnection()) { result = _conn.Query<FileUploadResponseModel>(spExecuteSql).ToList(); } var ftpConfigBaseModel = new FTPConfigBaseModel() { Server = ftpConfigSetting[1], Account = ftpConfigSetting[2], Password = ftpConfigSetting[3], RemotePath = ftpConfigSetting[4], LocalPath = ftpConfigSetting[5] }; _ftpService.UploadData(ftpConfigBaseModel, result); } } }
public void SyncProcess() { throw new NotImplementedException(); } }
|
SPExecuteJob
我們的重頭戲,SP執行類Job
,不過筆者設計都是肥在Service
,Job
會單純許多,就像MVC
中的C
要輕一樣,盡量一行內就解決,主要邏輯都塞在Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class SPExecuteJob : TimerJob<SPExecuteJob, CommCode> , ITimerJob<CommCode> { private readonly ISPExecuteJobService _spExecuteJobService; private string _jobId; public SPExecuteJob(IConfiguration config , ISPExecuteJobService spExecuteJobService , List<SchedulerLog> schedulerLogs , SchedulerLog schedulerLog , ILoggerManager loggerManager) : base(config, schedulerLogs, schedulerLog, loggerManager) { _spExecuteJobService = spExecuteJobService; } public override string JobId => "SPExecuteJob";
protected override void PerformJobTasks(List<CommCode> parameters) { _spExecuteJobService.SyncProcess(parameters.FirstOrDefault()); } }
|
註冊Service
筆者這邊就列出SP執行類相關的Service註冊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public void ConfigureServices(IServiceCollection services) { services.AddTransient<SPExecuteJob>(); services.AddTransient<ISPExecuteJobService, SPExecuteJobService>(); services.AddTransient<IBusinessDateService, BusinessDateService>(); services.AddTransient<FtpServiceResolver>(serviceProvider => key => { switch (key) { case "F": return serviceProvider.GetService<FtpService>(); case "S": return serviceProvider.GetService<SFtpService>(); default: return null; } }); }
|
結論
此篇介紹SPExecuteJob
,可以回頭看筆者的上篇文章,就會比較清楚了,筆者原本一篇就塞四種類型Job介紹,但篇幅會過長,只好切割成不同的三篇文章,加上第一篇動態排程就完整了筆者客製版的動態排程。
參考