第十七章. 平台核心:多租户管理(SaaS 架构的基石)

第十七章. 平台核心:多租户管理(SaaS 架构的基石)

摘要:本章我们将深入 RVP 5.x 架构的“灵魂”——多租户管理。我们将从 0 到 1 完整地构建一个“租户”,并深入剖析 RVP 是如何通过 “共享数据库、表级隔离”tenant_id)这一核心模式,来实现 SaaS 平台的数据隔离、菜单隔离与权限管理。


本章学习路径

我们将从“SaaS 架构”的理论出发,完整演练“创建套餐 -> 创建租户 -> 租户登录 -> 数据隔离”的全流程:

多租户管理


17.1. [核心] RVP 的多租户模型:共享数据库与表级隔离

“多租户”(Multi-Tenancy)是 SaaS(Software as a Service,软件即服务)架构的核心概念。

它允许我们用 一套 软件系统,同时服务于 多个 客户(即“租户”),而每个租户都以为自己是 独占 这套系统的。

  • 租户:可以是一个企业(如 A 公司、B 公司),一个组织,甚至一个个人。

17.1.1. 多租户的“独立数据库” vs “共享数据库”

实现“数据隔离”(A 公司看不到 B 公司的数据)主要有两种模式:

  1. 独立数据库模式(物理隔离)

    • 原理:每个租户(A 公司)都有一个 完全独立 的数据库。
    • 优点:隔离性最强,绝对安全,性能互不干扰。
    • 缺点成本极高。1000 个租户就需要 1000 个数据库实例,维护和升级将是灾难。
  2. 共享数据库模式(逻辑隔离)

    • 原理:所有租户(A、B、C…)共享同一个数据库,甚至共享同一套数据表。
    • 优点:成本极低,维护简单,易于扩展。
    • 缺点:隔离性依赖程序设计,一旦代码出错(比如 SQL 忘了加 WHERE 条件),可能导致数据泄露。

17.1.2. RVP 的选择:基于 MyBatis-Plustenant_id 隔离

RVP 5.x(作为一个追求高性价比、易于维护的框架)毫不意外地选择了 “共享数据库,表级隔离” 模式。

这是 RVP 实现数据隔离的流程:

  1. 在所有“需要隔离”的业务表(如 sys_usersys_dept)上,都增加一个字段:tenant_id(租户 ID)。
  2. A 公司(租户 000001)创建的用户,tenant_id 字段 全部000001
  3. B 公司(租户 000002)创建的用户,tenant_id 字段 全部000002
  4. RVP 后端利用 MyBatis-Plus 的“多租户插件”,在 系统底层“劫持”了 每一条 发出的 SQL。

当 A 公司的管理员登录后,他执行 SELECT * FROM sys_user
MyBatis-Plus 插件会自动将其 改写 为:
SELECT * FROM sys_user WHERE tenant_id = '000001'

这一切对业务开发者是完全透明的! 你在写 mapper.selectList(null) 时,根本不需要关心 tenant_id,RVP 框架会自动帮你完成“数据过滤”,确保租户 A 永远(在理论上)只能看到 tenant_id = '000001' 的数据。

17.1.3. “排除表”:哪些表是“全局共享”的?

理解了 tenant_id 的自动注入,我们立刻会想到一个问题:
“难道所有表都要加 tenant_id 吗?”

当然不是。有些表是“平台级”的,是所有租户 共享 的,绝不能tenant_id 隔离。

最典型的例子:

  • sys_menu(菜单管理):系统的“功能蓝图”只有一套。如果 A 租户能改菜单,B 租户的系统就乱套了。
  • sys_client(客户端管理):PC 端、APP 端的 Token 策略是全局的。
  • sys_config(参数设置):全局参数是平台维护者(超级管理员)管理的。

在 RVP 的 application.yml 配置文件中,有一个 tenant 配置项,里面明确定义了 ignore-tables(排除表),这些表将被 MyBatis-Plus 插件“放行”,不会 自动拼上 tenant_id 条件。


17.2. 租户套餐管理(sys_tenant_package):定义“卖什么”

理解了 RVP 的“SaaS 平台”本质,我们(作为平台方)要卖“产品”了。

