多租户实现原理
概述
什么是多租户
多租户(Multi-tenancy)是一种软件架构模式,支持在同一应用程序或系统中同时为多个用户或组织提供服务。在多租户架构中,每个租户都被视为相对独立的客户。租户之间共享相同的应用程序实例、硬件资源和基础设施。然而,租户的数据和配置是相互隔离的,每个租户只能访问自己的数据和配置,彼此之间互不干扰。租户可以是个人用户、企业、组织或其他实体。
多租户架构在许多云计算服务中得到广泛应用,如 SaaS(软件即服务)和 PaaS(平台即服务)。它也适用于企业内部部署的软件系统,以支持在组织内部不同部门或团队之间共享资源和服务。
需要注意的是,多租户并非适用于所有情况。在某些场景下,单租户架构可能更加合适,例如对于需要高度定制化和独立性的客户。因此,在设计和选择软件架构时,需要根据具体需求和情况来决定是否采用多租户模式。
特性
- 资源共享。由于多个租户共享同一实例,硬件资源和基础设施可以更有效地利用,从而降低了成本。
- 简化管理和维护工作。通过集中管理单个软件实例,可以更轻松地进行软件部署、升级和维护。
- 灵活性和可伸缩性。多租户架构可以根据客户需求进行扩展或收缩,从而实现更好的适应性和弹性。
实现思路
实现多租户功能需要考虑以下几个方面:
- 数据隔离:每个租户需要有自己的数据。可以通过数据库或数据库的 schema(模式)或数据库表字段来进行隔离。在 pi-admin 中,多个租户共享相同的数据库,每个表都包含
tenant_id
字段,用于区分不同的租户。 - 安全隔离:确保不同租户之间的数据和配置相互隔离,租户只能访问自己的数据。可以使用 MyBatis-Plus 的多租户插件通过为查询语句中的每个表添加
tenant_id
查询条件来实现这一点。 - 租户识别:识别不同的租户,可以通过不同的方式进行,比如在请求中添加租户标识符(在请求头或请求参数中),或者通过子域名来识别租户。在 pi-admin 中,租户编码保存在 Spring Security 的
principal
中,以此来区分不同的租户。 - 配置管理:根据租户的不同,可能需要对一些配置进行定制化。可以使用配置文件或数据库来管理租户特定的配置,然后在运行时根据租户进行加载和使用。
- 单元测试和集成测试:在实现多租户功能时,需要编写相应的单元测试和集成测试,确保不同租户之间的隔离和功能正常工作。
功能模块
企业管理:管理企业信息,用户根据企业信息新增租户,一个企业对应一个租户。
套餐管理:维护租户所拥有的的菜单。
租户管理:维护租户信息,设置租户套餐,用户数量等。
企业管理
管理企业信息,用户根据企业信息新增租户,一个企业对应一个租户。
数据库表:sys_enterprise
字段名称 | 类型 | 允许空 | 默认值 | 键 | 字段描述 |
---|---|---|---|---|---|
id | bigint unsigned | NO | PK | 主键 | |
create_time | datetime | YES | 创建时间 | ||
update_time | datetime | YES | 更新时间 | ||
create_by | varchar(16) | YES | 创建人 | ||
update_by | varchar(16) | YES | 更新人 | ||
name | varchar(128) | NO | UK | 企业名称 | |
name_en | varchar(128) | YES | 英文名称 | ||
short_name | varchar(128) | YES | 简称 | ||
usci | varchar(128) | YES | 统一社会信用代码 | ||
registered_currency | varchar(32) | YES | 注册币种 | ||
registered_capital | varchar(16) | YES | 注册资本 | ||
legal_person | varchar(16) | YES | 法人 | ||
establishing_time | datetime | YES | 成立时间 | ||
business_nature | varchar(256) | YES | 企业性质 | ||
industry_involved | varchar(256) | YES | 所属行业 | ||
registered_address | varchar(256) | YES | 注册地址 | ||
business_scope | varchar(1000) | YES | 经营范围 | ||
staff_number | varchar(32) | YES | 员工数 | ||
state | varchar(16) | YES | 状态 | ||
deleted | tinyint unsigned | YES | 0 | 是否删除(0:=未删除;null:=已删除) |
细节
新增时企业名称不能重复。
当企业成为租户时无法删除。
套餐管理
维护租户所拥有的的菜单。
数据库表:sys_package
字段名称 | 类型 | 允许空 | 默认值 | 键 | 字段描述 |
---|---|---|---|---|---|
id | bigint unsigned | NO | PRI | 主键 | |
create_time | datetime | YES | 创建时间 | ||
update_time | datetime | YES | 修改时间 | ||
create_by | varchar(16) | YES | 创建人 | ||
update_by | varchar(16) | YES | 更新人 | ||
name | varchar(64) | NO | UK | 套餐名称 | |
enabled | tinyint unsigned | YES | 1 | 状态(0:=禁用; 1:=启用) | |
deleted | tinyint unsigned | YES | 0 | 是否删除(0:=未删除;null:=已删除) | |
remark | varchar(500) | YES | 备注 |
数据库表:sys_package_menu
字段名称 | 类型 | 允许空 | 键 | 默认值 | 字段描述 |
---|---|---|---|---|---|
id | bigint unsigned | NO | PRI | 主键 | |
package_id | bigint unsigned | NO | FK | 套餐标识 | |
menu_id | bigint unsigned | NO | FK | 菜单标识 |
细节
当套餐已使用时无法删除。
套餐被禁用后无法查询,但不影响已经配置为该套餐的租户。
编辑套餐菜单时:
- 当为套餐新增了菜单时,为套餐租户管理员角色新增菜单
- 当为套餐减少了菜单时,为所有套餐租户角色删除指定菜单
租户管理
维护租户信息,设置租户套餐,用户数量等。
数据库表:sys_tenant
字段名称 | 类型 | 允许空 | 键 | 默认值 | 字段描述 |
---|---|---|---|---|---|
id | bigint unsigned | NO | 主键 | ||
create_time | datetime | YES | 创建时间 | ||
update_time | datetime | YES | 修改时间 | ||
create_by | varchar(16) | YES | 创建人 | ||
update_by | varchar(16) | YES | 更新人 | ||
tenant_code | char(6) | NO | UK | 租户编号 | |
enterprise_id | bigint unsigned | NO | FK | 企业主键 | |
enterprise_name | varchar(64) | NO | 企业名称 | ||
admin_id | bigint unsigned | NO | FK | 租户管理员主键 | |
contact | varchar(16) | NO | 联系人 | ||
account | varchar(16) | NO | UK | 账号 | |
phone | varchar(32) | YES | 手机 | ||
varchar(128) | YES | 邮箱 | |||
package_id | bigint unsigned | YES | FK | 租户套餐 | |
expires | datetime | YES | 到期时间 | ||
user_quantity | bigint | YES | -1 | 用户数量(-1:=不限制) | |
enabled | tinyint | YES | 1 | 状态(0:=禁用; 1:=启用) | |
deleted | tinyint | YES | 0 | 是否删除(0:=未删除;null:=已删除) | |
remark | varchar(500) | YES | 备注 |
新增租户
新增时后台做了一些事:
- 生成 6 位租户编码;
- 创建角色,角色名称为管理员,角色编码为 ADMIN,表示该角色为租户管理员,然后将角色与租户套餐菜单进行绑定;
- 创建部门,部门名称为企业名称;
- 创建租户管理员用户,联系人的名称为用户名称,将用户和角色、部门进行关联。
- 新增租户
编辑租户
编辑时如果修改了套餐:
- 当新套餐比原套餐新增了菜单时,为指定租户管理员角色新增菜单
- 当新套餐比原套餐减少了菜单时,为所有租户角色删除指定菜单
细节
新增或编辑租户时校验手机号、邮箱是否正确。
删除租户同时删除该租户下的用户、角色、部门。
租户禁用功能目前无效。
获取租户
工具类 me.pi.admin.common.util.SecurityUtils
提供了获取租户的方法:
String tenantId = user.SecurityUtils.getTenantId();
前端可以在 pinia 中获取:
import useStore from "@/stores"
const { useUserStore } = useStore();
const tenantId = useUserStore.tenantId
指定表忽略多租户
开发中有一些表需要忽略多租户,常见的如多对多关系的中间表。可以在 mybatis-plus.tenant.ignore
中配置需要忽略的表名:
mybatis-plus:
tenant:
ignores:
- sys_user_role
- sys_role_dept
指定语句忽略多租户
对指定的语句忽略多租户,可以在 Mapper 文件的方法上标注 @InterceptorIgnore(tenantLine = "true")
注解:
@InterceptorIgnore(tenantLine = "true")
Set<TenantRoleMenuDTO> getTenantRoleMenu(@Param("tenantId") Long tenantId);
租户登录
使用租户账号登录时,就不能再像以前一样使用用户名来检索用户信息了。取而代之的是,在登录界面,如果输入的用户名关联了多个租户,需要用户自己决定使用哪个租户来进行登录。登录时后台根据用户名以及租户编码来检索用户信息。
需要注意的是,租户登录成功后查看菜单时,查看的是套餐关联的菜单,因为菜单不受多租户的约束,并且租户无法新增、修改或删除菜单。
总结
多租户是一种软件架构模式,支持在同一应用程序或系统中同时为多个用户或组织提供服务。实现多租户功能需要考虑数据隔离、安全隔离、租户识别和配置管理等。
在 pi-admin 中,多租户功能分成了三个模块,分别是企业管理、套餐管理和租户管理。它们需要注意的细节如下:
企业管理:
- 新增时企业名称不能重复。
- 当企业成为租户时无法删除。
套餐管理:
-
当套餐已使用时无法删除。
-
套餐被禁用后无法查询,但不影响已经配置为该套餐的租户。
-
编辑套餐菜单时:
-
当为套餐新增了菜单时,为套餐租户管理员角色新增菜单
-
当为套餐减少了菜单时,为所有套餐租户角色删除指定菜单
-
租户管理:
-
编辑时如果修改了套餐:
-
当新套餐比原套餐新增了菜单时,为指定租户管理员角色新增菜单
-
当新套餐比原套餐减少了菜单时,为所有租户角色删除指定菜单
-
-
新增或编辑租户时校验手机号、邮箱是否正确。
-
删除租户同时删除该租户下的用户、角色、部门。
需要注意的是,租户登录成功后查看菜单时,查看的是套餐关联的菜单,因为菜单不受多租户的约束,并且租户无法新增、修改或删除菜单。
项目实战
基于 Spring Boot 2.7.12、MyBatis-Plus、Spring Security 等主流技术栈构建的后台管理系统:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性