深入浅出-多租户

摘要:
ABP的多租户模块提供了创建多租户应用程序的基本功能。租户是通过软件实例的特定权限共享通用访问的一组用户。多租户与多实例架构形成对比,将软件实例的行为根据不同的租户分割开来。然后你可以添加AbpMultiTenancyModule依赖到你的模块:usingVolo.Abp.Modularity;usingVolo.Abp.MultiTenancy;namespaceMyCompany.MyProject{[DependsOn]publicclassMyModule:AbpModule{//...}}随着"Multi-tenancyready"的概念,我们打算开发我们的代码和多租户方法兼容。然后它可以被用于多租户和非多租户的程序中,这取决于最终程序的需求。

ABP的多租户模块提供了创建多租户应用程序的基本功能。

维基百科中是这样定义多租户的:

软件多租户技术指的是一种软件架构,这种架构可以使用软件的单实例运行并为多个租户提供服务。租户是通过软件实例的特定权限共享通用访问的一组用户。使用多租户架构,软件应用为每个租户提供实例的专用共享,包括实例的数据、配置、用户管理、租户的私有功能和非功能属性。多租户与多实例架构形成对比,将软件实例的行为根据不同的租户分割开来。

Volo.Abp.MultiTenancy

Volo.Abp.MultiTenancy"multi-tenancy ready",使用包管理器控制台(PMC)将它安装到你的项目中:

Install-Package Volo.Abp.MultiTenancy

这个包默认安装在了快速启动模板中.所以,大多数情况下,你不需要手动安装它。

然后你可以添加AbpMultiTenancyModule依赖到你的模块:

using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpMultiTenancyModule))]
    public class MyModule : AbpModule
    {
        //...
    }
}

随着"Multi-tenancy ready"的概念,我们打算开发我们的代码和多租户方法兼容。然后它可以被用于多租户和非多租户的程序中,这取决于最终程序的需求。

定义实体

你可以在你的实体中实现IMultiTenant接口来实现多租户,例如:

using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    public class Product : AggregateRoot, IMultiTenant
    {
        public Guid? TenantId { get; set; } //IMultiTenant 定义了 TenantId 属性

        public string Name { get; set; }

        public float Price { get; set; }
    }
}

实现IMultiTenant接口,需要在实体中定义一个TenantId的属性(查看更多有关实体的文档)

获取当前租户的Id

你的代码中可能需要获取当前租户的Id(先不管它具体是怎么取得的)。对于这种情况你可以注入并使用ICurrentTenant接口。例如:

using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    public class MyService : ITransientDependency
    {
        private readonly ICurrentTenant _currentTenant;

        public MyService(ICurrentTenant currentTenant)
        {
            _currentTenant = currentTenant;
        }

        public void DoIt()
        {
            var tenantId = _currentTenant.Id;
            //在你的代码中使用tenantId
        }
    }
}

改变当前租户

using (CurrentUnitOfWork.SetTenantId(input.TenantId))
{
    // Do some thing...
}

确定当前租户

多租户的应用程序运行的时候首先要做的就是确定当前租户. Volo.Abp.MultiTenancy只提供了用于确定当前租户的抽象(称为租户解析器),但是并没有现成的实现。

Volo.Abp.AspNetCore.MultiTenancy已经实现了从当前Web请求(从子域名,请求头,cookie,路由...等)中确定当前租户.本文后面会介绍Volo.Abp.AspNetCore.MultiTenancy。

自定义租户解析器

你可以像下面这样,在你模块的ConfigureServices方法中将自定义解析器并添加到AbpTenantResolveOptions中:

using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpMultiTenancyModule))]
    public class MyModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            Configure<AbpTenantResolveOptions>(options =>
            {
                options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
            });

            //...
        }
    }
}

MyCustomTenantResolveContributor必须像下面这样实现ITenantResolveContributor接口:

using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    public class MyCustomTenantResolveContributor : ITenantResolveContributor
    {
        public void Resolve(ITenantResolveContext context)
        {
            context.TenantIdOrName = ... //从其他地方获取租户id或租户名字...
        }
    }
}

如果能确定租户id或租户名字可以在租户解析器中设置TenantIdOrName.如果不能确定,那就空着让下一个解析器来确定它。

租户存储

Volo.Abp.MultiTenancy中定义了ITenantStore从框架中抽象数据源.你可以实现ITenantStore,让它跟任何存储你租户的数据源(例如关系型数据库)一起工作。

