[Abp 源码分析]十五、自动审计记录 (2)

可以看到在判断是否绑定拦截器的时候,Abp 使用了 auditingConfiguration.Selectors 的属性来进行判断,那么默认 Abp 为我们添加了哪些类型是必定有审计日志的呢?

通过代码追踪,我们来到了 AbpKernalModule 类的内部,在其预加载方法里面有一个 AddAuditingSelectors() 的方法,该方法的作用就是添加了一个针对于应用服务类型的一个选择器对象。

public sealed class AbpKernelModule : AbpModule { public override void PreInitialize() { // ... 其他代码 AddAuditingSelectors(); // ... 其他代码 } // ... 其他代码 private void AddAuditingSelectors() { Configuration.Auditing.Selectors.Add( new NamedTypeSelector( "Abp.ApplicationServices", type => typeof(IApplicationService).IsAssignableFrom(type) ) ); } // ... 其他代码 }

我们先看一下 NamedTypeSelector 的一个作用是什么,其基本类型定义由一个 string 和 Func<Type, bool> 组成,十分简单,重点就出在这个断言委托上面。

public class NamedTypeSelector { // 选择器名称 public string Name { get; set; } // 断言委托 public Func<Type, bool> Predicate { get; set; } public NamedTypeSelector(string name, Func<Type, bool> predicate) { Name = name; Predicate = predicate; } }

回到最开始的地方,当 Abp 为 Selectors 添加了一个名字为 "Abp.ApplicationServices" 的类型选择器。其断言委托的大体意思就是传入的 type 参数是继承自 IApplicationService 接口的话,则返回 true,否则返回 false。

这样在程序启动的时候,首先注入类型的时候,会首先进入上文所述的拦截器绑定类当中,这个时候会使用 Selectors 内部的类型选择器来调用这个集合内部的断言委托,只要这些选择器对象有一个返回 true,那么就直接与当前注入的 type 绑定拦截器。

2.代码分析 2.1 过滤器代码分析

首先查看这个过滤器的整体类型结构,一个标准的过滤器,肯定要实现 IAsyncActionFilter 接口。从下面的代码我们可以看到其注入了 IAbpAspNetCoreConfiguration 和一个 IAuditingHelper 对象。这两个对象的作用分别是判断是否记录日志,另一个则是用来真正写入日志所使用的。

public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency { // 审计日志组件配置对象 private readonly IAbpAspNetCoreConfiguration _configuration; // 真正用来写入审计日志的工具类 private readonly IAuditingHelper _auditingHelper; public AbpAuditActionFilter(IAbpAspNetCoreConfiguration configuration, IAuditingHelper auditingHelper) { _configuration = configuration; _auditingHelper = auditingHelper; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // ... 代码实现 } // ... 其他代码 }

接着看 AbpAuditActionFilter() 方法内部的实现,进入这个过滤器的时候,通过 ShouldSaveAudit() 方法来判断是否要写审计日志。

之后呢与 DTO 自动验证的过滤器一样,通过 AbpCrossCuttingConcerns.Applying() 方法为当前的对象增加了一个标识,用来告诉拦截器说我已经处理过了,你就不要再重复处理了。

再往下就是创建审计信息,执行具体接口方法,并且如果产生了异常的话,也会存放到审计信息当中。

最后接口无论是否执行成功,还是说出现了异常信息,都会将其性能计数信息同审计信息一起,通过 IAuditingHelper 存储起来。

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 判断是否写日志 if (!ShouldSaveAudit(context)) { await next(); return; } // 为当前类型打上标识 using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Auditing)) { // 构造审计信息(AuditInfo) var auditInfo = _auditingHelper.CreateAuditInfo( context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(), context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo, context.ActionArguments ); // 开始性能计数 var stopwatch = Stopwatch.StartNew(); try { // 尝试调用接口方法 var result = await next(); // 产生异常之后,将其异常信息存放在审计信息之中 if (result.Exception != null && !result.ExceptionHandled) { auditInfo.Exception = result.Exception; } } catch (Exception ex) { // 产生异常之后,将其异常信息存放在审计信息之中 auditInfo.Exception = ex; throw; } finally { // 停止计数,并且存储审计信息 stopwatch.Stop(); auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); await _auditingHelper.SaveAsync(auditInfo); } } } 2.2 拦截器代码分析

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppgsg.html