以SqlServer為例子說明ServiceStack實現多租戶,在SqlServer中創建4個Database:TMaster、T1,T2,T3,為了安全起見 每個Database不用sa賬號,而是用獨立的資料庫的賬號和密碼,為了方便演示這密碼設置成一樣 租戶TMaster Database:TM ...
以SqlServer為例子說明ServiceStack實現多租戶,在SqlServer中創建4個Database:TMaster、T1,T2,T3,為了安全起見
每個Database不用sa賬號,而是用獨立的資料庫的賬號和密碼,為了方便演示這密碼設置成一樣
租戶TMaster Database:TMaster 賬號密碼: User Id=TMaster;Password=t123
租戶T1 Database:T1 賬號密碼: User Id=T1;Password=t123
租戶T2 Database:T2 賬號密碼: User Id=T2;Password=t123
租戶T3 Database:T3 賬號密碼: User Id=T3;Password=t123
創建資料庫的方法可以參見文章: https://www.cnblogs.com/tonge/p/3791029.html
每個登陸用自己的賬號和密碼登陸,其它的資料庫是沒有訪問許可權的,這個各個租戶是完全隔離的。
假設Node和npm已經安裝
npm install -g @servicestack/cli
執行命令dotnet-new selfhost SSHost
這樣就創建了ServiceStack的控制台程式,用VS2017解決方案,在ServiceModel的Types文件夾添加TenantConfig類文件
代碼如下:
using System; using System.Collections.Generic; using System.Text; namespace ssTest.ServiceModel.Types { public interface IForTenant { string TenantId { get; } } public class TenantConfig { public string Id { get; set; } public string Company { get; set; } } }
修改Hello.cs文件,代碼如下:
using ServiceStack; using ssTest.ServiceModel.Types; using System; namespace ssTest.ServiceModel { [Route("/hello")] [Route("/hello/{Name}")] public class Hello : IForTenant, IReturn<HelloResponse> { public string Name { get; set; } // 實現介面IForTenant(租戶標識Id) public string TenantId { get; set; } } public class HelloResponse { public string Result { get; set; } public DateTime Date { get; set; } // 返回租戶公司信息 public TenantConfig Config { get; set; } } }
主程式的Startup代碼如下
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // JsConfig.DateHandler = DateHandler.ISO8601; // 保證時間類型的欄位可以解析成js識別的時間類型 JsConfig<DateTime>.SerializeFn = time => new DateTime(time.Ticks, DateTimeKind.Local).ToString("o"); JsConfig<DateTime?>.SerializeFn = time => time != null ? new DateTime(time.Value.Ticks, DateTimeKind.Local).ToString("o") : null; JsConfig.DateHandler = DateHandler.ISO8601; app.UseServiceStack(new AppHost()); app.Run(context => { context.Response.Redirect("/metadata"); return Task.FromResult(0); }); } }
下麵就到核心代碼了,在主程式中建立多租戶Db工程類,讓程式可以自動的根據租戶Id訪問自己的資料庫
public class MultiTenantDbFactory : IDbConnectionFactory { private readonly IDbConnectionFactory dbFactory; public MultiTenantDbFactory(IDbConnectionFactory dbFactory) { this.dbFactory = dbFactory; } public IDbConnection OpenDbConnection() { var tenantId = RequestContext.Instance.Items["TenantId"] as string; return OpenTenant(tenantId); } public IDbConnection OpenTenant(string tenantId = null) { return tenantId != null ? dbFactory.OpenDbConnectionString($"Data Source=.; Initial Catalog={tenantId};User Id={tenantId};Password=t123;pooling=true;") : dbFactory.OpenDbConnection(); } public IDbConnection CreateDbConnection() { return dbFactory.CreateDbConnection(); } }
AppHost中加入如下代碼,GlobalRequestFilters的作用,根據傳入的租戶Id來選擇相應的資料庫,如果租戶Id為null,系統自動使用TMaster資料庫
InitDb的作用就是初始化三個資料庫,創建表TenantConfig並插入一條記錄。
public override void Configure(Container container) { ConigureSqlserver(container); } private void ConigureSqlserver(Container container) { var dbFactory = new OrmLiteConnectionFactory( "Data Source=.; Initial Catalog=TMaster;User Id=TMaster;Password=t123;pooling=true;", SqlServerDialect.Provider); const int noOfTennants = 3; container.Register<IDbConnectionFactory>(c =>new MultiTenantDbFactory(dbFactory)); var multiDbFactory = (MultiTenantDbFactory)container.Resolve<IDbConnectionFactory>(); using (var db = multiDbFactory.OpenTenant()) InitDb(db, "TMaster", "Masters inc."); for(int i=1; i<= noOfTennants; i++) { var tenantId = $"T{i}"; using (var db = multiDbFactory.OpenTenant(tenantId)) InitDb(db, tenantId, $"ACME {tenantId} inc."); } GlobalRequestFilters.Add((req, res, dto) => { var forTennant = dto as IForTenant; if (forTennant != null) RequestContext.Instance.Items.Add("TenantId", forTennant.TenantId); }); }
public void InitDb(IDbConnection db, string tenantId, string company) { db.DropAndCreateTable<TenantConfig>(); db.Insert(new TenantConfig { Id = tenantId, Company = company }); }
這樣核心代碼就完成了,我們用postman調用試試看,是不是達到了預期的效果
body為空,租戶Id沒有設置,系統認為是預設的資料庫TMaster,返回的是Master資料庫中的config表信息
body中設置json參數{"name":"joy", "tenantId":"t1"},可以看到查詢返回的是資料庫T1的信息
我們再試驗一下T2,body中設置json參數{"name":"peter", "tenantId":"t2"}
可以很驚喜的看到,查詢的是資料庫T2的信息
ServiceStack解決方案真是強大,本來一個複雜的多租戶問題就這樣輕易解決了,是不是很簡單。這裡例子用的都是sqlserver資料庫,實際上每個租戶可以使用不同的資料庫。
最近一年很流行ABP解決方案,我想說的是ServiceStack解決方案也很優秀,甚至更加優秀,當你瞭解越多你就會驚嘆當初作者的設計思路是多麼的優秀,有興趣的小伙伴可以一起挖掘和分享啊!