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