• 租户功能


    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;
                }
            }
  • 相关阅读:
    A Simple PlugIn Library For .NET
    (转).NET 一次查询多表,填充DataSet并指定表名(DataSet指定DataTable名称的技巧)
    Database Schema Create
    C++中关于指针入门的最好的文章
    oleDbCommand访问Excel
    the best simple c++
    c++连接数据库
    plugin framework 1
    c# invoke c++
    摩根士丹利赐与新浪增持评级
  • 原文地址:https://www.cnblogs.com/cloudsu/p/11169211.html
Copyright © 2020-2023  润新知