Skip to content

SaaS 多租户 ​

一套库给多家组织用。隔离方式是字段隔离:同一张表用 tenant_id 区分,登录用户默认只能看见本租户的行。透明能力在 yudao-spring-boot-starter-biz-tenant,覆盖 Web、Security、DB、Redis、Job、MQ。管理端前缀 /admin-api。

示意图:Header 写入上下文,拦截器给 SQL 拼本租户。

怎么配 ​

菜单 系统管理 → 租户管理。官方列表页 ruoyi-office-vben/apps/web-antd/src/views/system/tenant/。套餐页在 src/views/system/tenantPackage/。

新建租户会建租户管理员和角色,再按套餐灌菜单、流程模型、默认角色、组织骨架。packageId = 0 是系统内置租户,不走套餐。

租户列表

本系统截图:系统管理 → 租户。名称、套餐、过期时间、状态。

套餐编辑四个 Tab,见 tenantPackage/modules/form.vue:

Tab固化什么表
基本信息与菜单套餐名、勾选 system_menusystem_tenant_package
流程模型按分类勾选流程,快照进资产system_tenant_package_asset,asset_type = BPM_MODEL
默认角色角色模板ROLE_TEMPLATE
组织骨架根部门、岗位,缺省「总公司」ORG_SKELETON

流程要先保存套餐拿到 packageId,再固化。新建租户时 TenantServiceImpl 插入 system_tenant,再用 TenantUtils.execute 切到该租户建管理员,并写一条 CREATE 类型的 system_tenant_transfer_job 异步灌资产。已有租户用「补齐套餐」,只增不覆盖。

租户套餐

本系统截图:系统管理 → 租户套餐。点「修改」后四个 Tab:基本信息与菜单、流程模型、默认角色、组织骨架。流程要先保存再固化。

名称说明仓库路径
TenantDO租户主表,自身 @TenantIgnoreyudao-module-system-server/.../dal/dataobject/tenant/TenantDO.java
TenantPackageDO套餐与菜单同目录 TenantPackageDO.java
TenantPackageAssetDO套餐资产表 system_tenant_package_asset
管理端@RequestMapping("/system/tenant").../controller/admin/tenant/TenantController.java
套餐/system/tenant-packageTenantPackageController.java
编排建租户灌资产TenantPackageInitOrchestrator

websites 可填管理端域名或小程序 appId。登录页按 host 调 /system/tenant/get-by-website,拿到 tenant-id。

开关 ​

前后端各有一个开关,要一起改。

名称说明仓库路径
yudao.tenant.enable默认 true。false 关闭后端多租户ruoyi-office/yudao-server/src/main/resources/application.yaml
VITE_APP_TENANT_ENABLE登录页是否走租户ruoyi-office-vben/apps/web-antd/.env

只有一家公司用时,两边都设 false。不要先删代码。真要拆干净,见 删除功能。

同文件还有 ignore-urls、ignore-tables、ignore-visit-urls、ignore-caches。

请求头与切换 ​

默认每个请求必须带 Header tenant-id,值是 system_tenant.id。PC 在 apps/web-antd/src/api/request.ts 写入。不带且不在忽略名单,会报「租户的请求未传递」。

名称说明仓库路径
TenantContextWebFilter从 Header 写入上下文yudao-spring-boot-starter-biz-tenant/.../web/TenantContextWebFilter.java
TenantContextHolderTransmittableThreadLocal同模块 .../context/TenantContextHolder.java

登录按名称查租户、按域名换皮这类接口,方法上加 @TenantIgnore。Controller 上的注解启动时扫进忽略 URL。积木报表、资产扫码短链带不上 tenant-id,必须忽略。

TenantSecurityWebFilter:已登录用户不能拿别人的 tenant-id 越权;校验租户是否停用、过期。

拥有 system:tenant:visit 的用户可以切到别的租户看数据。前端额外带 visit-tenant-id,TenantVisitContextInterceptor 把上下文改成访问租户,请求结束后改回。个人信息、登录态不能跟切换走,路径放进 ignore-visit-urls(/admin-api/system/user/profile/**、/admin-api/system/auth/**)。权限点挂在「租户管理 → 租户切换」。

搬家分层 ​

跨环境搬家走 系统管理 → 租户 的导出 / 导入。始终新建 tenant_id,主键重映射。不迁 Flowable 运行实例和待办;业务单上的 process_instance_id 导入时清空。先办结源租户在途审批再搬。

层内容
L0 租户信息名称、套餐、品牌 Logo
L1 组织权限部门、岗位、角色、用户(导入未完成不要登录)
L2 流程模型分类与可移植定义,导入后自动发布
LBiz 业务表带 tenant_id 的业务表,排除引擎表和日志
L5 附件元数据附件行;文件本体可选

接口在 TenantTransferController:/system/tenant/transfer/export|import|job-get|download。菜单、字典不随包走,依赖目标环境版本。

SQL 与二开 ​

要隔离的业务表:加 tenant_id 列,DO 继承 TenantBaseDO。

java
public abstract class TenantBaseDO extends BaseDO {
    private Long tenantId;
}

不要隔离的表:写进 yudao.tenant.ignore-tables,或 DO 上标 @TenantIgnore。漏配时拦截器仍拼 tenant_id,没有这一列的表会报错。system_user_role 必须忽略,见 权限体系。

TenantDatabaseInterceptor 接 MyBatis Plus TenantLineInnerInterceptor。手写 Mapper XML 经常拼不上,要自己带条件。类内部 this.xxx() 不会走 @TenantIgnore 代理。

TenantUtils.execute(tenantId, runnable) 临时切租户;executeIgnore 整段不拼 tenant_id。创建租户、跨租户灌数据用前者。

Redis 走 Spring Cache 时,TenantRedisCacheManager 把缓存名收成 name:tenantId。Job 加 @TenantJob 才会按租户循环,方法必须幂等。MQ / Feign 发送时带 tenant-id。

配置与操作 ​

管理员在 租户列表 / 套餐 建租户、勾菜单和流程。忽略表和 URL 改 yudao.tenant.ignore-* 后重启。Job 加 @TenantJob。开启见 框架层。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版