🎨🎉 优化原有的DBS配置、新增数据库故障转移方案

1.优化原有的DBS配置,破坏性修改,原有的DBS配置在多库和读写分离无法兼容,配置写法不是合适,故此优化
2.新增数据库故障转移方案,例如主库挂了自动切换到备用库,备用库不会由程序维护,需要运维、dba去做数据库同步方案,比如Sqlserver事务日志传输等

故障转移方案兼容多种方式
1.数据库主从方案
在配置主从之后,需要将从库配置为备用链接就行了
一般就是:修改、写入、删除走主库,查询操作走从库,在主库挂了后则所有操作走从库
2.数据库主备方案
日常使用主数据库操作,备用库只是备份,只有主库挂了才会用备用库

从库和备库都属于slave库功能
This commit is contained in:
LemonNoCry 2023-10-19 16:28:20 +08:00
parent dfa067d214
commit 0901de2fbf
No known key found for this signature in database
16 changed files with 363 additions and 234 deletions

View File

@ -36,19 +36,19 @@ namespace Blog.Core.Controllers
{
var data = new MessageModel<string>() { success = true, msg = "" };
data.response += @"file path is:C:\my-file\}";
var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool();
var isMuti = BaseDBConfig.IsMulti;
if (Env.IsDevelopment())
{
data.response += $"Controller层生成{FrameSeed.CreateControllers(_sqlSugarClient)} || ";
BaseDBConfig.MutiConnectionString.allDbs.ToList().ForEach(m =>
BaseDBConfig.ValidConfig.ForEach(m =>
{
_sqlSugarClient.ChangeDatabase(m.ConnId.ToLower());
data.response += $"库{m.ConnId}-Model层生成{FrameSeed.CreateModels(_sqlSugarClient, m.ConnId, isMuti)} || ";
data.response += $"库{m.ConnId}-IRepositorys层生成{FrameSeed.CreateIRepositorys(_sqlSugarClient, m.ConnId, isMuti)} || ";
data.response += $"库{m.ConnId}-IServices层生成{FrameSeed.CreateIServices(_sqlSugarClient, m.ConnId, isMuti)} || ";
data.response += $"库{m.ConnId}-Repository层生成{FrameSeed.CreateRepository(_sqlSugarClient, m.ConnId, isMuti)} || ";
data.response += $"库{m.ConnId}-Services层生成{FrameSeed.CreateServices(_sqlSugarClient, m.ConnId, isMuti)} || ";
_sqlSugarClient.ChangeDatabase(m.ConfigId.ToLower());
data.response += $"库{m.ConfigId}-Model层生成{FrameSeed.CreateModels(_sqlSugarClient, m.ConfigId, isMuti)} || ";
data.response += $"库{m.ConfigId}-IRepositorys层生成{FrameSeed.CreateIRepositorys(_sqlSugarClient, m.ConfigId, isMuti)} || ";
data.response += $"库{m.ConfigId}-IServices层生成{FrameSeed.CreateIServices(_sqlSugarClient, m.ConfigId, isMuti)} || ";
data.response += $"库{m.ConfigId}-Repository层生成{FrameSeed.CreateRepository(_sqlSugarClient, m.ConfigId, isMuti)} || ";
data.response += $"库{m.ConfigId}-Services层生成{FrameSeed.CreateServices(_sqlSugarClient, m.ConfigId, isMuti)} || ";
});
// 切回主库
@ -74,7 +74,7 @@ namespace Blog.Core.Controllers
{
ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID;
var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool();
var isMuti = BaseDBConfig.IsMulti;
var data = new MessageModel<string>() { success = true, msg = "" };
if (Env.IsDevelopment())
{
@ -102,7 +102,7 @@ namespace Blog.Core.Controllers
{
ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID;
var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool();
var isMuti = BaseDBConfig.IsMulti;
var data = new MessageModel<string>() { success = true, msg = "" };
if (Env.IsDevelopment())
{
@ -112,7 +112,7 @@ namespace Blog.Core.Controllers
{
data.success = false;
data.msg = "当前不处于开发模式,代码生成不可用!";
}
}
return data;
}
/// <summary>
@ -126,7 +126,7 @@ namespace Blog.Core.Controllers
{
ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID;
var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool();
var isMuti = BaseDBConfig.IsMulti;
var data = new MessageModel<string>() { success = true, msg = "" };
if (Env.IsDevelopment())
{
@ -151,7 +151,7 @@ namespace Blog.Core.Controllers
{
ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID;
var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool();
var isMuti = BaseDBConfig.IsMulti;
var data = new MessageModel<string>() { success = true, msg = "" };
if (Env.IsDevelopment())
{

View File

@ -219,7 +219,7 @@ namespace Blog.Core.Controllers
{
List<ApiDate> apiDates = new List<ApiDate>();
if (AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool())
if (_applicationUserServices.IsEnable())
{
var users = await _applicationUserServices.Query(d => d.tdIsDelete == false);

View File

@ -80,14 +80,17 @@
"UseLoadTest": false
},
// MainDBConnId,Enabledtrue
// *** MutiDBEnabled false ***
// *** MutiDBEnabled trueEnabledtrue **
// https://www.bilibili.com/video/BV1BJ411B7mn?p=6
//Log:
"MainDB": "WMBLOG_SQLITE", //Enabledtrue
"MutiDBEnabled": true, //
"CQRSEnabled": false, //,SqlServer
//DB
//MainDbEnabledtrue
//Log:Enabledtrue
//Slaves,!SqlServer
//
//,
//,(+)
//ConnIdConnId+,ConnIdMain,ConnIdMian1
//!
//,
"MainDB": "Main", //Enabledtrue
"DBS": [
/*
DBType
@ -100,24 +103,40 @@
Kdbndp = 6,//
*/
{
"ConnId": "WMBLOG_SQLITE",
"ConnId": "Main",
"DBType": 2,
"Enabled": true,
"HitRate": 50, //
"Connection": "WMBlog.db" //sqlite
"Connection": "WMBlog.db", //sqlite
"Slaves": [
{
"HitRate": 0,// 0使
"Connection": "WMBlog2.db"
}
]
},
{
"ConnId": "Main2",
"DBType": 2,
"Enabled": true,
"Connection": "WMBlog3.db", //sqlite
"Slaves": [
{
"HitRate": 0,// 0使
"Connection": "WMBlog4.db"
}
]
},
{
"ConnId": "Log", //,
"DBType": 2,
"Enabled": true,
"HitRate": 50, //
"HitRate": 50,
"Connection": "WMBlogLog.db" //sqlite
},
{
"ConnId": "WMBLOG_MSSQL_1",
"DBType": 1,
"Enabled": false,
"HitRate": 40,
"Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_1;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"ProviderName": "System.Data.SqlClient"
},
@ -125,7 +144,6 @@
"ConnId": "WMBLOG_MSSQL_2",
"DBType": 1,
"Enabled": false,
"HitRate": 30,
"Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"ProviderName": "System.Data.SqlClient"
},
@ -133,35 +151,30 @@
"ConnId": "WMBLOG_MYSQL",
"DBType": 0,
"Enabled": false,
"HitRate": 20,
"Connection": "server=localhost;Database=blog;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;"
},
{
"ConnId": "WMBLOG_MYSQL_2",
"DBType": 0,
"Enabled": false,
"HitRate": 20,
"Connection": "server=localhost;Database=blogcore001;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;"
},
{
"ConnId": "WMBLOG_ORACLE",
"DBType": 3,
"Enabled": false,
"HitRate": 10,
"Connection": "Data Source=127.0.0.1/ops;User ID=OPS;Password=123456;Persist Security Info=True;Connection Timeout=60;"
},
{
"ConnId": "WMBLOG_DM",
"DBType": 5,
"Enabled": false,
"HitRate": 10,
"Connection": "Server=xxxxx:5236;User Id=xxxxx;PWD=xxxxx;SCHEMA=TESTDBA;"
},
{
"ConnId": "WMBLOG_KDBNDP",
"DBType": 6,
"Enabled": false,
"HitRate": 10,
"Connection": "Server=127.0.0.1;Port=54321;UID=SYSTEM;PWD=system;database=SQLSUGAR4XTEST1;"
}
],

View File

@ -0,0 +1,23 @@
using System.Linq;
using SqlSugar;
namespace Blog.Core.Common.DB.Aop;
public class SqlSugarReuse
{
public static void AutoChangeAvailableConnect(SqlSugarClient db)
{
if (db == null) return;
if (db.Ado.IsValidConnection()) return;
if (!BaseDBConfig.ReuseConfigs.Any()) return;
foreach (var connectionConfig in BaseDBConfig.ReuseConfigs)
{
var config = db.CurrentConnectionConfig.ConfigId;
db.ChangeDatabase(connectionConfig.ConfigId);
//移除旧的连接,只会在本次上下文移除,因为主库已经故障会导致多库事务无法使用
db.RemoveConnection(config);
if (db.Ado.IsValidConnection()) return;
}
}
}

View File

@ -1,24 +1,46 @@
using SqlSugar;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SqlSugar;
namespace Blog.Core.Common.DB
{
public class BaseDBConfig
{
public static readonly List<ConnectionConfig> AllConfigs = new(); //所有库配置
public static readonly List<SlaveConnectionConfig> AllSlaveConfigs = new(); //从库配置
public static List<ConnectionConfig> ValidConfig = new(); //有效的库连接(除去Log库)
public static ConnectionConfig LogConfig; //日志库
/// <summary>
/// 所有库配置
/// </summary>
public static readonly List<ConnectionConfig> AllConfigs = new();
/// <summary>
/// 主库的备用连接配置
/// </summary>
public static readonly List<ConnectionConfig> ReuseConfigs = new();
/// <summary>
/// 有效的库连接(除去Log库)
/// </summary>
public static List<ConnectionConfig> ValidConfig = new();
public static ConnectionConfig MainConfig;
public static ConnectionConfig LogConfig; //日志库
public static bool IsMulti => ValidConfig.Count > 1;
/* GitHub的历史记录
* appsettings.json设置为true的第一个db连接
*
*
* ,
*
*
*
* ,ConfigId为主库的ConfigId+便
*
*
*/
public static (List<MutiDBOperate> allDbs, List<MutiDBOperate> slaveDbs) MutiConnectionString => MutiInitConn();
private static string DifDBConnOfSecurity(params string[] conn)
{
@ -44,52 +66,13 @@ namespace Blog.Core.Common.DB
{
List<MutiDBOperate> listdatabase = AppSettings.app<MutiDBOperate>("DBS")
.Where(i => i.Enabled).ToList();
var mainDbId = AppSettings.app(new string[] { "MainDB" }).ObjToString();
var mainDbId = AppSettings.app(new string[] {"MainDB"}).ObjToString();
var mainDbModel = listdatabase.Single(d => d.ConnId == mainDbId);
listdatabase.Remove(mainDbModel);
listdatabase.Insert(0, mainDbModel);
foreach (var i in listdatabase)
{
SpecialDbString(i);
}
List<MutiDBOperate> listdatabaseSimpleDB = new List<MutiDBOperate>(); //单库
List<MutiDBOperate> listdatabaseSlaveDB = new List<MutiDBOperate>(); //从库
// 单库,且不开启读写分离,只保留一个
if (!AppSettings.app(new string[] {"CQRSEnabled"}).ObjToBool() && !AppSettings.app(new string[] {"MutiDBEnabled"}).ObjToBool())
{
if (listdatabase.Count == 1)
{
return (listdatabase, listdatabaseSlaveDB);
}
else
{
var dbFirst = listdatabase.FirstOrDefault(d => d.ConnId == AppSettings.app(new string[] {"MainDB"}).ObjToString());
if (dbFirst == null)
{
dbFirst = listdatabase.FirstOrDefault();
}
listdatabaseSimpleDB.Add(dbFirst);
return (listdatabaseSimpleDB, listdatabaseSlaveDB);
}
}
// 读写分离,且必须是单库模式,获取从库
if (AppSettings.app(new string[] {"CQRSEnabled"}).ObjToBool() && !AppSettings.app(new string[] {"MutiDBEnabled"}).ObjToBool())
{
if (listdatabase.Count > 1)
{
listdatabaseSlaveDB = listdatabase.Where(d => d.ConnId != AppSettings.app(new string[] {"MainDB"}).ObjToString()).ToList();
}
}
return (listdatabase, listdatabaseSlaveDB);
//}
foreach (var i in listdatabase) SpecialDbString(i);
return (listdatabase, mainDbModel.Slaves);
}
/// <summary>
@ -102,19 +85,23 @@ namespace Blog.Core.Common.DB
{
if (mutiDBOperate.DbType == DataBaseType.Sqlite)
{
mutiDBOperate.Connection = $"DataSource=" + Path.Combine(Environment.CurrentDirectory, mutiDBOperate.Connection);
mutiDBOperate.Connection =
$"DataSource=" + Path.Combine(Environment.CurrentDirectory, mutiDBOperate.Connection);
}
else if (mutiDBOperate.DbType == DataBaseType.SqlServer)
{
mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_SqlserverConn.txt", mutiDBOperate.Connection);
mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_SqlserverConn.txt",
mutiDBOperate.Connection);
}
else if (mutiDBOperate.DbType == DataBaseType.MySql)
{
mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_MySqlConn.txt", mutiDBOperate.Connection);
mutiDBOperate.Connection =
DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_MySqlConn.txt", mutiDBOperate.Connection);
}
else if (mutiDBOperate.DbType == DataBaseType.Oracle)
{
mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_OracleConn.txt", mutiDBOperate.Connection);
mutiDBOperate.Connection =
DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_OracleConn.txt", mutiDBOperate.Connection);
}
return mutiDBOperate;
@ -159,5 +146,10 @@ namespace Blog.Core.Common.DB
/// 数据库类型
/// </summary>
public DataBaseType DbType { get; set; }
/// <summary>
/// 从库
/// </summary>
public List<MutiDBOperate> Slaves { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Reflection;
using SqlSugar;
namespace Blog.Core.Common.DB.Extension;
public static class DbEntityException
{
public static object GetEntityTenant(this Type type)
{
var tenant = type.GetCustomAttribute<TenantAttribute>();
return tenant?.configId;
}
}

View File

@ -2,6 +2,6 @@
{
public static class MainDb
{
public static string CurrentDbConnId = "1";
public static string CurrentDbConnId = "Main";
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Blog.Core
@ -288,5 +289,12 @@ namespace Blog.Core
{
return JsonConvert.SerializeObject(value);
}
public static bool AnyNoException<T>(this ICollection<T> source)
{
if (source == null) return false;
return source.Any() && source.All(s => s != null);
}
}
}

View File

@ -41,40 +41,33 @@ namespace Blog.Core.Common.Seed
SeedDataFolder = Path.Combine(WebRootPath, SeedDataFolder);
Console.WriteLine("************ Blog.Core DataBase Set *****************");
Console.WriteLine($"Is multi-DataBase: {AppSettings.app(new string[] { "MutiDBEnabled" })}");
Console.WriteLine($"Is CQRS: {AppSettings.app(new string[] { "CQRSEnabled" })}");
Console.WriteLine();
Console.WriteLine($"Master DB ConId: {myContext.Db.CurrentConnectionConfig.ConfigId}");
Console.WriteLine($"Master DB Type: {myContext.Db.CurrentConnectionConfig.DbType}");
Console.WriteLine($"Master DB ConnectString: {myContext.Db.CurrentConnectionConfig.ConnectionString}");
Console.WriteLine();
if (AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool())
if (BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException())
{
var slaveIndex = 0;
BaseDBConfig.MutiConnectionString.allDbs.Where(x => x.ConnId != MainDb.CurrentDbConnId).ToList().ForEach(m =>
var index = 0;
BaseDBConfig.MainConfig.SlaveConnectionConfigs.ForEach(m =>
{
slaveIndex++;
Console.WriteLine($"Slave{slaveIndex} DB ID: {m.ConnId}");
Console.WriteLine($"Slave{slaveIndex} DB Type: {m.DbType}");
Console.WriteLine($"Slave{slaveIndex} DB ConnectString: {m.Connection}");
index++;
Console.WriteLine($"Slave{index} DB HitRate: {m.HitRate}");
Console.WriteLine($"Slave{index} DB ConnectString: {m.ConnectionString}");
Console.WriteLine($"--------------------------------------");
});
}
else if (AppSettings.app(new string[] { "CQRSEnabled" }).ObjToBool())
else if (BaseDBConfig.ReuseConfigs.AnyNoException())
{
var slaveIndex = 0;
BaseDBConfig.MutiConnectionString.slaveDbs.Where(x => x.ConnId != MainDb.CurrentDbConnId).ToList().ForEach(m =>
var index = 0;
BaseDBConfig.ReuseConfigs.ForEach(m =>
{
slaveIndex++;
Console.WriteLine($"Slave{slaveIndex} DB ID: {m.ConnId}");
Console.WriteLine($"Slave{slaveIndex} DB Type: {m.DbType}");
Console.WriteLine($"Slave{slaveIndex} DB ConnectString: {m.Connection}");
index++;
Console.WriteLine($"Reuse{index} DB ID: {m.ConfigId}");
Console.WriteLine($"Reuse{index} DB Type: {m.DbType}");
Console.WriteLine($"Reuse{index} DB ConnectString: {m.ConnectionString}");
Console.WriteLine($"--------------------------------------");
});
}
else
{
}
Console.WriteLine();
@ -97,7 +90,8 @@ namespace Blog.Core.Common.Seed
Console.WriteLine("Create Tables...");
var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll").Select(Assembly.LoadFrom).ToArray();
var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll")
.Select(Assembly.LoadFrom).ToArray();
var modelTypes = referencedAssemblies
.SelectMany(a => a.DefinedTypes)
.Select(type => type.AsType())
@ -117,7 +111,7 @@ namespace Blog.Core.Common.Seed
ConsoleHelper.WriteSuccessLine($"Tables created successfully!");
Console.WriteLine();
if (AppSettings.app(new string[] { "AppSettings", "SeedDBDataEnabled" }).ObjToBool())
if (AppSettings.app(new string[] {"AppSettings", "SeedDBDataEnabled"}).ObjToBool())
{
JsonSerializerSettings setting = new JsonSerializerSettings();
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
@ -143,7 +137,9 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<BlogArticle>().AnyAsync())
{
myContext.GetEntityDB<BlogArticle>().InsertRange(JsonHelper.ParseFormByJson<List<BlogArticle>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "BlogArticle"), Encoding.UTF8)));
myContext.GetEntityDB<BlogArticle>().InsertRange(
JsonHelper.ParseFormByJson<List<BlogArticle>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "BlogArticle"), Encoding.UTF8)));
Console.WriteLine("Table:BlogArticle created success!");
}
else
@ -158,7 +154,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<Modules>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<Modules>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Modules"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<Modules>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "Modules"), Encoding.UTF8), setting);
myContext.GetEntityDB<Modules>().InsertRange(data);
Console.WriteLine("Table:Modules created success!");
@ -175,7 +172,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<Permission>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<Permission>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Permission"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<Permission>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "Permission"), Encoding.UTF8), setting);
myContext.GetEntityDB<Permission>().InsertRange(data);
Console.WriteLine("Table:Permission created success!");
@ -192,7 +190,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<Role>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<Role>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Role"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<Role>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "Role"), Encoding.UTF8), setting);
//using var stream = new FileStream(Path.Combine(WebRootPath, "BlogCore.Data.excel", "Role.xlsx"), FileMode.Open);
//var result = await importer.Import<Role>(stream);
//var data = result.Data.ToList();
@ -212,7 +211,9 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<RoleModulePermission>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<RoleModulePermission>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "RoleModulePermission"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<RoleModulePermission>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "RoleModulePermission"), Encoding.UTF8),
setting);
myContext.GetEntityDB<RoleModulePermission>().InsertRange(data);
Console.WriteLine("Table:RoleModulePermission created success!");
@ -229,7 +230,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<Topic>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<Topic>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Topic"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<Topic>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "Topic"), Encoding.UTF8), setting);
myContext.GetEntityDB<Topic>().InsertRange(data);
Console.WriteLine("Table:Topic created success!");
@ -246,7 +248,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<TopicDetail>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<TopicDetail>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "TopicDetail"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<TopicDetail>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "TopicDetail"), Encoding.UTF8), setting);
myContext.GetEntityDB<TopicDetail>().InsertRange(data);
Console.WriteLine("Table:TopicDetail created success!");
@ -263,7 +266,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<UserRole>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<UserRole>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "UserRole"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<UserRole>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "UserRole"), Encoding.UTF8), setting);
myContext.GetEntityDB<UserRole>().InsertRange(data);
Console.WriteLine("Table:UserRole created success!");
@ -280,7 +284,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<SysUserInfo>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<SysUserInfo>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "sysUserInfo"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<SysUserInfo>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "sysUserInfo"), Encoding.UTF8), setting);
myContext.GetEntityDB<SysUserInfo>().InsertRange(data);
Console.WriteLine("Table:sysUserInfo created success!");
@ -297,7 +302,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<TasksQz>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<TasksQz>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "TasksQz"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<TasksQz>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "TasksQz"), Encoding.UTF8), setting);
myContext.GetEntityDB<TasksQz>().InsertRange(data);
Console.WriteLine("Table:TasksQz created success!");
@ -326,7 +332,8 @@ namespace Blog.Core.Common.Seed
if (!await myContext.Db.Queryable<Department>().AnyAsync())
{
var data = JsonConvert.DeserializeObject<List<Department>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Department"), Encoding.UTF8), setting);
var data = JsonConvert.DeserializeObject<List<Department>>(
FileHelper.ReadFile(string.Format(SeedDataFolder, "Department"), Encoding.UTF8), setting);
myContext.GetEntityDB<Department>().InsertRange(data);
Console.WriteLine("Table:Department created success!");
@ -367,7 +374,8 @@ namespace Blog.Core.Common.Seed
.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass)
.Where(u =>
{
var esd = u.GetInterfaces().FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>)));
var esd = u.GetInterfaces()
.FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>)));
if (esd is null)
{
return false;
@ -441,11 +449,13 @@ namespace Blog.Core.Common.Seed
logDb.DbMaintenance.CreateDatabase();
ConsoleHelper.WriteSuccessLine($"Log Database created successfully!");
var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll").Select(Assembly.LoadFrom).ToArray();
var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll")
.Select(Assembly.LoadFrom).ToArray();
var modelTypes = referencedAssemblies
.SelectMany(a => a.DefinedTypes)
.Select(type => type.AsType())
.Where(x => x.IsClass && x.Namespace != null && x.Namespace.StartsWith("Blog.Core.Model.Logs")).ToList();
.Where(x => x.IsClass && x.Namespace != null && x.Namespace.StartsWith("Blog.Core.Model.Logs"))
.ToList();
Stopwatch sw = Stopwatch.StartNew();
var tables = logDb.DbMaintenance.GetTableInfoList();
@ -482,7 +492,8 @@ namespace Blog.Core.Common.Seed
/// <returns></returns>
public static async Task TenantSeedAsync(MyContext myContext)
{
var tenants = await myContext.Db.Queryable<SysTenant>().Where(s => s.TenantType == TenantTypeEnum.Db).ToListAsync();
var tenants = await myContext.Db.Queryable<SysTenant>().Where(s => s.TenantType == TenantTypeEnum.Db)
.ToListAsync();
if (tenants.Any())
{
Console.WriteLine($@"Init Multi Tenant Db");
@ -493,7 +504,8 @@ namespace Blog.Core.Common.Seed
}
}
tenants = await myContext.Db.Queryable<SysTenant>().Where(s => s.TenantType == TenantTypeEnum.Tables).ToListAsync();
tenants = await myContext.Db.Queryable<SysTenant>().Where(s => s.TenantType == TenantTypeEnum.Tables)
.ToListAsync();
if (tenants.Any())
{
await InitTenantSeedAsync(myContext, tenants);
@ -526,7 +538,8 @@ namespace Blog.Core.Common.Seed
await TenantSeedDataAsync(myContext.Db, TenantTypeEnum.Tables);
}
ConsoleHelper.WriteSuccessLine($"Init Multi Tenant Tables : {myContext.Db.CurrentConnectionConfig.ConfigId} created successfully!");
ConsoleHelper.WriteSuccessLine(
$"Init Multi Tenant Tables : {myContext.Db.CurrentConnectionConfig.ConfigId} created successfully!");
}
#endregion
@ -580,7 +593,8 @@ namespace Blog.Core.Common.Seed
.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass)
.Where(u =>
{
var esd = u.GetInterfaces().FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>)));
var esd = u.GetInterfaces()
.FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>)));
if (esd is null)
{
return false;

View File

@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Text;
using Blog.Core.Common.DB;
namespace Blog.Core.Extensions
{
@ -189,18 +190,8 @@ namespace Blog.Core.Extensions
ConsoleHelper.WriteSuccessLine($"EventBus: True");
}
// 多库
if (!AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool())
{
Console.WriteLine($"Is multi-DataBase: False");
}
else
{
ConsoleHelper.WriteSuccessLine($"Is multi-DataBase: True");
}
// 读写分离
if (!AppSettings.app(new string[] { "CQRSEnabled" }).ObjToBool())
if (!BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException())
{
Console.WriteLine($"Is CQRS: False");
}
@ -235,8 +226,7 @@ namespace Blog.Core.Extensions
new string[] { "RabbitMQ消息列队", AppSettings.app("RabbitMQ", "Enabled") },
new string[] { "事件总线(必须开启消息列队)", AppSettings.app("EventBus", "Enabled") },
new string[] { "redis消息队列", AppSettings.app("Startup", "RedisMq", "Enabled") },
new string[] { "是否多库", AppSettings.app("MutiDBEnabled") },
new string[] { "读写分离", AppSettings.app("CQRSEnabled") },
new string[] { "读写分离", BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException()? "True" : "False" },
};
new ConsoleTable()

