多租戶(Multi Tenancy/Tenant)是一種軟體架構,其定義是:在一臺伺服器上運行單個應用實例,它為多個租戶提供服務。 本框架使用的是共用資料庫、共用 Schema、共用數據表的數據設計架構。 進入系統管理員界面,打開租戶管理界面,如下圖所示: 下麵是租戶管理界面: 這裡可以管理租戶成員 ...
概要
多租戶(Multi Tenancy/Tenant)是一種軟體架構,其定義是:在一臺伺服器上運行單個應用實例,它為多個租戶提供服務。
本框架使用的是共用資料庫、共用 Schema、共用數據表的數據設計架構。
操作說明
進入系統管理員界面,打開租戶管理界面,如下圖所示:
下麵是租戶管理界面:
這裡可以管理租戶成員,也可以讓管理員綁定微信。
下麵是公眾號配置界面:
這裡可以配置公眾號的信息。
系統管理員不僅可以管理自己的租戶,還可以管理其他租戶內容——公眾號管理。
下麵是公眾號管理界面:
架構實現
如上面所述,本框架使用的是共用資料庫、共用 Schema、共用數據表的數據設計架構。那麼,本框架是如何實現的呢?
主要是分為以下三步:
1. 建立TenantId
2. 擴展ASP.NET Indentity以支持多租戶
3. 註冊租戶篩選器
那麼首先,這裡需要介紹的是TenantId。
建立租戶Id(TenantId)
我們先來看看租戶表:
/// <summary> /// 租戶信息 /// </summary> public class Account_Tenant { /// <summary> /// 多租戶Id /// </summary> public int Id { get; set; } /// <summary> /// 是否為系統租戶(僅支持一個) /// </summary> public bool IsSystemTenant { get; set; } /// <summary> /// 租戶名稱 /// </summary> [Display(Name = "名稱")] [Required] [MaxLength(20)] public string Name { get; set; } [Display(Name = "備註")] [DataType(DataType.MultilineText)] [MaxLength(500)] public string Remark { get; set; } }
如上所示,Id為主鍵,標識列,由資料庫自動生成(EF Code First模式下,預設Id為主鍵,int類型主鍵自動設置為標識列)。
那麼,租戶Id產生了之後,所有租戶共用數據表存放數據,不同租戶的數據需要通過 TenantId 欄位來區分。
我們來看一個基類的設計:
public abstract class WeiChat_TenantBase<TKey> : ITenantId, IAdminCreate<string>, IAdminUpdate<string> { [Key] public virtual TKey Id { get; set; } /// <summary> /// 創建時間 /// </summary> [Display(Name = "創建時間")] public DateTime CreateTime { get; set; } /// <summary> /// 更新時間 /// </summary> [Display(Name = "更新時間")] public DateTime? UpdateTime { get; set; } /// <summary> /// 創建者 /// </summary> [MaxLength(128)] public string CreateBy { get; set; } /// <summary> /// 創建者 /// </summary> [Display(Name = "創建者")] //[NotMapped] [ForeignKey("CreateBy")] public AppUser CreateUser { get; set; } /// <summary> /// 更新者 /// </summary> [MaxLength(128)] public string UpdateBy { get; set; } /// <summary> /// 編輯者 /// </summary> [MaxLength(256)] [Display(Name = "最後編輯")] //[NotMapped] public AppUser UpdateUser { get; set; } public int TenantId { get; set; } }
如上所示,TenantId就是數據的分水嶺,不同數據的篩選需要根據其來篩選。
如下圖的設計:
從上圖可以看出,這塊錯綜複雜的類都缺不了TenantId,可能看類還是不太明白,我們來看表結構吧,比如說:
等等。如上面表結構所示,TenantId為個表間必備欄位。
而在Code First模式下,使用繼承可以很方便的將所有的模型類加上相關欄位。
眾所周知,本框架使用了ASP.NET Indentity,那麼如何對ASP.NET Indentity實現多租戶的擴展呢?
擴展ASP.NET Indentity以支持多租戶
在本框架中,編寫了庫Magicodes.WeiChat.Data.Multitenant,用於擴展ASP.NET Indentity以支持多租戶。
使用過ASP.NET Indentity的朋友應該都知道Microsoft.AspNet.Identity.EntityFramework——ASP.NET Indentity使用EF作為其數據存儲的實現庫。通過對象瀏覽器查看,不難看出,其主要定義了以下對象:
其中,IdentityDbContext 繼承自System.Data.Entity.DbContext,具體定義如下所示:
public class IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim> : System.Data.Entity.DbContext
where TUser : Microsoft.AspNet.Identity.EntityFramework.IdentityUser<TKey, TUserLogin, TUserRole, TUserClaim>
where TRole : Microsoft.AspNet.Identity.EntityFramework.IdentityRole<TKey, TUserRole>
where TUserLogin : Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin<TKey>
where TUserRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<TKey>
where TUserClaim : Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim<TKey>
Microsoft.AspNet.Identity.EntityFramework 的成員
那麼,我們現在的主要對象就是搞定她們了。
一一對應關係如下所示:
如上所示,通過擴展ASP.NET Identity的IUser、IdentityUser、IdentityDbContext、IdentityUserLogin、UserStore來完成了對多租戶的支持,同時需要註意的是,還要重寫方法FindByNameAsync、AddLoginAsync、FindAsync、FindByEmailAsync、CreateAsync,以支持多租戶。
完成了對ASP.NET Identity的多租戶的支持,我們還需要對數據進行篩選,但是所有地方都添加篩選代碼是一件很麻煩的事情,而且在編寫邏輯的時候還很容易健忘,那麼有什麼好的方式呢?是時候祭出我們的神器了——EntityFramework.DynamicFilters。
註冊租戶篩選器
篩選器依賴ENTITYFRAMEWORK.DYNAMICFILTERS,這是一個開源項目,相關介紹可以訪問以下鏈接:
https://github.com/jcachat/EntityFramework.DynamicFilters
這裡,我們定義瞭如下租戶篩選器:
modelBuilder.Filter("TenantEntryFilter", (ITenantId app, int tenantId) => (app.TenantId == tenantId), 0);
然後我們可以使用以下代碼來啟用篩選器:
db.EnableFilter(tenantFilterName);
//設置多租戶過濾
db.SetFilterScopedParameterValue(tenantFilterName, "tenantId", TenantId);
以上代碼大家可以寫到通用的地方進行封裝,比如控制器基類的OnActionExecuting方法中。
尾聲
至此,整個多租戶的架構就基本完成了。當然我們還可以進行擴展,比如實現租戶緩存、租戶資源管理等等,這是後續的話題了。