• AOP 之 实现日志记录(服务层)Castle


    原文:https://www.cnblogs.com/laozhang-is-phi/p/9547574.html#autoid-4-0-0

    老张

    一、AOP 之 实现日志记录(服务层)

    首先想一想,如果有一个需求(这个只是我的一个想法,真实工作中可能用不上),要记录整个项目的接口和调用情况,当然如果只是控制器的话,还是挺简单的,直接用一个过滤器或者一个中间件,还记得咱们开发Swagger拦截权限验证的中间件么,那个就很方便的把用户调用接口的名称记录下来,当然也可以写成一个切面,但是如果想看下与Service或者Repository层的调用情况呢,好像目前咱们只能在Service层或者Repository层去写日志记录了,那样的话,不仅工程大(当然你可以用工厂模式),而且耦合性瞬间就高了呀,想象一下,如果日志要去掉,关闭,修改,需要改多少地方!您说是不是,好不容易前边的工作把层级的耦合性降低了。别慌,这个时候就用到了AOP和Autofac的Castle结合的完美解决方案了。
      经过这么多天的开发,几乎每天都需要引入Nuget包哈,我个人表示也不想再添加了,现在都已经挺大的了 ,今天不会辣!其实都是基于昨天的两个Nuget包中已经自动生成的Castle组件(Autofac.Extras.DynamicProxy)。请看以下步骤:

    1、定义服务接口与实现类

    首先这里使用到了 BlogArticle 的实体类(这里我保留了sqlsugar的特性,没需要的可以手动删除):

    复制代码
        public class BlogArticle
        {
            /// <summary>
            /// 主键
            /// </summary>
            /// 这里之所以没用RootEntity,是想保持和之前的数据库一致,主键是bID,不是Id
            [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)]
            public int bID { get; set; }
            /// <summary>
            /// 创建人
            /// </summary>
            [SugarColumn(Length = 60, IsNullable = true)]
            public string bsubmitter { get; set; }
    
            /// <summary>
            /// 标题blog
            /// </summary>
            [SugarColumn(Length = 256, IsNullable = true)]
            public string btitle { get; set; }
    
            /// <summary>
            /// 类别
            /// </summary>
            [SugarColumn(Length = int.MaxValue, IsNullable = true)]
            public string bcategory { get; set; }
    
            /// <summary>
            /// 内容
            /// </summary>
            [SugarColumn(IsNullable = true, ColumnDataType = "text")]
            public string bcontent { get; set; }
    
            /// <summary>
            /// 访问量
            /// </summary>
            public int btraffic { get; set; }
    
            /// <summary>
            /// 评论数量
            /// </summary>
            public int bcommentNum { get; set; }
    
            /// <summary> 
            /// 修改时间
            /// </summary>
            public DateTime bUpdateTime { get; set; }
    
            /// <summary>
            /// 创建时间
            /// </summary>
            public System.DateTime bCreateTime { get; set; }
            /// <summary>
            /// 备注
            /// </summary>
            [SugarColumn(Length = int.MaxValue, IsNullable = true)]
            public string bRemark { get; set; }
    
            /// <summary>
            /// 逻辑删除
            /// </summary>
            [SugarColumn(IsNullable = true)]
            public bool? IsDeleted { get; set; }
    
        }
    复制代码
    在IBlogArticleServices.cs定义一个获取博客列表接口 ,并在BlogArticleServices实现该接口
    复制代码
       public interface IBlogArticleServices :IBaseServices<BlogArticle>
        {
            Task<List<BlogArticle>> getBlogs();
        }
    
       public class BlogArticleServices : BaseServices<BlogArticle>, IBlogArticleServices
        {
            IBlogArticleRepository dal;
            public BlogArticleServices(IBlogArticleRepository dal)
            {
                this.dal = dal;
                base.baseDal = dal;
            }
            /// <summary>
            /// 获取博客列表
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            public async Task<List<BlogArticle>> getBlogs()
            {
                var bloglist = await dal.Query(a => a.bID > 0, a => a.bID);
    
                return bloglist;
    
            }
        }
    复制代码

    2、在API层中添加对该接口引用

    (注意RESTful接口路径命名规范,我这么写只是为了测试)

    复制代码
          /// <summary>
            /// 获取博客列表
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            [Route("GetBlogs")]
            public async Task<List<BlogArticle>> GetBlogs()
            {
    
                return await blogArticleServices.getBlogs();
            }
    复制代码

    3、添加AOP拦截器

    在Blog.Core新建文件夹AOP,并添加拦截器BlogLogAOP,并设计其中用到的日志记录Logger方法或者类

     

    关键的一些知识点,注释中已经说明了,主要是有以下:
    1、继承接口IInterceptor
    2、实例化接口IINterceptor的唯一方法Intercept
    3、void Proceed();表示执行当前的方法和object ReturnValue { get; set; }执行后调用,object[] Arguments参数对象
    4、中间的代码是新建一个类,还是单写,就很随意了。

    复制代码
      /// <summary>
        /// 拦截器BlogLogAOP 继承IInterceptor接口
        /// </summary>
        public class BlogLogAOP : IInterceptor
        {
    
            /// <summary>
            /// 实例化IInterceptor唯一方法 
            /// </summary>
            /// <param name="invocation">包含被拦截方法的信息</param>
            public void Intercept(IInvocation invocation)
            {
                //记录被拦截方法信息的日志信息
                var dataIntercept = $"{DateTime.Now.ToString("yyyyMMddHHmmss")} " +
                    $"当前执行方法:{ invocation.Method.Name} " +
                    $"参数是: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} 
    ";
    
                //在被拦截的方法执行完毕后 继续执行当前方法
                invocation.Proceed();
    
                dataIntercept += ($"被拦截方法执行完毕,返回结果:{invocation.ReturnValue}");
    
                #region 输出到当前项目日志
                var path = Directory.GetCurrentDirectory() + @"Log";
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
    
                string fileName = path + $@"InterceptLog-{DateTime.Now.ToString("yyyyMMddHHmmss")}.log";
    
                StreamWriter sw = File.AppendText(fileName);
                sw.WriteLine(dataIntercept);
                sw.Close(); 
                #endregion
    
            }
        }
    复制代码
     

    提示:这里展示了如何在项目中使用AOP实现对 service 层进行日志记录,如果你想实现异常信息记录的话,很简单,

    注意,下边方法仅仅是针对同步的策略,如果你的service是异步的,这里获取不到,正确的写法,在文章底部的 GitHub 代码里,你可以查看我的源码。

     
    意思就说,同步和异步是分开来写的:
     
     
     
    下边的是完整代码:
    复制代码
    /// <summary>
    /// 实例化IInterceptor唯一方法 
    /// </summary>
    /// <param name="invocation">包含被拦截方法的信息</param>
    public void Intercept(IInvocation invocation)
    {
        //记录被拦截方法信息的日志信息
        var dataIntercept = "" +
            $"【当前执行方法】:{ invocation.Method.Name} 
    " +
            $"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} 
    ";
    
        try
        {
            MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> ");
            //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的
            invocation.Proceed();
    
    
            // 异步获取异常,先执行
            if (IsAsyncMethod(invocation.Method))
            {
    
                //Wait task execution and modify return value
                if (invocation.Method.ReturnType == typeof(Task))
                {
                    invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
                        (Task)invocation.ReturnValue,
                        async () => await TestActionAsync(invocation),
                        ex =>
                        {
                            LogEx(ex, ref dataIntercept);
                        });
                }
                else //Task<TResult>
                {
                    invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
                     invocation.Method.ReturnType.GenericTypeArguments[0],
                     invocation.ReturnValue,
                     async () => await TestActionAsync(invocation),
                     ex =>
                     {
                         LogEx(ex, ref dataIntercept);
                     });
    
                }
    
            }
            else
            {// 同步1
    
    
            }
        }
        catch (Exception ex)// 同步2
        {
            LogEx(ex, ref dataIntercept);
    
        }
    
        dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}");
    
        Parallel.For(0, 1, e =>
        {
            LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept });
        });
    
        _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait();
    
    
    }
    复制代码
     

    4、添加到Autofac容器中,实现注入

     
    还记得昨天的容器么,先把拦截器注入,然后对程序集的注入方法中添加拦截器服务即可
     
    复制代码
            builder.RegisterType<BlogLogAOP>();//可以直接替换其他拦截器!一定要把拦截器进行注册
    
                var assemblysServices = Assembly.Load("Blog.Core.Services");
    
                //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。
    
                builder.RegisterAssemblyTypes(assemblysServices)
                          .AsImplementedInterfaces()
                          .InstancePerLifetimeScope()
                          .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
                          .InterceptedBy(typeof(BlogLogAOP));//可以直接替换拦截器
    复制代码
     
    注意其中的两个方法
    .EnableInterfaceInterceptors()//对目标类型启用接口拦截。拦截器将被确定,通过在类或接口上截取属性, 或添加 InterceptedBy ()
    .InterceptedBy(typeof(BlogLogAOP));//允许将拦截器服务的列表分配给注册。
    说人话就是,将拦截器添加到要注入容器的接口或者类之上。
     
  • 相关阅读:
    Django之数据库--ORM
    Vue 父子组件
    axios封装
    DRF常用功能
    DRF框架之Serializer序列化器的反序列化操作
    Django、DRF有什么不同
    RESTFUL风格
    判断ViewPager滑动方向
    Incompatible integer to pointer conversion sending 'NSInteger' (aka 'int') to parameter of type 'id'
    AchartEngine使用
  • 原文地址:https://www.cnblogs.com/yang315/p/14514840.html
Copyright © 2020-2023  润新知