“租户套餐”(sys_tenant_package)就是我们定义“产品规格”的地方。

  • 套餐 A(基础版):每月 199 元,只提供“用户管理”、“部门管理”。
  • 套餐 B(专业版):每月 999 元,提供“用户管理”、“部门管理”、“系统工具”、“代码生成”等。

我们点击 系统管理 -> 租户管理 -> 租户套餐管理 来创建这些“产品”。

17.2.1. “套餐”的本质:一个“菜单 ID 集合”

点击“新增”,我们会看到一个非常熟悉的界面:

  • 套餐名称测试套餐(系统管理)
  • 关联菜单:一个“菜单权限树”。

“套餐”的本质,就是一个 menu_ids(菜单 ID 集合)的“快照”

当我们为租户 A 分配这个“测试套餐”时,RVP 做的事情就是:

  1. 记录租户 A 购买了“测试套餐”。
  2. 租户 A 的管理员登录时,系统会告诉他:“根据你的套餐,你只能看到这些 menu_id 对应的功能”。

例如,我们创建一个“测试套餐”,只勾选 系统管理 下的所有菜单。再创建“测试套餐二”,只勾选 系统工具 下的 代码生成

17.2.2. [重点] 隔离红线:哪些“全局菜单”绝不能分配给租户?

在勾选“关联菜单”时,有一个 致命的“红线”,这是 SaaS 平台能否安全运行的 关键

我们必须牢记:租户 只是“功能的使用者”,而 超级管理员 是“平台的维护者”。

那些“平台级”的、全局共享 的、绝不能tenant_id 隔离的模块,绝对、绝对不能 分配给租户。

以下菜单是“平台级”配置,一旦分配给租户,租户 A 的修改将 立刻影响 到租户 B,导致平台崩溃:

  1. 系统管理 -> 菜单管理
    • 后果:租户 A 修改了菜单,租户 B 的菜单也变了,平台功能全乱。
  2. 系统管理 -> 客户端管理
    • 后果:租户 A 修改了 PC 端的 Token 超时,所有租户的 PC 端都受影响。
  3. 系统管理 -> 文件管理 -> 配置管理
    • 后果:租户 A 换了 MinIO 的 AK/SK,所有租户的文件都上传失败了。
  4. 租户管理(整个模块)
    • 后果:租户 A 能看到“租户 B”、“租户 C”,甚至能把他们删了。
  5. 系统监控 -> Admin 监控 / 任务调度中心
    • 后果:租户 A 能看到平台的“心脏”(服务监控、定时任务),这是最高安全风险。

[结论] 在定义“套餐”时,我们 只应该 分配那些 纯业务模块已被 tenant_id 隔离 的“系统管理”功能(如 用户管理, 角色管理, 部门管理, 字典管理, 参数设置, 日志管理 等)。


17.3. 租户管理(sys_tenant):创建“新客户”

我们定义好了“产品”(套餐),现在就可以“开客户”了。

我们点击 租户管理 -> 租户管理

我们会看到一条默认的数据 租户编号 000000,企业名称 XXX 有限公司
这就是“平台方”,也就是“超级管理员”自己所在的“默认租户”。

image-20251112213628648

这条 000000 租户的数据是 RVP 运行的基石,它是“神”,不可修改、不可禁用、不可删除

17.3.1. [核心] 创建租户的“三要素”

我们点击“新增”,来创建我们的第一个“客户”(租户 A)。

这个表单看似复杂,但核心只有“三要素”:

  1. 企业信息(租户本身)
    • 企业名称A 企业
    • 联系人/电话Prorise / 138...
  2. 租户套餐(购买的产品)
    • 选择套餐测试套餐(系统管理)
  3. 租户管理员(客户的第一个账号)
    • 用户名prorise_admin
    • 用户密码admin123

这个“三要素”的流程至关重要:

image-20251112213946399

当我们点击“确定”时,RVP 在 一个事务 中,至少做了 三件 大事:

  1. sys_tenant 表中,插入“A 企业”的记录,生成一个新的 tenant_id(例如 100001)。
  2. sys_user 表中,创建一个新用户 prorise_admin,并将其 tenant_id 设为 100001
  3. sys_role / sys_user_role 表中,为 tenant_id = '100001' 的环境,创建一个“管理员”角色,并将其分配给 prorise_admin
  4. (可能)将“超级管理员”的 sys_dict 等“可隔离”的配置表,复制一份并标记 tenant_id = '100001',作为 A 企业的“初始数据”。

