01 | 模块化方案一html
01 | 前言git
02 | 简单的分库分表设计github
04 | 强化设计方案数据库
06 | 最终篇-经过AOP自动链接数据库-完成日志业务架构
这周比较忙,这篇来的有点迟到,不过咱们要讲的东西是很是精彩的,经过以前的文章咱们的设计已经完成,并且完成了 ProjectController 的业务操做,成功生成了分库的日志数据库和表,那么在操做日志 Controller 的时候,咱们如何来链接多个数据库 和 多张表呢。app
首先咱们若是要动态链接数据库那么第一想到的就是中间件,AOP,那咱们咱们的数据库链接存储在哪里呢 在第二节的时候将的 DefaultSqlSugarProviderStorage 链接提供程序存储器 DataMap 中存储着咱们的链接,咱们只要动态的往里面加入 链接就能够了。框架
首先咱们在 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
安装依赖包
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干了啥呢
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 注入所需依赖
[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; }
该教程的核心 部分已经所有讲解完毕,整套的架构设计也已经定下来了,若是你从头开始整套的跟完我想就算你是中级开发,我想你也能从中学到一些设计思想。
这一节我是先把代码写出来进行讲解,并且思考部分不少,我但愿该节能让你们本身手写,去会议咱们整套架构一步一步如何设计出来的,而不是直接抄代码运行没问题完事!
SqlSugar 直接业务代码写在控制器中,不能直接切换ORM
查询若是多个月进行查询,如何分页数据
定时计划进行数据库的建立
其余系统应该如何接入该系统
提出的问题请认真思考,若是只是看看那就过眼云烟吧! 后端暂定完结撒花-前端坑慢慢填(主要前端没啥技术点须要讲,这个项目前端就是CRUD)! 后面针对技术点进行基础 + 项目场景下的实战应用 喜欢的老板点关注不迷路!