文档章节

ServiceStack 多租户的实现方案

o
 osc_w9s1w4o0
发布于 2019/04/02 20:25
字数 984
阅读 3
收藏 0

精选30+云产品,助力企业轻松上云!>>>

以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解决方案也很优秀,甚至更加优秀,当你了解越多你就会惊叹当初作者的设计思路是多么的优秀,有兴趣的小伙伴可以一起挖掘和分享啊!

 

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。

暂无文章

pyhon

cython 相关的帖子. http://blog.behnel.de/categories/cython.html https://www.nexedi.com/ 我们追求的价值观 家 多核Python HTTP服务器(比Go更快)(破坏者:Cython) 价值观 当让-保罗·...

MtrS
16分钟前
9
0
多处理与线程Python - Multiprocessing vs Threading Python

问题: I am trying to understand the advantages of multiprocessing over threading . 我试图了解多处理优于线程的优势。 I know that multiprocessing gets around the Global Interpret......

法国红酒甜
21分钟前
9
0
格式编号始终显示2个小数位 - Format number to always show 2 decimal places

问题: I would like to format my numbers to always display 2 decimal places, rounding where applicable. 我想将数字格式化为始终显示2个小数位,并在适用的情况下四舍五入。 Examples...

富含淀粉
今天
22
0
Docker可视化工具Portainer

1 前言 从没想到Docker也有可视化的工具,因为它的命令还是非常清晰简单的。无聊搜了一下,原来已经有很多Docker可视化工具了。如DockerUI、Shipyard、Rancher、Portainer等。查看对比了一番...

南瓜慢说
今天
20
0
日志系统新贵 Loki,真香!!

最近,在对公司容器云的日志方案进行设计的时候,发现主流的ELK或者EFK比较重,再加上现阶段对于ES复杂的搜索功能很多都用不上最终选择了Grafana开源的Loki日志系统,下面介绍下Loki的背景。...

庞陆阳
今天
14
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部