租户功能
MultiTenancyMiddleware中间件
(1)ITenantResolver:获取TenantResolveResult,即TenantIdOrName以及用到AppliedResolvers,它的实现TenantResolver涉及遍历AbpTenantResolveOptions
多个ITenantResolveContributor,包括CurrentUser,基于http多个方法等等,默认是CurrentUserTenantResolveContributor
(2)ICurrentTenant:引入是为了给CurrentTenant赋值,它赋值在线程安全的ICurrentTenantAccessor,以后使用直接从容器解析出来就可以使用
它是通过CurrentTenant的Change方法,将_currentTenantAccessor.Current的值设置为通过ITenantSolve解析出来的TenantIdOrName,
同时并且存储一份在ITenantResolveResultAccessor里面,其实现HttpContextTenantResolveResultAccessor的TenantResolveResult,来源于httpContextAccessor.HttpContext.Items["__AbpTenantResolveResult"]里面
ITenantStore:查找并得到TenantConfiguration,包括Id,Name,连接字符串,
- DefaultTenantStore:[Dependency(TryRegister = true)],根据AbpDefaultTenantStoreOptions.TenantConfiguration
- Volo.Abp.TenantManagement.TenantStore,使用是数据库
public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var resolveResult = _tenantResolver.ResolveTenantIdOrName(); _tenantResolveResultAccessor.Result = resolveResult; TenantConfiguration tenant = null; if (resolveResult.TenantIdOrName != null) { tenant = await FindTenantAsync(resolveResult.TenantIdOrName); if (tenant == null) { //TODO: A better exception? throw new AbpException( "There is no tenant with given tenant id or name: " + resolveResult.TenantIdOrName ); } } using (_currentTenant.Change(tenant?.Id, tenant?.Name)) { await next(context); } }
1、AbpMultiTenancyModule模块,AbpDefaultTenantStoreOptions存储配置租户信息TenantConfiguration数组,每个租户包括Guid,Name,ConnectionStrings
1)配置文件Configure<AbpDefaultTenantStoreOptions>(configuration),来源于IConfiguration
[DependsOn(
typeof(AbpDataModule),
typeof(AbpSecurityModule)
)]
public class AbpMultiTenancyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpDefaultTenantStoreOptions>(configuration);
}
}
}
比如:通过手工配置
services.Configure<AbpDefaultTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
new TenantConfiguration(_tenant1Id, "tenant1")
{
ConnectionStrings =
{
{ ConnectionStrings.DefaultConnectionStringName, "tenant1-default-value"},
{"db1", "tenant1-db1-value"}
}
},
new TenantConfiguration(_tenant2Id, "tenant2")
};
});
配置通用的连接字符串
services.Configure<DbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = "default-value";
options.ConnectionStrings["db1"] = "db1-default-value";
});
AbpMultiTenancyOptions, 提供给应用,配置是否开启多租户功能,默认是不开启
模式1、共享数据库,2、每个租户单独数据库,3混合模式,默认是混合模式
public class MultiTenancyOptions
{
/// <summary>
/// A central point to enable/disable multi-tenancy.
/// Default: false.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Database style for tenants.
/// Default: <see cref="MultiTenancyDatabaseStyle.Hybrid"/>.
/// </summary>
public MultiTenancyDatabaseStyle DatabaseStyle { get; set; } = MultiTenancyDatabaseStyle.Hybrid;
}
2、ICurrentTenant 当前租户,依赖ICurrentTenantAccessor来确定,Change方法IDisposable Change(Guid? id, string name = null);
public IDisposable Change(Guid? id, string name = null)
{
return SetCurrent(id, name);
}
private IDisposable SetCurrent(Guid? tenantId, string name = null)
{
var parentScope = _currentTenantAccessor.Current;
_currentTenantAccessor.Current = new BasicTenantInfo(tenantId, name);
return new DisposeAction(() =>
{
_currentTenantAccessor.Current = parentScope;
});
}
3、ICurrentTenantAccessor,BasicTenantInfo判断(null值,表明没有设置使用host,不是null值,看TenantId是否赋值,基TenantId正确赋值,表明是准确设定Tenant
public class AsyncLocalCurrentTenantAccessor : ICurrentTenantAccessor, ISingletonDependency { public BasicTenantInfo Current { get => _currentScope.Value; set => _currentScope.Value = value; } private readonly AsyncLocal<BasicTenantInfo> _currentScope; public AsyncLocalCurrentTenantAccessor() { _currentScope = new AsyncLocal<BasicTenantInfo>(); } }
4、AbpTenantResolveOptions: ITenantResolveContributor实现方法的列表
TenantResolver的实现,遍历AbpTenantResolveOptions的ITenantResolveContributor,
ITenantResolveContributor.Name有哪些,结果存储方法在Resolve参数ITenantResolveContext,而且通过参数传递IServiceProvider
ITenantResolveContributor》TenantResolveContributorBase,
1、CurrentUser,根据currentUser.TenantId
2、Action,利用委托方法,
3、(重要):基于HttpTenantResolveContributorBase的方法, Volo.Abp.AspNetCore.MultiTenancy模块,获取租户的Id和名称有五种方法,Cookie,Domain,Header,QueryString,Route,实现在模块Volo.Abp.AspNetCore.MultiTenancy里面
ITenantResolveContributor.Resolve方法,参数ITenantResolveContext (IServiceProvider、TenantIdOrName),结果存储在TenantResolveContext的TenantIdOrName
TenantResolveResult,返回结果包装,AppliedResolvers存储ITenantResolveContributor.Name的列表, Handled || TenantIdOrName != null; 决定TenantIdOrName
ITenantResolveContributor,其http的基类,从GetTenantIdOrNameFromHttpContextOrNull
比如:Domain实现方法,没有配置在Option里面,因为它的实现需要域名参数格式,它需要手工注册
因此它的顺序是CurrentUser>Domain( 需要手工注册)>QueryString>RouteTenant>Header>Cookie,它使用是短路法
public override void ConfigureServices(ServiceConfigurationContext context) { Configure<AbpTenantResolveOptions>(options => { options.TenantResolvers.Add(new QueryStringTenantResolveContributor()); options.TenantResolvers.Add(new RouteTenantResolveContributor()); options.TenantResolvers.Add(new HeaderTenantResolveContributor()); options.TenantResolvers.Add(new CookieTenantResolveContributor()); }); }
Domain获取字符串的方法
protected override string GetTenantIdOrNameFromHttpContextOrNull( ITenantResolveContext context, HttpContext httpContext) { if (httpContext.Request?.Host == null) { return null; } var hostName = httpContext.Request.Host.Host.RemovePreFix(ProtocolPrefixes); var extractResult = FormattedStringValueExtracter.Extract(hostName, _domainFormat, ignoreCase: true); context.Handled = true; if (!extractResult.IsMatch) { return null; } return extractResult.Matches[0].Value; }
public const string DefaultTenantKey = "__tenant";
6、ITenantStore,查找租户TenantConfiguration,
IConnectionStringResolver,解决获取连接字符串
DefaultConnectionStringResolver,在AbpDataModule配置Configure<AbpDbConnectionOptions>(IConfiguration),先用模块特定的字符串
若空,则使用Default
public virtual string Resolve(string connectionStringName = null) { //Get module specific value if provided if (!connectionStringName.IsNullOrEmpty()) { var moduleConnString = Options.ConnectionStrings.GetOrDefault(connectionStringName); if (!moduleConnString.IsNullOrEmpty()) { return moduleConnString; } } //Get default value return Options.ConnectionStrings.Default; }
MultiTenantConnectionStringResolver,解决是租户的字符串,来源TenantConfiguration的ConnectionStrings 不能为空
还没connectionStringName ,决定用的是tenant.ConnectionStrings.Default,还是Options.ConnectionStrings.Default
若有connectionStringName,根据特定租户的字符串
还没有,再回到Option特定字符串,Defaut字符串。
public override string Resolve(string connectionStringName = null) { //No current tenant, fallback to default logic if (_currentTenant.Id == null) { return base.Resolve(connectionStringName); } using (var serviceScope = _serviceProvider.CreateScope()) { var tenantStore = serviceScope .ServiceProvider .GetRequiredService<ITenantStore>(); var tenant = tenantStore.Find(_currentTenant.Id.Value); if (tenant?.ConnectionStrings == null) { return base.Resolve(connectionStringName); } //Requesting default connection string if (connectionStringName == null) { return tenant.ConnectionStrings.Default ?? Options.ConnectionStrings.Default; } //Requesting specific connection string var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName); if (connString != null) { return connString; } /* Requested a specific connection string, but it's not specified for the tenant. * - If it's specified in options, use it. * - If not, use tenant's default conn string. */ var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); if (connStringInOptions != null) { return connStringInOptions; } return tenant.ConnectionStrings.Default ?? Options.ConnectionStrings.Default; } }
浙公网安备 33010602011771号