概要
多租戶(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方法中。
尾聲
至此,整個多租戶的架構就基本完成了。當然我們還可以進行擴展,比如實現租戶緩存、租戶資源管理等等,這是后續的話題了。