17.3.2. 租户的“过期时间”与“绑定域名”

在新增表单中,还有两个重要的 SASS 运营配置:

  • 过期时间
    • 如果不填,则“永不过期”。
    • 如果设置了(例如 2025-11-12),则到期后,该租户将 无法登录 系统。这是实现“SaaS 续费”功能的基础。
  • 绑定域名
    • 这是一个高级功能。
    • 如果留空,所有租户都访问同一个登录页(http://saas.com),登录时 必须手动选择 自己是“A 企业”还是“B 企业”。
    • 如果为“A 企业”绑定域名 a.com。当用户访问 a.com 时,RVP 会自动识别域名,锁定“A 企业”,登录框 不再 显示“租户选择”,体验更佳。

17.4. 租户登录与数据隔离验证

在 17.3 节中,我们(作为超级管理员)成功创建了“A 企业”租户,并为其分配了“测试套餐”和一个管理员账号 Prorise

image-20251112214440988

现在,我们是时候“扮演”这个新租户,来亲身验证 RVP 的“隔离”是否真的生效了。

17.4.1. 登录界面的“租户选择”

我们打开一个新的浏览器(或无痕窗口),访问 RVP 的登录页。我们会发现一个在 5.x 版本中 新增 的变化:在“登录”按钮上方,出现了一个“租户选择”下拉框。

  • 默认租户XXX 有限公司tenant_id = 000000
  • 新租户A 企业tenant_id = 100001

这个下拉框就是“SaaS 平台”的入口。我们必须先“选择”我们要登录哪家公司,然后再输入“这家公司”的账号密码。

  1. 选择租户A 企业
  2. 用户名prorise_admin
  3. 密码admin123
  4. 点击“登录”。

17.4.2. 菜单隔离验证:租户只看到“套餐”内的菜单

登录成功!我们首先观察左侧的“菜单栏”。

  • 现象:我们 只能 看到“系统管理”这一个顶级菜单。
  • 对比:超级管理员能看到 系统管理系统监控系统工具租户管理 等所有菜单。

image-20251112214631884

这就是“菜单隔离”

原理prorise 登录时,RVP 系统会检查:

  1. 他属于“A 企业”租户。
  2. “A 企业”租户购买的是“测试套餐”。
  3. “测试套餐”的“菜单 ID 集合”快照中,只包含系统管理 及其子菜单。
  4. 因此,RVP 只会给这个 prorise 用户返回这些菜单的路由。

17.4.3. [核心] 数据隔离验证:租户的“用户/部门”与超管的“用户/部门”

“菜单隔离”只是第一层。真正的“SaaS 隔离”核心,是“数据隔离”

我们点击 prorise 账号下的 系统管理 -> 用户管理

  • 现象
    • 用户列表:我们 只能 看到 prorise_admin一个 用户。
    • 部门树:我们 只能 看到 A 企业一个 根节点。

image-20251112214707341

我们再对比一下“超级管理员”登录时看到的“用户管理”:

  • 用户列表admin, ry, test… 一大堆平台用户。
  • 部门树XXX 科技, 深圳总公司, 长沙分公司… 庞大的组织架构。

这就是“数据隔离”

原理:此时 prorise_admintenant_id = 100001)发起了 SELECT * FROM sys_user 请求。
RVP 底层的 MyBatis-Plus 插件 自动 将其改写为:
SELECT * FROM sys_user WHERE tenant_id = '100001'

他自然只能看到自己租户下的数据。
prorise_admin 现在可以在他 自己 的“一亩三分地”里当“皇帝”。他可以在 A 企业 这个根节点下,新增自己的 A-研发部A-市场部;他也可以在自己的“用户管理”中,新增 A-员工1A-员工2

他新增的所有数据,tenant_id 都会被自动标记为 100001永远不会 被“超级管理员”(000000)或其他租户(100002)“看到”或“污染”。


17.5. [重点] 套餐同步与权限边界

我们在 17.2 节“隔离红线”中犯了一个“错误”:我们把 菜单管理文件配置客户端管理 也分配给了“测试套餐”。

