前情提要 筆者因為工作環境需要跟多個系統做串接,因最近參與的大系統,必須在某一塊作業使用外部廠商系統,需要透過各種介接方式做資料上的串接,例如﹔需要與廠商平台上面撰寫外掛程式,呼叫我們寫好的API,做為資料同步,這不在這篇的講的內容就不往下深入討論,另一種可能是我們在某些情境下,某個時段,需要驅動廠商系統進行其作業,廠商系統也有開放batch file
,讓我們方便去執行。
上述中的第二種執行batch file
變得格外繁瑣,為何這樣說呢,讓筆者娓娓道來,為各系統間不要互相影響,一定將各系統隔開,,試想若把各種不同系統都放在同一台,若該台伺服器遇到甚麼問題,其不就是自找死路,完全運作不下去。基於各種理由會將外部廠商系統獨立於一台伺服器中,這樣問題就來了,第一是batch file
是一個落地的檔案,要由另一台伺服器(筆者部門的系統)去觸發執行該batch file
,且也要能夠追蹤其執行狀況,因為有可能該bat作業會產生檔案,筆者部門系統需要該檔案做後續作業。
內容 介紹完情境後,開始來思考一件事情,我們需要完成哪些事情,如下:
通知執行 通知執行部分,由諸多選擇,利用SignalR
類的 web socket
通訊、利用Message Queue
、利用資料庫pooling
方式,基本上前兩種為留下軌跡紀錄,必須搭配資料庫做紀錄查詢,筆者這邊索性選擇最後一種資料庫Pooling的方式,當然也可以透過SqlDependency
的方式監聽資料表的變化,因該方式牽扯到資料庫權限問題,筆者公司環境不允許AP端擁有過高的資料庫權限,僅能有讀寫權限,只能放棄作罷。筆者會會再寫一篇單獨介紹SqlDependency
的文章,到時再詳細討論。
基本上是設計一張資料表存放欲執行的batch file
路徑及檔名,以及執行完成後產生的檔案路徑及名稱,筆者工作環境需要由產生檔案的資料做一些邏輯處理,會再加一些執行Store Procedure
的設定欄位。對應物件設計如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public partial class BatchLog { public string BatchId { get ; set ; } public string BatFilePath { get ; set ; } public string BatFileName { get ; set ; } public string ResultFilePath { get ; set ; } public string ResultFileName { get ; set ; } public int ? DataStartRow { get ; set ; } public string CapPublishName { get ; set ; } public string CapPublishParaFname { get ; set ; } public string CapPublishParaDataDate { get ; set ; } public string Result { get ; set ; } public string ErrorMessage { get ; set ; } public string AuditUser { get ; set ; } public DateTime? AuditTime { get ; set ; } }
執行Batch File 基本上外部廠商系統,已把執行邏輯包成一個batch file
,筆者負責部份使用command line
模式下執行該batch file
即可。需要用到ProcessStartInfo
及Process
這兩個類別,參考如下:
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 public void Execute (){ var processStartInfo = new ProcessStartInfo("cmd.exe" , $"/c {_batchLog.BatFileName} " ); processStartInfo.CreateNoWindow = true ; processStartInfo.UseShellExecute = false ; processStartInfo.RedirectStandardError = true ; processStartInfo.RedirectStandardOutput = true ; processStartInfo.WorkingDirectory = _nxbatLog.BatFilePath; var _process = Process.Start(processStartInfo); _process.OutputDataReceived += OutputDataReceived; _process.ErrorDataReceived += ErrorDataReceived; _process.EnableRaisingEvents = true ; _process.Exited += Exited; _process.BeginErrorReadLine(); _process.BeginOutputReadLine(); _process.WaitForExit(); _process.Close(); } private void ErrorDataReceived (object sender, DataReceivedEventArgs e ){ } private void OutputDataReceived (object sender, DataReceivedEventArgs e ){ } private void Exited (object sender, EventArgs e ){ }
執行結果監聽 上節式碼部份,需要說明一下,筆者測驗的結果,需要配合設定才會有相對應的event
事件觸發,須注意一下有些是針對ProcessStartInfo
的設定,有些是對Process
的設定,需釐清楚:
Error通知事件 Error通知事件要特別注意,經由筆者測試發現,即便batch file
執行完成後,exitcode
為0
,仍觸發ErrorDataReceived
事件,最保險的方式是裡頭再判斷string.IsNullOrEmpty(e.Data) == false
才做接下來要做的事情。
1 2 3 4 5 6 7 8 9 processStartInfo.RedirectStandardError = true ; _process.ErrorDataReceived += ErrorDataReceived; private void ErrorDataReceived (object sender, DataReceivedEventArgs e ){ }
Output通知事件 1 2 3 4 5 6 7 8 9 processStartInfo.RedirectStandardOutput = true ; _process.OutputDataReceived += OutputDataReceived; private void OutputDataReceived (object sender, DataReceivedEventArgs e ){ }
結束通知事件 1 2 3 4 5 6 7 8 9 _process.EnableRaisingEvents = true ; _process.Exited += Exited; private void Exited (object sender, EventArgs e ){ }
筆者不免俗的利用event
, action
對最終端使用開放擴充性,附上完整程式碼:
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 public class MyBatchJob : IDisposable { private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly BatchLog _batchLog; public event Action<BatchLog , DataReceivedEventArgs> MyOutputDataReceived; public event Action<BatchLog , DataReceivedEventArgs> MyErrorDataReceived; public event Action<BatchLog , int , EventArgs> MyExited; private Process _process; public MyBatchJob (BatchLog batchLog ) { _batchLog= batchLog; } public void Execute () { var processStartInfo = new ProcessStartInfo("cmd.exe" , $"/c {_batchLog.BatFileName} " ); processStartInfo.CreateNoWindow = true ; processStartInfo.UseShellExecute = false ; processStartInfo.RedirectStandardError = true ; processStartInfo.RedirectStandardOutput = true ; processStartInfo.WorkingDirectory = _batchLog.BatFilePath; _process = Process.Start(processStartInfo); _process.OutputDataReceived += OutputDataReceived; _process.ErrorDataReceived += ErrorDataReceived; _process.EnableRaisingEvents = true ; _process.Exited += Exited; _process.BeginErrorReadLine(); _process.BeginOutputReadLine(); _process.WaitForExit(); _process.Close(); } private void ErrorDataReceived (object sender, DataReceivedEventArgs e ) { MyErrorDataReceived(_batchLog, e); } private void OutputDataReceived (object sender, DataReceivedEventArgs e ) { MyOutputDataReceived(_batchLog, e); } private void Exited (object sender, EventArgs e ) { MyExited(_batchLog, _process.ExitCode, e); } public void Dispose () { _logger.Info($"[MyBatchJob Dispose]!!" ); _process.Dispose(); GC.SuppressFinalize(this ); } }
Client端使用 最終使用端使用起來也很簡便,如下:
1 2 3 4 5 6 var myBatchJob = new MyBatchJob(item);myBatchJob .MyOutputDataReceived += MyOutputDataReceived; myBatchJob .MyErrorDataReceived += MyErrorDataReceived; myBatchJob .MyExited += MyExited; myBatchJob .Execute();
結論 筆者因工作環境需要執行batch file
,藉機熟悉一下平常比較少使用到的ProcessStartInfo
及Process
,也因資料庫Pooling及Log,徹底研究了一下對應事件觸發及其設定方式,可以說是以後有類似的需求時,不用再爾外花時間研究其運作方式,基本上筆者程式碼片段中,可以將BatchLog
改成泛型T
基本上可以變成通用型,也不怕每份工作的不同資料結構設計。
參考