• Abp + MongoDb 改造默认的审计日志存储位置


    一、背景

    在实际项目的开发当中,使用 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 += "
    " + validationResult.ErrorMessage + memberNames;
                        }
                        break;
    
                    case UserFriendlyException userFriendlyException:
                        clearMessage =
                            $"业务相关错误,错误代码: {userFriendlyException.Code} 
     异常详细信息: {userFriendlyException.Details}";
                        break;
                }
    
                return exception + (string.IsNullOrEmpty(clearMessage) ? "" : "
    
    " + 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 地址

  • 相关阅读:
    python基本数据类型之整型和浮点型
    Java学习路线
    Linux学习笔记之VIM
    Java基础之流程控制
    Linux学习笔记之Shell
    Java基础之数据类型
    论文提交说明
    IDEA安装教程
    Link summary for writing papers
    1 类基础知识
  • 原文地址:https://www.cnblogs.com/myzony/p/10265395.html
Copyright © 2020-2023  润新知