7.5 KiB
7.5 KiB
多租户表隔离策略说明
📋 概述
本文档说明系统中各业务表的多租户隔离策略,帮助理解哪些表需要租户隔离,哪些表应该共享。
🎯 设计原则
1. 数据隔离(Tenant Isolation)
- 租户私有数据必须严格隔离
- 通过
tenant_id字段实现 - MyBatis-Plus 多租户插件自动添加过滤条件
2. 功能共享(Feature Sharing)
- 系统功能定义应该标准化
- 避免重复数据和维护成本
- 通过角色和权限控制访问
3. 灵活配置(Flexible Configuration)
- 通过配置文件控制隔离策略
- 可随时调整隔离范围
- 零成本切换单租户/多租户
📊 表隔离策略
✅ 需要租户隔离的表
这些表存储租户私有数据,必须添加 tenant_id 字段:
| 表名 | 说明 | 隔离原因 |
|---|---|---|
sys_user |
用户表 | 用户属于特定租户,数据必须隔离 |
sys_role |
角色表 | 角色是租户自定义的,不同租户角色不同 |
sys_dept |
部门表 | 部门结构是租户私有的组织架构 |
sys_notice |
通知公告表 | 通知是租户内部的信息 |
sys_log |
系统日志表 | 日志记录租户的操作行为 |
sys_role_menu |
角色菜单关联表 | 角色是租户隔离的,关联表也需要隔离 |
sys_user_role |
用户角色关联表 | 用户和角色都是租户隔离的 |
ai_command_record |
AI命令记录表 | 命令记录是租户私有数据 |
实现方式:
-- 添加 tenant_id 字段
ALTER TABLE `sys_user`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
-- 初始化为默认租户
UPDATE `sys_user` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
❌ 不需要租户隔离的表
这些表存储系统公共数据,应该所有租户共享:
| 表名 | 说明 | 共享原因 |
|---|---|---|
sys_tenant |
租户表 | 租户表本身不能隔离 |
sys_menu |
菜单表 | 功能入口定义,标准化共享 |
sys_dict |
字典表 | 系统字典通常是标准化的 |
sys_dict_item |
字典项表 | 字典值应该统一 |
sys_config |
系统配置表 | 系统级配置应该全局统一 |
配置方式:
youlai:
tenant:
enabled: true
ignore-tables:
- sys_tenant # 租户表本身
- sys_menu # 菜单表(重点!)
- sys_dict # 字典表
- sys_dict_item # 字典项表
- sys_config # 系统配置表
🔍 重点说明:为什么菜单不隔离?
问题背景
-- 错误示例:如果菜单隔离,会产生大量冗余
租户A的菜单:
- 系统管理 → 用户管理 → 角色管理
租户B的菜单:
- 系统管理 → 用户管理 → 角色管理
租户C的菜单:
- 系统管理 → 用户管理 → 角色管理
(完全相同的菜单定义重复了3次!)
推荐方案:菜单共享 + 角色控制
1. 菜单定义共享
所有租户共享同一套菜单定义:
├─ 系统管理
│ ├─ 用户管理
│ ├─ 角色管理
│ ├─ 菜单管理
│ └─ 租户管理
├─ 业务管理
│ ├─ 订单管理
│ └─ 商品管理
2. 权限通过角色控制
// 租户A的管理员角色
角色:租户A管理员
权限:系统管理、业务管理(全部菜单)
// 租户A的普通员工角色
角色:租户A员工
权限:业务管理(部分菜单)
// 租户B的管理员角色
角色:租户B管理员
权限:系统管理、业务管理(全部菜单)
3. 优势
| 维度 | 菜单共享 | 菜单隔离 |
|---|---|---|
| 数据量 | ✅ 少量 | ❌ 大量冗余 |
| 升级维护 | ✅ 一次升级 | ❌ 需迁移所有租户 |
| 管理成本 | ✅ 低 | ❌ 高 |
| 功能一致性 | ✅ 保证统一 | ⚠️ 可能不一致 |
| 定制能力 | ⚠️ 通过角色实现 | ✅ 每租户独立 |
💡 权限控制流程
用户访问菜单的流程
graph TD
A[用户登录] --> B[获取用户角色]
B --> C{角色是否有权限?}
C -->|是| D[显示菜单]
C -->|否| E[隐藏菜单]
style A fill:#e1f5ff
style D fill:#d4edda
style E fill:#f8d7da
示例代码
// 1. 菜单定义(所有租户共享)
sys_menu:
id: 1, name: "用户管理", perm: "sys:user:list"
// 2. 租户A的角色(租户隔离)
sys_role (tenant_id=1):
id: 10, name: "管理员", tenant_id: 1
// 3. 角色菜单关联(租户隔离)
sys_role_menu (tenant_id=1):
role_id: 10, menu_id: 1, tenant_id: 1
// 查询时自动过滤
SELECT t3.perm, t2.code
FROM sys_role_menu t1
INNER JOIN sys_role t2 ON t1.role_id = t2.id
AND t2.tenant_id = 1 -- ✅ 角色租户过滤
INNER JOIN sys_menu t3 ON t1.menu_id = t3.id
-- ❌ 菜单不需要租户过滤(通过 ignore-tables 配置)
WHERE t1.tenant_id = 1 -- ✅ 关联表租户过滤
🔧 配置示例
application-dev.yml
youlai:
tenant:
# 启用多租户
enabled: true
# 租户字段名
column: tenant_id
# 默认租户ID
default-tenant-id: 1
# 忽略多租户过滤的表(重点配置)
ignore-tables:
- sys_tenant # 租户表本身
- sys_menu # 菜单表(所有租户共享)
- sys_dict # 字典表
- sys_dict_item # 字典项表
- sys_config # 系统配置表
⚠️ 常见问题
Q1: 如果需要为不同租户定制菜单怎么办?
A: 有两种方案:
方案1: 通过角色权限控制(推荐)
租户A看到:菜单A、B、C(通过角色权限配置)
租户B看到:菜单A、B(通过角色权限配置)
方案2: 菜单隔离(不推荐)
# 将 sys_menu 从 ignore-tables 中移除
ignore-tables:
- sys_tenant
# - sys_menu # 注释掉,启用菜单隔离
# 然后执行 SQL 添加 tenant_id
ALTER TABLE sys_menu
ADD COLUMN tenant_id bigint DEFAULT 1;
Q2: 如果后端报错 Unknown column 't3.tenant_id' 怎么办?
A: 这个错误说明:
- ❌
sys_menu表没有tenant_id字段 - ❌ 但配置文件中没有将
sys_menu添加到ignore-tables - ✅ 解决方案:将
sys_menu添加到ignore-tables(本文档已说明)
Q3: 字典表需要隔离吗?
A: 通常不需要,原因:
- 字典是系统标准配置(如:性别、状态等)
- 所有租户应该使用统一的字典定义
- 如果需要租户级字典,可以单独创建
tenant_dict表
📝 总结
核心原则
- 数据隔离:用户、角色、部门等业务数据必须隔离
- 功能共享:菜单、字典、配置等系统定义应该共享
- 权限控制:通过角色和权限实现访问控制
最佳实践
✅ 推荐做法:
- 菜单定义共享
- 角色租户隔离
- 通过角色控制菜单访问权限
❌ 不推荐做法:
- 为每个租户复制菜单
- 菜单和角色都隔离但逻辑相同
- 升级时需要迁移所有租户的菜单
🔗 相关文档
- 多租户用户管理改进说明
- tenant_add.sql - 多租户SQL脚本
- TenantProperties.java - 配置类
更新时间:2025-12-12 版本:v3.0.0