View File

@ -9,6 +9,7 @@ using SqlSugar;
using StackExchange.Profiling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Blog.Core.Common.Caches;
using Blog.Core.Common.Core;
@ -30,16 +31,10 @@ namespace Blog.Core.Extensions
if (services == null) throw new ArgumentNullException(nameof(services));
// 默认添加主数据库连接
MainDb.CurrentDbConnId = AppSettings.app(new string[] { "MainDB" });
BaseDBConfig.MutiConnectionString.slaveDbs.ForEach(s =>
if (!AppSettings.app("MainDB").IsNullOrEmpty())
{
BaseDBConfig.AllSlaveConfigs.Add(new SlaveConnectionConfig()
{
HitRate = s.HitRate,
ConnectionString = s.Connection
});
});
MainDb.CurrentDbConnId = AppSettings.app("MainDB");
}
BaseDBConfig.MutiConnectionString.allDbs.ForEach(m =>
{
@ -47,7 +42,7 @@ namespace Blog.Core.Extensions
{
ConfigId = m.ConnId.ObjToString().ToLower(),
ConnectionString = m.Connection,
DbType = (DbType)m.DbType,
DbType = (DbType) m.DbType,
IsAutoCloseConnection = true,
// Check out more information: https://github.com/anjoy8/Blog.Core/issues/122
//IsShardSameThread = false,
@ -58,7 +53,11 @@ namespace Blog.Core.Extensions
SqlServerCodeFirstNvarchar = true,
},
// 从库
SlaveConnectionConfigs = BaseDBConfig.AllSlaveConfigs,
SlaveConnectionConfigs = m.Slaves?.Where(s => s.HitRate > 0).Select(s => new SlaveConnectionConfig
{
ConnectionString = s.Connection,
HitRate = s.HitRate
}).ToList(),
// 自定义特性
ConfigureExternalServices = new ConfigureExternalServices()
{
@ -79,6 +78,16 @@ namespace Blog.Core.Extensions
}
else
{
if (string.Equals(SqlSugarConst.LogConfigId, MainDb.CurrentDbConnId,
StringComparison.CurrentCultureIgnoreCase))
{
BaseDBConfig.MainConfig = config;
}
//复用连接
if (m.ConnId.ToLower().StartsWith(SqlSugarConst.LogConfigId.ToLower()))
BaseDBConfig.ReuseConfigs.Add(config);
BaseDBConfig.ValidConfig.Add(config);
}
@ -98,12 +107,14 @@ namespace Blog.Core.Extensions
{
BaseDBConfig.ValidConfig.ForEach(config =>
{
var dbProvider = db.GetConnectionScope((string)config.ConfigId);
var dbProvider = db.GetConnectionScope((string) config.ConfigId);
// 打印SQL语句
dbProvider.Aop.OnLogExecuting = (s, parameters) =>
{
SqlSugarAop.OnLogExecuting(dbProvider, App.User?.Name.ObjToString(), ExtractTableName(s), Enum.GetName(typeof(SugarActionType), dbProvider.SugarActionType), s, parameters, config);
SqlSugarAop.OnLogExecuting(dbProvider, App.User?.Name.ObjToString(), ExtractTableName(s),
Enum.GetName(typeof(SugarActionType), dbProvider.SugarActionType), s, parameters,
config);
};
// 数据审计
@ -114,6 +125,8 @@ namespace Blog.Core.Extensions
// 配置实体数据权限
RepositorySetting.SetTenantEntityFilter(dbProvider);
});
//故障转移,检查主库链接自动切换备用连接
SqlSugarReuse.AutoChangeAvailableConnect(db);
});
});
}

