- 新增菜单数据规则表和角色菜单数据规则关联表 - 实现菜单数据权限切面和处理器 - 添加数据规则条件和变量枚举 - 实现变量替换工具类和规则构建逻辑 - 在权限分配中集成菜单数据规则关联功能 - 优化部门ID解析逻辑,支持从用户信息中获取默认部门 - 添加菜单组件查询方法和公司访问上下文拦截器改进
17 KiB
菜单数据权限使用文档
📖 目录
功能介绍
什么是菜单数据权限?
菜单数据权限是一种基于菜单的动态数据过滤机制,允许管理员为不同的角色配置不同的数据查询规则,实现细粒度的数据权限控制。
核心特性
- ✅ 动态配置:无需修改代码,通过页面配置即可实现数据过滤
- ✅ 基于角色:不同角色可以看到不同的数据
- ✅ 灵活规则:支持多种条件(等于、大于、IN、LIKE等)
- ✅ 变量支持:支持动态变量(如当前用户ID、部门ID等)
- ✅ SQL级别:在SQL层面过滤,性能高效
使用场景
- 部门数据隔离:用户只能查看自己部门的数据
- 创建人过滤:用户只能查看自己创建的数据
- 状态过滤:某些角色只能查看特定状态的数据
- 自定义规则:根据业务需求配置任意过滤条件
架构说明
模块结构
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),检查是否包含以下依赖:
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
2. 如何检查?
方法1:查看 pom.xml
# 在项目根目录执行
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 中添加:
<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 注解:
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?
- 打开前端项目,找到对应的 Vue 文件路径
- 例如:
src/views/system/role/index.vue - pageComponent 就是:
system/role/index
步骤3:在菜单表中配置 component 字段
确保数据库 system_menu 表中,该菜单的 component 字段值与 pageComponent 一致:
-- 示例:角色管理菜单
UPDATE system_menu
SET component = 'system/role/index'
WHERE id = 角色管理菜单ID;
注解参数说明
@PermissionData(
pageComponent = "system/role/index", // 必填:页面组件路径
enable = true // 可选:是否启用,默认 true
)
何时设置 enable = false?
当某个方法不需要数据权限过滤时(如管理员查看所有数据):
@GetMapping("/all")
@PermissionData(pageComponent = "system/role/index", enable = false)
public CommonResult<List<RoleRespVO>> getAllRoles() {
// 返回所有角色,不受数据权限限制
}
配置指南
步骤1:配置菜单
- 登录系统,进入 系统管理 > 菜单管理
- 找到需要配置数据权限的菜单(如:角色管理)
- 确认该菜单的 组件路径 字段与代码中的
pageComponent一致
步骤2:配置数据规则
- 在菜单列表中,点击对应菜单的 "数据规则" 按钮
- 点击 "新增规则" 按钮
- 填写规则信息:
规则字段说明
| 字段 | 说明 | 示例 |
|---|---|---|
| 规则名称 | 规则的描述性名称 | 只看自己创建的角色 |
| 规则字段 | 数据库表的字段名 | 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 条件表达式 |
| 规则字段 | 可以留空(不使用) |
配置示例
- OR 逻辑 - 查看自己创建的或自己部门的数据
规则条件:SQL_RULE
规则值:(creator = #{userId} OR dept_id = #{deptId})
生成的 SQL:
WHERE (creator = '123' OR dept_id = '456')
- 多字段组合 - 特定部门的已启用数据
规则条件:SQL_RULE
规则值:(dept_id IN (#{deptIds}) AND status = 1)
生成的 SQL:
WHERE (dept_id IN ('100', '101', '102') AND status = 1)
- 复杂嵌套条件 - 管理员或本部门负责人
规则条件:SQL_RULE
规则值:(role_type = 'admin' OR (dept_id = #{deptId} AND is_leader = 1))
生成的 SQL:
WHERE (role_type = 'admin' OR (dept_id = '456' AND is_leader = 1))
- 时间范围 + 状态过滤
规则条件:SQL_RULE
规则值:(create_time >= '2024-01-01' AND status IN (1, 2))
生成的 SQL:
WHERE (create_time >= '2024-01-01' AND status IN (1, 2))
工作原理
当规则条件为 SQL_RULE 时:
- 系统会忽略"规则字段"和"规则条件"
- 直接使用"规则值"中的 SQL 表达式
- 先替换表达式中的变量(如
#{userId}) - 将替换后的表达式直接添加到 SQL WHERE 子句中
代码实现(MenuDataPermissionRule.java:64-67):
// 处理 SQL_RULE 类型(自定义 SQL)
if ("SQL_RULE".equals(ruleConditions)) {
return actualValue; // 直接返回替换变量后的 SQL 表达式
}
⚠️ 重要警告
-
SQL 注入风险
- SQL_RULE 直接拼接到 SQL 中,存在注入风险
- ✅ 安全做法:只使用预定义变量(
#{userId}等) - ❌ 危险做法:不要在规则值中拼接用户输入的内容
-
字段名必须正确
- SQL_RULE 中的字段名必须与数据库表字段完全一致
- 错误的字段名会导致 SQL 查询报错
- 建议先在数据库中测试 SQL 语句
-
括号很重要
- 建议始终用括号包裹整个表达式:
(condition1 OR condition2) - 避免与其他规则或系统条件产生优先级问题
- 建议始终用括号包裹整个表达式:
-
变量替换
- 变量会被替换为带引号的字符串值
- 例如:
#{userId}→'123' - 数据库会自动处理类型转换
SQL_RULE vs 普通规则对比
| 特性 | 普通规则 | SQL_RULE |
|---|---|---|
| 配置难度 | 简单,选择即可 | 需要 SQL 知识 |
| 灵活性 | 有限,单一条件 | 非常灵活,任意表达式 |
| 安全性 | 高,系统控制 | 需要注意 SQL 注入 |
| OR 逻辑 | ❌ 不支持 | ✅ 支持 |
| 嵌套条件 | ❌ 不支持 | ✅ 支持 |
| 多规则组合 | AND 连接 | 单个规则内实现 |
| 错误提示 | 友好 | SQL 错误信息 |
最佳实践
- 优先使用普通规则:能用普通规则解决的,不要用 SQL_RULE
- 测试后再上线:在测试环境验证 SQL 语句正确性
- 添加注释:在规则名称中说明 SQL_RULE 的用途
- 定期审查:定期检查 SQL_RULE 规则,删除不再使用的
- 权限控制:限制能配置 SQL_RULE 的管理员权限
步骤3:关联角色
- 配置完规则后,进入 系统管理 > 角色管理
- 编辑需要应用规则的角色
- 在 "数据权限" 标签页中,勾选对应的菜单数据规则
- 保存角色配置
步骤4:测试验证
- 使用该角色的用户登录系统
- 访问配置了数据规则的页面
- 验证数据是否按规则过滤
注意事项
⚠️ 重要提醒
1. 规则字段必须与数据库表字段一致
错误示例:
规则字段:dept_id_xxx
数据库字段:dept_id
结果:SQL 查询报错!
正确做法:
- 在配置规则前,先确认数据库表结构
- 字段名必须完全一致(包括大小写)
- 前端已添加警告提示,请仔细阅读
2. 只在菜单/页面级别配置规则
- ✅ 菜单/页面(type=2):需要配置数据规则
- ❌ 目录(type=1):不需要配置
- ❌ 按钮(type=3):不需要配置
前端已自动隐藏目录和按钮的"数据规则"按钮。
3. 多个规则使用 AND 连接
如果为同一个菜单配置了多个规则,它们会用 AND 连接:
-- 规则1:creator = #{userId}
-- 规则2:status = 1
-- 最终SQL:
WHERE creator = '当前用户ID' AND status = 1
4. 变量不存在时的处理
如果配置了不存在的变量(如 #{unknownVar}),系统会:
- 记录警告日志
- 将变量替换为空字符串
- 可能导致查询结果为空
建议:使用前端下拉框选择变量,避免手动输入错误。
5. 性能考虑
- 数据规则在 SQL 层面过滤,性能较好
- 但过多的规则会增加 SQL 复杂度
- 建议每个菜单不超过 5 条规则
6. 禁用数据权限的场景
某些查询不应该受数据权限限制,需要添加 @DataPermission(enable = false):
// 示例:查询所有根级部门(不受数据权限限制)
@Override
@DataPermission(enable = false)
public List<DeptDO> getTopLevelDeptList() {
// ...
}
完整示例
场景:角色管理 - 只看自己创建的角色
1. 后端代码
@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 表中角色管理菜单的配置:
SELECT id, name, component
FROM system_menu
WHERE name = '角色管理';
-- 结果:
-- id: 101
-- name: 角色管理
-- component: system/role/index ✅ 与代码中的 pageComponent 一致
3. 数据规则配置
在页面上配置规则:
| 字段 | 值 |
|---|---|
| 规则名称 | 只看自己创建的角色 |
| 规则字段 | creator |
| 规则条件 | = |
| 规则值 | #{userId} |
| 状态 | 启用 |
| 排序 | 0 |
4. 角色关联
- 进入角色管理,编辑"普通用户"角色
- 在"数据权限"标签页,勾选"只看自己创建的角色"规则
- 保存
5. 生成的 SQL
当普通用户(ID=123)查询角色列表时,实际执行的 SQL:
SELECT * FROM system_role
WHERE deleted = 0
AND tenant_id = 1
AND creator = '123' -- 自动添加的数据权限条件
ORDER BY sort ASC;
常见问题
Q1: 为什么配置了规则但不生效?
可能原因:
- ✅ 检查 Controller 方法是否添加了
@PermissionData注解 - ✅ 检查
pageComponent是否与菜单的component字段一致 - ✅ 检查规则是否启用(状态=启用)
- ✅ 检查角色是否关联了该规则
- ✅ 检查用户是否拥有该角色
Q2: 如何查看实际执行的 SQL?
在 application.yaml 中开启 SQL 日志:
# 方式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 层面过滤,不影响性能 ✅ 易于维护:规则集中管理,便于调整
最佳实践:
- 优先使用预定义变量,避免手动输入
- 规则字段必须与数据库表字段一致
- 合理使用规则,避免过度复杂
- 定期review规则配置,删除无用规则
技术支持
如有问题,请联系:
- 开发团队:ZT
- 文档版本:v1.0
- 更新日期:2026-01-27