伊比亚索 發表於 2020-8-28 08:37:00

企业项目实战 .Net Core + Vue/Angular 分库分表日志系统六 | 最终篇-通过AOP自动连接数据库-完成日志业务

<h3 id="教程">教程</h3>
<p>01 |模块化方案一</p>
<p>02 |模块化方案二</p>
<h1 id="其他教程预览">其他教程预览</h1>
<h3 id="分库分表项目实战教程">分库分表项目实战教程</h3>
<h3 id="git地址-httpsgithubcommrchujiueasylogger">Git地址: https://github.com/MrChuJiu/EasyLogger</h3>
<p>01 |前言</p>
<p>02 |简单的分库分表设计</p>
<p>03 |控制反转搭配简单业务</p>
<p>04 |强化设计方案</p>
<p>05 |完善业务自动创建数据库</p>
<p>06 |最终篇-通过AOP自动连接数据库-完成日志业务</p>
<h1 id="前言">前言</h1>
<p>这周比较忙,这篇来的有点迟到,不过我们要讲的东西是非常精彩的,通过之前的文章我们的设计已经完成,而且完成了 ProjectController 的业务操作,成功生成了分库的日志数据库和表,那么在操作日志 Controller 的时候,我们如何来连接多个数据库 和 多张表呢。</p>
<h1 id="理论讲解">理论讲解</h1>
<p>首先我们如果要动态连接数据库那么第一想到的就是中间件,AOP,那我们我们的数据库连接存储在哪里呢 在第二节的时候将的 DefaultSqlSugarProviderStorage 连接提供程序存储器 DataMap 中存储着我们的连接,我们只要动态的往里面加入 连接就可以了。</p>
<h1 id="正文">正文</h1>
<h3 id="1基本部分">1.基本部分</h3>
<p>首先我们在 EasyTools 文件夹新建 IocManager 类</p>
<pre><code>public class IocManager
    {
      public static IServiceCollection Services { get; private set; }

      public static IServiceProvider ServiceProvider { get; private set; }

      public static IConfiguration Configuration { get; private set; }

      static IocManager()
      {
            Services = new ServiceCollection();
      }

      public static IServiceProvider Build()
      {
            ServiceProvider = Services.BuildServiceProvider();
            return ServiceProvider;
      }

      public static void SetConfiguration(IConfiguration configuration)
      {
            Configuration = configuration;
      }

      public static void SetServiceProvider(IServiceProvider serviceProvider)
      {
            if (ServiceProvider == null) {
                return;
            }
            ServiceProvider = serviceProvider;
      }


    }
</code></pre>
<p>对IocManager的参数进行初始化,方便调用Configuration 和调用 ServiceProvider<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827231730.png" alt="" loading="lazy"><br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827232014.png" alt="" loading="lazy"></p>
<h3 id="2增加aop-之前说的我们不能手动去搞数据库连接那么这里我就借助aop来做">2.增加AOP 之前说的我们不能手动去搞数据库连接,那么这里我就借助AOP来做</h3>
<p>安装依赖包</p>
<pre><code>Autofac
Autofac.Extensions.DependencyInjection
Autofac.Extras.DynamicProxy
</code></pre>
<p>在 Program 中加入下面这行代码 这是 Autofac在Core 3.0之后的用法<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827232415.png" alt="" loading="lazy"></p>
<pre><code>.UseServiceProviderFactory(new AutofacServiceProviderFactory())
</code></pre>
<p>在Startup 新建方法 ConfigureContainerAutofac会在启动的时候默认调用,大家可能对我写在 ConfigureContainer 方法中的感到好奇,那么这是什么呢,<br>
和之前仓储一样,SqlSugar 和 其他ORM框架的动态连接数据库 代码不一样所以 我们先创建基类进行约束 然后各自ORM进行实现,因为这部分属于业务层代码,所以我没有 放到 EasyLogger.DbStorage 而是放在启动程序中<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827232542.png" alt="" loading="lazy"></p>
<pre><code>       public void ConfigureContainer(ContainerBuilder builder)
      {
            builder.RegisterType&lt;SqlSugarDynamicLink&gt;().As&lt;IDynamicLinkBase&gt;().EnableClassInterceptors();
            builder.RegisterType&lt;SqlSugarDynamicLinkAop&gt;();

      }
