Abp + MongoDb 改造預設的審計日誌存儲位置

来源:https://www.cnblogs.com/myzony/archive/2019/01/14/10265395.html
-Advertisement-
Play Games

一、背景 在實際項目的開發當中,使用 Abp Zero 自帶的審計日誌功能寫入效率比較低。其次審計日誌數據量中後期十分龐大,不適合與業務數據存放在一起。所以我們可以重新實現 Abp 的 介面,來讓我們的審計日誌數據存儲在 MongoDb 當中。 二、實現 2.0 引入相關包 這裡我們需要在模塊項目引 ...


一、背景

在實際項目的開發當中,使用 Abp Zero 自帶的審計日誌功能寫入效率比較低。其次審計日誌數據量中後期十分龐大,不適合與業務數據存放在一起。所以我們可以重新實現 Abp 的 IAuditingStore 介面,來讓我們的審計日誌數據存儲在 MongoDb 當中。

二、實現

2.0 引入相關包

這裡我們需要在模塊項目引入 Abp 與 mongocsharpdriver 包,引入之後項目如下圖。

2.1 實體封裝

基於 Abp 框架的設計,它許多組件都可以隨時被我們所替換。這裡我們先定義存儲到 MongoDb 資料庫的實體,取名叫做 MongoDbAuditEntity。下麵就是它的基本定義,它是我從 Zero 裡面單獨扒出來的,是基於 Abp 的審計信息定義重新進行封裝的一個實體。

using System;
using System.Linq;
using Abp.Extensions;
using Abp.Runtime.Validation;
using Abp.UI;

namespace Abp.Auditing.MongoDb
{
    /// <summary>
    /// 審計日誌記錄實體,僅用於 MongoDb 存儲使用。
    /// </summary>
    public class MongoDbAuditEntity
    {
        /// <summary>
        /// <see cref="ServiceName"/> 屬性的最大長度。
        /// </summary>
        public static int MaxServiceNameLength = 256;

        /// <summary>
        /// <see cref="MethodName"/> 屬性的最大長度。
        /// </summary>
        public static int MaxMethodNameLength = 256;

        /// <summary>
        /// <see cref="Parameters"/> 屬性的最大長度。
        /// </summary>
        public static int MaxParametersLength = 1024;

        /// <summary>
        /// <see cref="ClientIpAddress"/> 屬性的最大長度。
        /// </summary>
        public static int MaxClientIpAddressLength = 64;

        /// <summary>
        /// <see cref="ClientName"/> 屬性的最大長度。
        /// </summary>
        public static int MaxClientNameLength = 128;

        /// <summary>
        /// <see cref="BrowserInfo"/> 屬性的最大長度。
        /// </summary>
        public static int MaxBrowserInfoLength = 512;

        /// <summary>
        /// <see cref="Exception"/> 屬性的最大長度。
        /// </summary>
        public static int MaxExceptionLength = 2000;

        /// <summary>
        /// <see cref="CustomData"/> 屬性的最大長度。
        /// </summary>
        public static int MaxCustomDataLength = 2000;
        
        /// <summary>
        /// 調用介面時用戶的編碼,如果是匿名訪問,則可能為 null。
        /// </summary>
        public string UserCode { get; set; }

        /// <summary>
        /// 調用介面時用戶的集團 Id,如果是匿名訪問,則可能為 null。
        /// </summary>
        public int? GroupId { get; set; }

        /// <summary>
        /// 調用介面時,請求的應用服務/控制器名稱。
        /// </summary>
        public string ServiceName { get; set; }

        /// <summary>
        /// 調用介面時,請求的的具體方法/介面名稱。
        /// </summary>
        public string MethodName { get; set; }

        /// <summary>
        /// 調用介面時,傳遞的具體參數。
        /// </summary>
        public string Parameters { get; set; }

        /// <summary>
        /// 調用介面的時間,以伺服器的時間進行記錄。
        /// </summary>
        public DateTime ExecutionTime { get; set; }

        /// <summary>
        /// 調用介面執行方法時所消耗的時間,以毫秒為單位。
        /// </summary>
        public int ExecutionDuration { get; set; }

        /// <summary>
        /// 調用介面時客戶端的 IP 地址。
        /// </summary>
        public string ClientIpAddress { get; set; }

        /// <summary>
        /// 調用介面時客戶端的名稱(通常為電腦名)。
        /// </summary>
        public string ClientName { get; set; }
        
        /// <summary>
        /// 調用介面的瀏覽器信息。
        /// </summary>
        public string BrowserInfo { get; set; }

