# 菜单数据权限使用文档
## 📖 目录
1. [功能介绍](#功能介绍)
2. [架构说明](#架构说明)
3. [开发指南](#开发指南)
4. [配置指南](#配置指南)
5. [注意事项](#注意事项)
6. [完整示例](#完整示例)
7. [常见问题](#常见问题)
---
## 功能介绍
### 什么是菜单数据权限?
菜单数据权限是一种**基于菜单的动态数据过滤机制**,允许管理员为不同的角色配置不同的数据查询规则,实现细粒度的数据权限控制。
### 核心特性
- ✅ **动态配置**:无需修改代码,通过页面配置即可实现数据过滤
- ✅ **基于角色**:不同角色可以看到不同的数据
- ✅ **灵活规则**:支持多种条件(等于、大于、IN、LIKE等)
- ✅ **变量支持**:支持动态变量(如当前用户ID、部门ID等)
- ✅ **SQL级别**:在SQL层面过滤,性能高效
### 使用场景
1. **部门数据隔离**:用户只能查看自己部门的数据
2. **创建人过滤**:用户只能查看自己创建的数据
3. **状态过滤**:某些角色只能查看特定状态的数据
4. **自定义规则**:根据业务需求配置任意过滤条件
---
## 架构说明
### 模块结构
```
zt-framework/
└── zt-spring-boot-starter-biz-data-permission/ # 框架模块
└── menudatapermission/
├── annotation/ # @PermissionData 注解
├── aop/ # AOP 切面
├── context/ # ThreadLocal 上下文
├── handler/ # MyBatis 拦截器
├── model/ # DTO 模型
└── util/ # 工具类
zt-module-system/
└── zt-module-system-server/ # 业务模块
└── framework/permission/
└── MenuDataRuleLoaderImpl.java # 规则加载器实现
```
### 工作流程
```
1. 用户访问页面(如:角色管理)
↓
2. Controller 方法上有 @PermissionData 注解
↓
3. AOP 切面拦截,根据 pageComponent 查询菜单ID
↓
4. 加载该菜单下用户角色关联的数据规则
↓
5. 将规则存入 ThreadLocal
↓
6. MyBatis 执行查询时,拦截器读取规则
↓
7. 构建 SQL WHERE 条件并添加到查询中
↓
8. 返回过滤后的数据
↓
9. finally 块清理 ThreadLocal
```
---
## 前置条件
### ✅ 检查 Maven 依赖
在使用菜单数据权限功能前,**必须确保业务模块已引入框架依赖**。
#### 1. 检查依赖是否存在
打开业务模块的 `pom.xml` 文件(如 `zt-module-xxx-server/pom.xml`),检查是否包含以下依赖:
```xml
com.zt.plat
zt-spring-boot-starter-biz-data-permission
```
#### 2. 如何检查?
**方法1:查看 pom.xml**
```bash
# 在项目根目录执行
grep -r "zt-spring-boot-starter-biz-data-permission" zt-module-xxx/zt-module-xxx-server/pom.xml
```
**方法2:在 IDE 中查看**
- IDEA:打开 `pom.xml`,搜索 `zt-spring-boot-starter-biz-data-permission`
- 或者查看 Maven 依赖树
#### 3. 如果没有依赖,如何添加?
在业务模块的 `pom.xml` 中添加:
```xml
com.zt.plat
zt-spring-boot-starter-biz-data-permission
```
#### 4. 已包含该依赖的模块
以下模块已默认包含该依赖,可直接使用:
- ✅ `zt-module-system-server`
- ⚠️ 其他业务模块需自行检查
---
## 开发指南
### 步骤1:在 Controller 方法上添加注解
在需要数据过滤的查询方法上添加 `@PermissionData` 注解:
```java
import com.zt.plat.framework.datapermission.core.menudatapermission.annotation.PermissionData;
@RestController
@RequestMapping("/system/role")
public class RoleController {
@GetMapping("/page")
@PermissionData(pageComponent = "system/role/index") // 指定页面组件路径
public CommonResult> getRolePage(RolePageReqVO pageReqVO) {
PageResult pageResult = roleService.getRolePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, RoleRespVO.class));
}
}
```
### 步骤2:确定 pageComponent 值
`pageComponent` 是前端页面组件的路径,用于关联菜单:
- **格式**:`模块名/功能名/页面名`
- **示例**:
- `system/role/index` - 角色管理页面
- `system/user/index` - 用户管理页面
- `system/dept/index` - 部门管理页面
**如何查找 pageComponent?**
1. 打开前端项目,找到对应的 Vue 文件路径
2. 例如:`src/views/system/role/index.vue`
3. pageComponent 就是:`system/role/index`
### 步骤3:在菜单表中配置 component 字段
确保数据库 `system_menu` 表中,该菜单的 `component` 字段值与 `pageComponent` 一致:
```sql
-- 示例:角色管理菜单
UPDATE system_menu
SET component = 'system/role/index'
WHERE id = 角色管理菜单ID;
```
### 注解参数说明
```java
@PermissionData(
pageComponent = "system/role/index", // 必填:页面组件路径
enable = true // 可选:是否启用,默认 true
)
```
**何时设置 `enable = false`?**
当某个方法不需要数据权限过滤时(如管理员查看所有数据):
```java
@GetMapping("/all")
@PermissionData(pageComponent = "system/role/index", enable = false)
public CommonResult> getAllRoles() {
// 返回所有角色,不受数据权限限制
}
```
---
## 配置指南
### 步骤1:配置菜单
1. 登录系统,进入 **系统管理 > 菜单管理**
2. 找到需要配置数据权限的菜单(如:角色管理)
3. 确认该菜单的 **组件路径** 字段与代码中的 `pageComponent` 一致
### 步骤2:配置数据规则
1. 在菜单列表中,点击对应菜单的 **"数据规则"** 按钮
2. 点击 **"新增规则"** 按钮
3. 填写规则信息:
#### 规则字段说明
| 字段 | 说明 | 示例 |
|------|------|------|
| **规则名称** | 规则的描述性名称 | `只看自己创建的角色` |
| **规则字段** | 数据库表的字段名 | `creator`、`dept_id`、`status` |
| **规则条件** | 比较条件 | `=`、`IN`、`LIKE` 等 |
| **规则值** | 比较的值,支持变量 | `#{userId}`、`#{deptId}` |
| **状态** | 启用/禁用 | 启用 |
| **排序** | 规则执行顺序 | 0 |
#### 支持的规则条件
| 条件 | 说明 | 示例 |
|------|------|------|
| `=` | 等于 | `creator = #{userId}` |
| `!=` | 不等于 | `status != 0` |
| `>` | 大于 | `create_time > '2024-01-01'` |
| `<` | 小于 | `sort < 100` |
| `>=` | 大于等于 | `age >= 18` |
| `<=` | 小于等于 | `price <= 1000` |
| `IN` | 包含 | `dept_id IN (#{deptIds})` |
| `NOT_IN` | 不包含 | `status NOT_IN (0,1)` |
| `LIKE` | 模糊匹配 | `name LIKE '张'` |
| `NOT_LIKE` | 不匹配 | `name NOT_LIKE '测试'` |
| `IS_NULL` | 为空 | `deleted_time IS_NULL` |
| `IS_NOT_NULL` | 不为空 | `phone IS_NOT_NULL` |
| `BETWEEN` | 区间 | `age BETWEEN 18,60` |
| `NOT_BETWEEN` | 不在区间 | `score NOT_BETWEEN 0,60` |
| `SQL_RULE` | 自定义SQL | `(dept_id = 1 OR creator = 2)` |
#### 支持的变量
| 变量 | 说明 | 示例 |
|------|------|------|
| `#{userId}` | 当前用户ID | `creator = #{userId}` |
| `#{username}` | 当前用户名 | `create_by = #{username}` |
| `#{deptId}` | 当前部门ID | `dept_id = #{deptId}` |
| `#{companyId}` | 当前公司ID | `company_id = #{companyId}` |
| `#{tenantId}` | 当前租户ID | `tenant_id = #{tenantId}` |
| `#{deptIds}` | 用户所有部门ID | `dept_id IN (#{deptIds})` |
| `#{companyIds}` | 用户所有公司ID | `company_id IN (#{companyIds})` |
| `#{postIds}` | 用户所有岗位ID | `post_id IN (#{postIds})` |
#### SQL_RULE 类型详解
`SQL_RULE` 是一种特殊的规则类型,允许你直接编写自定义 SQL 表达式,实现更复杂的数据过滤逻辑。
**什么时候使用 SQL_RULE?**
- ✅ 需要 `OR` 逻辑连接多个条件
- ✅ 需要组合多个字段的复杂判断
- ✅ 需要嵌套条件或括号分组
- ✅ 标准规则条件无法满足业务需求
**配置方法**
| 字段 | 配置值 |
|------|--------|
| 规则条件 | 选择 `SQL_RULE` |
| 规则值 | 直接填写 SQL WHERE 条件表达式 |
| 规则字段 | 可以留空(不使用) |
**配置示例**
1. **OR 逻辑 - 查看自己创建的或自己部门的数据**
```
规则条件:SQL_RULE
规则值:(creator = #{userId} OR dept_id = #{deptId})
```
生成的 SQL:
```sql
WHERE (creator = '123' OR dept_id = '456')
```
2. **多字段组合 - 特定部门的已启用数据**
```
规则条件:SQL_RULE
规则值:(dept_id IN (#{deptIds}) AND status = 1)
```
生成的 SQL:
```sql
WHERE (dept_id IN ('100', '101', '102') AND status = 1)
```
3. **复杂嵌套条件 - 管理员或本部门负责人**
```
规则条件:SQL_RULE
规则值:(role_type = 'admin' OR (dept_id = #{deptId} AND is_leader = 1))
```
生成的 SQL:
```sql
WHERE (role_type = 'admin' OR (dept_id = '456' AND is_leader = 1))
```
4. **时间范围 + 状态过滤**
```
规则条件:SQL_RULE
规则值:(create_time >= '2024-01-01' AND status IN (1, 2))
```
生成的 SQL:
```sql
WHERE (create_time >= '2024-01-01' AND status IN (1, 2))
```
**工作原理**
当规则条件为 `SQL_RULE` 时:
1. 系统会忽略"规则字段"和"规则条件"
2. 直接使用"规则值"中的 SQL 表达式
3. 先替换表达式中的变量(如 `#{userId}`)
4. 将替换后的表达式直接添加到 SQL WHERE 子句中
代码实现(MenuDataPermissionRule.java:64-67):
```java
// 处理 SQL_RULE 类型(自定义 SQL)
if ("SQL_RULE".equals(ruleConditions)) {
return actualValue; // 直接返回替换变量后的 SQL 表达式
}
```
**⚠️ 重要警告**
1. **SQL 注入风险**
- SQL_RULE 直接拼接到 SQL 中,存在注入风险
- ✅ **安全做法**:只使用预定义变量(`#{userId}` 等)
- ❌ **危险做法**:不要在规则值中拼接用户输入的内容
2. **字段名必须正确**
- SQL_RULE 中的字段名必须与数据库表字段完全一致
- 错误的字段名会导致 SQL 查询报错
- 建议先在数据库中测试 SQL 语句
3. **括号很重要**
- 建议始终用括号包裹整个表达式:`(condition1 OR condition2)`
- 避免与其他规则或系统条件产生优先级问题
4. **变量替换**
- 变量会被替换为带引号的字符串值
- 例如:`#{userId}` → `'123'`
- 数据库会自动处理类型转换
**SQL_RULE vs 普通规则对比**
| 特性 | 普通规则 | SQL_RULE |
|------|---------|----------|
| 配置难度 | 简单,选择即可 | 需要 SQL 知识 |
| 灵活性 | 有限,单一条件 | 非常灵活,任意表达式 |
| 安全性 | 高,系统控制 | 需要注意 SQL 注入 |
| OR 逻辑 | ❌ 不支持 | ✅ 支持 |
| 嵌套条件 | ❌ 不支持 | ✅ 支持 |
| 多规则组合 | AND 连接 | 单个规则内实现 |
| 错误提示 | 友好 | SQL 错误信息 |
**最佳实践**
1. **优先使用普通规则**:能用普通规则解决的,不要用 SQL_RULE
2. **测试后再上线**:在测试环境验证 SQL 语句正确性
3. **添加注释**:在规则名称中说明 SQL_RULE 的用途
4. **定期审查**:定期检查 SQL_RULE 规则,删除不再使用的
5. **权限控制**:限制能配置 SQL_RULE 的管理员权限
### 步骤3:关联角色
1. 配置完规则后,进入 **系统管理 > 角色管理**
2. 编辑需要应用规则的角色
3. 在 **"数据权限"** 标签页中,勾选对应的菜单数据规则
4. 保存角色配置
### 步骤4:测试验证
1. 使用该角色的用户登录系统
2. 访问配置了数据规则的页面
3. 验证数据是否按规则过滤
---
## 注意事项
### ⚠️ 重要提醒
#### 1. 规则字段必须与数据库表字段一致
**错误示例**:
```
规则字段:dept_id_xxx
数据库字段:dept_id
结果:SQL 查询报错!
```
**正确做法**:
- 在配置规则前,先确认数据库表结构
- 字段名必须完全一致(包括大小写)
- 前端已添加警告提示,请仔细阅读
#### 2. 只在菜单/页面级别配置规则
- ✅ **菜单/页面**(type=2):需要配置数据规则
- ❌ **目录**(type=1):不需要配置
- ❌ **按钮**(type=3):不需要配置
前端已自动隐藏目录和按钮的"数据规则"按钮。
#### 3. 多个规则使用 AND 连接
如果为同一个菜单配置了多个规则,它们会用 `AND` 连接:
```sql
-- 规则1:creator = #{userId}
-- 规则2:status = 1
-- 最终SQL:
WHERE creator = '当前用户ID' AND status = 1
```
#### 4. 变量不存在时的处理
如果配置了不存在的变量(如 `#{unknownVar}`),系统会:
- 记录警告日志
- 将变量替换为空字符串
- 可能导致查询结果为空
**建议**:使用前端下拉框选择变量,避免手动输入错误。
#### 5. 性能考虑
- 数据规则在 SQL 层面过滤,性能较好
- 但过多的规则会增加 SQL 复杂度
- 建议每个菜单不超过 5 条规则
#### 6. 禁用数据权限的场景
某些查询不应该受数据权限限制,需要添加 `@DataPermission(enable = false)`:
```java
// 示例:查询所有根级部门(不受数据权限限制)
@Override
@DataPermission(enable = false)
public List getTopLevelDeptList() {
// ...
}
```
---
## 完整示例
### 场景:角色管理 - 只看自己创建的角色
#### 1. 后端代码
```java
@RestController
@RequestMapping("/system/role")
public class RoleController {
@Resource
private RoleService roleService;
@GetMapping("/page")
@Operation(summary = "获得角色分页")
@PreAuthorize("@ss.hasPermission('system:role:query')")
@PermissionData(pageComponent = "system/role/index") // 添加数据权限注解
public CommonResult> getRolePage(RolePageReqVO pageReqVO) {
PageResult pageResult = roleService.getRolePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, RoleRespVO.class));
}
}
```
#### 2. 菜单配置
确保 `system_menu` 表中角色管理菜单的配置:
```sql
SELECT id, name, component
FROM system_menu
WHERE name = '角色管理';
-- 结果:
-- id: 101
-- name: 角色管理
-- component: system/role/index ✅ 与代码中的 pageComponent 一致
```
#### 3. 数据规则配置
在页面上配置规则:
| 字段 | 值 |
|------|------|
| 规则名称 | 只看自己创建的角色 |
| 规则字段 | `creator` |
| 规则条件 | `=` |
| 规则值 | `#{userId}` |
| 状态 | 启用 |
| 排序 | 0 |
#### 4. 角色关联
1. 进入角色管理,编辑"普通用户"角色
2. 在"数据权限"标签页,勾选"只看自己创建的角色"规则
3. 保存
#### 5. 生成的 SQL
当普通用户(ID=123)查询角色列表时,实际执行的 SQL:
```sql
SELECT * FROM system_role
WHERE deleted = 0
AND tenant_id = 1
AND creator = '123' -- 自动添加的数据权限条件
ORDER BY sort ASC;
```
---
## 常见问题
### Q1: 为什么配置了规则但不生效?
**可能原因**:
1. ✅ 检查 Controller 方法是否添加了 `@PermissionData` 注解
2. ✅ 检查 `pageComponent` 是否与菜单的 `component` 字段一致
3. ✅ 检查规则是否启用(状态=启用)
4. ✅ 检查角色是否关联了该规则
5. ✅ 检查用户是否拥有该角色
### Q2: 如何查看实际执行的 SQL?
在 `application.yaml` 中开启 SQL 日志:
```yaml
# 方式1:MyBatis Plus SQL 日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 方式2:Logback 日志
logging:
level:
com.zt.plat.module.system.dal.mysql: debug
```
### Q3: 多个规则如何组合?
多个规则使用 `AND` 连接,如果需要 `OR` 逻辑,使用 `SQL_RULE` 类型:
```
规则条件:SQL_RULE
规则值:(dept_id = #{deptId} OR creator = #{userId})
```
### Q4: 如何配置"查看本部门及下级部门"的规则?
这需要在业务层实现,菜单数据权限只支持简单的字段过滤。建议使用原有的部门数据权限功能。
### Q5: 规则字段配置错误会怎样?
会导致 SQL 查询报错,因为数据库找不到该字段。前端已添加警告提示,请仔细检查。
---
## 总结
菜单数据权限提供了一种灵活、高效的数据过滤机制:
✅ **开发简单**:只需添加一个注解
✅ **配置灵活**:通过页面配置,无需修改代码
✅ **性能高效**:SQL 层面过滤,不影响性能
✅ **易于维护**:规则集中管理,便于调整
**最佳实践**:
1. 优先使用预定义变量,避免手动输入
2. 规则字段必须与数据库表字段一致
3. 合理使用规则,避免过度复杂
4. 定期review规则配置,删除无用规则
---
## 技术支持
如有问题,请联系:
- 开发团队:ZT
- 文档版本:v1.0
- 更新日期:2026-01-27