Files
zt-dsc/docs/菜单数据权限使用文档.md
wuzongyong 6c94476a8d feat(permission): 添加菜单数据权限功能
- 新增菜单数据规则表和角色菜单数据规则关联表
- 实现菜单数据权限切面和处理器
- 添加数据规则条件和变量枚举
- 实现变量替换工具类和规则构建逻辑
- 在权限分配中集成菜单数据规则关联功能
- 优化部门ID解析逻辑,支持从用户信息中获取默认部门
- 添加菜单组件查询方法和公司访问上下文拦截器改进
2026-01-28 09:16:32 +08:00

618 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 菜单数据权限使用文档
## 📖 目录
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
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
```
#### 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
<dependencies>
<!-- 其他依赖 -->
<!-- 菜单数据权限框架 -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
</dependencies>
```
#### 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<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {
PageResult<RoleDO> 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<List<RoleRespVO>> 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
-- 规则1creator = #{userId}
-- 规则2status = 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<DeptDO> 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<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {
PageResult<RoleDO> 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
# 方式1MyBatis Plus SQL 日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 方式2Logback 日志
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