深入淺出-多租戶

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_headers http://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
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章