        /// <summary>
        /// 調用介面時如果產生了異常,則記錄在本欄位,如果沒有異常則可能 null。
        /// </summary>
        public string Exception { get; set; }

        /// <summary>
        /// 自定義數據
        /// </summary>
        public string CustomData { get; set; }

        /// <summary>
        /// 從給定的 <see cref="auditInfo"/> 審計信息創建一個新的 MongoDb 審計日誌實體
        /// (<see cref="MongoDbAuditEntity"/>)。
        /// </summary>
        /// <param name="auditInfo">原始審計日誌信息。</param>
        /// <returns>創建完成的 <see cref="MongoDbAuditEntity"/> 實體對象。</returns>
        public static MongoDbAuditEntity CreateFromAuditInfo(AuditInfo auditInfo)
        {
            var expMsg = GetAbpClearException(auditInfo.Exception);
            
            return new MongoDbAuditEntity
            {
                UserCode = auditInfo.UserId?.ToString(),
                GroupId = null,
                ServiceName = auditInfo.ServiceName.TruncateWithPostfix(MaxServiceNameLength),
                MethodName = auditInfo.MethodName.TruncateWithPostfix(MaxMethodNameLength),
                Parameters = auditInfo.Parameters.TruncateWithPostfix(MaxParametersLength),
                ExecutionTime = auditInfo.ExecutionTime,
                ExecutionDuration = auditInfo.ExecutionDuration,
                ClientIpAddress = auditInfo.ClientIpAddress.TruncateWithPostfix(MaxClientIpAddressLength),
                ClientName = auditInfo.ClientName.TruncateWithPostfix(MaxClientNameLength),
                BrowserInfo = auditInfo.BrowserInfo.TruncateWithPostfix(MaxBrowserInfoLength),
                Exception = expMsg.TruncateWithPostfix(MaxExceptionLength),
                CustomData = auditInfo.CustomData.TruncateWithPostfix(MaxCustomDataLength)
            };
        }
        
        public override string ToString()
        {
            return string.Format(
                "審計日誌: {0}.{1} 由用戶 {2} 執行,花費了 {3} 毫秒,請求的源 IP 地址為: {4} 。",
                ServiceName, MethodName, UserCode, ExecutionDuration, ClientIpAddress
            );
        }
        
        /// <summary>
        /// 創建更加清楚明確的異常信息。
        /// </summary>
        /// <param name="exception">要處理的異常數據。</param>
        private static string GetAbpClearException(Exception exception)
        {
            var clearMessage = "";
            switch (exception)
            {
                case null:
                    return null;

                case AbpValidationException abpValidationException:
                    clearMessage = "異常為參數驗證錯誤,一共有 " + abpValidationException.ValidationErrors.Count + "個錯誤:";
                    foreach (var validationResult in abpValidationException.ValidationErrors) 
                    {
                        var memberNames = "";
                        if (validationResult.MemberNames != null && validationResult.MemberNames.Any())
                        {
                            memberNames = " (" + string.Join(", ", validationResult.MemberNames) + ")";
                        }

                        clearMessage += "\r\n" + validationResult.ErrorMessage + memberNames;
                    }
                    break;

                case UserFriendlyException userFriendlyException:
                    clearMessage =
                        $"業務相關錯誤,錯誤代碼: {userFriendlyException.Code} \r\n 異常詳細信息: {userFriendlyException.Details}";
                    break;
            }

            return exception + (string.IsNullOrEmpty(clearMessage) ? "" : "\r\n\r\n" + clearMessage);
        }
    }
}

2.2 編寫 MongoDb 配置類

一般來說,我們編寫一個 Abp 模塊肯定是需要構建一個配置類的,以便其他開發人員在使用我們的模塊可以進行一些自定義配置。這裡我們的 MongoDb 審計日誌模塊無非就是需要配置兩個信息,第一個就是 MongoDb 資料庫的連接字元串,第二個就是要存儲的庫名稱。

/// <summary>
/// 審計日誌的 MongoDb 存儲模塊。
/// </summary>
public interface IAuditingMongoDbConfiguration
{
    /// <summary>
    /// MongoDb 連接字元串。
    /// </summary>
    string ConnectionString { get; set; }

    /// <summary>
    /// 要連接的 MongoDb 資料庫名稱 
    /// </summary>
    string DataBaseName { get; set; }
}

同理,再編寫一個實現。

public class AuditingMongoDbConfiguration : IAuditingMongoDbConfiguration
{
    public string ConnectionString { get; set; }
    
    public string DataBaseName { get; set; }
}

