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

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

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

版權護體©作者:痴者工良,微信公眾號轉載文章需要 《NCC開源社群》同意。

目錄

這一篇將來學習 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<in TEvent> 介面,並實現以下函式:

Task HandleEventAsync(TEvent eventData);

一個系統中,事件服務可以有多個,每個服務的 TEvent 型別不能相同,因為 TEvent 的型別是呼叫服務的標識。當發生 TEvent 事件後,系統通過 TEvent 去找到這個服務。

事件服務建立完畢後,需要加入到依賴注入中,你可以多繼承一個 ITransientDependency 介面,然後統一掃描程式集加入到 依賴注入容器中(第三篇提到過)。

事件

即上面提到的 TEvent

假設有一個系統中所有的事件服務都放到一個容器中,釋出者只能傳遞一個事件,而不能指定誰來提供響應服務。

容器是通過 TEvent 來查詢服務的。

事件就是一個模型類,也可以使用 int或者 string 等簡單型別(請不要用簡單型別做事件),用於傳遞資訊。

一般使用 Event 做字尾。

釋出事件

如果需要釋出一個事件,只需要注入 ILocalEventBus 即可。

        private readonly ILocalEventBus _localEventBus;

        public MyService(ILocalEventBus localEventBus)
        {
            _localEventBus = localEventBus;
        }

然後釋出事件:

            await _localEventBus.PublishAsync(
                new TEvent
                {
					... ...
                }
            );

全域性異常加入事件匯流排功能

建立事件

AbpBase.Web 中,建立一個 Handlers 目錄,再在 Handlers 目錄下,建立 HandlerEvents 目錄。

然後在 HandlerEvents 目錄,建立一個 CustomerExceptionEvent.cs 檔案。

CustomerExceptionEvent 作為一個異常事件,用於傳遞異常的資訊,而不僅僅是將 Exception ex 記錄就了事。

其檔案內容如下:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace AbpBase.Application.Handlers.HandlerEvents
{
    /// <summary>
    /// 全域性異常推送事件
    /// </summary>
    public class CustomerExceptionEvent
    {
        /// <summary>
        /// 只記錄異常
        /// </summary>
        /// <param name="ex"></param>
        public CustomerExceptionEvent(Exception ex)
        {
            Exception = ex;
        }

        /// <summary>
        /// 此異常發生時,使用者請求的路由地址
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="actionRoute"></param>
        public CustomerExceptionEvent(Exception ex, string actionRoute)
        {
            Exception = ex;
            Action = actionRoute;
        }

        /// <summary>
        /// 此異常發生在哪個型別的方法中
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="method"></param>
        public CustomerExceptionEvent(Exception ex, MethodBase method)
        {
            Exception = ex;
            MethodInfo = (MethodInfo)method;
        }

        /// <summary>
        /// 記錄異常資訊
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="actionRoute"></param>
        /// <param name="method"></param>
        public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method)
        {
            Exception = ex;
            Action = actionRoute;
            MethodInfo = (MethodInfo)method;
        }

        /// <summary>
        /// 當前出現位置
        /// <example>
        /// <code>
        /// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod();
        /// </code>
        /// </example>
        /// </summary>
        public MethodInfo MethodInfo { get; private set; }

        /// <summary>
        /// 發生異常的 Action
        /// </summary>
        public string Action { get; private set; }

        /// <summary>
        /// 具體異常
        /// </summary>
        public Exception Exception { get; private set; }
    }
}

訂閱事件

訂閱事件,即將其定義為事件的響應者、服務提供者。

當異常發生後,異常的位置,推送異常資訊,那麼誰來處理這些資訊呢?是訂閱者。

這裡我們定義一個異常日誌處理類,來處理程式推送的異常資訊。

AbpBase.Web 專案的 Handlers 目錄中,新增一個 CustomerExceptionHandler 類,繼承:

public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency

服務要處理事件,必須繼承 ILocalEventHandler<T>,而 ITransientDependency 是為了此服務可以可以自動注入到容器中。

其檔案內容如下:

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
{
    /// <summary>
    /// 全域性異常記錄日誌
    /// </summary>
    public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, 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

增加依賴注入:

        private readonly ILocalEventBus _localEventBus;

        public WebGlobalExceptionFilter(ILocalEventBus localEventBus)
        {
            _localEventBus = localEventBus;
        }

釋出事件:

        public async Task OnExceptionAsync(ExceptionContext context)
        {

            if (!context.ExceptionHandled)
            {
                await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception,
                    context.ActionDescriptor?.DisplayName));
                    ...
                    ...

測試

建立一個 Action :

        [HttpGet("/T4")]
        public string MyWebApi4()
        {
            int a = 1;
            int b = 0;
            int c = a / b;
            return c.ToString();
        }

然後訪問 https://localhost:5001/T4 ,會發現請求後報錯

AbpBase.WebLogs 目錄中,開啟 -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 這個位置。

記錄事件

如果在普通方法裡面出現異常,我們這樣這樣記錄:

            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/13061059.html