X-Admin&ABP框架開發-租戶管理
軟體即服務概念的推動,定製化到通用化的發展,用一套程式碼完成適應不同企業的需求,利用多租戶技術可以去做到這一點。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,望技術有成後能回來看見自己的腳步