『贴代码』
我的博文
个人作品
优选问答
「搜索」
【本期话题】更多
三人寄语更多
慎用redis的同步我的意见是redis都走异步!!!
点赞:0
没有最好的语言,只有更合适的语言!
点赞:0
逻辑注解清晰的代码优于那些一眼看不明白的语法糖
点赞:0
在循环里面慎重的使用await!
点赞:0
对于答案来说,更重要的是找到答案的这个过程而不是答案本身!
点赞:0
时间与空间总是在换来换去,鱼和熊掌往往不可同得!
点赞:0
微信的app这个东西很鬼,有时候你刷新页面,会造成部分数据重置,部分不重置,不妨试试把对象放app.globalData里面去,会有意外惊喜!
点赞:0
需求就是系统的千年杀,相爱相杀那种!
点赞:0
能通过内网IP访问的,尽量不要使用域名访问!
点赞:1
时好时坏的结果,往往是多线程引起的逻辑混乱导致的!
点赞:0
论我是如何改造ABP官方的审计日志Auditing系统的!
尘埃 2025-05-11 485 37 1
在做开发中,审计日志应该是后续才会考虑的事情,前期可能会考虑下就是哪些数据要做日志记录,往往丢一个记录表,这个实现不能说不行,只是有点鸡肋!一起来看看我是如何改造官方的审计日志模块的!

在做开发中,审计日志应该是后续才会考虑的事情,前期可能会考虑下就是哪些数据要做日志记录,往往丢一个记录表,这个实现不能说不行,只是有点鸡肋!一起来看看我是如何改造官方的审计日志模块的!

源码解析

先说下我的理解
Auditing审计日志,主要起作用的是中间件!

app.UseAuditing();//官方调用

也就是上面这段代码,一起看看他里面说了啥

    public static IApplicationBuilder UseAuditing(this IApplicationBuilder app)
    {
        return app.UseMiddleware<AbpAuditingMiddleware>(Array.Empty<object>());
    }

看到了吧,调用了中间件AbpAuditingMiddleware
再看看这个里面干了啥

public class AbpAuditingMiddleware : IMiddleware, ITransientDependency
{
    private readonly IAuditingManager _auditingManager;

    protected AbpAuditingOptions AuditingOptions { get; }

    protected AbpAspNetCoreAuditingOptions AspNetCoreAuditingOptions { get; }

    protected ICurrentUser CurrentUser { get; }

    protected IUnitOfWorkManager UnitOfWorkManager { get; }

    public AbpAuditingMiddleware(IAuditingManager auditingManager, ICurrentUser currentUser, IOptions<AbpAuditingOptions> auditingOptions, IOptions<AbpAspNetCoreAuditingOptions> aspNetCoreAuditingOptions, IUnitOfWorkManager unitOfWorkManager)
    {
        _auditingManager = auditingManager;
        CurrentUser = currentUser;
        UnitOfWorkManager = unitOfWorkManager;
        AuditingOptions = auditingOptions.Value;
        AspNetCoreAuditingOptions = aspNetCoreAuditingOptions.Value;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (!AuditingOptions.IsEnabled || IsIgnoredUrl(context))
        {
            await next(context).ConfigureAwait(continueOnCapturedContext: false);
            return;
        }

        bool hasError = false;
        using IAuditLogSaveHandle saveHandle = _auditingManager.BeginScope();
        try
        {
            _ = 1;
            try
            {
                await next(context).ConfigureAwait(continueOnCapturedContext: false);
                if (_auditingManager.Current.Log.Exceptions.Any())
                {
                    hasError = true;
                }
            }
            catch (Exception item)
            {
                hasError = true;
                if (!_auditingManager.Current.Log.Exceptions.Contains(item))
                {
                    _auditingManager.Current.Log.Exceptions.Add(item);
                }

                throw;
            }
        }
        finally
        {
            if (await ShouldWriteAuditLogAsync(_auditingManager.Current.Log, context, hasError).ConfigureAwait(continueOnCapturedContext: false))
            {
                if (UnitOfWorkManager.Current != null)
                {
                    try
                    {
                        await UnitOfWorkManager.Current.SaveChangesAsync().ConfigureAwait(continueOnCapturedContext: false);
                    }
                    catch (Exception item2)
                    {
                        if (!_auditingManager.Current.Log.Exceptions.Contains(item2))
                        {
                            _auditingManager.Current.Log.Exceptions.Add(item2);
                        }
                    }
                }

                await saveHandle.SaveAsync().ConfigureAwait(continueOnCapturedContext: false);
            }
        }
    }

    private bool IsIgnoredUrl(HttpContext context)
    {
        if (context.Request.Path.Value != null)
        {
            return AspNetCoreAuditingOptions.IgnoredUrls.Any((string x) => context.Request.Path.Value.StartsWith(x));
        }

        return false;
    }

    private async Task<bool> ShouldWriteAuditLogAsync(AuditLogInfo auditLogInfo, HttpContext httpContext, bool hasError)
    {
        foreach (Func<AuditLogInfo, Task<bool>> alwaysLogSelector in AuditingOptions.AlwaysLogSelectors)
        {
            if (await alwaysLogSelector(auditLogInfo).ConfigureAwait(continueOnCapturedContext: false))
            {
                return true;
            }
        }

        if (AuditingOptions.AlwaysLogOnException && hasError)
        {
            return true;
        }

        if (!AuditingOptions.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated)
        {
            return false;
        }

        if (!AuditingOptions.IsEnabledForGetRequests && string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        return true;
    }
}

上面的代码,怎么说呢,我这个版本是5.3.4的
不管怎么说,反正我要改造的话,肯定是自己写一个中间件替换系统的,对吧!

现有痛点

为啥要改造呢,说白点就是官方的提供的太全了,全到我没这么多资源来处理它,有以下几个方面

字段过多

查看官方的Entity你会发现存储的字段是真的多的,很多我感觉是不需要存储的,还有些字段长短的问题,据说可以通过配置修改字段的长短,我感觉就是不够直,绕来绕去太晕乎了!
有些字段是真的没必要的,比如用户姓名啥的,在我理解上,你要么从比如token中解析用户名,要么从缓存中获取用户对象获取,总之是一个子浪费!
你不觉得?举个例子,我之前有一个系统,一天的访问量PV大概在2000万,对你没看到后面有一个万字,所以官方的JWT啥的压根不敢用,因为太浪费资源了啊!!!所以后续我会出一篇改造官方的Auth模块的帖子!

类型不符

里面很多字段的类型是约定的,特别让我奇怪的就是UserId,整个系统几乎都是使用Guid的,其实使用Guid不是不行,只是有没有这个必要,因为Guid的引入,会带来新的问题,并没有全方面碾压int/long,当然如果你说int/long有他的问题所在,这个就是互相博弈的结果了!总之有需求是非Guid的!

字段不足

这里的不足不是数量不足,是需要的信息不一,比如我之前的系统中需要存储店铺ID,经办ID,等,上面说的很多冗余字段,这里又说不足,那干脆字段借用???
我个人是很反对借用这个事的,因为这个就比字段命名ABC更加迷糊,字段的名字本身会给你一个暗示,比如UserName结果你存储的是Address,就问你迷糊不迷糊,难不成你查看代码的时候,旁边还得放一个文档字段映射解说???

性能问题

官方的默认实现,我感觉很多地方有改进的地方,比如存储,默认采用的是同步的方式(意思是同一个事务内,不是async的同步)!

总结

如果要处理以上的问题,你会发觉大改了,而且改完后使用还是不顺手,那就干脆照着官方的代码,重新修改一份,入口处修改下即可!!!

开始改造

我会从Entity开始改造,包括存储,中间件等,也可以引入自己的配置,比如你可以配置某些模块下的Entity做审计,某些不做,而不用一个一个的去标记审计的特性!

Entity

代码直接如下

    /// <summary>
    /// 
    /// </summary>
    [DisableAuditing]
    public class MyAuditingLog : Entity<Guid>
    {
        /// <summary>
        /// 用户ID 这里是一个Guid
        /// </summary>
        public Guid? UserId { get; set; }

        /// <summary>
        /// 登陆者ID 这里是一个int
        /// </summary>
        public int LoginId { get; set; }

        /// <summary>
        /// 客户端ID 最大64字符
        /// </summary>
        [MaxLength(64)]
        public string ClientId { get; set; }

        /// <summary>
        /// 程序名称 最大32字符
        /// </summary>
        [MaxLength(32)]
        public string AppName { get; set; }

        /// <summary>
        /// 时间
        /// </summary>
        public DateTime ExecutionTime { get; set; }

        /// <summary>
        /// 持续
        /// </summary>
        public int ExecutionDuration { get; set; }

        /// <summary>
        /// 地址 最大16
        /// </summary>
        [MaxLength(16)]
        public string ClientIpAddress { get; set; }

        /// <summary>
        /// 方法 最大8字符
        /// </summary>
        [MaxLength(8)]
        public string HttpMethod { get; set; }

