feat: 完成多租户saas框架搭建
This commit is contained in:
@@ -94,7 +94,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.SqlSugarCore", "module\tenant-management\Yi.Framework.TenantManagement.SqlSugarCore\Yi.Framework.TenantManagement.SqlSugarCore.csproj", "{FA5BBAA1-08DC-472F-BB2C-5314E59D1556}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.SqlSugarCore", "module\tenant-management\Yi.Framework.TenantManagement.SqlSugarCore\Yi.Framework.TenantManagement.SqlSugarCore.csproj", "{FA5BBAA1-08DC-472F-BB2C-5314E59D1556}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.TenantManagement.Domain", "module\tenant-management\Yi.Framework.TenantManagement.Domain\Yi.Framework.TenantManagement.Domain.csproj", "{54D8E2BC-591C-4344-A58E-874D49C00B41}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.Domain", "module\tenant-management\Yi.Framework.TenantManagement.Domain\Yi.Framework.TenantManagement.Domain.csproj", "{54D8E2BC-591C-4344-A58E-874D49C00B41}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -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; }
|
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||||
ISqlSugarClient SqlSugarClient { get; }
|
ISqlSugarClient SqlSugarClient { get; }
|
||||||
|
DbConnOptions Options { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据库备份
|
/// 数据库备份
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void BackupDataBase();
|
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>
|
/// <summary>
|
||||||
/// SqlSugar 客户端
|
/// SqlSugar 客户端
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ISqlSugarClient SqlSugarClient { get; }
|
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||||
|
|
||||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||||
@@ -36,8 +36,13 @@ namespace Yi.Framework.SqlSugarCore
|
|||||||
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
|
||||||
|
|
||||||
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
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)
|
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
|
||||||
{
|
{
|
||||||
LazyServiceProvider = lazyServiceProvider;
|
LazyServiceProvider = lazyServiceProvider;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
|||||||
{
|
{
|
||||||
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
|
||||||
{
|
{
|
||||||
|
private readonly string MasterTenantDbDefaultName = DbConnOptions.MasterTenantDbDefaultName;
|
||||||
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
|
||||||
|
|
||||||
protected readonly IUnitOfWorkManager UnitOfWorkManager;
|
protected readonly IUnitOfWorkManager UnitOfWorkManager;
|
||||||
@@ -47,17 +47,12 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
|||||||
if (unitOfWork == null)
|
if (unitOfWork == null)
|
||||||
{
|
{
|
||||||
UnitOfWorkManager.Begin(true);
|
UnitOfWorkManager.Begin(true);
|
||||||
unitOfWork=UnitOfWorkManager.Current;
|
unitOfWork = UnitOfWorkManager.Current;
|
||||||
//取消工作单元强制性
|
//取消工作单元强制性
|
||||||
//throw new AbpException("A DbContext can only be created inside a unit of work!");
|
//throw new AbpException("A DbContext can only be created inside a unit of work!");
|
||||||
}
|
}
|
||||||
//var sss= unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
|
||||||
//Console.WriteLine("反户的:"+sss.SqlSugarClient.ContextID);
|
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
|
||||||
//return sss;
|
|
||||||
]
|
|
||||||
|
|
||||||
var connectionStringName = "Default";
|
|
||||||
var connectionString = await ResolveConnectionStringAsync(null);
|
|
||||||
// var dbContextKey = $"{this.GetType().FullName}_{connectionString}";
|
// var dbContextKey = $"{this.GetType().FullName}_{connectionString}";
|
||||||
var dbContextKey = "Default";
|
var dbContextKey = "Default";
|
||||||
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
|
||||||
@@ -79,10 +74,54 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
|||||||
{
|
{
|
||||||
|
|
||||||
var dbContext = await CreateDbContextAsync(unitOfWork);
|
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;
|
return dbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
|
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
return unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
return unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||||
@@ -92,22 +131,22 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
|||||||
}
|
}
|
||||||
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
|
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;
|
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
|
||||||
//if (activeTransaction==null|| activeTransaction.Equals(default(SqlSugarTransactionApi)))
|
//if (activeTransaction==null|| activeTransaction.Equals(default(SqlSugarTransactionApi)))
|
||||||
//{
|
//{
|
||||||
|
|
||||||
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
|
||||||
var transaction = new SqlSugarTransactionApi(
|
var transaction = new SqlSugarTransactionApi(
|
||||||
dbContext
|
dbContext
|
||||||
);
|
);
|
||||||
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
|
||||||
|
|
||||||
|
|
||||||
//await Console.Out.WriteLineAsync("开始新的事务");
|
//await Console.Out.WriteLineAsync("开始新的事务");
|
||||||
// Console.WriteLine(dbContext.SqlSugarClient.ContextID);
|
// Console.WriteLine(dbContext.SqlSugarClient.ContextID);
|
||||||
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
|
||||||
return dbContext;
|
return dbContext;
|
||||||
//}
|
//}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
@@ -134,5 +173,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
|
|||||||
|
|
||||||
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
|
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ namespace Yi.Framework.SqlSugarCore
|
|||||||
var service = context.ServiceProvider;
|
var service = context.ServiceProvider;
|
||||||
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||||
|
|
||||||
|
|
||||||
|
//Todo:准备支持多租户种子数据及CodeFirst
|
||||||
|
|
||||||
if (options.EnabledCodeFirst)
|
if (options.EnabledCodeFirst)
|
||||||
{
|
{
|
||||||
CodeFirst(service);
|
CodeFirst(service);
|
||||||
@@ -73,7 +76,10 @@ namespace Yi.Framework.SqlSugarCore
|
|||||||
List<Type> types = new List<Type>();
|
List<Type> types = new List<Type>();
|
||||||
foreach (var module in moduleContainer.Modules)
|
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)
|
if (types.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.DependencyInjection;
|
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.ObjectMapping;
|
|
||||||
|
|
||||||
namespace Yi.Framework.TenantManagement.Domain
|
namespace Yi.Framework.TenantManagement.Domain
|
||||||
{
|
{
|
||||||
@@ -96,21 +94,23 @@ namespace Yi.Framework.TenantManagement.Domain
|
|||||||
private ConnectionStrings? MaptoString(string tenantConnectionString)
|
private ConnectionStrings? MaptoString(string tenantConnectionString)
|
||||||
{
|
{
|
||||||
|
|
||||||
tenantConnectionString = tenantConnectionString.TrimEnd(';');
|
//tenantConnectionString = tenantConnectionString.TrimEnd(';');
|
||||||
var strSpiteds = tenantConnectionString.Split(";");
|
//var strSpiteds = tenantConnectionString.Split(";");
|
||||||
if (strSpiteds.Count() == 0)
|
//if (strSpiteds.Count() == 0)
|
||||||
{
|
//{
|
||||||
return null;
|
// return null;
|
||||||
|
|
||||||
}
|
//}
|
||||||
|
|
||||||
var connectionStrings = new ConnectionStrings();
|
var connectionStrings = new ConnectionStrings();
|
||||||
foreach (string strSpited in strSpiteds)
|
//foreach (string strSpited in strSpiteds)
|
||||||
{
|
//{
|
||||||
var key = strSpited.Split('=')[0];
|
// var key = strSpited.Split('=')[0];
|
||||||
var value = strSpited.Split('=')[1];
|
// var value = strSpited.Split('=')[1];
|
||||||
connectionStrings[key] = value;
|
// connectionStrings[key] = value;
|
||||||
}
|
//}
|
||||||
|
connectionStrings["test"] = tenantConnectionString;
|
||||||
|
|
||||||
return connectionStrings;
|
return connectionStrings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ using Volo.Abp.Auditing;
|
|||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
using Volo.Abp.TenantManagement;
|
using Volo.Abp.TenantManagement;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
namespace Yi.Framework.TenantManagement.Domain
|
namespace Yi.Framework.TenantManagement.Domain
|
||||||
{
|
{
|
||||||
[SugarTable("Tenant")]
|
[SugarTable("Tenant")]
|
||||||
|
[MasterTenant]
|
||||||
public class TenantAggregateRoot : FullAuditedAggregateRoot<Guid>, IHasEntityVersion
|
public class TenantAggregateRoot : FullAuditedAggregateRoot<Guid>, IHasEntityVersion
|
||||||
{
|
{
|
||||||
public TenantAggregateRoot()
|
public TenantAggregateRoot()
|
||||||
@@ -21,8 +23,8 @@ namespace Yi.Framework.TenantManagement.Domain
|
|||||||
SetName(name);
|
SetName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SugarColumn(IsPrimaryKey =true)]
|
[SugarColumn(IsPrimaryKey = true)]
|
||||||
public override Guid Id { get ; protected set; }
|
public override Guid Id { get; protected set; }
|
||||||
public virtual string Name { get; protected set; }
|
public virtual string Name { get; protected set; }
|
||||||
public int EntityVersion { get; protected set; }
|
public int EntityVersion { get; protected set; }
|
||||||
|
|
||||||
@@ -30,9 +32,9 @@ namespace Yi.Framework.TenantManagement.Domain
|
|||||||
|
|
||||||
public DbType DbType { get; protected set; }
|
public DbType DbType { get; protected set; }
|
||||||
|
|
||||||
[SugarColumn(IsIgnore=true)]
|
[SugarColumn(IsIgnore = true)]
|
||||||
public override ExtraPropertyDictionary ExtraProperties { get => base.ExtraProperties; protected set => base.ExtraProperties = value; }
|
public override ExtraPropertyDictionary ExtraProperties { get => base.ExtraProperties; protected set => base.ExtraProperties = value; }
|
||||||
public virtual void SetConnectionString(DbType dbType,string connectionString)
|
public virtual void SetConnectionString(DbType dbType, string connectionString)
|
||||||
{
|
{
|
||||||
DbType = dbType;
|
DbType = dbType;
|
||||||
TenantConnectionString = connectionString;
|
TenantConnectionString = connectionString;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
namespace Yi.Framework.TenantManagement.Domain
|
||||||
|
{
|
||||||
|
public static class TenantManagementExtensions
|
||||||
|
{
|
||||||
|
public static IDisposable ChangeMaster(this ICurrentTenant currentTenant)
|
||||||
|
{
|
||||||
|
return currentTenant.Change(null, DbConnOptions.MasterTenantDbDefaultName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0" />
|
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0" />
|
||||||
<PackageReference Include="Volo.Abp.TenantManagement.Domain.Shared" Version="8.0.0" />
|
<PackageReference Include="Volo.Abp.TenantManagement.Domain.Shared" Version="8.0.0" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Yi.Framework.TenantManagement.Domain.Shared
|
||||||
|
{
|
||||||
|
public class TenantConst
|
||||||
|
{
|
||||||
|
public static string TenantDbDefaultName = "Master";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Volo.Abp.TenantManagement.Domain.Shared" Version="8.0.0" />
|
||||||
|
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Volo.Abp.Modularity;
|
||||||
|
using Volo.Abp.TenantManagement;
|
||||||
|
|
||||||
|
namespace YiFrameworkTenantManagementDomain.Shared
|
||||||
|
{
|
||||||
|
[DependsOn(typeof(AbpTenantManagementDomainSharedModule))]
|
||||||
|
public class YiFrameworkTenantManagementDomainSharedModule : AbpModule
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Volo.Abp.Data;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Volo.Abp.MultiTenancy;
|
|
||||||
|
|
||||||
[Dependency(ReplaceServices = true)]
|
|
||||||
public class MultiTenantConnectionStringResolver2 : DefaultConnectionStringResolver
|
|
||||||
{
|
|
||||||
private readonly ICurrentTenant _currentTenant;
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public MultiTenantConnectionStringResolver2(
|
|
||||||
IOptionsMonitor<AbpDbConnectionOptions> options,
|
|
||||||
ICurrentTenant currentTenant,
|
|
||||||
IServiceProvider serviceProvider)
|
|
||||||
: base(options)
|
|
||||||
{
|
|
||||||
_currentTenant = currentTenant;
|
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<string> ResolveAsync(string? connectionStringName = null)
|
|
||||||
{
|
|
||||||
if (_currentTenant.Id == null)
|
|
||||||
{
|
|
||||||
//No current tenant, fallback to default logic
|
|
||||||
return await base.ResolveAsync(connectionStringName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tenant = await FindTenantConfigurationAsync(_currentTenant.Id.Value);
|
|
||||||
|
|
||||||
if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
//Tenant has not defined any connection string, fallback to default logic
|
|
||||||
return await base.ResolveAsync(connectionStringName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default;
|
|
||||||
|
|
||||||
//Requesting default connection string...
|
|
||||||
if (connectionStringName == null ||
|
|
||||||
connectionStringName == ConnectionStrings.DefaultConnectionStringName)
|
|
||||||
{
|
|
||||||
//Return tenant's default or global default
|
|
||||||
return !tenantDefaultConnectionString.IsNullOrWhiteSpace()
|
|
||||||
? tenantDefaultConnectionString!
|
|
||||||
: Options.ConnectionStrings.Default!;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Requesting specific connection string...
|
|
||||||
var connString = tenant.ConnectionStrings?.GetOrDefault(connectionStringName);
|
|
||||||
if (!connString.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
//Found for the tenant
|
|
||||||
return connString!;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fallback to the mapped database for the specific connection string
|
|
||||||
var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName);
|
|
||||||
if (database != null && database.IsUsedByTenants)
|
|
||||||
{
|
|
||||||
connString = tenant.ConnectionStrings?.GetOrDefault(database.DatabaseName);
|
|
||||||
if (!connString.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
//Found for the tenant
|
|
||||||
return connString!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fallback to tenant's default connection string if available
|
|
||||||
if (!tenantDefaultConnectionString.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return tenantDefaultConnectionString!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await base.ResolveAsync(connectionStringName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Use ResolveAsync method.")]
|
|
||||||
public override string Resolve(string? connectionStringName = null)
|
|
||||||
{
|
|
||||||
if (_currentTenant.Id == null)
|
|
||||||
{
|
|
||||||
//No current tenant, fallback to default logic
|
|
||||||
return base.Resolve(connectionStringName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tenant = FindTenantConfiguration(_currentTenant.Id.Value);
|
|
||||||
|
|
||||||
if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
//Tenant has not defined any connection string, fallback to default logic
|
|
||||||
return base.Resolve(connectionStringName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default;
|
|
||||||
|
|
||||||
//Requesting default connection string...
|
|
||||||
if (connectionStringName == null ||
|
|
||||||
connectionStringName == ConnectionStrings.DefaultConnectionStringName)
|
|
||||||
{
|
|
||||||
//Return tenant's default or global default
|
|
||||||
return !tenantDefaultConnectionString.IsNullOrWhiteSpace()
|
|
||||||
? tenantDefaultConnectionString!
|
|
||||||
: Options.ConnectionStrings.Default!;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Requesting specific connection string...
|
|
||||||
var connString = tenant.ConnectionStrings?.GetOrDefault(connectionStringName);
|
|
||||||
if (!connString.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
//Found for the tenant
|
|
||||||
return connString!;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fallback to tenant's default connection string if available
|
|
||||||
if (!tenantDefaultConnectionString.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return tenantDefaultConnectionString!;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Try to find the specific connection string for given name
|
|
||||||
var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName);
|
|
||||||
if (!connStringInOptions.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return connStringInOptions!;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fallback to the global default connection string
|
|
||||||
var defaultConnectionString = Options.ConnectionStrings.Default;
|
|
||||||
if (!defaultConnectionString.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return defaultConnectionString!;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AbpException("No connection string defined!");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual async Task<TenantConfiguration?> FindTenantConfigurationAsync(Guid tenantId)
|
|
||||||
{
|
|
||||||
using (var serviceScope = _serviceProvider.CreateScope())
|
|
||||||
{
|
|
||||||
var tenantStore = serviceScope
|
|
||||||
.ServiceProvider
|
|
||||||
.GetRequiredService<ITenantStore>();
|
|
||||||
|
|
||||||
return await tenantStore.FindAsync(tenantId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Use FindTenantConfigurationAsync method.")]
|
|
||||||
protected virtual TenantConfiguration? FindTenantConfiguration(Guid tenantId)
|
|
||||||
{
|
|
||||||
using (var serviceScope = _serviceProvider.CreateScope())
|
|
||||||
{
|
|
||||||
var tenantStore = serviceScope
|
|
||||||
.ServiceProvider
|
|
||||||
.GetRequiredService<ITenantStore>();
|
|
||||||
|
|
||||||
return tenantStore.Find(tenantId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Volo.Abp.Data;
|
|
||||||
using Volo.Abp.MultiTenancy;
|
|
||||||
using Yi.Abp.Web;
|
using Yi.Abp.Web;
|
||||||
|
|
||||||
//创建日志,可使用{SourceContext}记录
|
//创建日志,可使用{SourceContext}记录
|
||||||
@@ -26,7 +23,6 @@ try
|
|||||||
builder.Host.UseAutofac();
|
builder.Host.UseAutofac();
|
||||||
builder.Host.UseSerilog();
|
builder.Host.UseSerilog();
|
||||||
await builder.Services.AddApplicationAsync<YiAbpWebModule>();
|
await builder.Services.AddApplicationAsync<YiAbpWebModule>();
|
||||||
builder.Services.Replace(new ServiceDescriptor(typeof(IConnectionStringResolver), typeof(MultiTenantConnectionStringResolver2), ServiceLifetime.Transient));
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
await app.InitializeApplicationAsync();
|
await app.InitializeApplicationAsync();
|
||||||
await app.RunAsync();
|
await app.RunAsync();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
@@ -14,6 +13,7 @@ using Volo.Abp.AspNetCore.Serilog;
|
|||||||
using Volo.Abp.Auditing;
|
using Volo.Abp.Auditing;
|
||||||
using Volo.Abp.Autofac;
|
using Volo.Abp.Autofac;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.Swashbuckle;
|
using Volo.Abp.Swashbuckle;
|
||||||
using Yi.Abp.Application;
|
using Yi.Abp.Application;
|
||||||
using Yi.Abp.SqlsugarCore;
|
using Yi.Abp.SqlsugarCore;
|
||||||
@@ -107,6 +107,12 @@ namespace Yi.Abp.Web
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//配置多租户
|
||||||
|
Configure<AbpTenantResolveOptions>(options =>
|
||||||
|
{
|
||||||
|
//基于cookie jwt不好用,有坑
|
||||||
|
options.TenantResolvers.RemoveAll(x => x.Name == CookieTenantResolveContributor.ContributorName);
|
||||||
|
});
|
||||||
|
|
||||||
//jwt鉴权
|
//jwt鉴权
|
||||||
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
|
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
|
||||||
|
|||||||
Reference in New Issue
Block a user