中国电力安全管理网,ncis第十季,最牛古董商
在实际项目的开发当中,使用 abp zero 自带的审计日志功能写入效率比较低。其次审计日志数据量中后期十分庞大,不适合与业务数据存放在一起。所以我们可以重新实现 abp 的 iauditingstore
接口,来让我们的审计日志数据存储在 mongodb 当中。
这里我们需要在模块项目引入 abp 与 mongocsharpdriver 包,引入之后项目如下图。
基于 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); } } }
一般来说,我们编写一个 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; } }
其实你直接 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); } }
上面几点都是做一些准备工作,下面我们需要实现 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 数据库当中。
每一个基于 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); } } }
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 包 github 地址
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Net Core Web Api项目与在NginX下发布的方法
asp.net core3.1 引用的元包dll版本兼容性问题解决方案
IdentityServer4实现.Net Core API接口权限认证(快速入门)
ASP.NET Core MVC通过IViewLocationExpander扩展视图搜索路径的实现
网友评论