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

@@ -11,6 +11,7 @@
<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\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" />
</ItemGroup>

View File

@@ -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),

View File

@@ -6,6 +6,7 @@
<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\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" />
</ItemGroup>

View File

@@ -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)
)]

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.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<YiAbpWebModule>();
builder.Services.Replace(new ServiceDescriptor(typeof(IConnectionStringResolver), typeof(MultiTenantConnectionStringResolver2), ServiceLifetime.Transient));
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();

View File

@@ -14,6 +14,7 @@
<PackageReference Include="Serilog.AspNetCore" Version="8.0.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.MultiTenancy" 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.Autofac" Version="8.0.0" />

View File

@@ -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<JwtOptions>();
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();