Magicodes.WeiChat——多租戶的設計與實現


概要

多租戶(Multi Tenancy/Tenant)是一種軟件架構,其定義是:在一台服務器上運行單個應用實例,它為多個租戶提供服務。

本框架使用的是共享數據庫、共享 Schema、共享數據表的數據設計架構。

操作說明

進入系統管理員界面,打開租戶管理界面,如下圖所示:

image

下面是租戶管理界面:

image

這里可以管理租戶成員,也可以讓管理員綁定微信。

下面是公眾號配置界面:

image

這里可以配置公眾號的信息。

系統管理員不僅可以管理自己的租戶,還可以管理其他租戶內容——公眾號管理。

下面是公眾號管理界面:

image

架構實現

如上面所述,本框架使用的是共享數據庫、共享 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就是數據的分水嶺,不同數據的篩選需要根據其來篩選。

如下圖的設計:

image

從上圖可以看出,這塊錯綜復雜的類都缺不了TenantId,可能看類還是不太明白,我們來看表結構吧,比如說:

imageimageimageimage

等等。如上面表結構所示,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作為其數據存儲的實現庫。通過對象瀏覽器查看,不難看出,其主要定義了以下對象:

image

其中,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 的成員

那么,我們現在的主要對象就是搞定她們了。

image

一一對應關系如下所示:

image

如上所示,通過擴展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方法中。

尾聲

至此,整個多租戶的架構就基本完成了。當然我們還可以進行擴展,比如實現租戶緩存、租戶資源管理等等,這是后續的話題了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM