# 菜单数据权限使用文档 ## 📖 目录 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