拦截器处理时的总体思路与过滤器类似,其核心都是通过 IAuditingHelper 来创建审计信息和持久化审计信息的。只不过呢由于拦截器不仅仅是处理 MVC 接口,也会处理内部的一些类型的方法,所以针对同步方法与异步方法的处理肯定会复杂一点。
拦截器呢,我们关心一下他的核心方法 Intercept() 就行了。
public void Intercept(IInvocation invocation) { // 判断过滤器是否已经处理了过了 if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Auditing)) { invocation.Proceed(); return; } // 通过 IAuditingHelper 来判断当前方法是否需要记录审计日志信息 if (!_auditingHelper.ShouldSaveAudit(invocation.MethodInvocationTarget)) { invocation.Proceed(); return; } // 构造审计信息 var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetType, invocation.MethodInvocationTarget, invocation.Arguments); // 判断方法的类型,同步方法与异步方法的处理逻辑不一样 if (invocation.Method.IsAsync()) { PerformAsyncAuditing(invocation, auditInfo); } else { PerformSyncAuditing(invocation, auditInfo); } } // 同步方法的处理逻辑与 MVC 过滤器逻辑相似 private void PerformSyncAuditing(IInvocation invocation, AuditInfo auditInfo) { var stopwatch = Stopwatch.StartNew(); try { invocation.Proceed(); } catch (Exception ex) { auditInfo.Exception = ex; throw; } finally { stopwatch.Stop(); auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); _auditingHelper.Save(auditInfo); } } // 异步方法处理 private void PerformAsyncAuditing(IInvocation invocation, AuditInfo auditInfo) { var stopwatch = Stopwatch.StartNew(); invocation.Proceed(); if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithFinally( (Task) invocation.ReturnValue, exception => SaveAuditInfo(auditInfo, stopwatch, exception) ); } else //Task<TResult> { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, exception => SaveAuditInfo(auditInfo, stopwatch, exception) ); } } private void SaveAuditInfo(AuditInfo auditInfo, Stopwatch stopwatch, Exception exception) { stopwatch.Stop(); auditInfo.Exception = exception; auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); _auditingHelper.Save(auditInfo); }这里异步方法的处理在很早之前的工作单元拦截器就有过讲述,这里就不再重复说明了。
2.3 核心的 IAuditingHelper从代码上我们就可以看到,不论是拦截器还是过滤器都是最终都是通过 IAuditingHelper 对象来储存审计日志的。Abp 依旧为我们实现了一个默认的 AuditingHelper ,实现了其接口的所有方法。我们先查看一下这个接口的定义:
public interface IAuditingHelper { // 判断当前方法是否需要存储审计日志信息 bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false); // 根据参数集合创建一个审计信息,一般用于拦截器 AuditInfo CreateAuditInfo(Type type, MethodInfo method, object[] arguments); // 根据一个参数字典类来创建一个审计信息,一般用于 MVC 过滤器 AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary<string, object> arguments); // 同步保存审计信息 void Save(AuditInfo auditInfo); // 异步保存审计信息 Task SaveAsync(AuditInfo auditInfo); }我们来到其默认实现 AuditingHelper 类型,先看一下其内部注入了哪些接口。
public class AuditingHelper : IAuditingHelper, ITransientDependency { // 日志记录器,用于记录日志 public ILogger Logger { get; set; } // 用于获取当前登录用户的信息 public IAbpSession AbpSession { get; set; } // 用于持久话审计日志信息 public IAuditingStore AuditingStore { get; set; } // 主要作用是填充审计信息的客户端调用信息 private readonly IAuditInfoProvider _auditInfoProvider; // 审计日志组件的配置相关 private readonly IAuditingConfiguration _configuration; // 在调用 AuditingStore 进行持久化的时候使用,创建一个工作单元 private readonly IUnitOfWorkManager _unitOfWorkManager; // 用于序列化参数信息为 JSON 字符串 private readonly IAuditSerializer _auditSerializer; public AuditingHelper( IAuditInfoProvider auditInfoProvider, IAuditingConfiguration configuration, IUnitOfWorkManager unitOfWorkManager, IAuditSerializer auditSerializer) { _auditInfoProvider = auditInfoProvider; _configuration = configuration; _unitOfWorkManager = unitOfWorkManager; _auditSerializer = auditSerializer; AbpSession = NullAbpSession.Instance; Logger = NullLogger.Instance; AuditingStore = SimpleLogAuditingStore.Instance; } // ... 其他实现的接口 } 2.3.1 判断是否创建审计信息首先分析一下其内部的 ShouldSaveAudit() 方法,整个方法的核心作用就是根据传入的方法类型来判定是否为其创建审计信息。