1. 程式人生 > >淺入 ABP 系列(4):事件匯流排

淺入 ABP 系列(4):事件匯流排

# 淺入 ABP 系列(4):事件匯流排 版權護體©作者:痴者工良,微信公眾號轉載文章需要 《NCC開源社群》同意。 [TOC] 這一篇將來學習 ABP 中的事件匯流排,然後結合在我們的基架專案中,逐漸構建一個完整的系統。 原始碼地址:https://github.com/whuanle/AbpBaseStruct ## 事件匯流排 ### 關於事件匯流排 ABP 中,為了方便程序間通訊,給開發者提供了一個叫 `事件匯流排` 的功能,事件匯流排分為 `本地事件匯流排`、`分散式事件匯流排`,本篇文章講的是 `本地事件匯流排`,系列教程中暫時不考慮講解 `分散式事件匯流排`。 `事件匯流排` 需要使用 `Volo.Abp.EventBus` 庫,ABP 包中自帶,不需要額外引入。 事件匯流排是通過 訂閱-釋出 形式使用的,某一方只需要按照格式推送事件,而不需要關注是誰接收了事件和如何處理事件。 你可以參考官方文件:https://docs.abp.io/zh-Hans/abp/latest/Local-Event-Bus ### 為什麼需要這個東西 首先列舉一下,你工作開發的專案中,編寫 控制器時,是不是有這幾種程式碼。 ``` // 記錄日誌 1 Task.Run(()=> { _apiLog.Info($"xxxxxxxx"); }); ``` ``` // 記錄日誌 2 catch(Exception ex) { _apiLog.Error(ex); } ``` ``` // 記錄日誌 3 _apiLog.Info($"登陸資訊:使用者 [{userName}({clientAdrress})]\); ``` 筆者認為,改善的上述問的方法之一是將函式的功能跟記錄日誌分開,函式執行任務時,只需要把狀態和資訊通過事件匯流排推送,而不需要了關注應該如何處理這些內容。 另外,還有當函式執行某些步驟時,產生了事件,開發者喜歡 `new Thread` 一個新的執行緒去執行別的任務,或者 `Task.Run`。 其實,通過事件匯流排,我們更加好地隔離程式碼,遵從 `單一職責原則` 。當然還有很多方面值得使用事件匯流排,這裡我們就不再扯淡了。 前面,我們編寫了全域性異常攔截器,還有日誌元件,這一篇我們將通過事件匯流排,將 Web 程式的一些部件組合起來。 ### 事件匯流排建立過程 #### 訂閱事件 建立一個服務來訂閱事件,當程式中發生某種事件時,此服務將被呼叫。 事件服務必須繼承 `ILocalEventHandler` 介面,並實現以下函式: ```csharp Task HandleEventAsync(TEvent eventData); ``` 一個系統中,事件服務可以有多個,每個服務的 `TEvent` 型別不能相同,因為 `TEvent` 的型別是呼叫服務的標識。當發生 `TEvent` 事件後,系統通過 `TEvent` 去找到這個服務。 事件服務建立完畢後,需要加入到依賴注入中,你可以多繼承一個 `ITransientDependency` 介面,然後統一掃描程式集加入到 依賴注入容器中(第三篇提到過)。 #### 事件 即上面提到的 `TEvent`。 假設有一個系統中所有的事件服務都放到一個容器中,釋出者只能傳遞一個事件,而不能指定誰來提供響應服務。 容器是通過 `TEvent` 來查詢服務的。 事件就是一個模型類,也可以使用 `int`或者 `string` 等簡單型別(請不要用簡單型別做事件),用於傳遞資訊。 一般使用 `Event` 做字尾。 #### 釋出事件 如果需要釋出一個事件,只需要注入 `ILocalEventBus` 即可。 ```csharp private readonly ILocalEventBus _localEventBus; public MyService(ILocalEventBus localEventBus) { _localEventBus = localEventBus; } ``` 然後釋出事件: ```csharp await _localEventBus.PublishAsync( new TEvent { ... ... } ); ``` ## 全域性異常加入事件匯流排功能 ### 建立事件 在 `AbpBase.Web` 中,建立一個 `Handlers` 目錄,再在 `Handlers` 目錄下,建立 `HandlerEvents` 目錄。 然後在 `HandlerEvents` 目錄,建立一個 `CustomerExceptionEvent.cs` 檔案。 `CustomerExceptionEvent` 作為一個異常事件,用於傳遞異常的資訊,而不僅僅是將 `Exception ex` 記錄就了事。 其檔案內容如下: ```csharp using System; using System.Collections.Generic; using System.Reflection; using System.Text; namespace AbpBase.Application.Handlers.HandlerEvents { /// /// 全域性異常推送事件 ///
public class CustomerExceptionEvent { /// /// 只記錄異常 /// /// public CustomerExceptionEvent(Exception ex) { Exception = ex; } /// /// 此異常發生時,使用者請求的路由地址 /// /// /// public CustomerExceptionEvent(Exception ex, string actionRoute) { Exception = ex; Action = actionRoute; } /// /// 此異常發生在哪個型別的方法中 ///
/// /// public CustomerExceptionEvent(Exception ex, MethodBase method) { Exception = ex; MethodInfo = (MethodInfo)method; } /// /// 記錄異常資訊 /// /// /// /// public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method) { Exception = ex; Action = actionRoute; MethodInfo = (MethodInfo)method; } /// /// 當前出現位置 /// /// /// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod(); ///
///
///
public MethodInfo MethodInfo { get; private set; } /// /// 發生異常的 Action /// public string Action { get; private set; } /// /// 具體異常 /// public Exception Exception { get; private set; } } } ``` ### 訂閱事件 訂閱事件,即將其定義為事件的響應者、服務提供者。 當異常發生後,異常的位置,推送異常資訊,那麼誰來處理這些資訊呢?是訂閱者。 這裡我們定義一個異常日誌處理類,來處理程式推送的異常資訊。 在 `AbpBase.Web` 專案的 `Handlers` 目錄中,新增一個 `CustomerExceptionHandler` 類,繼承: ```csharp public class CustomerExceptionHandler : ILocalEventHandler, ITransientDependency ``` 服務要處理事件,必須繼承 ` ILocalEventHandler`,而 `ITransientDependency` 是為了此服務可以可以自動注入到容器中。 其檔案內容如下: ```csharp using AbpBase.Application.Handlers.HandlerEvents; using Serilog; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; namespace AbpBase.Application.Handlers { /// /// 全域性異常記錄日誌 /// public class CustomerExceptionHandler : ILocalEventHandler, ITransientDependency { private readonly ILogger _ILogger; public CustomerExceptionHandler(ILogger logger) { _ILogger = logger; } public async Task HandleEventAsync(CustomerExceptionEvent eventData) { StringBuilder stringBuilder = new StringBuilder(256); stringBuilder.AppendLine(); stringBuilder.Append("Action: "); stringBuilder.AppendLine(eventData.Action); if (eventData.MethodInfo != null) { stringBuilder.Append("Class-Method: "); stringBuilder.Append(eventData.MethodInfo?.DeclaringType.FullName); stringBuilder.AppendLine(eventData.MethodInfo?.Name); } stringBuilder.Append("Source: "); stringBuilder.AppendLine(eventData.Exception.Source); stringBuilder.Append("TargetSite: "); stringBuilder.AppendLine(eventData.Exception.TargetSite?.ToString()); stringBuilder.Append("InnerException: "); stringBuilder.AppendLine(eventData.Exception.InnerException?.ToString()); stringBuilder.Append("Message: "); stringBuilder.AppendLine(eventData.Exception.Message); stringBuilder.Append("HelpLink: "); stringBuilder.AppendLine(eventData.Exception.HelpLink); _ILogger.Fatal(stringBuilder.ToString()); await Task.CompletedTask; } } } ``` 這樣寫,記錄的日誌可以有很好的層次結構。 ### 釋出事件 定義了事件的格式和定義服務來訂閱事件後,我們來建立一個釋出者。 我們修改一下 `WebGlobalExceptionFilter`。 增加依賴注入: ```csharp private readonly ILocalEventBus _localEventBus; public WebGlobalExceptionFilter(ILocalEventBus localEventBus) { _localEventBus = localEventBus; } ``` 釋出事件: ```csharp public async Task OnExceptionAsync(ExceptionContext context) { if (!context.ExceptionHandled) { await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception, context.ActionDescriptor?.DisplayName)); ... ... ``` ### 測試 建立一個 Action : ```csharp [HttpGet("/T4")] public string MyWebApi4() { int a = 1; int b = 0; int c = a / b; return c.ToString(); } ``` 然後訪問 https://localhost:5001/T4 ,會發現請求後報錯 在 `AbpBase.Web` 的 `Logs` 目錄中,開啟 `-Fatal.txt` 檔案。 可以看到: ``` 2020-09-16 18:49:27.750 +08:00 [FTL] Action: ApbBase.HttpApi.Controllers.TestController.MyWebApi4 (ApbBase.HttpApi) Source: ApbBase.HttpApi TargetSite: System.String MyWebApi4() InnerException: Message: Attempted to divide by zero. HelpLink: ``` 除了異常資訊外,我們還可以很方便的知道異常發生在 `TestController.MyWebApi4` 這個位置。 ### 記錄事件 如果在普通方法裡面出現異常,我們這樣這樣記錄: ```csharp catch (Exception ex) { ... new CustomerExceptionEvent(ex, MethodBase.GetCurrentMethod()); ... } ``` `MethodBase.GetCurrentMethod()` 可以獲取當前正在執行的方法,獲得資訊後將此引數傳遞給異常記錄服務,會自動解析出具體是哪個地方發生異常。 由於目前 Web 程式中還沒有編寫什麼服務,因此我們先結合到異常日誌功能中,後面編寫服務時,會再次用到事件匯流排。 完整程式碼參考:https://github.com/whuanle/AbpBaseStruct/tree/master/src/4/AbpBase 下一篇文章地址是 https://www.cnblogs.com/whuanle/p/13061