租户功能

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

 

 

posted on 2019-07-11 16:44  dollymi  阅读(1419)  评论(0编辑  收藏  举报

导航