现在 prorise_admin 登录后,他也能看到这些“平台级”菜单,这是 极其危险 的。

我们现在必须“纠正”这个错误。

17.5.1. “同步套餐”:修改“模板”后,如何更新“实例”

  1. 超级管理员 登录。
  2. 进入 租户套餐管理,找到“测试套餐”,点击“修改”。
  3. 在“关联菜单”树中,取消勾选 菜单管理客户端管理 以及 文件管理 下的“配置管理”相关权限。
  4. 点击“确定”保存。

问题来了:此时,我们切换到 prorise_admin 的浏览器,刷新页面(甚至退出重登)。

  • 现象prorise_admin 依然 能看到 菜单管理 等危险菜单!

RVP 的“套餐”是一个“模板”。租户在创建时,只是“复制”了模板当时的“菜单 ID 快照”。

你现在修改“模板”,并 不会 自动更新所有“已售出”的“实例”(租户)。

[重点] 解决方案:“同步套餐”
我们必须(作为超级管理员)手动 去“推送”这个更新。

  1. 进入 租户管理 -> 租户管理
  2. 找到 A 企业 这一行。
  3. 在“操作”列中,点击 ... 更多,找到一个关键按钮:“同步套餐”。
  4. 点击它,确认“同步”。

image-20251112215303717

验证:此时,我们再让 prorise_admin 退出并重新登录
结果菜单管理客户端管理 等危险菜单,终于消失了

17.5.2. 再次强调:权限的“红线”

这个“同步”的实操,再次验证了 17.2 节的“安全红线”是多么重要。

在设计 SaaS 平台的“套餐”时,必须 在第一步就规划好“哪些能给,哪些绝不能给”。

  • 租户(使用者)权限用户管理角色管理部门管理岗位管理字典管理参数设置日志管理通知公告 以及 所有业务模块(如 订单管理CRM)。
  • 平台(维护者)权限菜单管理客户端管理OSS配置租户管理监控调度

17.6. 超级管理员的“租户切换”

prorise_admin(租户 A)的浏览器中,右上角只有“个人中心”、“退出登录”等。但在“超级管理员”的浏览器中,右上角多了一个“租户切换”的下拉框。

这个功能是 RVP 多租户的“上帝模式”(God Mode)。

image-20251112215706838

17.6.1. “切换租户”不是“切换用户”

我们必须搞清楚一个核心区别:当超级管理员(admin)从 XXX 有限公司 切换到 A 企业 时:

  • 错误理解:我“登录成”了 prorise_admin
  • 正确理解:我(admin依然是 admin,我的 身份(User)和 权限(Role)没有变,但我看 数据 的“视角”(tenant_id)变了。

[核心] 原理:
“租户切换”功能,仅仅是admin 的 Session(或 Token)中,动态修改 了 RVP 框架所使用的 tenant_id 上下文。

  • 切换前:MyBatis-Plus 插件使用 tenant_id = '000000'
  • 切换后:MyBatis-Plus 插件使用 tenant_id = '100001'

17.6.2. 作为“平台客服”的运维视角

这个“上帝模式”的真正价值,在于“平台运维”。

场景:租户 A 的管理员 prorise_admin 打电话投诉:“我在我的‘用户管理’里,看不到我昨天新增的员工!”

你(超管)问他要 prorise_admin 的密码,登录他的账号去排查。

RVP 高效运维(God Mode)

  1. 你(超管)登录自己的 admin 账号。
  2. 右上角“租户切换”到 A 企业
  3. 打开 系统管理 -> 用户管理
  4. 现象:你(admin)的 菜单 还是“平台级”的(你依然能看到“租户管理”),但你的 数据 已经切换到了 A 企业 的视角。
  5. 你立刻在“用户列表”中看到了租户 A 的数据,发现那个“员工”被误“停用”了。
  6. 你(admin)利用自己的“平台级”权限,跨越 租户 A 的权限限制,直接帮他点击了“启用”。
  7. 排查完毕
  8. 你(admin)切换回 XXX 有限公司(默认租户),继续你的平台管理工作。

这就是“租户切换”的精髓:超级管理员(平台方)在 不丢失自己“上帝”权限 的前提下,可以 随意切换“数据视野”,去审查、管理、维护 任何一个 租户的数据。