首先简单说下多租户的几种实现方式

多租户(Multi-Tenant ),即多个租户共用一个实例,租户的数据既有隔离又有共享,说到底是要解决数据存储的问题。

常用的数据存储方式有三种。

方案一:独立数据库

一个Tenant,一个Database的数据存储方式。隔离级别最高、最安全,但成本也高。

优点:a.为不同租户提供独立数据库,有助于简化数据模型的扩展设计,满足个性化需求;

   b.数据恢复简单;

缺点:增大了数据库的安装数量,购置和维护成本高;

方案二:共享数据库,隔离数据架构

多个租户或所有租户共享Database,但一个Tenant,一个Schema的方式。

优点:a.一定程度的逻辑数据隔离(并非完全),可满足较高程度的安全性保障;

      b.每个数据库,可支持更多租户数量;

缺点:a.恢复数据较困难,因为将牵扯到其他租户数据;

b.跨租户统计数据,实现难度大;

方案三:共享数据库,共享数据架构

一种租户共享同一个Database、同一个Schema,而另行通过TenantID区分租户数据的方式。

优点:a.每个数据库可支持租户数量多,维护和购置成本低;

缺点:a. 隔离级别低,安全性低,开发时需做大量安全开发工作;

b. 逐表逐条备份和还原数据,数据备份和恢复困难。

今天主要讲的就是用WTM 改造简易的多租户,我这里用的是Layui版本,其他UI也可以用这种方式实现,我还没有试过,大家有空可以自己试一试。我用的是方案一 独立数据库方式。技术有限,只是希望在这里可以给大家提供一个思路。

开始说下整体步骤

咱们先来创建一个租户表,我这里简单创建几个字段,为了演示,大家根据实际需要自己调整。我这里为了演示方便租户角色直接用系统自带的角色表了,大家自己可以增加一个租户角色表。

public class Tenant : BasePoco
{
[Display(Name = "编号")]
[Required(ErrorMessage = "{0}是必填项")]
public string Code { get; set; } [Display(Name = "域名")]
[Required(ErrorMessage = "{0}是必填项")]
public string DomainName { get; set; } [Display(Name = "租户角色")]
[Required(ErrorMessage = "{0}是必填项")]
public Guid RoleId { get; set; }
[Display(Name = "租户角色")]
public FrameworkRole Role { get; set; } [Display(Name = "账号")]
[Required(ErrorMessage = "{0}是必填项")]
public string Account { get; set; } [Display(Name = "名称")]
[Required(ErrorMessage = "{0}是必填项")]
public string Name { get; set; }
}

​​

添加完租户信息后,Create方法里需要创建好 这个租户的库、基本信息和提供的域名。我这里租户的库生成规则直接就是默认用主库名+编号生成的。

