热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

一、背景在实际项目的开发当中,使用AbpZero自带的审计日志功能写入效率比较低。其次审计日志数据量中后期十分庞大,不适合与业务数据存放在一起。所以我们可以重新实现A

一、背景

在实际项目的开发当中,使用 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
{
	/// 
    /// 审计日志记录实体,仅用于 MongoDb 存储使用。
    /// 
    public class MongoDbAuditEntity
    {
        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxServiceNameLength = 256;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxMethodNameLength = 256;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxParametersLength = 1024;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxClientIpAddressLength = 64;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxClientNameLength = 128;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxBrowserInfoLength = 512;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxExceptiOnLength= 2000;

        /// 
        ///  属性的最大长度。
        /// 
        public static int MaxCustomDataLength = 2000;
        
        /// 
        /// 调用接口时用户的编码,如果是匿名访问,则可能为 null。
        /// 
        public string UserCode { get; set; }

        /// 
        /// 调用接口时用户的集团 Id,如果是匿名访问,则可能为 null。
        /// 
        public int? GroupId { get; set; }

        /// 
        /// 调用接口时,请求的应用服务/控制器名称。
        /// 
        public string ServiceName { get; set; }

        /// 
        /// 调用接口时,请求的的具体方法/接口名称。
        /// 
        public string MethodName { get; set; }

        /// 
        /// 调用接口时,传递的具体参数。
        /// 
        public string Parameters { get; set; }

        /// 
        /// 调用接口的时间,以服务器的时间进行记录。
        /// 
        public DateTime ExecutionTime { get; set; }

        /// 
        /// 调用接口执行方法时所消耗的时间,以毫秒为单位。
        /// 
        public int ExecutionDuration { get; set; }

        /// 
        /// 调用接口时客户端的 IP 地址。
        /// 
        public string ClientIpAddress { get; set; }

        /// 
        /// 调用接口时客户端的名称(通常为计算机名)。
        /// 
        public string ClientName { get; set; }
        
        /// 
        /// 调用接口的浏览器信息。
        /// 
        public string BrowserInfo { get; set; }

        /// 
        /// 调用接口时如果产生了异常,则记录在本字段,如果没有异常则可能 null。
        /// 
        public string Exception { get; set; }

        /// 
        /// 自定义数据
        /// 
        public string CustomData { get; set; }

        /// 
        /// 从给定的  审计信息创建一个新的 MongoDb 审计日志实体
        /// ()。
        /// 
        /// 原始审计日志信息。
        /// 创建完成的  实体对象。
        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
            );
        }
        
        /// 
        /// 创建更加清楚明确的异常信息。
        /// 
        /// 要处理的异常数据。
        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 数据库的连接字符串,第二个就是要存储的库名称。

/// 
/// 审计日志的 MongoDb 存储模块。
/// 
public interface IAuditingMongoDbConfiguration
{
    /// 
    /// MongoDb 连接字符串。
    /// 
    string ConnectionString { get; set; }

    /// 
    /// 要连接的 MongoDb 数据库名称 
    /// 
    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 数据库当中。

/// 
///  的特殊实现,使用的是 MongoDb 作为持久化存储。
/// 
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(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();
            IocManager.Register();
            
            // 替换自带的审计日志存储实现
            Configuration.ReplaceService(typeof(IAuditingStore),() =>
            {
                IocManager.Register(DependencyLifeStyle.Transient);
            });
        }

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

2.6 编写集成的扩展方法

Abp 模块都会基于 IModuleConfigurations 接口编写一个扩展方法,这样其他基于 Abp 框架的项目开发人员就可以很方便地在其启动模块的 PreInitialzie() 方法当中通过 Configuration.Modules 来进行配置。

/// 
/// MongoDb 审计日志存储提供器的配置类的扩展方法。
/// 
public static class AuditingMongoDbConfigurationExtensions
{
	/// 
	/// 配置审计日志的 MongoDb 实现的相关参数。 
	/// 
	/// 模块配置类
	/// MongoDb 连接字符串。
	/// 要操作的 MongoDb 数据库。
	public static void ConfigureMongoDbAuditingStore(this IModuleConfigurations modules,string connectString,string dataBaseName)
	{
		var cOnfiguration= modules.AbpConfiguration.Get();
		
		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 地址


推荐阅读
  • Windows服务与数据库交互问题解析
    本文探讨了在Windows 10(64位)环境下开发的Windows服务,旨在定期向本地MS SQL Server (v.11)插入记录。尽管服务已成功安装并运行,但记录并未正确插入。我们将详细分析可能的原因及解决方案。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了IBM DB2数据库在大型应用系统中的应用,强调其卓越的可扩展性和多环境支持能力。文章深入分析了DB2在数据利用性、完整性、安全性和恢复性方面的优势,并提供了优化建议以提升其在不同规模应用程序中的表现。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • MySQL缓存机制深度解析
    本文详细探讨了MySQL的缓存机制,包括主从复制、读写分离以及缓存同步策略等内容。通过理解这些概念和技术,读者可以更好地优化数据库性能。 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • python的交互模式怎么输出名文汉字[python常见问题]
    在命令行模式下敲命令python,就看到类似如下的一堆文本输出,然后就进入到Python交互模式,它的提示符是>>>,此时我们可以使用print() ... [详细]
author-avatar
金里昂钢琴艺术中心
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有