当前位置: 移动技术网 > IT编程>开发语言>.net > ServiceStack 多租户的实现方案

ServiceStack 多租户的实现方案

2019年04月03日  | 移动技术网IT编程  | 我要评论

杜达雄2013味道,陈豪陈茵媺车震,虹桥书吧

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

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网