View File

@ -1,9 +1,11 @@
using Blog.Core.IServices.BASE;
using System.Threading.Tasks;
using Blog.Core.IServices.BASE;
using Blog.Core.Model.IDS4DbModels;
namespace Blog.Core.IServices
{
public partial interface IApplicationUserServices : IBaseServices<ApplicationUser>
{
bool IsEnable();
}
}

View File

@ -27,21 +27,14 @@ namespace Blog.Core.Repository.Base
{
ISqlSugarClient db = _dbBase;
/*
* 1appsettings.json MutiDBEnabled节点为true
* 2IDMainDBEnabled也必须true
*/
if (AppSettings.app(new[] { "MutiDBEnabled" }).ObjToBool())
//修改使用 model备注字段作为切换数据库条件使用sqlsugar TenantAttribute存放数据库ConnId
//参考 https://www.donet5.com/Home/Doc?typeId=2246
var tenantAttr = typeof(TEntity).GetCustomAttribute<TenantAttribute>();
if (tenantAttr != null)
{
//修改使用 model备注字段作为切换数据库条件使用sqlsugar TenantAttribute存放数据库ConnId
//参考 https://www.donet5.com/Home/Doc?typeId=2246
var tenantAttr = typeof(TEntity).GetCustomAttribute<TenantAttribute>();
if (tenantAttr != null)
{
//统一处理 configId 小写
db = _dbBase.GetConnectionScope(tenantAttr.configId.ToString().ToLower());
return db;
}
//统一处理 configId 小写
db = _dbBase.GetConnectionScope(tenantAttr.configId.ToString().ToLower());
return db;
}
//多租户

View File

@ -1,4 +1,7 @@
using Blog.Core.IRepository.Base;
using System.Threading.Tasks;
using Blog.Core.Common.DB;
using Blog.Core.Common.DB.Extension;
using Blog.Core.IRepository.Base;
using Blog.Core.Model.IDS4DbModels;
using Blog.Core.Services.BASE;
@ -6,6 +9,10 @@ namespace Blog.Core.IServices
{
public class ApplicationUserServices : BaseServices<ApplicationUser>, IApplicationUserServices
{
public bool IsEnable()
{
var configId = typeof(ApplicationUser).GetEntityTenant();
return Db.AsTenant().IsAnyConnection(configId);
}
}
}

View File

@ -11,13 +11,6 @@
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Moq" Version="4.18.2" />
@ -38,6 +31,14 @@
</None>
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
</Project>

View File

@ -1,30 +1,26 @@
{
"urls": "http://*:9291", //webIIS
"Logging": {
"LogLevel": {
"Default": "Information", //Defaultlog4net
"Blog.Core.AuthHelper.ApiResponseHandler": "Error"
},
"Debug": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Information",
"Microsoft.AspNetCore": "Warning",
"System": "Warning",
"System.Net.Http.HttpClient": "Warning",
"Hangfire": "Information",
"Magicodes": "Warning",
"DotNetCore.CAP": "Information",
"Savorboard.CAP": "Information",
"Quartz": "Information"
}
},
"Console": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Debug"
}
},
"Log4Net": {
"Name": "Blog.Core"
}
},
"AllowedHosts": "*",
"Redis": {
"ConnectionString": "127.0.0.1:6319,password=admin"
"Enable": false,
"ConnectionString": "127.0.0.1:6379",
"InstanceName": "" //
},
"RabbitMQ": {
"Enabled": false,
@ -48,24 +44,34 @@
"CachingAOP": {
"Enabled": true
},
"LogToDb": true,
"LogAOP": {
"Enabled": false
"Enabled": false,
"LogToFile": {
"Enabled": true
},
"LogToDB": {
"Enabled": true
}
},
"TranAOP": {
"Enabled": true
},
"UserAuditAOP": {
"Enabled": false
},
"SqlAOP": {
"Enabled": true,
"OutToLogFile": {
"Enabled": false
"LogToFile": {
"Enabled": true
},
"OutToConsole": {
"LogToDB": {
"Enabled": true
},
"LogToConsole": {
"Enabled": true
}
},
"LogToDb": {
"Enabled": true
},
"Date": "2018-08-28",
"SeedDBEnabled": true, //
"SeedDBDataEnabled": true, //,
@ -74,14 +80,17 @@
"UseLoadTest": false
},
// MainDBConnId,Enabledtrue
// *** MutiDBEnabled false ***
// *** MutiDBEnabled trueEnabledtrue **
// https://www.bilibili.com/video/BV1BJ411B7mn?p=6
"MainDB": "WMBLOG_SQLITE", //Enabledtrue
"MutiDBEnabled": false, //
"CQRSEnabled": false, //,SqlServer
//DB
//MainDbEnabledtrue
//Log:Enabledtrue
//Slaves,!SqlServer
//
//,
//,(+)
//ConnIdConnId+,ConnIdMain,ConnIdMian1
//!
//,
"MainDB": "Main", //Enabledtrue
"DBS": [
/*
DBType
@ -94,17 +103,40 @@
Kdbndp = 6,//
*/
{
"ConnId": "WMBLOG_SQLITE",
"ConnId": "Main",
"DBType": 2,
"Enabled": true,
"HitRate": 50, //
"Connection": "WMBlog.db" //sqlite
"Connection": "WMBlog.db", //sqlite
"Slaves": [
{
"HitRate": 0,// 0使
"Connection": "WMBlog2.db"
}
]
},
{
"ConnId": "Main2",
"DBType": 2,
"Enabled": true,
"Connection": "WMBlog3.db", //sqlite
"Slaves": [
{
"HitRate": 0,// 0使
"Connection": "WMBlog4.db"
}
]
},
{
"ConnId": "Log", //,
"DBType": 2,
"Enabled": true,
"HitRate": 50,
"Connection": "WMBlogLog.db" //sqlite
},
{
"ConnId": "WMBLOG_MSSQL_1",
"DBType": 1,
"Enabled": false,
"HitRate": 40,
"Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_1;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"ProviderName": "System.Data.SqlClient"
},
@ -112,7 +144,6 @@
"ConnId": "WMBLOG_MSSQL_2",
"DBType": 1,
"Enabled": false,
"HitRate": 30,
"Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"ProviderName": "System.Data.SqlClient"
},
@ -120,35 +151,30 @@
"ConnId": "WMBLOG_MYSQL",
"DBType": 0,
"Enabled": false,
"HitRate": 20,
"Connection": "server=.;Database=ddd;Uid=root;Pwd=123456;Port=10060;Allow User Variables=True;"
"Connection": "server=localhost;Database=blog;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;"
},
{
"ConnId": "WMBLOG_MYSQL_2",
"DBType": 0,
"Enabled": true,
"HitRate": 20,
"Connection": "server=.;Database=blogcore001;Uid=root;Pwd=123456;Port=3096;Allow User Variables=True;"
"Enabled": false,
"Connection": "server=localhost;Database=blogcore001;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;"
},
{
"ConnId": "WMBLOG_ORACLE",
"DBType": 3,
"Enabled": false,
"HitRate": 10,
"Connection": "Data Source=127.0.0.1/ops;User ID=OPS;Password=123456;Persist Security Info=True;Connection Timeout=60;"
},
{
"ConnId": "WMBLOG_DM",
"DBType": 5,
"Enabled": false,
"HitRate": 10,
"Connection": "PORT=5236;DATABASE=DAMENG;HOST=localhost;PASSWORD=SYSDBA;USER ID=SYSDBA;"
"Connection": "Server=xxxxx:5236;User Id=xxxxx;PWD=xxxxx;SCHEMA=TESTDBA;"
},
{
"ConnId": "WMBLOG_KDBNDP",
"DBType": 6,
"Enabled": true,
"HitRate": 10,
"Enabled": false,
"Connection": "Server=127.0.0.1;Port=54321;UID=SYSTEM;PWD=system;database=SQLSUGAR4XTEST1;"
}
],
@ -163,12 +189,13 @@
"Database": "BlogCoreDb"
},
"Startup": {
"Domain": "http://localhost:9291",
"Cors": {
"PolicyName": "CorsIpAccess", //
"EnableAllIPs": false, //trueIP访
// /localhost:8000/
// http://127.0.0.1:1818 http://localhost:1818
"IPs": "http://127.0.0.1:2364,http://localhost:2364"
"IPs": "http://127.0.0.1:2364,http://localhost:2364,http://127.0.0.1:6688,http://localhost:6688"
},
"AppConfigAlert": {
"Enabled": true
@ -179,6 +206,12 @@
"AuthorizationUrl": "http://localhost:5004", //
"ApiName": "blog.core.api" //
},
"Authing": {
"Enabled": false,
"Issuer": "https://uldr24esx31h-demo.authing.cn/oidc",
"Audience": "63d51c4205c2849803be5178",
"JwksUri": "https://uldr24esx31h-demo.authing.cn/oidc/.well-known/jwks.json"
},
"RedisMq": {
"Enabled": false //redis
},
@ -191,17 +224,38 @@
},
"Middleware": {
"RequestResponseLog": {
"Enabled": false
"Enabled": true,
"LogToFile": {
"Enabled": true
},
"LogToDB": {
"Enabled": true
}
},
"IPLog": {
"Enabled": true
"Enabled": true,
"LogToFile": {
"Enabled": true
},
"LogToDB": {
"Enabled": true
}
},
"RecordAccessLogs": {
"Enabled": true,
"LogToFile": {
"Enabled": true
},
"LogToDB": {
"Enabled": true
},
"IgnoreApis": "/api/permission/getnavigationbar,/api/monitor/getids4users,/api/monitor/getaccesslogs,/api/monitor/server,/api/monitor/getactiveusers,/api/monitor/server,"
},
"SignalR": {
"Enabled": false
"Enabled": true
},
"SignalRSendLog": {
"Enabled": true
},
"QuartzNetJob": {
"Enabled": true
@ -285,5 +339,10 @@
"FiedValue": "Blog.Core.Api"
}
]
},
"Seq": {
"Enabled": true,
"Address": "http://localhost:5341/",
"ApiKey": ""
}
}
}