企業項目實戰 .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)!
后面針對技術點進行基礎 + 項目場景下的實戰應用 喜歡的老板點關注不迷路!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM