企業專案實戰 .Net Core + Vue/Angular 分庫分表日誌系統六 | 最終篇-通過AOP自動連線資料庫-完成日誌業務
阿新 • • 發佈:2020-08-28
# 教程預覽
[01 | 前言](https://www.cnblogs.com/HDONG/p/13517146.html)
[02 | 簡單的分庫分表設計](https://www.cnblogs.com/HDONG/p/13517207.html)
[03 | 控制反轉搭配簡單業務](https://www.cnblogs.com/HDONG/p/13527308.html)
[04 | 強化設計方案](https://www.cnblogs.com/HDONG/p/13539186.html)
[05 | 完善業務自動建立資料庫](https://www.cnblogs.com/HDONG/p/13552014.html)
[06 | 最終篇-通過AOP自動連線資料庫-完成日誌業務](https://www.cnblogs.com/HDONG/p/13575511.html)
#前言
這周比較忙,這篇來的有點遲到,不過我們要講的東西是非常精彩的,通過之前的文章我們的設計已經完成,而且完成了 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
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827231730.png)
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827232014.png)
###2.增加AOP 之前說的我們不能手動去搞資料庫連線,那麼這裡我就藉助AOP來做
安裝依賴包
```
Autofac
Autofac.Extensions.DependencyInjection
Autofac.Extras.DynamicProxy
```
在 Program 中加入下面這行程式碼 這是 Autofac在Core 3.0之後的用法
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827232415.png)
```
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
```
在Startup 新建方法 ConfigureContainer Autofac會在啟動的時候預設呼叫,大家可能對我寫在 ConfigureContainer 方法中的感到好奇,那麼這是什麼呢,
和之前倉儲一樣,SqlSugar 和 其他ORM框架的動態連線資料庫 程式碼不一樣所以 我們先建立基類進行約束 然後各自ORM進行實現,因為這部分屬於業務層程式碼,所以我沒有 放到 EasyLogger.DbStorage 而是放在啟動程式中
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827232542.png)
```
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType().As().EnableClassInterceptors();
builder.RegisterType();
}
```
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827233044.png)
至於 SqlSugarDynamicLinkAop 就是我們的動態連線資料庫的AOP方法,下面我們開始實現他們
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827232921.png)
首先動態連線資料庫的關鍵依據 是查詢的時間,我們的資料庫分庫規則是一個月一個數據庫,一天一張表,那麼我們就先來定義一個規範的DTO
```
public class DynamicLinkInput: PagedInput
{
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
}
```
在 AOP 資料夾 新建 DynamicLinkAopBase 介面約束ORM的連線
```
public abstract class DynamicLinkAopBase : IInterceptor
{
///
/// AOP的攔截方法
///
///
public abstract void Intercept(IInvocation invocation);
///
/// 獲取查詢所需的必要條件
///
///
///
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().ToArray();
if(attrs.Length > 0) {
return attrs[0];
}
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType().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 之前沒有寫 其實如果看懂了,之前的說明,這裡怎麼寫大家都能寫出來,就是獲取到 連線提供程式儲存器 往裡面加入了一個連線。
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827234703.png)
```
public static IServiceProvider AddSqlSugarDatabaseProvider(this IServiceProvider serviceProvider, ISqlSugarSetting dbSetting)
{
if (dbSetting == null)
{
throw new ArgumentNullException(nameof(dbSetting));
}
var fSqlProviderStorage = serviceProvider.GetRequiredService();
fSqlProviderStorage.AddOrUpdate(dbSetting.Name, new SqlSugarProvider(dbSetting));
return serviceProvider;
}
```
那麼這個AOP 怎麼用呢,新建介面 IDynamicLinkBase 來提供 AOP呼叫 實現類是
SqlSugarDynamicLink (這裡直接用類也可以 我只是個人習慣)
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827233436.png)
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827235023.png)
####基本上到此為止,大家已經看明白路線了
1.我們呼叫約束的Dto 傳遞開始、結束時間
2.AOP攔截到我們條件,判斷方法是否需要動態注入連線
3.根據開始結束時間 把範圍內的資料庫都連線上,其中 我們做了一個最大開始時間 和 最大結束時間的判斷,防止資料庫沒有出現連線錯誤
#業務測驗邏輯
老規矩 新建 EasyLoggerRecordDto資料夾 儲存Dto
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827235533.png)
```
public class CreateOrUpdateEasyLoggerRecordInput
{
public EasyLoggerRecordEditDto EasyLoggerRecord { get; set; }
}
public class EasyLoggerRecordEditDto
{
public int? Id { get; set; }
///
/// 專案Id
///
public int ProjectId { get; set; }
///
/// 型別.自定義標籤
///
public string LogType { get; set; }
///
/// 狀態-成功、失敗、警告等
///
public string LogState { get; set; }
///
/// 標題
///
public string LogTitle { get; set; }
///
/// 內容描述
///
public string LogContent { get; set; }
///
/// 在系統中產生的時間
///
public DateTime LogTime { get; set; }
}
public class EasyLoggerRecordInput : DynamicLinkInput
{
///
/// 專案Id
///
public int? ProjectId { get; set; }
///
/// 型別.自定義標籤
///
public string LogType { get; set; }
///
/// 狀態-成功、失敗、警告等
///
public string LogState { get; set; }
///
/// 標題
///
public string LogTitle { get; set; }
}
public class EasyLoggerRecordListDto
{
public int Id { get; set; }
///
/// 專案Id
///
public int ProjectId { get; set; }
///
/// 型別.自定義標籤
///
public string LogType { get; set; }
///
/// 狀態-成功、失敗、警告等
///
public string LogState { get; set; }
///
/// 標題
///
public string LogTitle { get; set; }
///
/// 內容描述
///
public string LogContent { get; set; }
///
/// 在系統中產生的時間
///
public DateTime LogTime { get; set; }
public EasyLoggerProjectEditDto EasyLoggerProject { get; set; }
///
/// 建立時間
///
public DateTime CreateTime { get; set; }
}
```
新建 LoggerController 注入所需依賴
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827235635.png)
###這裡我就先貼一張圖大家來看看整個流程 怎麼玩的思考一下
#####1.首先執行AOP 並且拿到注入了那些連線
#####2.從預設庫中獲取我們的專案資訊儲存到記憶體
#####3.我們通過 得到的注入連線,來進行日誌的查詢
#####4.我們關聯上每個日誌所屬的專案 返回結果
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200827235740.png)
###我們在來細看摺疊部分的邏輯
![](https://git.imweb.io/hdong/ImageBed/raw/master/EasyLoggerImages/20200828000143.png)
#####1.Item是一個庫 我們拿到這個庫所有的天數表
#####2.使用 ChangeProvider 來切換資料庫連線 連線到 Item這個時間點的資料庫
#####3.通過SqlSugar提供的方法進行 UnionAll
#####4.查詢資料加入返回列表中
```
[HttpPost("GetEasyLoggerAsync")]
[DynamicLink]
public async Task> GetEasyLoggerAsync(EasyLoggerRecordInput input) {
// 獲取查詢的時間範圍
var dateList = _linkBase.DynamicLinkOrm(input).OrderByDescending(s => s).ToList();
var result = new PagedResultDto();
// 查詢初始資料庫資料
var projectList = _sqlRepository.GetCurrentSqlSugar().Queryable().ToList();
var DbName = IocManager.Configuration["EasyLogger:DbName"];
var entityList = new List();
// 為跨庫查詢定義的引數
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>();
_sqlRepository.GetCurrentSqlSugar().Queryable();
foreach (var day in dayList)
{
queryables.Add(sqlSugarClient.Queryable().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
- >(entityList);
foreach (var item in result.List)
{
var project = projectList.Where(s => s.Id == item.ProjectId).FirstOrDefault();
item.EasyLoggerProject = _mapper.Map