</code></pre>
<p><img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827233044.png" alt="" loading="lazy"><br>
至于 SqlSugarDynamicLinkAop 就是我们的动态连接数据库的AOP方法,下面我们开始实现他们<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827232921.png" alt="" loading="lazy"></p>
<p>首先动态连接数据库的关键依据 是查询的时间,我们的数据库分库规则是一个月一个数据库,一天一张表,那么我们就先来定义一个规范的DTO</p>
<pre><code> public class DynamicLinkInput: PagedInput
{
      public DateTime TimeStart { get; set; }

      public DateTime TimeEnd { get; set; }
}
</code></pre>
<p>在 AOP 文件夹 新建DynamicLinkAopBase 接口约束ORM的连接</p>
<pre><code> public abstract class DynamicLinkAopBase : IInterceptor
    {
      /// &lt;summary&gt;
      ///AOP的拦截方法
      /// &lt;/summary&gt;
      /// &lt;param name="invocation"&gt;&lt;/param&gt;
      public abstract void Intercept(IInvocation invocation);

      /// &lt;summary&gt;
      /// 获取查询所需的必要条件
      /// &lt;/summary&gt;
      /// &lt;param name="invocation"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public DynamicLinkInput GetTiemRange(IInvocation invocation) {
            var methodArguments = invocation.Arguments.FirstOrDefault();//获取参数列表
            var input = (DynamicLinkInput)methodArguments;
            return input;
      }

      public DynamicLinkAttribute GetDynamicLinkAttributeOrNull(MethodInfo methodInfo) {

            var attrs = methodInfo.GetCustomAttributes(true).OfType&lt;DynamicLinkAttribute&gt;().ToArray();
            if(attrs.Length &gt; 0) {
                return attrs;
            }
            attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType&lt;DynamicLinkAttribute&gt;().ToArray();
            if (attrs.Length &gt; 0)
            {
                return attrs;
            }
            return null;
      }



    }
</code></pre>
<p>在 AOP 文件夹 新建 DynamicLinkAttribute 注解</p>
<pre><code>
public class DynamicLinkAttribute: Attribute
{
      public bool IsDisabled { get; set; }
}
</code></pre>
<p>实现 SqlSugar 的 动态连接 SqlSugarDynamicLinkAop 那么这个AOP干了啥呢</p>
<h5 id="1我判断这个类是否进行动态数据库连接">1.我判断这个类是否进行动态数据库连接</h5>
<h5 id="2我获取到必要的开始结束时间来获取之前产生了多少个月份">2.我获取到必要的开始结束时间,来获取之前产生了多少个月份</h5>
<h5 id="3把这些月份动态的加入到-连接提供程序存储器">3.把这些月份动态的加入到 连接提供程序存储器。</h5>
<pre><code> public class SqlSugarDynamicLinkAop : DynamicLinkAopBase
    {
      private readonly IServiceProvider _serviceProvider;


      public override void Intercept(IInvocation invocation)
      {
            MethodInfo method;
            try
            {
                method = invocation.MethodInvocationTarget;
            }
            catch (Exception ex)
            {

                method = invocation.GetConcreteMethod();
            }


            var dynamicLinkAttr = GetDynamicLinkAttributeOrNull(method);
            if (dynamicLinkAttr == null || dynamicLinkAttr.IsDisabled)
            {
                invocation.Proceed();//直接执行被拦截方法
            }
            else
            {

                var input = this.GetTiemRange(invocation);

                var dateList = TimeTools.GetMonthByList(input.TimeStart.ToString("yyyy-MM"), input.TimeEnd.ToString("yyyy-MM"));

                foreach (var item in dateList)
                {
                  var DbName = $"{IocManager.Configuration["EasyLogger:DbName"]}-{item.ToString("yyyy-MM")}";
                  var dbPathName = Path.Combine(PathExtenstions.GetApplicationCurrentPath(), DbName + ".db");

                  IocManager.ServiceProvider.AddSqlSugarDatabaseProvider(new SqlSugarSetting()
                  {
                        Name = DbName,
                        ConnectionString = @$"Data Source={dbPathName}",
                        DatabaseType = DbType.Sqlite,
                        LogExecuting = (sql, pars) =&gt;
                        {
                            Console.WriteLine($"sql:{sql}");
                        }
                  });

                }


                invocation.Proceed();//直接执行被拦截方法
            }


      }
    }
</code></pre>
<p>这里用的 AddSqlSugarDatabaseProvider 之前没有写 其实如果看懂了,之前的说明,这里怎么写大家都能写出来,就是获取到 连接提供程序存储器 往里面加入了一个连接。<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827234703.png" alt="" loading="lazy"></p>
<pre><code>      public static IServiceProvider AddSqlSugarDatabaseProvider(this IServiceProvider serviceProvider, ISqlSugarSetting dbSetting)
      {
            if (dbSetting == null)
            {
                throw new ArgumentNullException(nameof(dbSetting));
            }

            var fSqlProviderStorage = serviceProvider.GetRequiredService&lt;ISqlSugarProviderStorage&gt;();

            fSqlProviderStorage.AddOrUpdate(dbSetting.Name, new SqlSugarProvider(dbSetting));

            return serviceProvider;
      }
</code></pre>
<p>那么这个AOP 怎么用呢,新建接口 IDynamicLinkBase 来提供 AOP调用 实现类是<br>
SqlSugarDynamicLink (这里直接用类也可以 我只是个人习惯)<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827233436.png" alt="" loading="lazy"><br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827235023.png" alt="" loading="lazy"></p>
<h4 id="基本上到此为止大家已经看明白路线了">基本上到此为止,大家已经看明白路线了</h4>
<p>1.我们调用约束的Dto 传递开始、结束时间<br>
2.AOP拦截到我们条件,判断方法是否需要动态注入连接<br>
3.根据开始结束时间 把范围内的数据库都连接上,其中 我们做了一个最大开始时间 和 最大结束时间的判断,防止数据库没有出现连接错误</p>
<h1 id="业务测验逻辑">业务测验逻辑</h1>
<p>老规矩 新建 EasyLoggerRecordDto文件夹 存储Dto<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827235533.png" alt="" loading="lazy"></p>
<pre><code> public class CreateOrUpdateEasyLoggerRecordInput
    {
      public EasyLoggerRecordEditDto EasyLoggerRecord { get; set; }
    }

   public class EasyLoggerRecordEditDto
    {
      public int? Id { get; set; }
      /// &lt;summary&gt;
      /// 项目Id
      /// &lt;/summary&gt;
      public int ProjectId { get; set; }
      /// &lt;summary&gt;
      /// 类型.自定义标签
      /// &lt;/summary&gt;
      public string LogType { get; set; }
      /// &lt;summary&gt;
      /// 状态-成功、失败、警告等
      /// &lt;/summary&gt;
      public string LogState { get; set; }
      /// &lt;summary&gt;
      /// 标题
      /// &lt;/summary&gt;
      public string LogTitle { get; set; }
      /// &lt;summary&gt;
      /// 内容描述
      /// &lt;/summary&gt;
      public string LogContent { get; set; }
      /// &lt;summary&gt;
      /// 在系统中产生的时间
      /// &lt;/summary&gt;
      public DateTime LogTime { get; set; }
    }


   public class EasyLoggerRecordInput : DynamicLinkInput
    {
      /// &lt;summary&gt;
      /// 项目Id
      /// &lt;/summary&gt;
      public int? ProjectId { get; set; }
      /// &lt;summary&gt;
      /// 类型.自定义标签
      /// &lt;/summary&gt;
      public string LogType { get; set; }
      /// &lt;summary&gt;
      /// 状态-成功、失败、警告等
      /// &lt;/summary&gt;
      public string LogState { get; set; }
      /// &lt;summary&gt;
      /// 标题
      /// &lt;/summary&gt;
      public string LogTitle { get; set; }
    }

   public class EasyLoggerRecordListDto
    {
      public int Id { get; set; }
      /// &lt;summary&gt;
      /// 项目Id
      /// &lt;/summary&gt;
      public int ProjectId { get; set; }
      /// &lt;summary&gt;
      /// 类型.自定义标签
      /// &lt;/summary&gt;
      public string LogType { get; set; }
      /// &lt;summary&gt;
      /// 状态-成功、失败、警告等
      /// &lt;/summary&gt;
      public string LogState { get; set; }
      /// &lt;summary&gt;
      /// 标题
      /// &lt;/summary&gt;
      public string LogTitle { get; set; }
      /// &lt;summary&gt;
      /// 内容描述
      /// &lt;/summary&gt;
      public string LogContent { get; set; }
      /// &lt;summary&gt;
      /// 在系统中产生的时间
      /// &lt;/summary&gt;
      public DateTime LogTime { get; set; }

      public EasyLoggerProjectEditDto EasyLoggerProject { get; set; }
      /// &lt;summary&gt;
      /// 创建时间
      /// &lt;/summary&gt;
      public DateTime CreateTime { get; set; }
    }
</code></pre>
<p>新建 LoggerController 注入所需依赖<br>
<img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827235635.png" alt="" loading="lazy"></p>
<h3 id="这里我就先贴一张图大家来看看整个流程-怎么玩的思考一下">这里我就先贴一张图大家来看看整个流程 怎么玩的思考一下</h3>
<h5 id="1首先执行aop-并且拿到注入了那些连接">1.首先执行AOP 并且拿到注入了那些连接</h5>
<h5 id="2从默认库中获取我们的项目信息存储到内存">2.从默认库中获取我们的项目信息存储到内存</h5>
<h5 id="3我们通过-得到的注入连接来进行日志的查询">3.我们通过 得到的注入连接,来进行日志的查询</h5>
<h5 id="4我们关联上每个日志所属的项目-返回结果">4.我们关联上每个日志所属的项目 返回结果</h5>
<p><img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200827235740.png" alt="" loading="lazy"></p>
<h3 id="我们在来细看折叠部分的逻辑">我们在来细看折叠部分的逻辑</h3>
<p><img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200828000143.png" alt="" loading="lazy"></p>
<h5 id="1item是一个库-我们拿到这个库所有的天数表">1.Item是一个库 我们拿到这个库所有的天数表</h5>
<h5 id="2使用-changeprovider-来切换数据库连接-连接到-item这个时间点的数据库">2.使用 ChangeProvider 来切换数据库连接 连接到 Item这个时间点的数据库</h5>
<h5 id="3通过sqlsugar提供的方法进行-unionall">3.通过SqlSugar提供的方法进行 UnionAll</h5>
<h5 id="4查询数据加入返回列表中">4.查询数据加入返回列表中</h5>
<pre><code>
      
      
      public async Task&lt;PagedResultDto&lt;EasyLoggerRecordListDto&gt;&gt; GetEasyLoggerAsync(EasyLoggerRecordInput input) {
            // 获取查询的时间范围
            var dateList = _linkBase.DynamicLinkOrm(input).OrderByDescending(s =&gt; s).ToList();
            var result = new PagedResultDto&lt;EasyLoggerRecordListDto&gt;();
            // 查询初始数据库数据
            var projectList = _sqlRepository.GetCurrentSqlSugar().Queryable&lt;EasyLoggerProject&gt;().ToList();
            var DbName = IocManager.Configuration["EasyLogger:DbName"];
            var entityList = new List&lt;EasyLoggerRecord&gt;();
            // 为跨库查询定义的参数
            int Sumtotal = 0;
            foreach (var item in dateList)
            {


                var dayList = TimeTools.GetDayDiff(item.AddDays(1 - DateTime.Now.Day).Date, item.AddDays(1 - DateTime.Now.Day).Date.AddMonths(1).AddSeconds(-1));
                using (_sqlRepository.ChangeProvider($"{DbName}-" + item.ToString("yyyy-MM")))
                {
                  var sqlSugarClient = _sqlRepository.GetCurrentSqlSugar();
                  var queryables = new List&lt;ISugarQueryable&lt;EasyLoggerRecord&gt;&gt;();
                  _sqlRepository.GetCurrentSqlSugar().Queryable&lt;EasyLoggerRecord&gt;();
                  foreach (var day in dayList)
                  {
                        queryables.Add(sqlSugarClient.Queryable&lt;EasyLoggerRecord&gt;().AS($"EasyLoggerRecord_{day}"));
                  }
                  var sqlSugarLogger = sqlSugarClient.UnionAll(queryables);
                  var data = sqlSugarLogger
                         .Where(s =&gt; s.CreateTime &gt;= input.TimeStart)
                         .Where(s =&gt; s.CreateTime &lt;= input.TimeEnd)
                         .WhereIF(!string.IsNullOrWhiteSpace(input.LogTitle), s =&gt; s.LogTitle == input.LogTitle)
                         .WhereIF(!string.IsNullOrWhiteSpace(input.LogType), s =&gt; s.LogType == input.LogType)
                         .WhereIF(input.ProjectId != null, s =&gt; s.ProjectId == input.ProjectId)
                         .WhereIF(input.LogState != null, s =&gt; s.LogState == input.LogState)
                         .OrderBy(s =&gt; s.CreateTime, OrderByType.Desc)
                         .ToPageList(input.PageIndex, input.PageSize, ref Sumtotal);
                  entityList.AddRange(data);
                }
            }
            result.Total = Sumtotal;
            result.List = _mapper.Map&lt;List&lt;EasyLoggerRecordListDto&gt;&gt;(entityList);
            foreach (var item in result.List)
            {
                var project = projectList.Where(s =&gt; s.Id == item.ProjectId).FirstOrDefault();
                item.EasyLoggerProject = _mapper.Map&lt;EasyLoggerProjectEditDto&gt;(project);
            }
            return result;
      }
</code></pre>
<h2 id="测试">测试</h2>
<h4 id="aop拿到时间进行动态添加">AOP拿到时间,进行动态添加</h4>
<p><img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200828000621.png" alt="" loading="lazy"></p>
<h4 id="datamap也有我们需要的连接">DataMap也有我们需要的连接</h4>
<p><img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200828000743.png" alt="" loading="lazy"></p>
<h4 id="我们将8月31张表进行-unionall">我们将8月31张表进行 UnionAll</h4>
<p><img src="https://raw.githubusercontent.com/MrChuJiu/BlogImageBed/main/EasyLoggerImages/20200828000837.png" alt="" loading="lazy"></p>
<h4 id="到此我们整个项目业务重点部分完成">到此我们整个项目业务重点部分完成</h4>
<h1 id="思考">思考</h1>
<p>该教程的核心 部分已经全部讲解完毕,整套的架构设计也已经定下来了,如果你从头开始整套的跟完我想就算你是中级开发,我想你也能从中学到一些设计思想。<br>
这一节我是先把代码写出来进行讲解,而且思考部分很多,我希望该节能让大家自己手写,去会议我们整套架构一步一步如何设计出来的,而不是直接抄代码运行没问题完事!</p>
<h1 id="问题">问题</h1>
<p>SqlSugar 直接业务代码写在控制器中,不能直接切换ORM<br>
查询如果多个月进行查询,如何分页数据<br>
定时计划进行数据库的创建<br>
其他系统应该如何接入该系统</p>
<h1 id="结尾">结尾</h1>
<p>提出的问题请认真思考,如果只是看看那就过眼云烟吧!<br>
后端暂定完结撒花-前端坑慢慢填(主要前端没啥技术点需要讲,这个项目前端就是CRUD)!<br>
后面针对技术点进行基础 + 项目场景下的实战应用 喜欢的老板点关注不迷路!</p><br><br>
来源:https://www.cnblogs.com/MrChuJiu/p/13575511.html
頁: [1]
查看完整版本: 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统六 | 最终篇-通过AOP自动连接数据库-完成日志业务