WTM多租户改造
首先简单说下多租户的几种实现方式
多租户(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多租户改造的更多相关文章
- 多租户实现之基于Mybatis,Mycat的共享数据库,共享数据架构
前言 SaaS模式是什么? 传统的软件模式是在开发出软件产品后,需要去客户现场进行实施,通常部署在局域网,这样开发.部署及维护的成本都是比较高的. 现在随着云服务技术的蓬勃发展,就出现了SaaS模式. ...
- 一种Django多租户解决方案
什么是多租户? 多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性. 多租 ...
- Spring Cloud Alibaba | Nacos集群部署
目录 Spring Cloud Alibaba | Nacos集群部署 1. Nacos支持三种部署模式 2. 集群模式下部署Nacos 2.1 架构图 2.2 下载源码或者安装包 2.3 配置集群配 ...
- Docker安装Alibaba Nacos教程(单机)
SpringCloudAlibaba实战教程系列 阿里巴巴Nacos官方文档 docker:官网 docker:镜像官网:镜像官网可以所有应用,选择安装环境:会给出安装命令,例如:docker pul ...
- CentOS 7 Nacos 集群搭建
环境 CentOS 7.4 MySQL 5.7 nacos-server-1.1.2 本次安装的软件全部在 /home/javateam 目录下. MySQL 安装 首先下载 rpm 安装包,地址:h ...
- Docker安装Nacos动态服务发现、配置和服务管理平台
一.通过DockerHub拉镜像,版本查看:https://github.com/nacos-group/nacos-docker //稳定版,有权限 docker pull nacos/nacos- ...
- Nacos集群部署:
Nacos集群部署: 官网: https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html 1: 下载 Nacos1.2.0 链接:http ...
- Linux使用docker部署nacos
官网地址:https://nacos.io/zh-cn/docs/quick-start-docker.html 先把sql文件导入到mysql中 我也放了基础的sql /* * Copyright ...
- Docker 环境 Nacos2 MySQL8
本文介绍 docker 环境下安装并单机运行 Nacos2,使用 docker 环境下的 MySQL 8 存储数据. 1 拉取镜像 1.1 创建目录 在硬盘上创建 nacos 的有关目录: mkdir ...
随机推荐
- 【LeetCode】889. Construct Binary Tree from Preorder and Postorder Traversal 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- DP? (hdu3944)
DP? Time Limit: 10000/3000 MS (Java/Others) Memory Limit: 128000/128000 K (Java/Others)Total Subm ...
- 1145 - Dice (I)
1145 - Dice (I) PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 MB You hav ...
- 洛谷 P1439 【模板】最长公共子序列(DP,LIS?)
题目描述 给出1-n的两个排列P1和P2,求它们的最长公共子序列. 输入输出格式 输入格式: 第一行是一个数n, 接下来两行,每行为n个数,为自然数1-n的一个排列. 输出格式: 一个数,即最长公共子 ...
- 第二十个知识点:Merkle-Damgaard hash函数如何构造
第二十个知识点:Merkle-Damgaard hash函数如何构造 这里讲的是MD变换,MD变换的全称为Merkle-Damgaard变换.我们平时接触的hash函数都是先构造出一个防碰撞的压缩函数 ...
- uniapp使用uni.openDocument打开文件时,安卓打开成功,iOS打开失败【原因:打开的文件的文件名是中文】
解决办法:使用escape进行文件名编码 uni.downloadFile({ url: url, success: function(res) { var filePath = res.tempFi ...
- CS5211替代PS8625|设计DP转LVDS转接板|替代PS8625方案
1.CS5211与PS8625功能概述 CS5211是一个eDP到LVDS转换器,配置灵活,适用于低成本显示系统.CS5211与eDP 1.2兼容,支持1通道和2通道模式,每通道速度为1.62Gbps ...
- 基于Spring MVC + Spring + MyBatis的【图书资源管理系统】
资源下载:https://download.csdn.net/download/weixin_44893902/45598347 练习点设计:模糊查询.删除.新增 一.语言和环境 实现语言:JAVA语 ...
- Java中List与数组互相转换
1.说明 在Java中,经常遇到需要List与数组互相转换的场景. List转换成数组,可以使用List的toArray()或者toArray(T[] a)方法. 数组转换成List,可以使用Arra ...
- MySQL启用SSL连接
1.手动创建自认证证书 1.1 创建CA证书 openssl genrsa 2048 > ca-key.pem openssl req -new -x509 -nodes -days 3600 ...