以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();
});
}
}

下面就到核心代码了,在主程序中建立多租户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 = ; 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=; 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解决方案也很优秀,甚至更加优秀,当你了解越多你就会惊叹当初作者的设计思路是多么的优秀,有兴趣的小伙伴可以一起挖掘和分享啊!

ServiceStack 多租户的实现方案的更多相关文章

  1. 多租户SaaS平台的数据库方案

    1.1 多租户是什么 多租户技术(Multi-TenancyTechnology)又称多重租赁技术:是一种软件架构技术,是实现如何在多用户环境下 (此处的多用户一般是面向企业用户)共用相同的系统或程序 ...

  2. 实现saas多租户方案比较

    看到一篇比较多租户数据隔离方案的文章,总结挺不错.其实大部分内容在我前几年写的文章都有. 文章翻译自: https://blog.arkency.com/comparison-of-approache ...

  3. 数据层的多租户浅谈(SAAS多租户数据库设计)

    在上一篇“浅析多租户在 Java 平台和某些 PaaS 上的实现”中我们谈到了应用层面的多租户架构,涉及到 PaaS.JVM.OS 等,与之相应的是数据层也有多租户的支持. 数据层的多租户综述 多租户 ...

  4. EF Core 实现多租户

    目录 SAAS 和多租户 多租户数据隔离方案 使用 EF Core 简单实现多租户 单数据库实现 多数据库实现 源代码 参考 SAAS 和多租户 SaaS(软件及服务)区别于其他应用程序的主要特征就是 ...

  5. 在 Windows Azure 上设计多租户应用程序

    作者:Suren Machiraju 和 Ralph Squillace 审校:Christian Martinez.James Podgorski.Valery Mizonov 和 Michael ...

  6. [转载]数据层的多租户浅谈(SAAS多租户数据库设计)

    原文:http://www.ibm.com/developerworks/cn/java/j-lo-dataMultitenant/index.html 在上一篇“浅析多租户在 Java 平台和某些 ...

  7. 一种Django多租户解决方案

    什么是多租户? 多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性. 多租 ...

  8. 如何在多模型的情况下进行EF6的结构迁移

    所谓多模型就是在一个数据库中包含两个不同模型,或者换句话说就是两个不同DbContext的数据都放到同一个数据库中.这里的多模型不是指多租户的数据库(有谁知道EF很好处理多租户数据库的方案,可以联系我 ...

  9. DAL – RDBMS 的分区

    编辑人员注释:本文章由AzureCAT 云与企业工程组的高级项目经理Shaun Tinline-Jones 和Chris Clayton 共同撰写. "云服务基础"应用程序也称作& ...

随机推荐

  1. 一台老服务器的升级日志(PHP4.4.9+MySQL5.1)升级MySQL5.6

    1.备份数据库 mysqldump -uroot -p --all-databases > databases.sql 2.停止mysql服务 service mysqld stop 3.卸载旧 ...

  2. JavaScript 内存泄漏教程

    一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来 ...

  3. dedecms首页搜索 添加仿百度下拉框

    1:找到uploads/templets/default/head.htm 2: 找到 <input name="q" type="text"  clas ...

  4. PHP 写文件的例子

    $contents = "All the content"; $dir = 'c:'; $file_path = $dir . "\\content.txt"; ...

  5. response输出随机图片、定时刷新网页

    第一招:利用response向浏览器输出图片: //获取验证码 在<img />标签内的src属性设为请求路径/verifyCode?goodsId=xxx&token=xxxxx ...

  6. Python终端彩色字体的输出

    实现过程: 终端的字符颜色是用转义序列控制的,是文本模式下的系统显示功能,和具体的语言无关. 转义序列是以ESC开头,即用\033来完成(ESC的ASCII码用十进制表示是27,用八进制表示就是033 ...

  7. ubuntu新建、删除用户

    新建用户名为newuser的用户,并赋予sudo权限 adduser newuser --ingroup sudo 删除用户以及用户目录 deluser -r newuser

  8. 2018.11.02 洛谷P3952 时间复杂度(模拟)

    传送门 惊叹考场dubuffdubuffdubuff. 这题还没有梭哈难啊233. 直接按照题意模拟就行了. 代码: #include<bits/stdc++.h> using names ...

  9. linux yum 本地源配置

    1.查看硬盘情况 lsblk sr0就是光驱了 2.执行挂载命令 查看光驱cd /devls 执行命令 mount /dev/sr0  /mnt 将光驱挂载到 /mnt 目录 这样光驱就挂载好了 2. ...

  10. 解决python代码中含有中文报错

    python中写入中文时报错如下图所示: 依照网上解决方法:在py文件中加入:#encoding=utf-8 然后继续报错如下图所示: 解决方法: 在py文件中加入: import sysreload ...