为了账号安全,请及时绑定邮箱和手机立即绑定

从壹开始前后端分离【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】

标签:
Redis

时间真快,转眼已经十天过去了,感谢大家的鼓励,批评指正,希望我的文章,对您有一点点儿的帮助,哪怕是有学习新知识的动力也行,至少至少,可以为以后跳槽增加新的谈资 [哭笑],这些天我们从面向对象OOP的开发,后又转向了面向接口开发,到分层解耦,现在到了面向切面编程AOP,往下走将会是,分布式,微服务等等,技术真是永无止境啊!好啦,马上开始动笔。

零、今天完成的深红色部分

webp

image

一、面向切面编程AOP实现日志记录接口使用情况功能(服务层)

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

经过这么多天的开发,几乎每天都需要引入Nuget包哈,我个人表示也不想再添加了,现在都已经挺大的了(47M当然包括全部dll文件),今天不会辣!其实都是基于昨天的两个Nuget包中已经自动生成的Castle组件。请看以下步骤:

1、在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、在Blog.Core新建文件夹AOP,并添加拦截器BlogLogAOP,并设计其中用到的日志记录Logger方法或者类**

webp

image

关键的一些知识点,注释中已经说明了,主要是有以下:
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())} \r\n"; //在被拦截的方法执行完毕后 继续执行当前方法
 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 }
    }

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));//允许将拦截器服务的列表分配给注册。

说人话就是,将拦截器加上要注入容器的的接口或者类上

5、运行项目,嗯,你就看到这根目录下生成了一个Log文件夹,里边有日志记录,当然记录很简陋,里边是获取到的实体类,大家可以自己根据需要扩展



****

webp

image

****

这里,面向服务层的日志记录就完成了,大家感觉是不是很平时的不一样?

二、需求2.面向切面编程AOP实现接口数据的缓存功能

想一想,如果我们要实现缓存功能,一般咱们都是将数据获取到以后,定义缓存,然后在其他地方使用的时候,在根据key去获取当前数据,然后再操作等等,平时都是在API接口层获取数据后进行缓存,今天咱们可以试试,在接口之前就缓存下来。

1、老规矩,定义一个缓存类和接口,你会问了,为什么上边的日志没有定义,因为我会在之后讲Redis的时候用到这个缓存接口

   /// <summary>
    /// 简单的缓存接口,只有查询和添加,以后会进行扩展 /// </summary>
    public interface ICaching
    { object Get(string cacheKey); void Set(string cacheKey, object cacheValue);
    } /// <summary>
    /// 实例化缓存接口ICaching /// </summary>
    public class MemoryCaching : ICaching
    { //引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了
        private IMemoryCache _cache; //还是通过构造函数的方法,获取
        public MemoryCaching(IMemoryCache cache)
        {
            _cache = cache;
        } public object Get(string cacheKey)
        { return _cache.Get(cacheKey);
        } public void Set(string cacheKey, object cacheValue)
        {
            _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(7200));
        }
    }

2、定义一个拦截器还是继承IInterceptor,并实现Intercept

   /// <summary>
    /// 面向切面的缓存使用 /// </summary>
    public class BlogCacheAOP : IInterceptor
    { //通过注入的方式,把缓存操作接口通过构造函数注入
        private ICaching _cache; public BlogCacheAOP(ICaching cache)
        {
            _cache = cache;
        } //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义
        public void Intercept(IInvocation invocation)
        { //获取自定义缓存键
            var cacheKey = CustomCacheKey(invocation); //根据key获取相应的缓存值
            var cacheValue = _cache.Get(cacheKey); if (cacheValue != null)
            { //将当前获取到的缓存值,赋值给当前执行方法
                invocation.ReturnValue = cacheValue; return;
            } //去执行当前的方法
 invocation.Proceed(); //存入缓存
            if (!string.IsNullOrWhiteSpace(cacheKey))
            {
                _cache.Set(cacheKey, invocation.ReturnValue);
            }
        } //自定义缓存键
        private string CustomCacheKey(IInvocation invocation)
        { var typeName = invocation.TargetType.Name; var methodName = invocation.Method.Name; var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个

            string key = $"{typeName}:{methodName}:"; foreach (var param in methodArguments)
            {
                key += $"{param}:";
            } return key.TrimEnd(':');
        } //object 转 string
        private string GetArgumentValue(object arg)
        { if (arg is int || arg is long || arg is string) return arg.ToString(); if (arg is DateTime) return ((DateTime)arg).ToString("yyyyMMddHHmmss"); return "";
        }
    }

注释的很清楚,基本都是情况

3、ConfigureServices不用动,只需要改下拦截器的名字就行

****注意:****

//将 TService 中指定的类型的范围服务添加到实现
services.AddScoped<ICaching, MemoryCaching>();//记得把缓存注入!!!

webp

image

4、运行,你会发现,首次缓存是空的,然后将Repository仓储中取出来的数据存入缓存,第二次使用就是有值了,其他所有的地方使用,都不用再写了,而且也是面向整个程序集合的



****

webp

image

****

webp

image



作者:SAYLINING
链接:https://www.jianshu.com/p/76cc582f2bed


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
手记
粉丝
201
获赞与收藏
871

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消