淺入 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM