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

    教程

    01 | 模块化方案一

    02 | 模块化方案二

    其他教程预览

    分库分表项目实战教程

    Git地址: https://github.com/MrChuJiu/EasyLogger

    01 | 前言

    02 | 简单的分库分表设计

    03 | 控制反转搭配简单业务

    04 | 强化设计方案

    05 | 完善业务自动创建数据库

    06 | 最终篇-通过AOP自动连接数据库-完成日志业务

    前言

    这周比较忙,这篇来的有点迟到,不过我们要讲的东西是非常精彩的,通过之前的文章我们的设计已经完成,而且完成了 ProjectController 的业务操作,成功生成了分库的日志数据库和表,那么在操作日志 Controller 的时候,我们如何来连接多个数据库 和 多张表呢。

    理论讲解

    首先我们如果要动态连接数据库那么第一想到的就是中间件,AOP,那我们我们的数据库连接存储在哪里呢 在第二节的时候将的 DefaultSqlSugarProviderStorage 连接提供程序存储器 DataMap 中存储着我们的连接,我们只要动态的往里面加入 连接就可以了。

    正文

    1.基本部分

    首先我们在 EasyTools 文件夹新建 IocManager 类

    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;
            }
    
    
        }
    

    对IocManager的参数进行初始化,方便调用Configuration 和调用 ServiceProvider

    2.增加AOP 之前说的我们不能手动去搞数据库连接,那么这里我就借助AOP来做

    安装依赖包

    Autofac
    Autofac.Extensions.DependencyInjection
    Autofac.Extras.DynamicProxy
    

    在 Program 中加入下面这行代码 这是 Autofac在Core 3.0之后的用法

    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    

    在Startup 新建方法 ConfigureContainer Autofac会在启动的时候默认调用,大家可能对我写在 ConfigureContainer 方法中的感到好奇,那么这是什么呢,
    和之前仓储一样,SqlSugar 和 其他ORM框架的动态连接数据库 代码不一样所以 我们先创建基类进行约束 然后各自ORM进行实现,因为这部分属于业务层代码,所以我没有 放到 EasyLogger.DbStorage 而是放在启动程序中

           public void ConfigureContainer(ContainerBuilder builder)
            {
                builder.RegisterType<SqlSugarDynamicLink>().As<IDynamicLinkBase>().EnableClassInterceptors();
                builder.RegisterType<SqlSugarDynamicLinkAop>();
    
            }
    


    至于 SqlSugarDynamicLinkAop 就是我们的动态连接数据库的AOP方法,下面我们开始实现他们

    首先动态连接数据库的关键依据 是查询的时间,我们的数据库分库规则是一个月一个数据库,一天一张表,那么我们就先来定义一个规范的DTO

     public class DynamicLinkInput: PagedInput
    {
            public DateTime TimeStart { get; set; }
    
            public DateTime TimeEnd { get; set; }
    }
    

    在 AOP 文件夹 新建 DynamicLinkAopBase 接口约束ORM的连接

     public abstract class DynamicLinkAopBase : IInterceptor
        {
            /// <summary>
            ///  AOP的拦截方法
            /// </summary>
            /// <param name="invocation"></param>
            public abstract void Intercept(IInvocation invocation);
    
            /// <summary>
            /// 获取查询所需的必要条件
            /// </summary>
            /// <param name="invocation"></param>
            /// <returns></returns>
            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<DynamicLinkAttribute>().ToArray();
                if(attrs.Length > 0) {
                    return attrs[0];
                }
                attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<DynamicLinkAttribute>().ToArray();
                if (attrs.Length > 0)
                {
                    return attrs[0];
                }
                return null;
            }
    
    
    
        }
    

    在 AOP 文件夹 新建 DynamicLinkAttribute 注解

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public class DynamicLinkAttribute: Attribute
    {
            public bool IsDisabled { get; set; }
    }
    

    实现 SqlSugar 的 动态连接 SqlSugarDynamicLinkAop 那么这个AOP干了啥呢

    1.我判断这个类是否进行动态数据库连接
    2.我获取到必要的开始结束时间,来获取之前产生了多少个月份
    3.把这些月份动态的加入到 连接提供程序存储器。
     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) =>
                            {
                                Console.WriteLine($"sql:{sql}");
                            }
                        });
    
                    }
    
    
                    invocation.Proceed();//直接执行被拦截方法
                }
    
    
            }
        }
    

    这里用的 AddSqlSugarDatabaseProvider 之前没有写 其实如果看懂了,之前的说明,这里怎么写大家都能写出来,就是获取到 连接提供程序存储器 往里面加入了一个连接。

            public static IServiceProvider AddSqlSugarDatabaseProvider(this IServiceProvider serviceProvider, ISqlSugarSetting dbSetting)
            {
                if (dbSetting == null)
                {
                    throw new ArgumentNullException(nameof(dbSetting));
                }
    
                var fSqlProviderStorage = serviceProvider.GetRequiredService<ISqlSugarProviderStorage>();
    
                fSqlProviderStorage.AddOrUpdate(dbSetting.Name, new SqlSugarProvider(dbSetting));
    
                return serviceProvider;
            }
    

    那么这个AOP 怎么用呢,新建接口 IDynamicLinkBase 来提供 AOP调用 实现类是
    SqlSugarDynamicLink (这里直接用类也可以 我只是个人习惯)

    基本上到此为止,大家已经看明白路线了

    1.我们调用约束的Dto 传递开始、结束时间
    2.AOP拦截到我们条件,判断方法是否需要动态注入连接
    3.根据开始结束时间 把范围内的数据库都连接上,其中 我们做了一个最大开始时间 和 最大结束时间的判断,防止数据库没有出现连接错误

    业务测验逻辑

    老规矩 新建 EasyLoggerRecordDto文件夹 存储Dto

     public class CreateOrUpdateEasyLoggerRecordInput
        {
            public EasyLoggerRecordEditDto EasyLoggerRecord { get; set; }
        }
    
         public class EasyLoggerRecordEditDto
        {
            public int? Id { get; set; }
            /// <summary>
            /// 项目Id
            /// </summary>
            public int ProjectId { get; set; }
            /// <summary>
            /// 类型.自定义标签
            /// </summary>
            public string LogType { get; set; }
            /// <summary>
            /// 状态-成功、失败、警告等
            /// </summary>
            public string LogState { get; set; }
            /// <summary>
            /// 标题
            /// </summary>
            public string LogTitle { get; set; }
            /// <summary>
            /// 内容描述
            /// </summary>
            public string LogContent { get; set; }
            /// <summary>
            /// 在系统中产生的时间
            /// </summary>
            public DateTime LogTime { get; set; }
        }
    
    
         public class EasyLoggerRecordInput : DynamicLinkInput
        {
            /// <summary>
            /// 项目Id
            /// </summary>
            public int? ProjectId { get; set; }
            /// <summary>
            /// 类型.自定义标签
            /// </summary>
            public string LogType { get; set; }
            /// <summary>
            /// 状态-成功、失败、警告等
            /// </summary>
            public string LogState { get; set; }
            /// <summary>
            /// 标题
            /// </summary>
            public string LogTitle { get; set; }
        }
    
         public class EasyLoggerRecordListDto
        {
            public int Id { get; set; }
            /// <summary>
            /// 项目Id
            /// </summary>
            public int ProjectId { get; set; }
            /// <summary>
            /// 类型.自定义标签
            /// </summary>
            public string LogType { get; set; }
            /// <summary>
            /// 状态-成功、失败、警告等
            /// </summary>
            public string LogState { get; set; }
            /// <summary>
            /// 标题
            /// </summary>
            public string LogTitle { get; set; }
            /// <summary>
            /// 内容描述
            /// </summary>
            public string LogContent { get; set; }
            /// <summary>
            /// 在系统中产生的时间
            /// </summary>
            public DateTime LogTime { get; set; }
    
            public EasyLoggerProjectEditDto EasyLoggerProject { get; set; }
            /// <summary>
            /// 创建时间
            /// </summary>
            public DateTime CreateTime { get; set; }
        }
    

    新建 LoggerController 注入所需依赖

    这里我就先贴一张图大家来看看整个流程 怎么玩的思考一下

    1.首先执行AOP 并且拿到注入了那些连接
    2.从默认库中获取我们的项目信息存储到内存
    3.我们通过 得到的注入连接,来进行日志的查询
    4.我们关联上每个日志所属的项目 返回结果

    我们在来细看折叠部分的逻辑

    1.Item是一个库 我们拿到这个库所有的天数表
    2.使用 ChangeProvider 来切换数据库连接 连接到 Item这个时间点的数据库
    3.通过SqlSugar提供的方法进行 UnionAll
    4.查询数据加入返回列表中
    
            [HttpPost("GetEasyLoggerAsync")]
            [DynamicLink]
            public async Task<PagedResultDto<EasyLoggerRecordListDto>> GetEasyLoggerAsync(EasyLoggerRecordInput input) {
                // 获取查询的时间范围
                var dateList = _linkBase.DynamicLinkOrm(input).OrderByDescending(s => s).ToList();
                var result = new PagedResultDto<EasyLoggerRecordListDto>();
                // 查询初始数据库数据
                var projectList = _sqlRepository.GetCurrentSqlSugar().Queryable<EasyLoggerProject>().ToList();
                var DbName = IocManager.Configuration["EasyLogger:DbName"];
                var entityList = new List<EasyLoggerRecord>();
                // 为跨库查询定义的参数
                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<ISugarQueryable<EasyLoggerRecord>>();
                        _sqlRepository.GetCurrentSqlSugar().Queryable<EasyLoggerRecord>();
                        foreach (var day in dayList)
                        {
                            queryables.Add(sqlSugarClient.Queryable<EasyLoggerRecord>().AS($"EasyLoggerRecord_{day}"));
                        }
                        var sqlSugarLogger = sqlSugarClient.UnionAll(queryables);
                        var data = sqlSugarLogger
                             .Where(s => s.CreateTime >= input.TimeStart)
                             .Where(s => s.CreateTime <= input.TimeEnd)
                             .WhereIF(!string.IsNullOrWhiteSpace(input.LogTitle), s => s.LogTitle == input.LogTitle)
                             .WhereIF(!string.IsNullOrWhiteSpace(input.LogType), s => s.LogType == input.LogType)
                             .WhereIF(input.ProjectId != null, s => s.ProjectId == input.ProjectId)
                             .WhereIF(input.LogState != null, s => s.LogState == input.LogState)
                             .OrderBy(s => s.CreateTime, OrderByType.Desc)
                             .ToPageList(input.PageIndex, input.PageSize, ref Sumtotal);
                        entityList.AddRange(data);
                    }
                }
                result.Total = Sumtotal;
                result.List = _mapper.Map<List<EasyLoggerRecordListDto>>(entityList);
                foreach (var item in result.List)
                {
                    var project = projectList.Where(s => s.Id == item.ProjectId).FirstOrDefault();
                    item.EasyLoggerProject = _mapper.Map<EasyLoggerProjectEditDto>(project);
                }
                return result;
            }
    

    测试

    AOP拿到时间,进行动态添加

    DataMap也有我们需要的连接

    我们将8月31张表进行 UnionAll

    到此我们整个项目业务重点部分完成

    思考

    该教程的核心 部分已经全部讲解完毕,整套的架构设计也已经定下来了,如果你从头开始整套的跟完我想就算你是中级开发,我想你也能从中学到一些设计思想。
    这一节我是先把代码写出来进行讲解,而且思考部分很多,我希望该节能让大家自己手写,去会议我们整套架构一步一步如何设计出来的,而不是直接抄代码运行没问题完事!

    问题

    SqlSugar 直接业务代码写在控制器中,不能直接切换ORM
    查询如果多个月进行查询,如何分页数据
    定时计划进行数据库的创建
    其他系统应该如何接入该系统

    结尾

    提出的问题请认真思考,如果只是看看那就过眼云烟吧!
    后端暂定完结撒花-前端坑慢慢填(主要前端没啥技术点需要讲,这个项目前端就是CRUD)!
    后面针对技术点进行基础 + 项目场景下的实战应用 喜欢的老板点关注不迷路!

  • 相关阅读:
    如何让div中的img图片显示在div下面。
    Windows常用配置和sublime快捷键
    626 echarts图表5 地图:地图图表的使用方式,.矢量地图的实现,roam,label,zoom,center,显示某个区域,不同城市颜色不同,地图和散点图结合
    625 echarts图表4 饼图:实现,显示数值label,南丁格尔图,选中效果,圆环,特点
    624 Echarts常用图表 直角坐标系常见配置: 网格 grid, 坐标轴 axis, 区域缩放 dataZoom
    623 ECharts常用图表 散点图:实现步骤,气泡图,涟漪动画effectScatter,showEffectOn,rippleEffect,itemStyle
    622 ECharts常用图表 折线图:折线图的实现步骤,标记markPoint,平均值 markLine,线条控制,填充风格 areaStyle,紧挨边缘 boundaryGap,缩放&脱离0值比例scale ,堆叠图,
    621 echarts通用配置:title,tooltip,toolbox,legend
    620 ECharts常用图表 柱状图: 标记markPoint,平均值 markLine,数值显示 label,柱宽度 barWidth,横向柱状图
    619 数据可视化概述,ECharts的基本使用,相关配置讲解
  • 原文地址:https://www.cnblogs.com/MrChuJiu/p/13575511.html
Copyright © 2011-2022 走看看