diff --git a/Yi.Abp.Net8/Yi.Abp.sln b/Yi.Abp.Net8/Yi.Abp.sln index 38e83ef6..9dcccd99 100644 --- a/Yi.Abp.Net8/Yi.Abp.sln +++ b/Yi.Abp.Net8/Yi.Abp.sln @@ -88,7 +88,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}" +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}" +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}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -224,6 +230,14 @@ Global {68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.Build.0 = Release|Any CPU + {FA5BBAA1-08DC-472F-BB2C-5314E59D1556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA5BBAA1-08DC-472F-BB2C-5314E59D1556}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA5BBAA1-08DC-472F-BB2C-5314E59D1556}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA5BBAA1-08DC-472F-BB2C-5314E59D1556}.Release|Any CPU.Build.0 = Release|Any CPU + {54D8E2BC-591C-4344-A58E-874D49C00B41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54D8E2BC-591C-4344-A58E-874D49C00B41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54D8E2BC-591C-4344-A58E-874D49C00B41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54D8E2BC-591C-4344-A58E-874D49C00B41}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -264,6 +278,9 @@ Global {320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B} {70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7} = {01300F0F-686E-47B3-821D-12424177867B} {68627BC2-F049-4C69-AD17-81DF9478E8CE} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B} + {499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853} + {FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16} + {54D8E2BC-591C-4344-A58E-874D49C00B41} = {499A8C71-7892-42D0-A77E-48756E1EFF16} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18} diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs index 9ce7d0d5..6d83cd70 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs @@ -54,10 +54,10 @@ namespace Yi.Framework.SqlSugarCore.Uow //var sss= unitOfWork.ServiceProvider.GetRequiredService(); //Console.WriteLine("反户的:"+sss.SqlSugarClient.ContextID); //return sss; - + ] var connectionStringName = "Default"; - var connectionString = await ResolveConnectionStringAsync(connectionStringName); + var connectionString = await ResolveConnectionStringAsync(null); // var dbContextKey = $"{this.GetType().FullName}_{connectionString}"; var dbContextKey = "Default"; var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey); diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/ISqlSugarTenantRepository.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/ISqlSugarTenantRepository.cs new file mode 100644 index 00000000..0fb8a8d6 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/ISqlSugarTenantRepository.cs @@ -0,0 +1,20 @@ +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.TenantManagement.Domain +{ + public interface ISqlSugarTenantRepository : ISqlSugarRepository + { + Task FindByNameAsync(string name, bool includeDetails = true); + + Task> GetListAsync(string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false); + + + Task GetCountAsync( + string filter = null); + + } +} diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs new file mode 100644 index 00000000..c02bcd44 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs @@ -0,0 +1,122 @@ +using JetBrains.Annotations; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectMapping; + +namespace Yi.Framework.TenantManagement.Domain +{ + public class SqlSugarTenantStore : ITenantStore + { + private ISqlSugarTenantRepository TenantRepository { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IDistributedCache Cache { get; } + public SqlSugarTenantStore(ISqlSugarTenantRepository repository, + IDistributedCache cache, + ICurrentTenant currentTenant) + { TenantRepository = repository; + Cache=cache; + CurrentTenant=currentTenant; + } + + public TenantConfiguration? Find(string name) + { + throw new NotImplementedException("请使用异步方法"); + } + + public TenantConfiguration? Find(Guid id) + { + throw new NotImplementedException("请使用异步方法"); + } + + public async Task FindAsync(string name) + { + return (await GetCacheItemAsync(null, name)).Value; + } + + public async Task FindAsync(Guid id) + { + return (await GetCacheItemAsync(id, null)).Value; + } + + + + + + protected virtual async Task GetCacheItemAsync(Guid? id, string name) + { + var cacheKey = CalculateCacheKey(id, name); + + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); + if (cacheItem != null) + { + return cacheItem; + } + + if (id.HasValue) + { + using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities! + { + var tenant = await TenantRepository.FindAsync(id.Value); + return await SetCacheAsync(cacheKey, tenant); + } + } + + if (!name.IsNullOrWhiteSpace()) + { + using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities! + { + var tenant = await TenantRepository.FindByNameAsync(name); + return await SetCacheAsync(cacheKey, tenant); + } + } + throw new AbpException("Both id and name can't be invalid."); + } + + protected virtual async Task SetCacheAsync(string cacheKey, [CanBeNull] TenantAggregateRoot tenant) + { + var tenantConfiguration = tenant != null ? MapToConfiguration(tenant) : null; + var cacheItem = new TenantCacheItem(tenantConfiguration); + await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); + return cacheItem; + } + + private TenantConfiguration MapToConfiguration(TenantAggregateRoot tenantAggregateRoot) + { + var tenantConfiguration = new TenantConfiguration(); + tenantConfiguration.Id = tenantAggregateRoot.Id; + tenantConfiguration.Name = tenantAggregateRoot.Name; + tenantConfiguration.ConnectionStrings = MaptoString(tenantAggregateRoot.TenantConnectionString); + tenantConfiguration.IsActive = true; + return tenantConfiguration; + } + + private ConnectionStrings? MaptoString(string tenantConnectionString) + { + + tenantConnectionString = tenantConnectionString.TrimEnd(';'); + var strSpiteds = tenantConnectionString.Split(";"); + if (strSpiteds.Count() == 0) + { + return null; + + } + + var connectionStrings = new ConnectionStrings(); + foreach (string strSpited in strSpiteds) + { + var key = strSpited.Split('=')[0]; + var value = strSpited.Split('=')[1]; + connectionStrings[key] = value; + } + return connectionStrings; + } + + protected virtual string CalculateCacheKey(Guid? id, string name) + { + return TenantCacheItem.CalculateCacheKey(id, name); + } + } +} diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/TenantAggregateRoot.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/TenantAggregateRoot.cs new file mode 100644 index 00000000..e261b155 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/TenantAggregateRoot.cs @@ -0,0 +1,47 @@ +using JetBrains.Annotations; +using SqlSugar; +using Volo.Abp; +using Volo.Abp.Auditing; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.TenantManagement; + +namespace Yi.Framework.TenantManagement.Domain +{ + [SugarTable("Tenant")] + public class TenantAggregateRoot : FullAuditedAggregateRoot, IHasEntityVersion + { + public TenantAggregateRoot() + { + + } + protected internal TenantAggregateRoot(Guid id, [NotNull] string name) + : base(id) + { + SetName(name); + } + + [SugarColumn(IsPrimaryKey =true)] + public override Guid Id { get ; protected set; } + public virtual string Name { get; protected set; } + public int EntityVersion { get; protected set; } + + public string TenantConnectionString { get; protected set; } + + public DbType DbType { get; protected set; } + + [SugarColumn(IsIgnore=true)] + public override ExtraPropertyDictionary ExtraProperties { get => base.ExtraProperties; protected set => base.ExtraProperties = value; } + public virtual void SetConnectionString(DbType dbType,string connectionString) + { + DbType = dbType; + TenantConnectionString = connectionString; + } + + protected internal virtual void SetName([NotNull] string name) + { + Name = Volo.Abp.Check.NotNullOrWhiteSpace(name, nameof(name), TenantConsts.MaxNameLength); + } + + } +} diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/TenantCacheItem.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/TenantCacheItem.cs new file mode 100644 index 00000000..01791a4d --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/TenantCacheItem.cs @@ -0,0 +1,35 @@ +using Volo.Abp; +using Volo.Abp.MultiTenancy; + +namespace Yi.Framework.TenantManagement.Domain; + +[Serializable] +[IgnoreMultiTenancy] +public class TenantCacheItem +{ + private const string CacheKeyFormat = "i:{0},n:{1}"; + + public TenantConfiguration Value { get; set; } + + public TenantCacheItem() + { + + } + + public TenantCacheItem(TenantConfiguration value) + { + Value = value; + } + + public static string CalculateCacheKey(Guid? id, string name) + { + if (id == null && name.IsNullOrWhiteSpace()) + { + throw new AbpException("Both id and name can't be invalid."); + } + + return string.Format(CacheKeyFormat, + id?.ToString() ?? "null", + name.IsNullOrWhiteSpace() ? "null" : name); + } +} diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/Yi.Framework.TenantManagement.Domain.csproj b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/Yi.Framework.TenantManagement.Domain.csproj new file mode 100644 index 00000000..934dbab0 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/Yi.Framework.TenantManagement.Domain.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/YiFrameworkTenantManagementDomainModule.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/YiFrameworkTenantManagementDomainModule.cs new file mode 100644 index 00000000..6912b097 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Domain/YiFrameworkTenantManagementDomainModule.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Domain; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TenantManagement; + +namespace Yi.Framework.TenantManagement.Domain +{ + [DependsOn(typeof(AbpDddDomainModule), + typeof(AbpTenantManagementDomainSharedModule))] + public class YiFrameworkTenantManagementDomainModule : AbpModule + { + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var services = context.Services; + services.Replace(new ServiceDescriptor(typeof(ITenantStore),typeof(SqlSugarTenantStore), ServiceLifetime.Transient)); + } + } +} diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/SqlSugarTenantRepository.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/SqlSugarTenantRepository.cs new file mode 100644 index 00000000..7d20cae0 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/SqlSugarTenantRepository.cs @@ -0,0 +1,33 @@ +using Volo.Abp.DependencyInjection; +using Yi.Framework.SqlSugarCore.Abstractions; +using Yi.Framework.SqlSugarCore.Repositories; +using Yi.Framework.TenantManagement.Domain; + +namespace Yi.Framework.TenantManagement.SqlSugarCore +{ + public class SqlSugarTenantRepository : SqlSugarRepository, ISqlSugarTenantRepository,ITransientDependency + { + public SqlSugarTenantRepository(ISugarDbContextProvider sugarDbContextProvider) : base(sugarDbContextProvider) + { + } + + public async Task FindByNameAsync(string name, bool includeDetails = true) + { + return await _DbQueryable.FirstAsync(x => x.Name == name); + } + + public async Task GetCountAsync(string filter = null) + { + return await _DbQueryable.WhereIF(!string.IsNullOrEmpty(filter),x=>x.Name.Contains(filter)) .CountAsync(); + } + + public async Task> GetListAsync(string sorting = null, int maxResultCount = int.MaxValue, int skipCount = 0, string filter = null, bool includeDetails = false) + { + + + return await _DbQueryable.WhereIF(!string.IsNullOrEmpty(filter), x => x.Name.Contains(filter)) + .OrderByIF(!string.IsNullOrEmpty(sorting), sorting) + .ToPageListAsync(skipCount, maxResultCount); + } + } +} diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/Yi.Framework.TenantManagement.SqlSugarCore.csproj b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/Yi.Framework.TenantManagement.SqlSugarCore.csproj new file mode 100644 index 00000000..afb79a66 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/Yi.Framework.TenantManagement.SqlSugarCore.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/YiFrameworkTenantManagementSqlSugarCoreModule.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/YiFrameworkTenantManagementSqlSugarCoreModule.cs new file mode 100644 index 00000000..3d7bb0a4 --- /dev/null +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.SqlSugarCore/YiFrameworkTenantManagementSqlSugarCoreModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; +using Yi.Framework.TenantManagement.Domain; + +namespace Yi.Framework.TenantManagement.SqlSugarCore +{ + [DependsOn(typeof(YiFrameworkTenantManagementDomainModule))] + public class YiFrameworkTenantManagementSqlSugarCoreModule : AbpModule + { + } +} diff --git a/Yi.Abp.Net8/src/Yi.Abp.Domain/Yi.Abp.Domain.csproj b/Yi.Abp.Net8/src/Yi.Abp.Domain/Yi.Abp.Domain.csproj index 83718dc5..612ac2fd 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Domain/Yi.Abp.Domain.csproj +++ b/Yi.Abp.Net8/src/Yi.Abp.Domain/Yi.Abp.Domain.csproj @@ -11,6 +11,7 @@ + diff --git a/Yi.Abp.Net8/src/Yi.Abp.Domain/YiAbpDomainModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Domain/YiAbpDomainModule.cs index 89d59769..52443beb 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Domain/YiAbpDomainModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Domain/YiAbpDomainModule.cs @@ -5,13 +5,14 @@ using Yi.Abp.Domain.Shared; using Yi.Framework.Bbs.Domain; using Yi.Framework.Mapster; using Yi.Framework.Rbac.Domain; +using Yi.Framework.TenantManagement.Domain; namespace Yi.Abp.Domain { [DependsOn( typeof(YiAbpDomainSharedModule), - + typeof(YiFrameworkTenantManagementDomainModule), typeof(YiFrameworkRbacDomainModule), typeof(YiFrameworkBbsDomainModule), diff --git a/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/Yi.Abp.SqlSugarCore.csproj b/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/Yi.Abp.SqlSugarCore.csproj index 85a4b7a7..0b231f03 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/Yi.Abp.SqlSugarCore.csproj +++ b/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/Yi.Abp.SqlSugarCore.csproj @@ -6,6 +6,7 @@ + diff --git a/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiAbpSqlSugarCoreModule.cs b/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiAbpSqlSugarCoreModule.cs index d32796b1..6bf5b9b1 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiAbpSqlSugarCoreModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiAbpSqlSugarCoreModule.cs @@ -7,6 +7,7 @@ using Yi.Framework.Mapster; using Yi.Framework.Rbac.SqlSugarCore; using Yi.Framework.SqlSugarCore; using Yi.Framework.SqlSugarCore.Abstractions; +using Yi.Framework.TenantManagement.SqlSugarCore; namespace Yi.Abp.SqlsugarCore { @@ -16,6 +17,7 @@ namespace Yi.Abp.SqlsugarCore typeof(YiFrameworkRbacSqlSugarCoreModule), typeof(YiFrameworkBbsSqlSugarCoreModule), + typeof(YiFrameworkTenantManagementSqlSugarCoreModule), typeof(YiFrameworkMapsterModule), typeof(YiFrameworkSqlSugarCoreModule) )] diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/MultiTenantConnectionStringResolver2.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/MultiTenantConnectionStringResolver2.cs new file mode 100644 index 00000000..263aa0de --- /dev/null +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/MultiTenantConnectionStringResolver2.cs @@ -0,0 +1,168 @@ +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 options, + ICurrentTenant currentTenant, + IServiceProvider serviceProvider) + : base(options) + { + _currentTenant = currentTenant; + _serviceProvider = serviceProvider; + } + + public override async Task 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 FindTenantConfigurationAsync(Guid tenantId) + { + using (var serviceScope = _serviceProvider.CreateScope()) + { + var tenantStore = serviceScope + .ServiceProvider + .GetRequiredService(); + + 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(); + + return tenantStore.Find(tenantId); + } + } +} diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs index 48832fce..14da5fa8 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs @@ -1,5 +1,8 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; using Serilog; using Serilog.Events; +using Volo.Abp.Data; +using Volo.Abp.MultiTenancy; using Yi.Abp.Web; //创建日志,可使用{SourceContext}记录 @@ -23,6 +26,7 @@ try builder.Host.UseAutofac(); builder.Host.UseSerilog(); await builder.Services.AddApplicationAsync(); + builder.Services.Replace(new ServiceDescriptor(typeof(IConnectionStringResolver), typeof(MultiTenantConnectionStringResolver2), ServiceLifetime.Transient)); var app = builder.Build(); await app.InitializeApplicationAsync(); await app.RunAsync(); diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj b/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj index 35da68d9..e18343b9 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj @@ -14,6 +14,7 @@ + diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs index 64138127..f4427b69 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs @@ -7,6 +7,7 @@ using Microsoft.OpenApi.Models; using Newtonsoft.Json.Converters; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.AntiForgery; using Volo.Abp.AspNetCore.Serilog; @@ -31,8 +32,9 @@ namespace Yi.Abp.Web [DependsOn( typeof(YiAbpSqlSugarCoreModule), typeof(YiAbpApplicationModule), - + + typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAspNetCoreMvcModule), typeof(AbpAutofacModule), typeof(AbpSwashbuckleModule), @@ -105,7 +107,7 @@ namespace Yi.Abp.Web }); }); - + //jwt鉴权 var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get(); context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) @@ -143,7 +145,7 @@ namespace Yi.Abp.Web .AddGitee(options => { configuration.GetSection("OAuth:Gitee").Bind(options); - }); + }); //授权 context.Services.AddAuthorization(); @@ -166,6 +168,9 @@ namespace Yi.Abp.Web //鉴权 app.UseAuthentication(); + //多租户 + app.UseMultiTenancy(); + //swagger app.UseYiSwagger();