租户功能

Posted cloudsu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了租户功能相关的知识,希望对你有一定的参考价值。

1、AbpMultiTenancyModule模块,DefaultTenantStoreOptions存储配置租户信息TenantConfiguration数组,每个租户包括Guid,Name,ConnectionStrings

  [DependsOn(
        typeof(AbpDataModule),
        typeof(AbpSecurityModule)
        )]
    public class AbpMultiTenancyModule : AbpModule
    
        public override void ConfigureServices(ServiceConfigurationContext context)
        
            var configuration = context.Services.GetConfiguration();
            Configure<DefaultTenantStoreOptions>(configuration);
        
    

比如,如何从配置或ITenantStore得到租户信息

services.Configure<DefaultTenantStoreOptions>(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";
            );

 

另外关注MultiTenancyOptions

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是指示是否赋值,AsyncLocal<BasicTenantInfo>

4、ITenantResolver,这里有一个传递ITenantResolveContext ,传递入IServiceProvider,通过ITenantResolveContributor的实现来确定当前租户TenantResolveResult,ITenantResolveContributor,从GetTenantIdOrNameFromHttpContextOrNull实现方法在Volo.Abp.AspNetCore.MultiTenancy模块,获取租户的Id和名称有五种方法,Cookie,Domain,Header,QueryString,Route,实现在模块Volo.Abp.AspNetCore.MultiTenancy里面

   public interface ITenantResolveContributor
    
        string Name  get; 

        void Resolve(ITenantResolveContext context);
    

    public abstract class TenantResolveContributorBase : ITenantResolveContributor
    
        public abstract string Name  get; 

        //TODO: We can make this async
        public abstract void Resolve(ITenantResolveContext context);
    

public abstract class HttpTenantResolveContributorBase : TenantResolveContributorBase
    

。。。。
 protected abstract string GetTenantIdOrNameFromHttpContextOrNull([NotNull] ITenantResolveContext context, [NotNull] HttpContext httpContext);
。。。。

 

[DependsOn(
        typeof(AbpMultiTenancyModule), 
        typeof(AbpAspNetCoreModule)
        )]
    public class AbpAspNetCoreMultiTenancyModule : AbpModule
    
        public override void ConfigureServices(ServiceConfigurationContext context)
        
            Configure<TenantResolveOptions>(options =>
            
                options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
                options.TenantResolvers.Add(new RouteTenantResolveContributor());
                options.TenantResolvers.Add(new HeaderTenantResolveContributor());
                options.TenantResolvers.Add(new CookieTenantResolveContributor());
            );
        
    

  public const string DefaultTenantKey = "__tenant";

 其实现TenantResolver,通过遍历TenantResolveOptions下面的List<ITenantResolveContributor> TenantResolvers,获取得TenantIdOrName

6、ITenantStore,查找租户

[Dependency(TryRegister = true)]
    public class DefaultTenantStore : ITenantStore, ITransientDependency
    
        private readonly DefaultTenantStoreOptions _options;

        public DefaultTenantStore(IOptionsSnapshot<DefaultTenantStoreOptions> options)
        
            _options = options.Value;
        

        public Task<TenantConfiguration> FindAsync(string name)
        
            return Task.FromResult(_options.Tenants?.FirstOrDefault(t => t.Name == name));
        

        public Task<TenantConfiguration> FindAsync(Guid id)
        
            return Task.FromResult(_options.Tenants?.FirstOrDefault(t => t.Id == id));
        
    

  8、IConnectionStringResolver,解决连接字符串,MultiTenantConnectionStringResolver是替换实现服务, _connectionResolver.ShouldBeOfType<MultiTenantConnectionStringResolver>();

有实现如何获取字符串,包括从 ITenantStore查到TenantConfiguration或DbConnectionOptions或DefaultTenantStoreOptions配置文件

No tenant in current context:默认DbConnectionOptions

Overrided connection strings for tenant1:对应租户DefaultTenantStoreOptions

Undefined connection strings for tenant2:没有对应租户字符串使用默认字符串

 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>();
                //租户存储获取,tenant-management进行管理
                var tenant = AsyncHelper.RunSync(() => tenantStore.FindAsync(_currentTenant.Id.Value)); //TODO: Can we avoid from RunSync?
                // 当前租户没有连接字符串,则返回默认连接字符串
                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;
            
        

  9、多租户的中间件

public async Task Invoke(HttpContext httpContext)
        
            //实现是遍历Cookie,Domain,Header,QueryString,Route不同ITenantResolveContributor
            //,获取Tenant的ID和Name
            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(httpContext);
            
        

  

以上是关于租户功能的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 MyBatisPlus 轻松实现多租户功能

新功能|如何打造一个高可用多租户的企业级Maven私有仓库服务

(译)Istio 的软性多租户支持

基于AdminLTE 多租户权限隔离

Serenity框架官方文档翻译3.2(多租户)

Magicodes.WeiChat——V3.0(多租户)版本发布