[HttpPost]
[ActionDescription("Sys.Create")]
public ActionResult Create(TenantVM vm)
{
using (var trans = DC.BeginTransaction())
{
if (!ModelState.IsValid)
{
return PartialView(vm);
}
else
{
vm.DoAdd();
if (!ModelState.IsValid)
{
vm.DoReInit();
return PartialView(vm);
}
else
{
//我这代码直接写这了 生成租户库和基本信息 随便写了下简单的系统表数据
var NDC = new DataContext(Wtm.ConfigInfo.Connections[0].Value.Replace("SAASDEMODB", "SAASDEMODB" + vm.Entity.Code), DBTypeEnum.SqlServer);
var Result = NDC.Database.EnsureCreated(); if (Result)
{
var role = DC.Set<FrameworkRole>().Where(x => x.ID == vm.Entity.RoleId).FirstOrDefault();
//角色拥有的菜单权限
var pr = DC.Set<FunctionPrivilege>().Where(x => x.RoleCode == role.RoleCode).ToList();
var user = new FrameworkUser
{
ITCode = vm.Entity.Account,
Password = Utils.GetMD5String("000000"),
IsValid = true,
Name = vm.Entity.Name
}; var userrole = new FrameworkUserRole
{
UserCode = vm.Entity.Account,
RoleCode = role.RoleCode
}; NDC.Set<FrameworkUser>().Add(user);
NDC.Set<FrameworkRole>().Add(role);
NDC.Set<FrameworkUserRole>().Add(userrole);
//这里框架自带角色表 页面权限FunctionPrivilege表没有加父级菜单数据 会导致约束冲突。后期自己添加租户角色表吧
NDC.Set<FrameworkMenu>().AddRange(DC.Set<FrameworkMenu>().CheckContain(pr.Select(x => x.MenuItemId).ToList(), x => x.ID).ToList());
NDC.Set<FunctionPrivilege>().AddRange(pr);
NDC.SaveChanges(); //云解析DNS-添加解析记录
if (!new CommonHelp().DomainNameResolution(vm.Entity.Code))
{
trans.Rollback();
return FFResult().CloseDialog().RefreshGrid().Alert("域名解析失败!");
}
}
else
{
trans.Rollback();
return FFResult().CloseDialog().RefreshGrid().Alert("租户信息初始化失败!");
}
trans.Commit();
return FFResult().CloseDialog().RefreshGrid();
}
}
}
}

主要用到了一个云解析DNS-添加解析记录的方法。调用AddDomainRecord根据传入参数添加解析记录。

云解析 DNS(Domain Name System,简称DNS) 是一种安全、快速、稳定、可靠的权威DNS解析管理服务。 它能够帮助企业和开发者将易于管理识别的域名转换为计算机用于互连通信的数字IP地址,从而将用户的访问路由到相应的网站或应用服务器。

具体看文档 添加解析记录 (aliyun.com)

#region 云解析DNS-添加解析记录 可以去阿里云地址看文档 https://help.aliyun.com/document_detail/29772.html 但是这种方式服务器要求 80端口只允许部署这一套系统,因为现在这种方式是域名直接指向服务器IP
public bool DomainNameResolution(string Name)
{
IClientProfile profile = DefaultProfile.GetProfile("cn-hangzhou", "", "");//域名 AccessKeyID Secret
DefaultAcsClient client = new DefaultAcsClient(profile); var request = new AddDomainRecordRequest();
request._Value = ""; //指向服务器IP
request.Type = "A";
request.RR = Name; //随便定义
request.DomainName = "xxx.com"; //域名
try
{
var response = client.GetAcsResponse(request);
return true;
//Console.WriteLine(System.Text.Encoding.Default.GetString(response.HttpResponse.Content));
}
catch (ServerException e)
{
return false;
}
catch (ClientException e)
{
return false;
}
}
#endregion

这种方式不好的一点是 服务器要求 80端口只允许部署这一套系统,因为现在这种方式是域名直接指向服务器IP。我是部署在IIS上,需要注意的一点是应用中不要绑定主机名。(如果大家有更好的办法可以一起沟通沟通​​)

​​

到这里创建的这个租户的库和基本信息和域名就创建好了。

这个时候所有域名都可以访问到部署的系统了,但是appsettings.json文件中Connections只有一个默认的库,当然不可能添加一个租户就在这加一个连接字符串,不现实。

正好框架支持动态选择连接字符串。框架可以根据页面传递过来的数据,或者session里的信息等动态选择需要连接的数据库,只需编辑Startup文件中的CSSelector方法。

访问系统肯定会先读主库,我这里是根据域名去租户表里查,如果存在就动态添加一个ConnectionStrings,利用Wtm.Session.Set("TenantKey", CS.key);,否则就正常访问主库。

#region 获取当前url
public string GetAbsoluteUri(HttpRequest request)
{
return new StringBuilder()
.Append(request.Scheme)
.Append("://")
.Append(request.Host)
.Append(request.PathBase)
.Append(request.Path)
.Append(request.QueryString)
.ToString();
}
#endregion [Public]
[ActionDescription("Login")]
public IActionResult Login()
{
LoginVM vm = Wtm.CreateVM<LoginVM>();
string TenantKey = "default";
string displayUrl = GetAbsoluteUri(HttpContext.Request);
displayUrl = displayUrl.Replace("http://", "").Replace("https://", "") + "/";
displayUrl = displayUrl.Substring(0, displayUrl.IndexOf("/"));
var ZDC = new DataContext(Wtm.ConfigInfo.Connections[0].Value, DBTypeEnum.SqlServer);
var Tenant = ZDC.Set<Tenant>().Where(x => x.DomainName.Replace("http://", "").Replace("https://", "") == displayUrl).FirstOrDefault();
if (Tenant != null)
{
TenantKey = "SAASDEMODB" + Tenant.Code;
Wtm.Session.Set("TenantKey", "SAASDEMODB" + Tenant.Code);
}
else
{
Wtm.Session.Set("TenantKey", TenantKey);
}
int i = 0;
foreach (var item in Wtm.ConfigInfo.Connections)
{
if (item.Key == TenantKey)
{
i++;
break;
}
}
if (i == 0)
{
CS cs = new CS();
cs.DbContext = "DataContext";
cs.DbType = DBTypeEnum.SqlServer;
cs.Key = TenantKey;
cs.Value = Wtm.ConfigInfo.Connections[0].Value.Replace("SAASDEMODB", cs.Key);
cs.DcConstructor = Wtm.ConfigInfo.Connections[0].DcConstructor;
Wtm.ConfigInfo.Connections.Add(cs);
}
vm.Redirect = HttpContext.Request.Query["ReturnUrl"];
if (Wtm.ConfigInfo.IsQuickDebug == true)
{
vm.ITCode = "admin";
vm.Password = "000000";
}
return View(vm);
} Startup文件中的CSSelector方法 public string CSSelector(ActionExecutingContext context)
{
var wtm = (context.Controller as IBaseController)?.Wtm;
var TenantKey = wtm.Session.Get<string>("TenantKey");
return TenantKey;
}

OK,咱们来测试一下下。

我这里就用默认超级管理员角色创建租户了,为了添加一个租户让大家看下效果。

添加成功,访问一下租户的地址

添加一条新的角色数据,跟主库作下比较,发下数据已经隔离了。

如果你跟着测试到这一步,说明已经通了,可以自己多试试。有问题或者有好的想法,可以在群里一起沟通学习学习。

有些可能需要用到数据共享,框架本身支持在控制器中设置[FixConnection(DBOperationEnum.Default, CsName = "")]设置Cs指定连接字符串。

项目我已经上传到Gitee上了,大家可以下载看一下。

地址:wtm-layui版本多租户: wtm框架 layui版本都租户改造

下载完项目,如果想直接运行调试的话,记得去Common文件下的CommonHelp类中把DomainNameResolution方法中的参数补充全,就可以直接运行看效果了。

目前这种方式有正式的项目,目前也比较稳定。第一次写文章,希望大家多支持哈。

==========================================================

WTM框架地址 https://wtmdoc.walkingtec.cn

支持4个版本:Layui React Vue Blazor

WtmPlus是建立在WTM开源框架基础上的低代码开发平台,他提供了可视化的模型和页面编辑,更加复杂和智能的代码生成,可使开发效率提升50%以上。

感兴趣可以看一下 地址 WtmPlus

