news 2026/1/7 9:24:44

Ruoyi框架 | 扩展部门数据权限实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ruoyi框架 | 扩展部门数据权限实现

一、背景与目标

在若依框架原有DataScope的基础上,实现一套独立的、基于部门层级的数据权限过滤机制,用于按组织结构灵活控制数据可见范围。

设计目标

  • 不依赖角色、不判断是否管理员

  • 通过注解参数动态控制数据范围

  • 支持:

    • 是否包含本部门
    • 向上查询 N 级部门
    • 向下查询 N 级部门 / 所有子部门
  • 与若依原有BaseEntity + params + MyBatis XML机制完全兼容


二、核心设计思路

1. 技术方案

  • 使用AOP + 自定义注解拦截查询方法

  • 在方法执行前:

    • 根据当前用户部门 ID
    • 动态拼接部门过滤 SQL
    • 注入到BaseEntity.params.dataScope
  • Mapper XML 中通过${params.dataScope}拼接 WHERE 条件

2. 依赖表结构(sys_dept)

/* by 01130.hk - online tools website : 01130.hk/zh/endecodejs.html */ dept_id 部门ID parent_id 父部门ID ancestors 祖先路径,如:0,1,3,10

三、自定义注解:ExtendedDataScope

/* by 01130.hk - online tools website : 01130.hk/zh/endecodejs.html */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExtendedDataScope { // 部门表别名(必填) String deptAlias() default ""; // 用户表别名(预留扩展) String userAlias() default ""; // 权限类型(当前主要使用 dept) String type() default "dept"; // 向上级部门层数(0 = 不包含) int upLevel() default 0; // 向下级部门层数(0 = 不包含,999 = 所有子级) int downLevel() default 0; // 是否包含本部门 boolean includeSelf() default true; }

四、AOP 实现要点

1. 切面职责

  • 拦截所有标注@ExtendedDataScope的方法
  • 清空历史 dataScope,防止 SQL 注入
  • 生成部门层级 SQL
  • 写入BaseEntity.params.dataScope

2. 核心处理流程

@Before ├─ clearDataScope() ├─ 获取当前用户 ├─ 读取注解参数 ├─ 构建部门范围 SQL └─ 写入 params.dataScope
点击查看代码
@Aspect @Component public class ExtendedDataScopeAspect { /** * 参数 key(和若依一致) */ public static final String DATA_SCOPE = "dataScope"; @Before("@annotation(dataScope)") public void doBefore(JoinPoint joinPoint, ExtendedDataScope dataScope) { clearDataScope(joinPoint); handleDataScope(joinPoint, dataScope); } private void handleDataScope(JoinPoint joinPoint, ExtendedDataScope dataScope) { LoginUser loginUser = SecurityUtils.getLoginUser(); if (loginUser == null) { return; } String deptAlias = dataScope.deptAlias(); if (StringUtils.isBlank(deptAlias)) { return; } String sql = buildDeptScopeSql(loginUser.getDeptId(), deptAlias, dataScope); if (StringUtils.isBlank(sql)) { return; } Object params = joinPoint.getArgs()[0]; if (params instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) params; baseEntity.getParams().put(DATA_SCOPE, " AND (" + sql + ")"); } } /** * 构建部门层级 SQL */ private String buildDeptScopeSql(Long deptId, String deptAlias, ExtendedDataScope scope) { List<String> conditions = new ArrayList<>(); /* ========== 本部门 ========== */ if (scope.includeSelf()) { conditions.add(deptAlias + ".dept_id = " + deptId); } /* ========== 向上 ========== */ if (scope.upLevel() > 0) { // ancestors 形如:0,1,3,10 // 向上 N 级:取 ancestors 中倒数 N 位 conditions.add(buildUpDeptSql(deptAlias, deptId, scope.upLevel())); } /* ========== 向下 ========== */ if (scope.downLevel() > 0) { if (scope.downLevel() >= 999) { // 所有子级 conditions.add( deptAlias + ".dept_id IN (" + "SELECT dept_id FROM sys_dept " + "WHERE find_in_set(" + deptId + ", ancestors)" + ")" ); } else { conditions.add(buildDownDeptSql(deptAlias, deptId, scope.downLevel())); } } return String.join(" OR ", conditions); } /** * 向上 N 级部门 */ private String buildUpDeptSql(String deptAlias, Long deptId, int upLevel) { // 使用子查询,取 ancestors 中的上级 return deptAlias + ".dept_id IN (" + " SELECT t.dept_id FROM sys_dept t " + " WHERE t.dept_id IN ( " + " SELECT SUBSTRING_INDEX(" + " SUBSTRING_INDEX(d.ancestors, ',', -( " + upLevel + " + 1)), ',', 1" + " ) FROM sys_dept d WHERE d.dept_id = " + deptId + " )" + ")"; } /** * 向下 N 级部门 */ private String buildDownDeptSql(String deptAlias, Long deptId, int downLevel) { // ancestors 深度控制(当前 depth + N) return deptAlias + ".dept_id IN (" + " SELECT d.dept_id FROM sys_dept d " + " WHERE find_in_set(" + deptId + ", d.ancestors) " + " AND (LENGTH(d.ancestors) - LENGTH(REPLACE(d.ancestors, ',', ''))) <= " + " ( " + " SELECT (LENGTH(ancestors) - LENGTH(REPLACE(ancestors, ',', ''))) + " + downLevel + " FROM sys_dept WHERE dept_id = " + deptId + " )" + ")"; } /** * 清空 dataScope,防止 SQL 注入 */ private void clearDataScope(JoinPoint joinPoint) { Object params = joinPoint.getArgs()[0]; if (params instanceof BaseEntity) { ((BaseEntity) params).getParams().put(DATA_SCOPE, ""); } } }

五、部门层级 SQL 构建规则

1. 本部门

d.dept_id = {currentDeptId}

includeSelf = true控制


2. 向上 N 级部门(upLevel)

原理:

  • 利用ancestors字段
  • 从 ancestors 中向前截取 N 个父级

示意 SQL:

d.dept_id IN ( SELECT SUBSTRING_INDEX( SUBSTRING_INDEX(ancestors, ',', -(N + 1)), ',', 1 ) FROM sys_dept WHERE dept_id = 当前部门ID )

3. 向下 N 级部门(downLevel)

3.1 所有子级(downLevel = 999)
d.dept_id IN ( SELECT dept_id FROM sys_dept WHERE find_in_set(当前部门ID, ancestors) )
3.2 限定层级子部门

思路:

  • 计算 ancestors 的层级深度(逗号个数)
  • 控制最大深度 = 当前深度 + N
d.dept_id IN ( SELECT d.dept_id FROM sys_dept d WHERE find_in_set(当前部门ID, d.ancestors) AND 层级深度(d) <= 当前层级 + N )

六、Mapper XML 使用方式

<select id="selectList" resultType="xxx"> SELECT * FROM biz_table t LEFT JOIN sys_dept d ON t.dept_id = d.dept_id <where> 1 = 1 ${params.dataScope} </where> </select>

说明:

  • ${params.dataScope}必须保留
  • AOP 动态注入AND ( ... )

七、使用示例

@ExtendedDataScope( deptAlias = "d", includeSelf = true, upLevel = 1, downLevel = 2 ) public List<SysDept> selectDeptList(SysDept dept) { return deptMapper.selectDeptList(dept); }

含义说明

参数含义
includeSelf包含本部门
upLevel=1包含上一级部门
downLevel=2包含下两级部门
downLevel=999包含所有子部门

八、方案特点总结

  • ✅ 与若依原生 DataScope 解耦
  • ✅ 仅依赖部门层级,不依赖角色权限
  • ✅ 控制粒度细,适合复杂组织结构
  • ✅ 非侵入式,Mapper 无需改动
  • ✅ 特别适合安全监管 / GIS / 组织树场景

九、可扩展方向(后续优化)

  • 使用 MySQL 8 / PostgreSQL 的WITH RECURSIVE优化层级查询
  • 部门层级缓存(Redis)减少子查询
  • 扩展到:部门 + 用户混合数据权限
  • 支持多部门归属(兼职部门)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/4 20:21:40

YOLOFuse部署实践:在GPU算力平台快速启动双流检测

YOLOFuse部署实践&#xff1a;在GPU算力平台快速启动双流检测 在智能安防、夜间巡检和自动驾驶等现实场景中&#xff0c;我们常常面临这样的困境&#xff1a;摄像头在白天表现良好&#xff0c;但一到夜晚或烟雾弥漫的环境中&#xff0c;目标检测性能便急剧下降。可见光图像失去…

作者头像 李华
网站建设 2026/1/4 6:44:55

YOLOFuse typora写博客引流策略:发布SEO优化文章吸引用户

YOLOFuse 多模态目标检测技术深度解析&#xff1a;基于双流融合的开箱即用解决方案 在智能安防、自动驾驶和夜间巡检等实际场景中&#xff0c;单一可见光&#xff08;RGB&#xff09;图像常常“力不从心”——低光照下细节模糊&#xff0c;烟雾遮挡时目标丢失&#xff0c;极端天…

作者头像 李华
网站建设 2026/1/4 2:58:20

YOLOFuse javascript FileReader读取本地图像上传

YOLOFuse 与 JavaScript FileReader&#xff1a;构建轻量级多模态目标检测系统 在智能安防、无人巡检和夜间监控等场景中&#xff0c;单一可见光图像常常因光照不足或环境遮挡而失效。如何让机器“看得更清”&#xff0c;尤其是在黑暗或烟雾弥漫的环境中&#xff1f;答案逐渐指…

作者头像 李华
网站建设 2026/1/3 19:41:55

YOLOFuse 百度地图API结合位置信息打标检测对象

YOLOFuse&#xff1a;融合红外与可见光的目标检测系统及其地理信息集成实践 在城市安防监控中心的大屏上&#xff0c;深夜的街道画面常常陷入一片漆黑——传统摄像头在低光照环境下几乎“失明”。而与此同时&#xff0c;边境巡逻无人机搭载的红外传感器却能清晰捕捉到热源移动。…

作者头像 李华
网站建设 2026/1/6 10:23:52

YOLOFuse typora数学公式渲染异常解决办法

YOLOFuse 数学公式渲染异常的根源与实战解决方案 在撰写多模态目标检测项目文档时&#xff0c;你是否曾遇到这样的尴尬&#xff1a;精心写好的 LaTeX 公式&#xff0c;在 Typora 里却显示为空白、乱码&#xff0c;甚至整段文字“失灵”&#xff1f;尤其是在描述 YOLOFuse 这类基…

作者头像 李华
网站建设 2026/1/4 11:25:48

YOLOFuse github镜像同步更新机制:保持代码最新状态

YOLOFuse 镜像同步机制与多模态检测实践 在复杂环境感知系统日益智能化的今天&#xff0c;单一视觉模态的局限性愈发明显。夜间监控中可见光图像模糊不清&#xff0c;烟雾遮挡下传统目标检测频频漏检——这些现实问题不断推动研究者探索更鲁棒的技术路径。正是在这样的背景下&a…

作者头像 李华