配置数据存储

有一个内置的(默认的)租户存储,叫ConfigurationTenantStore.它可以被用于存储租户,通过标准的配置系统(使用Microsoft.Extensions.Configuration).因此,你可以通过硬编码或者在appsettings.json文件中定义租户。

例子:硬编码定义租户

using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpMultiTenancyModule))]
    public class MyModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            Configure<AbpDefaultTenantStoreOptions>(options =>
            {
                options.Tenants = new[]
                {
                    new TenantInformation(
                        Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"), //Id
                        "tenant1" //Name
                    ),
                    new TenantInformation(
                        Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"), //Id
                        "tenant2" //Name
                    )
                    {
                        //tenant2 有单独的数据库连接字符串
                        ConnectionStrings =
                        {
                            {ConnectionStrings.DefaultConnectionStringName, "..."}
                        }
                    }
                };
            });
        }
    }
}

例子:appsettings.json定义租户

首先从appsetting.json文件中创建你的配置。

using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpMultiTenancyModule))]
    public class MyModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var configuration = BuildConfiguration();

            Configure<AbpDefaultTenantStoreOptions>(configuration);
        }

        private static IConfigurationRoot BuildConfiguration()
        {
            return new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .Build();
        }
    }
}

然后在appsettings.json中添加 "Tenants" 节点:

"Tenants": [
    {
      "Id": "446a5211-3d72-4339-9adc-845151f8ada0",
      "Name": "tenant1"
    },
    {
      "Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d",
      "Name": "tenant2",
      "ConnectionStrings": {
        "Default": "...write tenant2's db connection string here..."
      }
    }
  ]

租户信息

ITenantStore跟TenantInformation类一起工作,并且包含了几个租户属性:

  • Id:租户的唯一Id。
  • Name: 租户的唯一名称。
  • ConnectionStrings:如果这个租户有专门的数据库来存储数据.它可以提供数据库的字符串(它可以具有默认的连接字符串和每个模块的连接字符串)。

多租户应用程序可能需要其他租户属性,但这些属性是框架与多个租户一起使用的最低要求。

代码中改变租户

using (CurrentUnitOfWork.SetTenantId(input.TenantId))
{
    // Do some thing...
}

Volo.Abp.AspNetCore.MultiTenancy

Volo.Abp.AspNetCore.MultiTenancy将多租户整合到了ASP.NET Core的程序中.在PMC中使用下面的代码将它安装到项目中。

Install-Package Volo.Abp.AspNetCore.MultiTenancy

然后添加AbpAspNetCoreMultiTenancyModule依赖到你的模块:

using Volo.Abp.Modularity;
using Volo.Abp.AspNetCore.MultiTenancy;

namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))]
    public class MyModule : AbpModule
    {
        //...
    }
}

多租户中间件

Volo.Abp.AspNetCore.MultiTenancy包含了多租户中间件...

app.UseMultiTenancy();

从Web请求中确定当前租户

Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前Web请求(按优先级排序)中确定当前租户。

  • CurrentUserTenantResolveContributor: 如果当前用户已登录,从当前用户的声明中获取租户Id.出于安全考虑,应该始终将其做为第一个Contributor
  • QueryStringTenantResolver: 尝试从query string参数中获取当前租户,默认参数名为"__tenant"。
  • RouteTenantResolver:尝试从当前路由中获取(URL路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户。
  • HeaderTenantResolver: 尝试从HTTP header中获取当前租户,默认的header名称是"__tenant"。
  • CookieTenantResolver: 尝试从当前cookie中获取当前租户.默认的Cookie名称是"__tenant"。

如果你使用nginx作为反向代理服务器,请注意如果TenantKey包含下划线或其他特殊字符可能存在问题, 请参考:http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headershttp://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers

可以使用AbpAspNetCoreMultiTenancyOptions修改默认的参数名"__tenant".例如:

services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
    options.TenantKey = "MyTenantKey";
});

域名租户解析器

实际项目中,大多数情况下你想通过子域名(如mytenant1.mydomain.com)或全域名(如mytenant.com)中确定当前租户.如果是这样,你可以配置AbpTenantResolveOptions添加一个域名租户解析器。

using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;

