feat: 搭建多租户框架

This commit is contained in:
橙子
2024-01-21 00:26:21 +08:00
parent 3412ce64cb
commit 68c1d59638
19 changed files with 527 additions and 7 deletions

View File

@@ -88,7 +88,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}"
EndProject 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.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.ActiveCfg = Release|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -264,6 +278,9 @@ Global
{320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B} {320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B}
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7} = {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} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18} SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}

View File

@@ -54,10 +54,10 @@ namespace Yi.Framework.SqlSugarCore.Uow
//var sss= unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); //var sss= unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
//Console.WriteLine("反户的:"+sss.SqlSugarClient.ContextID); //Console.WriteLine("反户的:"+sss.SqlSugarClient.ContextID);
//return sss; //return sss;
]
var connectionStringName = "Default"; var connectionStringName = "Default";
var connectionString = await ResolveConnectionStringAsync(connectionStringName); 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);

View File

@@ -0,0 +1,20 @@
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.TenantManagement.Domain
{
public interface ISqlSugarTenantRepository : ISqlSugarRepository<TenantAggregateRoot, Guid>
{
Task<TenantAggregateRoot> FindByNameAsync(string name, bool includeDetails = true);
Task<List<TenantAggregateRoot>> GetListAsync(string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false);
Task<long> GetCountAsync(
string filter = null);
}
}

View File

@@ -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<TenantCacheItem> Cache { get; }
public SqlSugarTenantStore(ISqlSugarTenantRepository repository,
IDistributedCache<TenantCacheItem> 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<TenantConfiguration?> FindAsync(string name)
{
return (await GetCacheItemAsync(null, name)).Value;
}
public async Task<TenantConfiguration?> FindAsync(Guid id)
{
return (await GetCacheItemAsync(id, null)).Value;
}
protected virtual async Task<TenantCacheItem> 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<TenantCacheItem> 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);
}
}
}

View File

@@ -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<Guid>, 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0" />
<PackageReference Include="Volo.Abp.TenantManagement.Domain.Shared" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -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));
}
}
}

View File

@@ -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<TenantAggregateRoot, Guid>, ISqlSugarTenantRepository,ITransientDependency
{
public SqlSugarTenantRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(sugarDbContextProvider)
{
}
public async Task<TenantAggregateRoot> FindByNameAsync(string name, bool includeDetails = true)
{
return await _DbQueryable.FirstAsync(x => x.Name == name);
}
public async Task<long> GetCountAsync(string filter = null)
{
return await _DbQueryable.WhereIF(!string.IsNullOrEmpty(filter),x=>x.Name.Contains(filter)) .CountAsync();
}
public async Task<List<TenantAggregateRoot>> 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);
}
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.SqlSugarCore\Yi.Framework.SqlSugarCore.csproj" />
<ProjectReference Include="..\Yi.Framework.TenantManagement.Domain\Yi.Framework.TenantManagement.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -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
{
}
}

View File

