refactor: 重构多租户模块,优化上线
This commit is contained in:
@@ -51,66 +51,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
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 MasterTenantName = "Master";
|
|
||||||
public static string DefaultTenantName = "Default";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取默认数据库
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public SaasMultiTenancyOptions GetDefaultSaasMultiTenancy()
|
|
||||||
{
|
|
||||||
return new SaasMultiTenancyOptions { Name = DefaultTenantName, Url = Url };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取主数据库
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public SaasMultiTenancyOptions? GetMasterSaasMultiTenancy()
|
|
||||||
{
|
|
||||||
if (EnabledSaasMultiTenancy == false)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(MasterSaasMultiTenancyUrl))
|
|
||||||
{
|
|
||||||
|
|
||||||
return new SaasMultiTenancyOptions { Name = MasterTenantName, Url = Url };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new SaasMultiTenancyOptions()
|
|
||||||
{
|
|
||||||
Name = MasterTenantName,
|
|
||||||
Url = MasterSaasMultiTenancyUrl
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaasMultiTenancyOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 租户名称标识
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 连接Url
|
|
||||||
/// </summary>
|
|
||||||
public string Url { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||||||
namespace Yi.Framework.SqlSugarCore.Abstractions
|
namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class MasterTenantAttribute : Attribute
|
public class DefaultTenantTableAttribute : Attribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,6 @@ namespace Yi.Framework.SqlSugarCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ISqlSugarClient SqlSugarClient { get; private set; }
|
public ISqlSugarClient SqlSugarClient { get; private set; }
|
||||||
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
|
||||||
private readonly string MasterTenantDbDefaultName = DbConnOptions.MasterTenantName;
|
|
||||||
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
private IAbpLazyServiceProvider LazyServiceProvider { get; }
|
||||||
|
|
||||||
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
|
||||||
@@ -38,6 +37,7 @@ namespace Yi.Framework.SqlSugarCore
|
|||||||
|
|
||||||
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
|
||||||
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
|
||||||
|
public AbpDbConnectionOptions ConnectionOptions=> LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbConnectionOptions>>().Value;
|
||||||
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
|
||||||
|
|
||||||
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
|
||||||
@@ -69,21 +69,23 @@ namespace Yi.Framework.SqlSugarCore
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual string GetCurrentConnectionString()
|
protected virtual string GetCurrentConnectionString()
|
||||||
{
|
{
|
||||||
|
var defautlUrl = Options.Url ?? ConnectionOptions.GetConnectionStringOrNull(ConnectionStrings.DefaultConnectionStringName);
|
||||||
|
//如果未开启多租户,返回db url 或者 默认连接字符串
|
||||||
|
if (!Options.EnabledSaasMultiTenancy)
|
||||||
|
{
|
||||||
|
return defautlUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
//开启了多租户
|
||||||
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
|
||||||
var connectionString = connectionStringResolver.ResolveAsync().Result;
|
var connectionString = connectionStringResolver.ResolveAsync().Result;
|
||||||
|
|
||||||
|
|
||||||
//没有检测到使用多租户功能,默认使用默认库即可
|
//没有检测到使用多租户功能,默认使用默认库即可
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
{
|
{
|
||||||
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
|
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
|
||||||
connectionString = Options.Url;
|
connectionString = defautlUrl;
|
||||||
}
|
|
||||||
//如果当前租户是主库,单独使用主要库
|
|
||||||
if (CurrentTenant.Name == MasterTenantDbDefaultName)
|
|
||||||
{
|
|
||||||
var conStrOrNull = Options.GetMasterSaasMultiTenancy();
|
|
||||||
Volo.Abp.Check.NotNull(conStrOrNull, "租户主库Master未找到");
|
|
||||||
connectionString = conStrOrNull.Url;
|
|
||||||
}
|
}
|
||||||
return connectionString!;
|
return connectionString!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Volo.Abp;
|
|||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
using Volo.Abp.Uow;
|
||||||
using Yi.Framework.Ddd.Application;
|
using Yi.Framework.Ddd.Application;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
using Yi.Framework.TenantManagement.Application.Contracts;
|
using Yi.Framework.TenantManagement.Application.Contracts;
|
||||||
@@ -113,6 +114,7 @@ namespace Yi.Framework.TenantManagement.Application
|
|||||||
[HttpPut("tenant/init/{id}")]
|
[HttpPut("tenant/init/{id}")]
|
||||||
public async Task InitAsync([FromRoute] Guid id)
|
public async Task InitAsync([FromRoute] Guid id)
|
||||||
{
|
{
|
||||||
|
await CurrentUnitOfWork.SaveChangesAsync();
|
||||||
using (CurrentTenant.Change(id))
|
using (CurrentTenant.Change(id))
|
||||||
{
|
{
|
||||||
await CodeFirst(this.LazyServiceProvider);
|
await CodeFirst(this.LazyServiceProvider);
|
||||||
@@ -124,10 +126,20 @@ namespace Yi.Framework.TenantManagement.Application
|
|||||||
private async Task CodeFirst(IServiceProvider service)
|
private async Task CodeFirst(IServiceProvider service)
|
||||||
{
|
{
|
||||||
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
var moduleContainer = service.GetRequiredService<IModuleContainer>();
|
||||||
var db = await _repository.GetDbContextAsync();
|
|
||||||
|
|
||||||
//尝试创建数据库
|
//没有数据库,不能创工作单元,创建库,先关闭
|
||||||
db.DbMaintenance.CreateDatabase();
|
ISqlSugarClient db = null;
|
||||||
|
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
|
||||||
|
{
|
||||||
|
db = await _repository.GetDbContextAsync();
|
||||||
|
//尝试创建数据库
|
||||||
|
db.DbMaintenance.CreateDatabase();
|
||||||
|
await uow.CompleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<Type> types = new List<Type>();
|
List<Type> types = new List<Type>();
|
||||||
foreach (var module in moduleContainer.Modules)
|
foreach (var module in moduleContainer.Modules)
|
||||||
@@ -135,6 +147,7 @@ namespace Yi.Framework.TenantManagement.Application
|
|||||||
types.AddRange(module.Assembly.GetTypes()
|
types.AddRange(module.Assembly.GetTypes()
|
||||||
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
|
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
|
||||||
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
|
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
|
||||||
|
.Where(x=>x.GetCustomAttribute<DefaultTenantTableAttribute>() is null)
|
||||||
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
|
||||||
}
|
}
|
||||||
if (types.Count > 0)
|
if (types.Count > 0)
|
||||||
|
|||||||
@@ -93,24 +93,8 @@ namespace Yi.Framework.TenantManagement.Domain
|
|||||||
|
|
||||||
private ConnectionStrings? MaptoString(string tenantConnectionString)
|
private ConnectionStrings? MaptoString(string tenantConnectionString)
|
||||||
{
|
{
|
||||||
|
|
||||||
//tenantConnectionString = tenantConnectionString.TrimEnd(';');
|
|
||||||
//var strSpiteds = tenantConnectionString.Split(";");
|
|
||||||
//if (strSpiteds.Count() == 0)
|
|
||||||
//{
|
|
||||||
// return null;
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
var connectionStrings = new ConnectionStrings();
|
var connectionStrings = new ConnectionStrings();
|
||||||
//foreach (string strSpited in strSpiteds)
|
connectionStrings[ConnectionStrings.DefaultConnectionStringName] = tenantConnectionString;
|
||||||
//{
|
|
||||||
// var key = strSpited.Split('=')[0];
|
|
||||||
// var value = strSpited.Split('=')[1];
|
|
||||||
// connectionStrings[key] = value;
|
|
||||||
//}
|
|
||||||
connectionStrings["test"] = tenantConnectionString;
|
|
||||||
|
|
||||||
return connectionStrings;
|
return connectionStrings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
|||||||
namespace Yi.Framework.TenantManagement.Domain
|
namespace Yi.Framework.TenantManagement.Domain
|
||||||
{
|
{
|
||||||
[SugarTable("YiTenant")]
|
[SugarTable("YiTenant")]
|
||||||
[MasterTenant]
|
[DefaultTenantTable]
|
||||||
public class TenantAggregateRoot : FullAuditedAggregateRoot<Guid>, IHasEntityVersion
|
public class TenantAggregateRoot : FullAuditedAggregateRoot<Guid>, IHasEntityVersion
|
||||||
{
|
{
|
||||||
public TenantAggregateRoot()
|
public TenantAggregateRoot()
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
namespace Yi.Framework.TenantManagement.Domain
|
namespace Yi.Framework.TenantManagement.Domain
|
||||||
{
|
{
|
||||||
public static class TenantManagementExtensions
|
public static class TenantManagementExtensions
|
||||||
{
|
{
|
||||||
public static IDisposable ChangeMaster(this ICurrentTenant currentTenant)
|
|
||||||
{
|
|
||||||
return currentTenant.Change(null, DbConnOptions.MasterTenantName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IDisposable ChangeDefalut(this ICurrentTenant currentTenant)
|
public static IDisposable ChangeDefalut(this ICurrentTenant currentTenant)
|
||||||
{
|
{
|
||||||
return currentTenant.Change(null, DbConnOptions.DefaultTenantName);
|
return currentTenant.Change(null, ConnectionStrings.DefaultConnectionStringName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,14 +44,16 @@ public class YiMultiTenantConnectionStringResolver : DefaultConnectionStringReso
|
|||||||
var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default;
|
var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default;
|
||||||
|
|
||||||
//Requesting default connection string...
|
//Requesting default connection string...
|
||||||
//if (connectionStringName == null ||
|
if (connectionStringName == null ||
|
||||||
// connectionStringName == ConnectionStrings.DefaultConnectionStringName)
|
connectionStringName == ConnectionStrings.DefaultConnectionStringName)
|
||||||
//{
|
{
|
||||||
// //Return tenant's default or global default
|
//Return tenant's default or global default
|
||||||
// return !tenantDefaultConnectionString.IsNullOrWhiteSpace()
|
return !tenantDefaultConnectionString.IsNullOrWhiteSpace()
|
||||||
// ? tenantDefaultConnectionString!
|
? tenantDefaultConnectionString!
|
||||||
// : Options.ConnectionStrings.Default!;
|
: Options.ConnectionStrings.Default!;
|
||||||
//}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Requesting specific connection string...
|
//Requesting specific connection string...
|
||||||
var connString = tenant.ConnectionStrings?.FirstOrDefault().Value;
|
var connString = tenant.ConnectionStrings?.FirstOrDefault().Value;
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Volo.Abp.MultiTenancy.Localization;
|
||||||
|
|
||||||
|
namespace Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
|
[Dependency(ReplaceServices =true)]
|
||||||
|
public class YiTenantConfigurationProvider : ITenantConfigurationProvider, ITransientDependency
|
||||||
|
{
|
||||||
|
protected virtual ITenantResolver TenantResolver { get; }
|
||||||
|
protected virtual ITenantStore TenantStore { get; }
|
||||||
|
protected virtual ITenantResolveResultAccessor TenantResolveResultAccessor { get; }
|
||||||
|
protected virtual IStringLocalizer<AbpMultiTenancyResource> StringLocalizer { get; }
|
||||||
|
|
||||||
|
public YiTenantConfigurationProvider(
|
||||||
|
ITenantResolver tenantResolver,
|
||||||
|
ITenantStore tenantStore,
|
||||||
|
ITenantResolveResultAccessor tenantResolveResultAccessor,
|
||||||
|
IStringLocalizer<AbpMultiTenancyResource> stringLocalizer)
|
||||||
|
{
|
||||||
|
TenantResolver = tenantResolver;
|
||||||
|
TenantStore = tenantStore;
|
||||||
|
TenantResolveResultAccessor = tenantResolveResultAccessor;
|
||||||
|
StringLocalizer = stringLocalizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<TenantConfiguration?> GetAsync(bool saveResolveResult = false)
|
||||||
|
{
|
||||||
|
var resolveResult = await TenantResolver.ResolveTenantIdOrNameAsync();
|
||||||
|
|
||||||
|
if (saveResolveResult)
|
||||||
|
{
|
||||||
|
TenantResolveResultAccessor.Result = resolveResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
TenantConfiguration? tenant = null;
|
||||||
|
if (resolveResult.TenantIdOrName != null)
|
||||||
|
{
|
||||||
|
tenant = await FindTenantAsync(resolveResult.TenantIdOrName);
|
||||||
|
|
||||||
|
if (tenant == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException(
|
||||||
|
code: "Volo.AbpIo.MultiTenancy:010001",
|
||||||
|
message: StringLocalizer["TenantNotFoundMessage"],
|
||||||
|
details: StringLocalizer["TenantNotFoundDetails", resolveResult.TenantIdOrName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tenant.IsActive)
|
||||||
|
{
|
||||||
|
throw new BusinessException(
|
||||||
|
code: "Volo.AbpIo.MultiTenancy:010002",
|
||||||
|
message: StringLocalizer["TenantNotActiveMessage"],
|
||||||
|
details: StringLocalizer["TenantNotActiveDetails", resolveResult.TenantIdOrName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async Task<TenantConfiguration?> FindTenantAsync(string tenantIdOrName)
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(tenantIdOrName, out var parsedTenantId))
|
||||||
|
{
|
||||||
|
return await TenantStore.FindAsync(parsedTenantId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return await TenantStore.FindAsync(tenantIdOrName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Yi.Abp.Net8/src/Yi.Abp.Web/TestOptions.cs
Normal file
30
Yi.Abp.Net8/src/Yi.Abp.Web/TestOptions.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Volo.Abp.Data;
|
||||||
|
|
||||||
|
namespace Yi.Abp.Web
|
||||||
|
{
|
||||||
|
public class TestOptions
|
||||||
|
{
|
||||||
|
public ConnectionStrings2 ConnectionStrings { get; set; }=new ConnectionStrings2();
|
||||||
|
|
||||||
|
public AbpDatabaseInfoDictionary2 Databases { get; set; }=new AbpDatabaseInfoDictionary2();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConnectionStrings2 : Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public class AbpDatabaseInfoDictionary2 : Dictionary<string, AbpDatabaseInfo2>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AbpDatabaseInfo2
|
||||||
|
{
|
||||||
|
internal AbpDatabaseInfo2(string databaseName)
|
||||||
|
{
|
||||||
|
DatabaseName = databaseName;
|
||||||
|
MappedConnections = new HashSet<string>();
|
||||||
|
}
|
||||||
|
public string DatabaseName { get; }
|
||||||
|
public HashSet<string> MappedConnections { get; }
|
||||||
|
public bool IsUsedByTenants { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,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.Caching;
|
using Volo.Abp.Caching;
|
||||||
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.Swashbuckle;
|
using Volo.Abp.Swashbuckle;
|
||||||
using Yi.Abp.Application;
|
using Yi.Abp.Application;
|
||||||
@@ -249,6 +250,8 @@ namespace Yi.Abp.Web
|
|||||||
|
|
||||||
//授权
|
//授权
|
||||||
context.Services.AddAuthorization();
|
context.Services.AddAuthorization();
|
||||||
|
|
||||||
|
Configure<TestOptions>(configuration);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +260,10 @@ namespace Yi.Abp.Web
|
|||||||
{
|
{
|
||||||
var service = context.ServiceProvider;
|
var service = context.ServiceProvider;
|
||||||
|
|
||||||
|
var sss=service.GetRequiredService<Microsoft.Extensions.Options.IOptions<AbpDbConnectionOptions>>().Value;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var env = context.GetEnvironment();
|
var env = context.GetEnvironment();
|
||||||
var app = context.GetApplicationBuilder();
|
var app = context.GetApplicationBuilder();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user