        /// <summary>
        /// 地址 最大128
        /// </summary>
        [MaxLength(128)]
        public string Url { get; set; }

        /// <summary>
        /// 状态码
        /// </summary>
        public int? HttpStatusCode { get; set; }

        /// <summary>
        /// 异常 是否有异常信息
        /// </summary>
        public bool HasException { get; set; }

        /// <summary>
        /// 异常信息
        /// </summary>
        public ICollection<MyAuditingException> Exceptions { get; set; }

        /// <summary>
        /// 执行
        /// </summary>
        [NotMapped]
        public ICollection<MyAuditingAction> Actions { get; set; }

        /// <summary>
        /// 变更
        /// </summary>
        [NotMapped]
        public ICollection<MyEntityChange> EntityChanges { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    [DisableAuditing]
    public class MyAuditingException:Entity<Guid>
    {
        /// <summary>
        /// 错误信息
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 代码块
        /// </summary>
        public string StackTrace { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    [DisableAuditing]
    public class MyAuditingAction : Entity<Guid>
    {
        /// <summary>
        /// 
        /// </summary>
        public Guid AuditLogId { get; set; }

        /// <summary>
        /// 最大64
        /// </summary>
        [MaxLength(64)]
        public string ServiceName { get; set; }

        /// <summary>
        /// 函数名称 最大32字符
        /// </summary>
        [MaxLength(32)]
        public string MethodName { get; set; }

        /// <summary>
        /// 最大128
        /// </summary>
        [MaxLength(128)]
        public string Parameters { get; set; }

        /// <summary>
        /// 发生时间
        /// </summary>
        public DateTime ExecutionTime { get; set; }

        /// <summary>
        /// 持续时间
        /// </summary>
        public int ExecutionDuration { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    [DisableAuditing]
    public class MyEntityChange : Entity<Guid>
    {
        /// <summary>
        /// 
        /// </summary>
        public Guid AuditLogId { get; set; }

        ///// <summary>
        ///// 
        ///// </summary>
        //public Guid? TenantId { get; set; }

        /// <summary>
        /// 发生时间
        /// </summary>
        public DateTime ChangeTime { get; set; }

        /// <summary>
        /// 变更类型
        /// </summary>
        public EntityChangeType ChangeType { get; set; }

        ///// <summary>
        ///// 
        ///// </summary>
        //public Guid? EntityTenantId { get; set; }

        /// <summary>
        /// 最大64
        /// </summary>
        [MaxLength(64)]
        public string EntityId { get; set; }

        /// <summary>
        /// 最大128
        /// </summary>
        [MaxLength(128)]
        public string EntityTypeFullName { get; set; }

        /// <summary>
        /// 变更内容
        /// </summary>
        public ICollection<MyEntityPropertyChange> PropertyChanges { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    [DisableAuditing]
    public class MyEntityPropertyChange : Entity<Guid>
    {

        ///// <summary>
        ///// 变换ID
        ///// </summary>
        //public Guid EntityChangeId { get; set; }

        /// <summary>
        /// 新值 长度不限
        /// </summary>
        public string NewValue { get; set; }

        /// <summary>
        /// 旧值 长度不限
        /// </summary>
        public string OriginalValue { get; set; }

        /// <summary>
        /// 最大32
        /// </summary>
        [MaxLength(32)]
        public string PropertyName { get; set; }

        /// <summary>
        /// 最大64
        /// </summary>
        [MaxLength(64)]
        public string PropertyTypeFullName { get; set; }
    }

大致意思是保留要的,添加自己的,去除不需要的!

Helper

以下包含多个信息的改造,我全部放一起好找

 /// <summary>
 /// 
 /// </summary>
 public class MyAuditingHelper
 {
     /// <summary>
     /// 
     /// </summary>
     public static MyAuditingLog BuildEntity(AuditLogInfo auditInfo)
     {
         try
         {
             var mylog = new MyAuditingLog
             {
                 AppName = auditInfo.ApplicationName.AutoString(32),
                 ClientIpAddress = auditInfo.ClientIpAddress.AutoString(16),
                 ExecutionDuration = auditInfo.ExecutionDuration,
                 ExecutionTime = auditInfo.ExecutionTime,
                 HttpMethod = auditInfo.HttpMethod.AutoString(8),
                 ClientId = auditInfo.ClientId,
                 HttpStatusCode = auditInfo.HttpStatusCode,
                 Url = auditInfo.Url.AutoString(128),
                 UserId = auditInfo.UserId,
             };
             //_dbContext.Add(mylog);
             //await _dbContext.SaveChangesAsync();

             if (auditInfo.Actions?.Any() == true)
             {
                 var actions = new List<MyAuditingAction>();
                 foreach (var item in auditInfo.Actions)
                 {
                     var one = new MyAuditingAction
                     {
                         AuditLogId = mylog.Id,
                         ExecutionDuration = item.ExecutionDuration,
                         ExecutionTime = item.ExecutionTime,
                         MethodName = item.MethodName.AutoString(32),
                         Parameters = item.Parameters.AutoString(128),
                         ServiceName = item.ServiceName.AutoString(64),
                     };
                     actions.Add(one);
                 }
                 mylog.Actions = actions;
                 //_dbContext.AddRange(actions);
                 //await _dbContext.SaveChangesAsync();
             }

             if (auditInfo.EntityChanges?.Any() == true)
             {
                 var entitys = new List<MyEntityChange>();
                 foreach (var item in auditInfo.EntityChanges.ToList())
                 {
                     if (item.ChangeType != EntityChangeType.Created)
                     {
                         var one = new MyEntityChange
                         {
                             AuditLogId = mylog.Id,
                             ChangeTime = item.ChangeTime,
                             ChangeType = item.ChangeType,
                             EntityId = item.EntityId.AutoString(64),
                             EntityTypeFullName = item.EntityTypeFullName.AutoString(128),
                         };
                         //await _dbContext.SaveChangesAsync();
                         if (item.PropertyChanges?.Any() == true)
                         {
                             one.PropertyChanges = item.PropertyChanges
                                 .Select(x => new MyEntityPropertyChange
                                 {
                                     NewValue = x.NewValue,
                                     //EntityChangeId = one.Id,
                                     OriginalValue = x.OriginalValue,
                                     PropertyName = x.PropertyName.AutoString(32),
                                     PropertyTypeFullName = x.PropertyTypeFullName.AutoString(64),
                                 })
                                 .ToList();
                         }
                         entitys.Add(one);
                     }
                 }
                 if (entitys.Any())
                 {
                     mylog.EntityChanges = entitys;
                     //_dbContext.AddRange(entitys);
                     //await _dbContext.SaveChangesAsync();
                 }
             }

             if (auditInfo.Exceptions?.Any() == true)
             {
                 mylog.HasException = true;
                 mylog.Exceptions = auditInfo.Exceptions.Select(x => new MyAuditingException { Message = x.Message, StackTrace = x.StackTrace }).ToList();
             }

             //await _dbContext.SaveChangesAsync();
             return mylog;
         }
         catch (Exception exl)
         {
             Log.Error(exl.ToString());
         }
         return null;
     }
 }

 /// <summary>
 /// 
 /// </summary>
 public class MyAuditLogContributor : AuditLogContributor
 {

     /// <summary>
     /// 
     /// </summary>
     public MyAuditLogContributor(){}
     /// <summary>
     /// 
     /// </summary>
     /// <param name="context"></param>
     public override void PostContribute(AuditLogContributionContext context)
     {
         base.PostContribute(context);
         if (context.GetHttpContext().Request.Headers.TryGetValue(PublicString.TokenHeadName, out var val))
         {
             context.AuditInfo.ClientId = val.FirstOrDefault().AutoString(32);//这里要按照需求变更
             //从这里解析出用户ID 可以通过解析token获取自己需要的数据
             Console.WriteLine($"PostContribute.ClientId:{context.AuditInfo.ClientId}");
         }

     }
 }

 /// <summary>
 /// 
 /// </summary>
 public class MyAuditingMiddleware : IMiddleware, ITransientDependency
 {
     /// <summary>
     /// 
     /// </summary>
     private readonly IAuditingManager _auditingManager;

     /// <summary>
     /// 
     /// </summary>
     protected AbpAuditingOptions AuditingOptions { get; }

     /// <summary>
     /// 
     /// </summary>
     protected AbpAspNetCoreAuditingOptions AspNetCoreAuditingOptions { get; }

     /// <summary>
     /// 
     /// </summary>
     protected ICurrentUser CurrentUser { get; }

     /// <summary>
     /// 
     /// </summary>
     protected IUnitOfWorkManager UnitOfWorkManager { get; }

     /// <summary>
     /// 
     /// </summary>
     /// <param name="auditingManager"></param>
     /// <param name="currentUser"></param>
     /// <param name="auditingOptions"></param>
     /// <param name="aspNetCoreAuditingOptions"></param>
     /// <param name="unitOfWorkManager"></param>
     public MyAuditingMiddleware(IAuditingManager auditingManager, ICurrentUser currentUser, IOptions<AbpAuditingOptions> auditingOptions, IOptions<AbpAspNetCoreAuditingOptions> aspNetCoreAuditingOptions, IUnitOfWorkManager unitOfWorkManager)
     {
         _auditingManager = auditingManager;
         CurrentUser = currentUser;
         UnitOfWorkManager = unitOfWorkManager;
         AuditingOptions = auditingOptions.Value;
         AspNetCoreAuditingOptions = aspNetCoreAuditingOptions.Value;
     }

     /// <summary>
     /// 
     /// </summary>
     /// <param name="context"></param>
     /// <param name="next"></param>
     /// <returns></returns>
     public async Task InvokeAsync(HttpContext context, RequestDelegate next)
     {
         if (!AuditingOptions.IsEnabled || IsIgnoredUrl(context))
         {
             await next(context).ConfigureAwait(continueOnCapturedContext: false);
             return;
         }
         //其实可以通过AspNetCoreAuditingOptions配置

         bool hasError = false;
         using IAuditLogSaveHandle saveHandle = _auditingManager.BeginScope();
         try
         {
             try
             {
                 await next(context).ConfigureAwait(continueOnCapturedContext: false);
                 if (_auditingManager.Current.Log.Exceptions.Any())
                 {
                     hasError = true;
                 }
             }
             catch (Exception item)
             {
                 hasError = true;
                 if (!_auditingManager.Current.Log.Exceptions.Contains(item))
                 {
                     _auditingManager.Current.Log.Exceptions.Add(item);
                 }
                 throw;
             }
         }
         finally
         {
             if (ShouldWriteAuditLogAsync(_auditingManager.Current.Log, context, hasError))
             {
                 var currentUow = UnitOfWorkManager.Current; // 安全获取
                 if (currentUow != null)
                 {
                     try
                     {
                         await currentUow.SaveChangesAsync();
                     }
                     catch (Exception ex)
                     {
                         _auditingManager.Current.Log.Exceptions.Add(ex);
                     }
                 }
                 await saveHandle.SaveAsync().ConfigureAwait(continueOnCapturedContext: false);
             }
         }
     }

     /// <summary>
     /// 
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
     private bool IsIgnoredUrl(HttpContext context)
     {
         if (AspNetCoreAuditingOptions.IgnoredUrls != null && AspNetCoreAuditingOptions.IgnoredUrls.Any())
         {
             if (context.Request.Path.Value != null)
             {
                 return AspNetCoreAuditingOptions.IgnoredUrls.Any((string x) => context.Request.Path.Value.StartsWith(x));
             }
         }
         return false;
     }

     /// <summary>
     /// 
     /// </summary>
     /// <param name="auditLogInfo"></param>
     /// <param name="httpContext"></param>
     /// <param name="hasError"></param>
     /// <returns></returns>
     private bool ShouldWriteAuditLogAsync(AuditLogInfo auditLogInfo, HttpContext httpContext, bool hasError)
     {

         if (AuditingOptions.AlwaysLogOnException && hasError)
         {
             return true;
         }

         if (auditLogInfo.HttpStatusCode.HasValue)
         {
             if (auditLogInfo.HttpStatusCode.Value == 204)
             {
                 return false;
             }
         }

         if (httpContext.Request != null)
         {
             if (httpContext.Request.Path.HasValue)
             {
                 if (httpContext.Request.Path.Value.StartsWith("/api/cluster"))
                 {
                     return false;
                 }
                 if (httpContext.Request.Path.Value.StartsWith("/statushub/negotiate"))
                 {
                     return false;
                 }
             }
         }

         //if (!AuditingOptions.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated)
         //{
         //    return false;
         //}

         if (!AuditingOptions.IsEnabledForGetRequests && string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
         {
             return false;
         }

         //当特性冲突的时候,最后的为准!
         var _end = httpContext.GetEndpoint();
         if (_end != null)
         {
             if (_end.Metadata != null)
             {
                 var _size = _end.Metadata.Count;
                 if (_size > 0)
                 {
                     for (var k = _size - 1; k >= 0; k--)
                     {
                         var _item = _end.Metadata[k];
                         if (_item.GetType() == typeof(AuditedAttribute))
                         {
                             break;//跳出循环,由后续的判断接手
                         }
                         if (_item.GetType() == typeof(DisableAuditingAttribute))
                         {
                             return false;
                         }
                     }
                 }
             }
         }
         return true;
     }
 }

 /// <summary>
 /// 
 /// </summary>
 public class MyAuditingStore : IAuditingStore, ITransientDependency
 {
     #region 下面这个是采用后台作业的模式进行

     private readonly IBackgroundJobManager _jobManager;

     /// <summary>
     /// 
     /// </summary>
     /// <param name="job"></param>
     public MyAuditingStore(IBackgroundJobManager job)
     {
         _jobManager = job;
     }

     /// <summary>
     /// 新版本 采用后台作业模式
     /// </summary>
     /// <param name="auditInfo"></param>
     /// <returns></returns>
     public async Task SaveAsync(AuditLogInfo auditInfo)
     {
         var log = MyAuditingHelper.BuildEntity(auditInfo);
         if (log != null)
         {
             await _jobManager.EnqueueAsync(new AuditLogQueueModel { LogInfo = log }, BackgroundJobPriority.Normal, delay: TimeSpan.Zero);
         }
         //var clonedInfo = JsonSerializer.Deserialize<AuditLogInfo>(
         //        JsonSerializer.Serialize(auditInfo, new JsonSerializerOptions
         //        {
         //            ReferenceHandler = ReferenceHandler.Preserve // 处理循环引用
         //        }),
         //        new JsonSerializerOptions
         //        {
         //            ReferenceHandler = ReferenceHandler.Preserve // 处理循环引用
         //        }
         //    );

     }
     #endregion

     #region 以下是旧版本,同步模式,影响主程序

     ///// <summary>
     ///// 
     ///// </summary>
     //private readonly MyAuditingDb _dbContext;

     ///// <summary>
     ///// 
     ///// </summary>
     ///// <param name="dbContext"></param>
     //public MyAuditingStore(MyAuditingDb dbContext)
     //{
     //    _dbContext = dbContext;
     //}

     ///// <summary>
     ///// 
     ///// </summary>
     ///// <param name="auditInfo"></param>
     ///// <returns></returns>
     ///// <exception cref="NotImplementedException"></exception>
     //public async Task SaveAsync(AuditLogInfo auditInfo)
     //{
     //    //下面后续要改成队列模式,提高吞吐!
     //    try
     //    {
     //        var mylog = new MyAuditingLog
     //        {
     //            AppName = auditInfo.ApplicationName.AutoString(32),
     //            ClientIpAddress = auditInfo.ClientIpAddress.AutoString(16),
     //            ExecutionDuration = auditInfo.ExecutionDuration,
     //            ExecutionTime = auditInfo.ExecutionTime,
     //            HttpMethod = auditInfo.HttpMethod.AutoString(8),
     //            ClientId = auditInfo.ClientId,
     //            HttpStatusCode = auditInfo.HttpStatusCode,
     //            Url = auditInfo.Url.AutoString(128),
     //            UserId = auditInfo.UserId,
     //        };
     //        _dbContext.Add(mylog);
     //        await _dbContext.SaveChangesAsync();
     //        if (auditInfo.Actions?.Any() == true)
     //        {
     //            var actions = new List<MyAuditingAction>();
     //            foreach (var item in auditInfo.Actions)
     //            {
     //                var one = new MyAuditingAction
     //                {
     //                    AuditLogId = mylog.Id,
     //                    ExecutionDuration = item.ExecutionDuration,
     //                    ExecutionTime = item.ExecutionTime,
     //                    MethodName = item.MethodName.AutoString(32),
     //                    Parameters = item.Parameters.AutoString(128),
     //                    ServiceName = item.ServiceName.AutoString(64),
     //                };
     //                actions.Add(one);
     //            }
     //            _dbContext.AddRange(actions);
     //            await _dbContext.SaveChangesAsync();
     //        }
     //        if (auditInfo.EntityChanges?.Any() == true)
     //        {
     //            var entitys = new List<MyEntityChange>();
     //            foreach (var item in auditInfo.EntityChanges.ToList())
     //            {
     //                if (item.ChangeType != EntityChangeType.Created)
     //                {
     //                    var one = new MyEntityChange
     //                    {
     //                        AuditLogId = mylog.Id,
     //                        ChangeTime = item.ChangeTime,
     //                        ChangeType = item.ChangeType,
     //                        EntityId = item.EntityId.AutoString(64),
     //                        EntityTypeFullName = item.EntityTypeFullName.AutoString(128),
     //                    };
     //                    //await _dbContext.SaveChangesAsync();
     //                    if (item.PropertyChanges?.Any() == true)
     //                    {
     //                        one.PropertyChanges = item.PropertyChanges
     //                            .Select(x => new MyEntityPropertyChange
     //                            {
     //                                NewValue = x.NewValue,
     //                                //EntityChangeId = one.Id,
     //                                OriginalValue = x.OriginalValue,
     //                                PropertyName = x.PropertyName.AutoString(32),
     //                                PropertyTypeFullName = x.PropertyTypeFullName.AutoString(64),
     //                            })
     //                            .ToList();
     //                    }
     //                    entitys.Add(one);
     //                }
     //            }
     //            if (entitys.Any())
     //            {
     //                _dbContext.AddRange(entitys);
     //                await _dbContext.SaveChangesAsync();
     //            }
     //        }
     //    }
     //    catch (Exception exl)
     //    {
     //        Log.Error(exl.ToString());
     //    }
     //}

     #endregion
 }

 /// <summary>
 /// 
 /// </summary>
 public class AuditLogQueueModel
 {
     /// <summary>
     /// 
     /// </summary>
     public MyAuditingLog LogInfo { get; set; }

     /// <summary>
     /// 
     /// </summary>
     public DateTime CreateDate { get; set; } = DateTime.Now;
 }

 /// <summary>
 /// 这个后续修改下,改成Channel的方式
 /// </summary>
 public class MyAuditJobHandler : AsyncBackgroundJob<AuditLogQueueModel>, ITransientDependency
 {
     private readonly MyAuditingDb _dbContext;

     /// <summary>
     /// 
     /// </summary>
     /// <param name="dbContext"></param>
     public MyAuditJobHandler(MyAuditingDb dbContext)
     {
         _dbContext = dbContext;
     }

     /// <summary>
     /// 
     /// </summary>
     /// <param name="args"></param>
     /// <returns></returns>
     /// <exception cref="NotImplementedException"></exception>
     public override async Task ExecuteAsync(AuditLogQueueModel args)
     {
         var mylog = args.LogInfo;
         try
         {
             if (mylog.Exceptions?.Any() == true)
             {
                 mylog.HasException = true;
             }
             _dbContext.Add(mylog);
             await _dbContext.SaveChangesAsync();

             if (mylog.Actions?.Any() == true)
             {
                 foreach(var item in mylog.Actions)
                 {
                     item.AuditLogId = mylog.Id;
                 }
                 _dbContext.AddRange(mylog.Actions);
             }

             if (mylog.EntityChanges?.Any() == true)
             {
                 foreach(var item in mylog.EntityChanges)
                 {
                     item.AuditLogId = mylog.Id;
                 }
                 _dbContext.AddRange(mylog.EntityChanges);
             }


             await _dbContext.SaveChangesAsync();
         }
         catch (Exception exl)
         {
             Log.Error(exl.ToString());
         }
     }
 }

配置

然后就是在入口处如何配置的问题了

            context.Services.AddTransient<MyAuditLogContributor>();
            // 注册你的中间件(如果是ASP.NET Core中间件)
            context.Services.AddTransient<MyAuditingMiddleware>();
            context.Services.Configure<AbpAuditingOptions>(options =>
            {
                options.IsEnabled = true;//启用日志记录
                options.IsEnabledForGetRequests = false;//get的都忽略
                options.IsEnabledForAnonymousUsers = true;
                options.EntityHistorySelectors.Clear();//
                var contributor = context.Services.GetRequiredService<MyAuditLogContributor>();
                if (!options.Contributors.Contains(contributor))
                {
                    options.Contributors.Add(contributor);
                }
            });

启用

这里就是启用中间件

            app.UseMiddleware<MyAuditingMiddleware>();

注意别忘了引用官方的模块

typeof(AbpAuditingModule)

DbContext

上面弄了一通,少了数据库的配置,添加下如下代码

    /// <summary>
    /// 
    /// </summary>
    [ConnectionStringName(PasteTestDbProperties.AbpAuditingConnectionStringName)]
    public class MyAuditingDb : AbpDbContext<MyAuditingDb>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="options"></param>
        public MyAuditingDb(DbContextOptions<MyAuditingDb> options) : base(options)
        {

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="builder"></param>
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<MyAuditingLog>(b =>
            {
                b.ToTable("MyAuditingLog");
                b.Property(x => x.Id).HasValueGenerator<SequentialGuidValueGenerator>().ValueGeneratedOnAdd();
                b.ConfigureByConvention();
            });

            builder.Entity<MyAuditingAction>(b =>
            {
                b.ToTable("MyAuditingAction");
                b.Property(x => x.Id).HasValueGenerator<SequentialGuidValueGenerator>().ValueGeneratedOnAdd();
                b.ConfigureByConvention();
            });

            builder.Entity<MyEntityChange>(b =>
            {
                b.ToTable("MyEntityChange");
                b.Property(x => x.Id).HasValueGenerator<SequentialGuidValueGenerator>().ValueGeneratedOnAdd();
                b.ConfigureByConvention();
            });

            builder.Entity<MyEntityPropertyChange>(b =>
            {
                b.ToTable("MyEntityPropertyChange");
                b.Property(x => x.Id).HasValueGenerator<SequentialGuidValueGenerator>().ValueGeneratedOnAdd();
                b.ConfigureByConvention();
            });

            builder.Entity<MyAuditingException>(b =>
            {
                b.ToTable("MyAuditingException");
                b.Property(x => x.Id).HasValueGenerator<SequentialGuidValueGenerator>().ValueGeneratedOnAdd();
                b.ConfigureByConvention();
            });
        }

        /// <summary>
        /// 
        /// </summary>
        public DbSet<MyAuditingLog> MyAuditingLog { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public DbSet<MyAuditingAction> MyAuditingAction { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public DbSet<MyEntityChange> MyEntityChange { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public DbSet<MyAuditingException> MyAuditingException { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public DbSet<MyEntityPropertyChange> MyEntityPropertyChange { get; set; }

    }

没看错,就是独立的数据库链接,不和业务的挂钩,这样可以做到切割,然后是日志的记录其实数据很大!
上面提到的自增有序Guid

    /// <summary>
    /// 
    /// </summary>
    public class SequentialGuidValueGenerator : ValueGenerator<Guid>
    {
        /// <summary>
        /// 模块初始化的时候,需要进行赋值
        /// </summary>
        public static IGuidGenerator _guidGenerator;

        //public SequentialGuidValueGenerator():this(new SequentialGuidGenerator())
        //{
        //    //_guidGenerator = guidGenerator;
        //}

        /// <summary>
        /// 
        /// </summary>
        public SequentialGuidValueGenerator()
        {
            //IGuidGenerator guidGenerator
            //_guidGenerator = guidGenerator;

            //_guidGenerator = ServiceLocator.Current.GetService<IGuidGenerator>();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public override Guid Next(EntityEntry entry)
            => _guidGenerator.Create(); // 生成有序 GUID
        /// <summary>
        /// 
        /// </summary>
        public override bool GeneratesTemporaryValues => false;
    }

测试

启动项目后,就可以测试结果了,以下是我的记录
图片alt
这样,是不是哪里不爽改哪里!

总结

上面的代码其实还是有问题的,

1.比如我改成了异步方式写入日志,如果出现问题了,怎么一个处理方案,记录错误是肯定的,然后就是如何补回日志了

2.如果日志一直扩大也不是个办法,可能要采用按需删除过期日志,要基于不同纬度做不同处理

3.上面的BackgroundJob其实我感觉不好用,可能会改成本地的Channel模式,或者是改成RabbitMQ的模式以便应对集群部署

4.日志记录了,那就是剩下查询的问题了,比如我要查询某一个数据库表的某一个字段的变更记录!!!

评论列表
尘埃

更深层次的改造估计是AuditLogInfo的Property了

估计要使用到SetProperty和GetProperty,后续试试,是否可以自定义多一些字段!

尘埃
37 485 1
快捷注册
热门推荐更多
PasteBuilder
;
最新动态
  • 216.****.214 正在查看 开发者部署工具PasteSpiderV5新版本更新内容 !
  • 216.****.214 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-动态表单(2) !
  • 216.****.214 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-效果展示(4) !
  • 63.****.58 正在查看 PasteSpider的测试环境之在Docker中安装centos7并设定SSH的密码 !
  • 63.****.58 正在查看 PasteSpider的测试环境之在Docker中安装centos7并设定SSH的密码 !
  • 155.****.80 正在查看 框架PasteForm实际开发案例,支持多级对象的表单看看有多简单只要几个特性即可!(1) !
  • 155.****.80 正在查看 框架PasteForm实际开发案例,支持多级对象的表单看看有多简单只要几个特性即可!(1) !
  • 153.****.2 正在查看 使用ABP框架不得不留意的一个工具,PasteBuilder代码生成器使用介绍,特别适用于PasteForm框架 !
  • 153.****.2 正在查看 使用ABP框架不得不留意的一个工具,PasteBuilder代码生成器使用介绍,特别适用于PasteForm框架 !
  • 213.****.87 正在查看 PasteSpider之私有仓库的创建和使用 !
  • 213.****.87 正在查看 PasteSpider之私有仓库的创建和使用 !
欢迎加入QQ讨论群 296245685 [PasteSpider]介绍 [PasteForm]介绍 @2022-2023 PasteCode.cn 版权所有 ICP证 闽ICP备2021013869号-2