WTM多租户改造的更多相关文章

  1. 多租户实现之基于Mybatis,Mycat的共享数据库,共享数据架构

    前言 SaaS模式是什么? 传统的软件模式是在开发出软件产品后,需要去客户现场进行实施,通常部署在局域网,这样开发.部署及维护的成本都是比较高的. 现在随着云服务技术的蓬勃发展,就出现了SaaS模式. ...

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

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

  3. Spring Cloud Alibaba | Nacos集群部署

    目录 Spring Cloud Alibaba | Nacos集群部署 1. Nacos支持三种部署模式 2. 集群模式下部署Nacos 2.1 架构图 2.2 下载源码或者安装包 2.3 配置集群配 ...

  4. Docker安装Alibaba Nacos教程(单机)

    SpringCloudAlibaba实战教程系列 阿里巴巴Nacos官方文档 docker:官网 docker:镜像官网:镜像官网可以所有应用,选择安装环境:会给出安装命令,例如:docker pul ...

  5. CentOS 7 Nacos 集群搭建

    环境 CentOS 7.4 MySQL 5.7 nacos-server-1.1.2 本次安装的软件全部在 /home/javateam 目录下. MySQL 安装 首先下载 rpm 安装包,地址:h ...

  6. Docker安装Nacos动态服务发现、配置和服务管理平台

    一.通过DockerHub拉镜像,版本查看:https://github.com/nacos-group/nacos-docker //稳定版,有权限 docker pull nacos/nacos- ...

  7. Nacos集群部署:

    Nacos集群部署: 官网:    https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html 1: 下载 Nacos1.2.0 链接:http ...

  8. Linux使用docker部署nacos

    官网地址:https://nacos.io/zh-cn/docs/quick-start-docker.html 先把sql文件导入到mysql中 我也放了基础的sql /* * Copyright ...

  9. Docker 环境 Nacos2 MySQL8

    本文介绍 docker 环境下安装并单机运行 Nacos2,使用 docker 环境下的 MySQL 8 存储数据. 1 拉取镜像 1.1 创建目录 在硬盘上创建 nacos 的有关目录: mkdir ...

随机推荐

  1. 【剑指Offer】07. 重建二叉树 解题报告(Java & Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人微信公众号:负雪明烛 目录 题目描述 解题方法 基本方法:线性查找根节点的位置 方法优 ...

  2. 【LeetCode】11. Container With Most Water 盛最多水的容器

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:盛水,容器,题解,leetcode, 力扣,python ...

  3. 【剑指Offer】二叉树中和为某一值的路径 解题报告(Python)

    [剑指Offer]二叉树中和为某一值的路径 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-intervi ...

  4. SOFA 数据透析

    数据透传: 在 RPC调用中,数据的传递,是通过接口方法参数来传递的,需要接口方定义好一些参数允许传递才可以,在一些场景下,我们希望,能够更通用的传递一些参数,比如一些标识性的信息.业务方可能希望,在 ...

  5. MySQL 高级内容

    MyISAM 和 MEMORY 存储引擎支持表级锁定(table-level locking),InnoDB 存储引擎支持行级锁定(row-level locking),BDB 存储引擎支持页级锁定( ...

  6. salesforce零基础学习(一百一十)list button实现的一些有趣事情

    本篇参考: salesforce零基础学习(九十五)lightning out https://developer.salesforce.com/docs/component-library/docu ...

  7. Histogram Processing

    目录 HISTOGRAM EQUALIZATION 代码示例 HISTOGRAM MATCHING (SPECIFICATION) 其它 Gonzalez R. C. and Woods R. E. ...

  8. Proximal Algorithms 2 Properties

    目录 可分和 基本的运算 不动点 fixed points Moreau decomposition 可分和 如果\(f\)可分为俩个变量:\(f(x, y)=\varphi(x) + \psi(y) ...

  9. haproxy-详解

    负载均衡类型: 四层: LVS (Linux Virtual Server)HAProxy (High Availability Proxy)Nginx (1.9以上) 七层: HAProxyNgin ...

  10. gRPC创建Java RPC服务

    1.说明 本文介绍使用gRPC创建Java版本的RPC服务, 包括通过.proto文件生成Java代码的方法, 以及服务端和客户端代码使用示例. 2.创建生成代码工程 创建Maven工程,grpc-c ...