@@ -11,6 +11,7 @@
<ProjectReference Include="..\..\framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj" /> <ProjectReference Include="..\..\framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj" />
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.Domain\Yi.Framework.Bbs.Domain.csproj" /> <ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.Domain\Yi.Framework.Bbs.Domain.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Domain\Yi.Framework.Rbac.Domain.csproj" /> <ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Domain\Yi.Framework.Rbac.Domain.csproj" />
<ProjectReference Include="..\..\module\tenant-management\Yi.Framework.TenantManagement.Domain\Yi.Framework.TenantManagement.Domain.csproj" />
<ProjectReference Include="..\Yi.Abp.Domain.Shared\Yi.Abp.Domain.Shared.csproj" /> <ProjectReference Include="..\Yi.Abp.Domain.Shared\Yi.Abp.Domain.Shared.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -5,13 +5,14 @@ using Yi.Abp.Domain.Shared;
using Yi.Framework.Bbs.Domain; using Yi.Framework.Bbs.Domain;
using Yi.Framework.Mapster; using Yi.Framework.Mapster;
using Yi.Framework.Rbac.Domain; using Yi.Framework.Rbac.Domain;
using Yi.Framework.TenantManagement.Domain;
namespace Yi.Abp.Domain namespace Yi.Abp.Domain
{ {
[DependsOn( [DependsOn(
typeof(YiAbpDomainSharedModule), typeof(YiAbpDomainSharedModule),
typeof(YiFrameworkTenantManagementDomainModule),
typeof(YiFrameworkRbacDomainModule), typeof(YiFrameworkRbacDomainModule),
typeof(YiFrameworkBbsDomainModule), typeof(YiFrameworkBbsDomainModule),

View File

@@ -6,6 +6,7 @@
<ProjectReference Include="..\..\framework\Yi.Framework.SqlSugarCore\Yi.Framework.SqlSugarCore.csproj" /> <ProjectReference Include="..\..\framework\Yi.Framework.SqlSugarCore\Yi.Framework.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.SqlSugarCore\Yi.Framework.Bbs.SqlSugarCore.csproj" /> <ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.SqlSugarCore\Yi.Framework.Bbs.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj" /> <ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\tenant-management\Yi.Framework.TenantManagement.SqlSugarCore\Yi.Framework.TenantManagement.SqlSugarCore.csproj" />
<ProjectReference Include="..\Yi.Abp.Domain\Yi.Abp.Domain.csproj" /> <ProjectReference Include="..\Yi.Abp.Domain\Yi.Abp.Domain.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -7,6 +7,7 @@ using Yi.Framework.Mapster;
using Yi.Framework.Rbac.SqlSugarCore; using Yi.Framework.Rbac.SqlSugarCore;
using Yi.Framework.SqlSugarCore; using Yi.Framework.SqlSugarCore;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.TenantManagement.SqlSugarCore;
namespace Yi.Abp.SqlsugarCore namespace Yi.Abp.SqlsugarCore
{ {
@@ -16,6 +17,7 @@ namespace Yi.Abp.SqlsugarCore
typeof(YiFrameworkRbacSqlSugarCoreModule), typeof(YiFrameworkRbacSqlSugarCoreModule),
typeof(YiFrameworkBbsSqlSugarCoreModule), typeof(YiFrameworkBbsSqlSugarCoreModule),
typeof(YiFrameworkTenantManagementSqlSugarCoreModule),
typeof(YiFrameworkMapsterModule), typeof(YiFrameworkMapsterModule),
typeof(YiFrameworkSqlSugarCoreModule) typeof(YiFrameworkSqlSugarCoreModule)
)] )]

View File

@@ -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<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);
}
}
}

View File

@@ -1,5 +1,8 @@
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}记录
@@ -23,6 +26,7 @@ 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();

View File

@@ -14,6 +14,7 @@
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="8.0.0" /> <PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.MultiTenancy" Version="8.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="8.0.0" /> <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="8.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="8.0.0" /> <PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="8.0.0" />
<PackageReference Include="Volo.Abp.Autofac" Version="8.0.0" /> <PackageReference Include="Volo.Abp.Autofac" Version="8.0.0" />

View File

@@ -7,6 +7,7 @@ using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery; using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.AspNetCore.Serilog;
@@ -31,8 +32,9 @@ namespace Yi.Abp.Web
[DependsOn( [DependsOn(
typeof(YiAbpSqlSugarCoreModule), typeof(YiAbpSqlSugarCoreModule),
typeof(YiAbpApplicationModule), typeof(YiAbpApplicationModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpAspNetCoreMvcModule), typeof(AbpAspNetCoreMvcModule),
typeof(AbpAutofacModule), typeof(AbpAutofacModule),
typeof(AbpSwashbuckleModule), typeof(AbpSwashbuckleModule),
@@ -105,7 +107,7 @@ namespace Yi.Abp.Web
}); });
}); });
//jwt鉴权 //jwt鉴权
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>(); var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
@@ -143,7 +145,7 @@ namespace Yi.Abp.Web
.AddGitee(options => .AddGitee(options =>
{ {
configuration.GetSection("OAuth:Gitee").Bind(options); configuration.GetSection("OAuth:Gitee").Bind(options);
}); });
//授权 //授权
context.Services.AddAuthorization(); context.Services.AddAuthorization();
@@ -166,6 +168,9 @@ namespace Yi.Abp.Web
//鉴权 //鉴权
app.UseAuthentication(); app.UseAuthentication();
//多租户
app.UseMultiTenancy();
//swagger //swagger
app.UseYiSwagger(); app.UseYiSwagger();