2.3 編寫 IMongoClient 的工廠類

其實你直接 new 也可以,這裡編寫一個工廠類是省去一些構建流程而已,首先為工廠類定義一個介面,該介面只有一個方法,就是創建 IMongoClient 的實例對象。

public interface IMongoClientFactory
{
    IMongoClient Create();
}

這個工廠的實現也很簡單,只不過我們在工廠當中註入了 IAuditingMongoDbConfiguration ,方便我們創建實例。

public class MongoClientFactory : IMongoClientFactory
{
    private readonly IAuditingMongoDbConfiguration _mongoDbConfiguration;
    
    public MongoClientFactory(IAuditingMongoDbConfiguration mongoDbConfiguration)
    {
        _mongoDbConfiguration = mongoDbConfiguration;
    }
    
    public IMongoClient Create()
    {
        return new MongoClient(_mongoDbConfiguration.ConnectionString);
    }
}

2.4 審計日誌的具體存儲動作

上面幾點都是做一些準備工作,下麵我們需要實現 IAuditingStore 介面,以便將我們的審計日誌存儲在 MongoDb 資料庫當中。IAuditingStore 介面只定義了一個方法,就是 SaveAsync(AuditInfo auditInfo) 方法。該方法是在每次介面請求的時候,通過過濾器/攔截器的時候會被調用。當然整個審計日誌的構成不是這麼簡單的,如果大家有興趣可以查看我的另一篇博客 《[Abp 源碼分析] 十五、自動審計記錄》 ,在這篇博客有詳細講述審計日誌的相關知識。

我們接著繼續,因為 SaveAsync(AuditInfo auditInfo) 方法傳入了一個 AuditInfo 對象,我們就可以基於這個對象來構造我們的數據實體。構造完成之後,將其通過 IMongoClient 對象存儲到 MongoDb 資料庫當中。

/// <summary>
/// <see cref="IAuditingStore"/> 的特殊實現,使用的是 MongoDb 作為持久化存儲。
/// </summary>
public class MongoDbAuditingStore : IAuditingStore
{
    private readonly IMongoClientFactory _clientFactory;
    private readonly IAuditingMongoDbConfiguration _mongoDbConfiguration;
    
    public MongoDbAuditingStore(IMongoClientFactory clientFactory, IAuditingMongoDbConfiguration mongoDbConfiguration)
    {
        _clientFactory = clientFactory;
        _mongoDbConfiguration = mongoDbConfiguration;
    }

    public async Task SaveAsync(AuditInfo auditInfo)
    {
        var entity = MongoDbAuditEntity.CreateFromAuditInfo(auditInfo);
        
        await _clientFactory.Create()
            .GetDatabase(_mongoDbConfiguration.DataBaseName)
            .GetCollection<MongoDbAuditEntity>(typeof(MongoDbAuditEntity).Name)
            .InsertOneAsync(entity);
    }
}

可以看到整體代碼還是十分簡單的,直接通過 auditInfo 對象構造好數據實體之後,插入到 MongoDb 資料庫當中。

2.5 編寫模塊類

每一個基於 Abp 的第三方模塊都會有一個模塊類,模塊類的主要作用就是針對於第三方模塊進行一些基本配置,以及對一些組件的替換動作。

using Abp.Auditing.MongoDb.Configuration;
using Abp.Auditing.MongoDb.Infrastructure;
using Abp.Dependency;
using Abp.Modules;

namespace Abp.Auditing.MongoDb
{
    [DependsOn(typeof(AbpKernelModule))]
    public class AbpAuditingMongoDbModule : AbpModule
    {
        public override void PreInitialize()
        {
            IocManager.Register<IAuditingMongoDbConfiguration,AuditingMongoDbConfiguration>();
            IocManager.Register<IMongoClientFactory,MongoClientFactory>();
            
            // 替換自帶的審計日誌存儲實現
            Configuration.ReplaceService(typeof(IAuditingStore),() =>
            {
                IocManager.Register<IAuditingStore, MongoDbAuditingStore>(DependencyLifeStyle.Transient);
            });
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(AbpAuditingMongoDbModule).Assembly);
        }
    }
}

2.6 編寫集成的擴展方法

Abp 模塊都會基於 IModuleConfigurations 介面編寫一個擴展方法,這樣其他基於 Abp 框架的項目開發人員就可以很方便地在其啟動模塊的 PreInitialzie() 方法當中通過 Configuration.Modules 來進行配置。

/// <summary>
/// MongoDb 審計日誌存儲提供器的配置類的擴展方法。
/// </summary>
public static class AuditingMongoDbConfigurationExtensions
{
    /// <summary>
    /// 配置審計日誌的 MongoDb 實現的相關參數。 
    /// </summary>
    /// <param name="modules">模塊配置類</param>
    /// <param name="connectString">MongoDb 連接字元串。</param>
    /// <param name="dataBaseName">要操作的 MongoDb 資料庫。</param>
    public static void ConfigureMongoDbAuditingStore(this IModuleConfigurations modules,string connectString,string dataBaseName)
    {
        var configuration = modules.AbpConfiguration.Get<IAuditingMongoDbConfiguration>();
        
        configuration.ConnectionString = connectString;
        configuration.DataBaseName = dataBaseName;
    }
}

三、測試

新建一個項目,並添加對我們庫的引用,在其啟動模塊當中添加對 AbpAuditingMongoDbModule 模塊的依賴,在其 PreInitialize() 方法當中加入以下代碼,以配置審計日誌相關功能。

[DependsOn(typeof(AbpAuditingMongoDbModule))]
public class StartupModule : AbpModule
{
    public override void PreInitialize()
    {
        // 其他代碼...
    
        // 開啟審計日誌記錄
        Configuration.Auditing.IsEnabled = true;
        // 允許記錄匿名用戶請求
        Configuration.Auditing.IsEnabledForAnonymousUsers = true;
        // 配置 MonggoDb 資料庫地址與名稱
        Configuration.Modules.ConfigureMongoDbAuditingStore("mongodb://username:Zpassword@ip:port","TestDataBase");
        
        // 其他代碼...
    }
}

啟動項目之後,我們嘗試訪問測試方法,之後來到 MongoDb 資料庫當中,查看具體的審計日誌信息。

可以看到,所有對介面的請求都被記錄到了 MongoDb 當中,這樣後續可以基於這些數據進行二次分析。

四、結語

Abp.Auditing.MongoDb 包下載地址

Abp.Auditing.MongoDb 包 GitHub 地址


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • upstream模塊的使用方法1,使用upstream和server指令來選擇上游伺服器這兩個指令的語法如下圖:示例:2,對上游服務使用keepalive長連接負載均衡策略round-robin加權Round-Robin演算法是其它所有負載均衡演算法的基礎。 ...
  • 反向代理是nginx的一個非常重要的功能。反向代理nginx支持四層反向代理和七層反向代理,如下圖。負載均衡負載均衡是實現服務高性能和高可用的重要手段,而nginx是實現負載均衡的重要工具。 ...
  • 目錄:https://www.cnblogs.com/liqingwen/p/10261436.html 可以創建泛型類,像這樣 這是一個泛型類,它使用類型 T 作為 Write 方法中的方法參數。可以按這種方式去使用: 即使類本身不是泛型,也可以創建泛型方法。 請註意, ThingWriter 類 ...
  • 目錄:https://www.cnblogs.com/liqingwen/p/10261436.html 通過重寫 Equals 方法可以改善結構體相等比較的性能方法。 如果結構體包含引用類型欄位(而不是僅僅只有值類型,如 int)。 預設情況下,結構體的相等性是通過對記憶體中的兩個結構體對象進行逐字 ...
  • 目錄:https://www.cnblogs.com/liqingwen/p/10261436.html 當需要一系列整型值時,可以使用某種迴圈手動創建,或者可以使用 Enumerable.Range 方法。以下為代碼說明: var oneToTen = Enumerable.Range(1, 10 ...
  • " 【.NET Core項目實戰 統一認證平臺】開篇及目錄索引 " 上一篇我介紹了 的生成驗證及流程內容,相信大家也對 非常熟悉了,今天將從一個小眾的需求出發,介紹如何強制令牌過期的思路和實現過程。 .netcore項目實戰交流群(637326624),有興趣的朋友可以在群里交流討論。 一、前言 眾 ...
  • 聲明:問題雖然已經被解決,但是並沒有明白具體原理,歡迎大佬補充。 最近網站出現一個問題,在C#裡面使用 HttpWebRequest 類去發送post請求,偶爾 會出現 “套接字(協議/網路地址/埠)只允許使用一次” 的異常,很明顯應該是埠被占用。 原因排查: 1、網上說最多就是其他程式占用埠 ...
  • 一.概述 在上二篇中,主要是介紹了asp.net core mvc中路由的使用,這篇繼續介紹路由在ASP.NET Core Razor中的使用。Razor Pages應該使用預設的傳統路由,從應用程式的Pages文件夾中提供命令資源。還可以使用其他約定來自定義 Razor Pages 路由行為。 在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...