租户功能
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; } }