namespace MyCompany.MyProject
{
    [DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))]
    public class MyModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            Configure<AbpTenantResolveOptions>(options =>
            {
                //子域名格式: {0}.mydomain.com (作为第二优先级解析器添加, 位于CurrentUserTenantResolveContributor之后)
                options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));
            });

            //...
        }
    }
}

{0}是用来确定当前租户唯一名称的占位符。

你可以使用下面的方法,代替options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));:

options.AddDomainTenantResolver("{0}.mydomain.com");

例子:添加全域名解析器

options.AddDomainTenantResolver("{0}.com");

老版本ABP中这样做

首先创建租户信息,租户 Name 对应前面的 {0} 这样DomainTenantResolveContributor 在自动解析时就能根据域名对应定位到租户数据,从而实现自动匹配当前请求属于哪个租户。

/// <summary>多租户配置</summary>
private void ConfigureMultiTenancy()
{
    Configuration.MultiTenancy.IsEnabled = bool.Parse(appConfiguration["Abp:MultiTenancyIsEnabled"]); //是否启用多租户            
    if (Configuration.MultiTenancy.IsEnabled)
    {
        Configuration.MultiTenancy.Resolvers.Add<DomainTenantResolveContributor>();
        Configuration.Modules.AbpWebCommon().MultiTenancy.DomainFormat = appConfiguration["Abp:MultiTenancyDomainFormat"];
    }
    else
    {
        Configuration.Modules.AbpWebCommon().MultiTenancy.DomainFormat = appConfiguration["App:ServerRootAddress"] ?? "http://localhost:22742/";
    }
    Configuration.MultiTenancy.TenantIdResolveKey = "TenantId"; //设置默认 TenantId 字符,默认为 Abp.TenantId
}

免责声明:文章转载自《深入浅出-多租户》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【Git&amp;amp;GitHub idea中使用Git 03】华大MCU的应用中的问题记录下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

ubuntu和centos操作命令对比

ubuntu和centos操作命令对比 1,安装软件,比如安装jdk CentOS : rpm -i jdk-XXX_linux-x64_bin.rpm 进行安装, Ubuntu : dpkg -i jdk-XXX_linux-x64_bin.deb 进行安装。 其中 -i 就是 install 的意思。 2,管理软件 查询已安装的软件 rpm -qa...

Mac 删除非当前AppleID安装的APP

Mac上面的APP一般都是通过App Store安装,这样 APP就与AppleID建立了绑定关系。 Mac无法更新一款APP,是因为下载这个APP的AppleID已经不能正常登录。 不要紧,独辟蹊径,找到APP和AppleID的关联文件,删除之,就可以通过当前登录的AppleID更新了。 需要删除的关联文件叫“_MASReceipt”。 它的路径地址一般...

SQL Server 2008 R2 启动企业管理器,出现“无法读取此系统上以前注册的服务器的列表”

方法 打开C:Users<username>AppDataLocalTemp 要先删除名为1和2的文件,然后分别创建两个名为 1 和 2 的文件夹。 出现问题的原因有可能是清理了*.tmp文件。 下面是图,打开SQL2008R2 点击继续,登陆进去,弹出...

微软Azure通知中心 (Azure Notification Hubs)

Azure Notification Hubs 提供简单的方法从后台(azure或者on-promise)去发送通知在不同的平台上面(iOS, Android, Windows, Kindle, Baidu, 等等). 下面是一些发送的例子: 低延迟的发送突发事件通知. 发送区域性优惠券给感兴趣的部分顾客 给传媒/运动/金融/游戏app 发送活动相关通知...

WPF 中托管 UWP

托管标准 UWP 控件 1. 新建空白应用(通用 Windows)项目,确保目标版本和最低版本均设置为 Windows 10 版本 1903 或更高版本。在 UWP 应用项目中,安装Microsoft.Toolkit.Win32.UI.XamlApplication 2. 修改App的默认基类为XamlApplication <xaml:XamlAp...

同行——项目系统设计与数据库设计

所属班级 2019秋福大软件工程实践Z班 (福州大学) 作业要求 团队作业第四次—项目系统设计与数据库设计 团队名称 同行 这个作业的目标 设计好系统和数据库,根据组员分工,完成自己相应任务,然后汇总给,有问题再一起交流讨论修改,直到完成对数据库系统说明书和体系说明书的设计。 参考文献 《数据库设计说明书》国家标准,《软件工程》,《构建之法》 团队项目的预...