一、背景与目标
在若依框架原有 DataScope 的基础上,实现一套独立的、基于部门层级的数据权限过滤机制,用于按组织结构灵活控制数据可见范围。
设计目标
- 不依赖角色、不判断是否管理员
- 通过注解参数动态控制数据范围
- 支持:
- 是否包含本部门
- 向上查询 N 级部门
- 向下查询 N 级部门 / 所有子部门
- 与若依原有 BaseEntity + params + MyBatis XML 机制完全兼容
二、核心设计思路
1. 技术方案
- 使用 AOP + 自定义注解 拦截查询方法
- 在方法执行前:
- 根据当前用户部门 ID
- 动态拼接部门过滤 SQL
- 注入到 BaseEntity.params.dataScope
- Mapper XML 中通过 ${params.dataScope} 拼接 WHERE 条件
2. 依赖表结构(sys_dept)
- dept_id 部门ID
- parent_id 父部门ID
- ancestors 祖先路径,如:0,1,3,10
复制代码 三、自定义注解:ExtendedDataScope
- @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
复制代码 点击查看代码[code]@Aspect@Componentpublic class ExtendedDataScopeAspect { @Autowired private SysDeptMapper deptMapper; /** * 参数 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 conditions = new ArrayList(); /* ========== 本部门 ========== */ if (scope.includeSelf()) { conditions.add(deptAlias + ".dept_id = " + deptId); } /* ========== 向上 ========== */ if (scope.upLevel() > 0) { // ancestors 形如:0,1,3,10 // 向上 N 级:取 ancestors 中倒数 N 位 SysDept sysDept = deptMapper.selectDeptById(deptId); String ancestors = sysDept.getAncestors(); if (ancestors != null) { conditions.add(buildUpDeptSql(deptAlias, ancestors, 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, String ancestors, int upLevel) { // 使用子查询,取 ancestors 中的上级 return "find_in_set(" + deptAlias + ".dept_id," + " SUBSTRING_INDEX('" + ancestors + "', ',', -" + upLevel + ") " + ")"; } /** * 向下 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, ',', ''))) |