軟件即服務概念的推動,定制化到通用化的發展,用一套代碼完成適應不同企業的需求,利用多租戶技術可以去做到這一點。ABP里提供了多租戶這一概念並且也在Zero模塊中實現了這一概念。
一、多租戶的概念
單部署-單數據庫:部署應用程序的單個實例和單個數據庫。在每個數據表(關系型數據庫)里用一個TenantId(租戶Id或類似的如企業Id)字段來隔離區分每個租戶數據。
單部署-多數據庫:部署應用的單一實例,用一個主(宿主)數據庫存儲租戶元數據(像租戶名和子域),並為每個租戶建立並維護一個隔離的數據庫。操作上通過識別當前租戶並從主數據庫中讀取相對應的存儲數據庫地址,切換到該租戶獨有的數據庫中執行操作。
多部署-多數據庫:為每個租戶部署應用的一個實例並使用一個獨立的數據庫,那么我們就可以在一台服務器上為多個租戶服務,只需確保相同應用的多個實例在一個服務器的環境下不會互相沖突就行。
還有兩種多租戶形式,諸如單部署-混搭數據庫、集群部署-單/多/混搭數據庫,不再提及,ABP針對於這五種多租戶形式都可以使用。
宿主與租戶:對於這兩個概念,最好的理解方式便是酒店,酒店老板即是宿主,租戶只擁有房間權限,酒店老板可以提供管理所有租戶和房間。
二、ABP中多租戶配置
1、啟用/禁用多租戶
如果不需要多租戶,比如說沒有多租戶情形,應用部署在企業私有服務器上,那么也可以不考慮多租戶的使用,可以在ABP中關閉多租戶(盡管關閉了,但默認還是會使用一個租戶,默認租戶Id為1,此時只有這個默認租戶沒有宿主)。在WebCoreModule中PreInitialize方法內可以添加如下代碼啟用或關閉多租戶,默認是啟用的。
public override void PreInitialize() { ... Configuration.MultiTenancy.IsEnabled = true; ... }
2、偵測當前租戶並在Session中獲取租戶
ABP中租戶名稱是唯一的,對於識別當前租戶或是宿主身份,ABP沒有使用Asp.Net 提供的Session,聲明了IAbpSession接口並提供了默認的實現(ClaimAbpSession)去測定當前租戶信息,按照如下思路去確定租戶。
- 如果當前用戶登錄了系統,那么可以從當前用戶的聲明信息中讀取到當前租戶信息,如果沒有讀取到租戶信息,那么可以斷定是宿主。
- 如果當前用戶沒有登錄系統,那么會有幾種方式去獲取,如果以下幾種方式仍未獲取到租戶Id,則認為是使用宿主登錄。
- 從當前域名或是子域名去獲取域名名稱,然后通過租戶倉儲去查詢是否存在相關的域名或子域名存在則可以確定租戶Id。
- 從Http請求頭中獲取在ABP中默認配置項Abp.TenantId(該配置項可更換名稱)。
- 從Http請求的cookie中獲取Abp.TenantId
IAbpSession聲明了獲取當前用戶和租戶信息的方法,該方法允許我們獲取當前登錄的用戶及當前的租戶信息。並且獲取到的信息按照不同的規則,有着不同的作用。
- 如果獲取到的用戶和租戶Id都是空的,那么意味着當前用戶沒有登錄系統,因此也無法斷定出當前是宿主還是租戶。
- 如果用戶Id是空的,但是租戶Id不是空的,那么可以知道是哪個租戶,但是用戶仍然是沒有登錄的,只是選擇了租戶。
- 如果用戶Id不是空的,但是租戶Id是空的,那么可以知道是用戶使用宿主登錄了系統。
- 如果用戶Id且租戶Id不是空的,那么就知道是選擇了租戶並且是租戶中的某個用戶登錄了系統。
3、數據過濾
如果使用了多租戶,那么在讀取數據時,會依據當前租戶Id加上額外的過濾條件,這一點ABP已經處理好了,我們無需在linq中敲代碼,但是有個前提條件是,讀取數據的這個實體有設置多租戶。
1、如果實體使用的是IMustHaveTenant接口,那么讀取時會依照當前租戶Id進行條件過濾。
2、如果實體使用的是IMayHaveTenant接口,那么讀取到的數據會依照當前租戶Id的有無值進行區分,如果當前租戶Id為空,那么將讀取到宿主的數據,如果租戶Id不為空,則讀取相應租戶數據。
3、如果實體沒使用這兩個接口,則讀取到的數據不區分宿主和租戶。
這兩個接口使用場景:如果是宿主和租戶都需要的,比如角色、用戶、部門等,那么使用IMayHaveTenant接口,如果僅是租戶所需要的那只需使用IMustHaveTenant接口。
4、宿主與租戶間切換
此處切換可以這么理解,給我一個其它租戶Id,我可以在我的租戶中獲取到其它租戶的數據,相應的,其它租戶也可以獲取到我租戶的數據,或是宿主獲取租戶數據。如果不給定租戶Id,租戶可以獲取宿主數據。
public class ProductService : ITransientDependency { private readonly IRepository<Product> _productRepository; private readonly IUnitOfWorkManager _unitOfWorkManager; public ProductService(IRepository<Product> productRepository, IUnitOfWorkManager unitOfWorkManager) { _productRepository = productRepository; _unitOfWorkManager = unitOfWorkManager; } [UnitOfWork] public virtual List<Product> GetProducts(int tenantId) { using (_unitOfWorkManager.Current.SetTenantId(tenantId)) { return _productRepository.GetAllList(); } } }
三、配置一個多租戶實體
基於之前的數據字典進行改造,以便適用於多個租戶使用,並且考慮到宿主無需使用數據字典,將其繼承IMustHaveTenant接口后,更新數據庫便可。
public class DataDictionary : Entity<long>, IMustHaveTenant { public const int MaxNameLength = 30; /// <summary> /// 租戶Id /// </summary> public int? TenantId { get; set; } /// <summary> /// 字典類型 /// </summary> [StringLength(MaxNameLength)] public string TypeName { get; set; } /// <summary> /// 關聯數據字典項 /// </summary> public virtual ICollection<DataDictionaryItem> DataDictionaryItem { get; set; } }
我設置了默認當前租戶,並且不提供選擇租戶的頁面,通過Url去區分宿主和默認租戶,登錄當前租戶賬號后,查看當前網站內的數據字典看到之前已有數據全部消失,獲取數據字典數據時,默認是ABP將當前租戶Id帶入作為查詢條件了。
修改添加方法,添加當前租戶Id的賦值,再次為當前租戶添加幾條相應的數據字典,頁面中即可存在相關數據了。
dataDictionary.TenantId = AbpSession.TenantId.Value;
最后兩條數據是指定租戶Id為1下的,之前的數據可以手動清除。
至此,對於ABP中租戶的相關使用了解的清楚了,對於多租戶下數據存儲量大,拆分成集群部署-多數據庫情形沒有做嘗試,希望有機會可以使用一番。
倉庫地址:https://gitee.com/530521314/Partner.Surround.git
2020-01-11,望技術有成后能回來看見自己的腳步