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
}