feat: 完成多租户saas框架搭建
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
public class DbConnOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接字符串(如果开启多租户,也就是默认库了),必填
|
||||
/// </summary>
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库类型
|
||||
/// </summary>
|
||||
public DbType? DbType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启种子数据
|
||||
/// </summary>
|
||||
public bool EnabledDbSeed { get; set; } = false;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开启codefirst
|
||||
/// </summary>
|
||||
public bool EnabledCodeFirst { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启sql日志
|
||||
/// </summary>
|
||||
public bool EnabledSqlLog { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 实体程序集
|
||||
/// </summary>
|
||||
public List<string>? EntityAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启读写分离
|
||||
/// </summary>
|
||||
public bool EnabledReadWrite { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 读写分离
|
||||
/// </summary>
|
||||
public List<string>? ReadUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启Saas多租户
|
||||
/// </summary>
|
||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 默认租户库连接,如果不填,那就是默认库的地址
|
||||
/// </summary>
|
||||
public string? MasterSaasMultiTenancyUrl { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Saas租户连接
|
||||
/// </summary>
|
||||
public List<SaasMultiTenancyOptions>? SaasMultiTenancy { get; set; }
|
||||
|
||||
public static string MasterTenantDbDefaultName = "Master";
|
||||
public static string TenantDbDefaultName = "Default";
|
||||
|
||||
public SaasMultiTenancyOptions GetDefaultSaasMultiTenancy()
|
||||
{
|
||||
return new SaasMultiTenancyOptions { Name = TenantDbDefaultName, Url = Url };
|
||||
}
|
||||
public SaasMultiTenancyOptions? GetDefaultMasterSaasMultiTenancy()
|
||||
{
|
||||
if (EnabledSaasMultiTenancy == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (string.IsNullOrEmpty(MasterSaasMultiTenancyUrl))
|
||||
{
|
||||
|
||||
return new SaasMultiTenancyOptions { Name = MasterTenantDbDefaultName, Url = Url };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SaasMultiTenancyOptions()
|
||||
{
|
||||
Name = MasterTenantDbDefaultName,
|
||||
Url = MasterSaasMultiTenancyUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SaasMultiTenancyOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户名称标识
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接Url
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,12 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||
ISqlSugarClient SqlSugarClient { get; }
|
||||
DbConnOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库备份
|
||||
/// </summary>
|
||||
void BackupDataBase();
|
||||
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class IgnoreCodeFirstAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class MasterTenantAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace Yi.Framework.SqlSugarCore
|
||||
{
|
||||
public class DbConnOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接字符串,必填
|
||||
/// </summary>
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库类型
|
||||
/// </summary>
|
||||
public DbType? DbType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启种子数据
|
||||
/// </summary>
|
||||
public bool EnabledDbSeed { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启读写分离
|
||||
/// </summary>
|
||||
public bool EnabledReadWrite { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启codefirst
|
||||
/// </summary>
|
||||
public bool EnabledCodeFirst { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开启sql日志
|
||||
/// </summary>
|
||||
public bool EnabledSqlLog { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 实体程序集
|
||||
/// </summary>
|
||||
public List<string>? EntityAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读写分离
|
||||
/// </summary>
|
||||
public List<string>? ReadUrl { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Yi.Framework.SqlSugarCore
|
||||
/// <summary>
|
||||
/// SqlSugar 客户端
|
||||
/// </summary>
|
||||
public ISqlSugarClient SqlSugarClient { get; }
|
||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
|
||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||
@@ -36,8 +36,13 @@ namespace Yi.Framework.SqlSugarCore
|
||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||
|
||||
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
|
||||
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
||||
{
|
||||
SqlSugarClient=sqlSugarClient;
|
||||
}
|
||||
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||
{
|
||||
LazyServiceProvider = lazyServiceProvider;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
{
|
||||
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
||||
{
|
||||
|
||||
private readonly string MasterTenantDbDefaultName = DbConnOptions.MasterTenantDbDefaultName;
|
||||
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
||||
|
||||
protected readonly IUnitOfWorkManager UnitOfWorkManager;
|
||||
@@ -47,17 +47,12 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
if (unitOfWork == null)
|
||||
{
|
||||
UnitOfWorkManager.Begin(true);
|
||||
unitOfWork=UnitOfWorkManager.Current;
|
||||
unitOfWork = UnitOfWorkManager.Current;
|
||||
//取消工作单元强制性
|
||||
//throw new AbpException("A DbContext can only be created inside a unit of work!");
|
||||
}
|
||||
//var sss= unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
//Console.WriteLine("反户的:"+sss.SqlSugarClient.ContextID);
|
||||
//return sss;
|
||||
]
|
||||
|
||||
var connectionStringName = "Default";
|
||||
var connectionString = await ResolveConnectionStringAsync(null);
|
||||
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
|
||||
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
|
||||
// var dbContextKey = $"{this.GetType().FullName}_{connectionString}";
|
||||
var dbContextKey = "Default";
|
||||
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
||||
@@ -77,12 +72,56 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
|
||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
|
||||
{
|
||||
|
||||
|
||||
var dbContext = await CreateDbContextAsync(unitOfWork);
|
||||
// Console.WriteLine("111111:" + dbContext.SqlSugarClient.ContextID);
|
||||
|
||||
//没有检测到使用多租户功能,默认使用默认库即可
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
connectionString = dbContext.Options.Url;
|
||||
connectionStringName = DbConnOptions.TenantDbDefaultName;
|
||||
}
|
||||
|
||||
//获取到DB之后,对多租户多库进行处理
|
||||
var changedDbContext = DatabaseChange(dbContext, connectionStringName, connectionString);
|
||||
|
||||
|
||||
return changedDbContext;
|
||||
}
|
||||
|
||||
protected virtual TDbContext DatabaseChange(TDbContext dbContext, string configId, string connectionString)
|
||||
{
|
||||
var dbOption = dbContext.Options;
|
||||
var db = dbContext.SqlSugarClient.AsTenant();
|
||||
//主库的Db切换,当操作的是租户表的时候
|
||||
if (CurrentTenant.Name == MasterTenantDbDefaultName)
|
||||
{
|
||||
//直接切换
|
||||
configId = MasterTenantDbDefaultName;
|
||||
var conStrOrNull= dbOption.GetDefaultMasterSaasMultiTenancy();
|
||||
Volo.Abp.Check.NotNull(conStrOrNull,"租户主库未找到");
|
||||
connectionString = conStrOrNull.Url;
|
||||
}
|
||||
|
||||
//租户Db的动态切换
|
||||
//二级缓存
|
||||
if (!db.IsAnyConnection(configId))
|
||||
{
|
||||
//添加一个db到当前上下文 (Add部分不线上下文不会共享)
|
||||
db.AddConnection(new ConnectionConfig()
|
||||
{
|
||||
DbType = dbOption.DbType!.Value,
|
||||
ConfigId = configId,//设置库的唯一标识
|
||||
IsAutoCloseConnection = true,
|
||||
ConnectionString = connectionString
|
||||
});
|
||||
}
|
||||
var currentDb = db.GetConnection(configId) as ISqlSugarClient;
|
||||
dbContext.SetSqlSugarClient(currentDb);
|
||||
return dbContext;
|
||||
}
|
||||
|
||||
|
||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
|
||||
{
|
||||
return unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
@@ -92,22 +131,22 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
}
|
||||
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
|
||||
{
|
||||
var transactionApiKey = $"Sqlsugar_Default"+Guid.NewGuid().ToString();
|
||||
var transactionApiKey = $"Sqlsugar_Default" + Guid.NewGuid().ToString();
|
||||
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
|
||||
//if (activeTransaction==null|| activeTransaction.Equals(default(SqlSugarTransactionApi)))
|
||||
//{
|
||||
|
||||
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
var transaction = new SqlSugarTransactionApi(
|
||||
dbContext
|
||||
);
|
||||
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
||||
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||
var transaction = new SqlSugarTransactionApi(
|
||||
dbContext
|
||||
);
|
||||
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
||||
|
||||
|
||||
//await Console.Out.WriteLineAsync("开始新的事务");
|
||||
// Console.WriteLine(dbContext.SqlSugarClient.ContextID);
|
||||
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
||||
return dbContext;
|
||||
//await Console.Out.WriteLineAsync("开始新的事务");
|
||||
// Console.WriteLine(dbContext.SqlSugarClient.ContextID);
|
||||
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
||||
return dbContext;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
@@ -134,5 +173,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
||||
|
||||
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ namespace Yi.Framework.SqlSugarCore
|
||||
var service = context.ServiceProvider;
|
||||
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||
|
||||
|
||||
//Todo:准备支持多租户种子数据及CodeFirst
|
||||
|
||||
if (options.EnabledCodeFirst)
|
||||
{
|
||||
CodeFirst(service);
|
||||
@@ -73,7 +76,10 @@ namespace Yi.Framework.SqlSugarCore
|
||||
List<Type> types = new List<Type>();
|
||||
foreach (var module in moduleContainer.Modules)
|
||||
{
|
||||
types.AddRange(module.Assembly.GetTypes().Where(x => x.GetCustomAttribute<SugarTable>() != null).Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
||||
types.AddRange(module.Assembly.GetTypes()
|
||||
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
|
||||
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
|
||||
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
||||
}
|
||||
if (types.Count > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user