在业务开发过程中,经常会碰到需要查询某个人所在部门的数据或者某个人所在部门相关子部门的数据等需要做数据权限的场景,这里结合若依开发平台描述下如何实现数据权限的控制。
原理:
通过自定义注解的方式在查询数据之前查询配置的权限标记(如:当前部门数据权限等),拼接出相应的sql作为变量传递给Mapper层进行关联查询
1、定义数据权限注解
import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
/** * 数据权限过滤注解 * */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataScope{/** * 部门表的别名 */ public String deptAlias() default "";
/** * 用户表的别名 */ public String userAlias() default "";
/** * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 */ public String permission() default "";}
2、定义注解切面处理逻辑
/** * 数据过滤处理 * * */@Aspect@Componentpublic class DataScopeAspect{/** * 全部数据权限 */ public static final String DATA_SCOPE_ALL = "1";
/** * 自定数据权限 */ public static final String DATA_SCOPE_CUSTOM = "2";
/** * 部门数据权限 */ public static final String DATA_SCOPE_DEPT = "3";
/** * 部门及以下数据权限 */ public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/** * 仅本人数据权限 */ public static final String DATA_SCOPE_SELF = "5";
/** * 数据权限过滤关键字 */ public static final String DATA_SCOPE = "dataScope";
@Before("@annotation(controllerDataScope)") public voiddoBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable{clearDataScope(point);handleDataScope(point, controllerDataScope);}
protected voidhandleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope){ // 获取当前的用户 LoginUser loginUser = SecurityUtils.getLoginUser();if(StringUtils.isNotNull(loginUser)){ SysUser currentUser = loginUser.getUser(); // 如果是超级管理员,则不过滤数据if(StringUtils.isNotNull(currentUser)&& !currentUser.isAdmin()){ String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission);}}}
/** * 数据范围过滤 * * @param joinPoint 切点 * @param user 用户 * @param deptAlias 部门别名 * @param userAlias 用户别名 * @param permission 权限字符 */ public static voiddataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission){ StringBuilder sqlString = newStringBuilder(); List<String> conditions = new ArrayList<String>();
for(SysRole role : user.getRoles()){ String dataScope = role.getDataScope();if(!DATA_SCOPE_CUSTOM.equals(dataScope)&& conditions.contains(dataScope)){ continue;}if(StringUtils.isNotEmpty(permission)&& StringUtils.isNotEmpty(role.getPermissions())&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))){ continue;}if(DATA_SCOPE_ALL.equals(dataScope)){ sqlString = newStringBuilder(); conditions.add(dataScope); break;}elseif(DATA_SCOPE_CUSTOM.equals(dataScope)){ sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId()));}elseif(DATA_SCOPE_DEPT.equals(dataScope)){ sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));}elseif(DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)){ sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId()));}elseif(DATA_SCOPE_SELF.equals(dataScope)){if(StringUtils.isNotBlank(userAlias)){ sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));}else{ // 数据权限为仅本人且没有userAlias别名不查询任何数据 sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));}} conditions.add(dataScope);}
// 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据if(StringUtils.isEmpty(conditions)){ sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));}
if(StringUtils.isNotBlank(sqlString.toString())){ Object params = joinPoint.getArgs()[0];if(StringUtils.isNotNull(params)&& params instanceof BaseEntity){ BaseEntity baseEntity = (BaseEntity) params; baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");}}}
/** * 拼接权限sql前先清空params.dataScope参数防止注入 */ private voidclearDataScope(final JoinPoint joinPoint){ Object params = joinPoint.getArgs()[0];if(StringUtils.isNotNull(params)&& params instanceof BaseEntity){ BaseEntity baseEntity = (BaseEntity) params; baseEntity.getParams().put(DATA_SCOPE, "");}}}
3、在service层使用
/** * 根据条件分页查询用户列表 * * @param user 用户信息 * @return 用户信息集合信息 */ @Override @DataScope(deptAlias = "d", userAlias = "u") public List<SysUser>selectUserList(SysUser user){return userMapper.selectUserList(user);}
需要注意的是,注解中使用的表别名应与mapper定义的别名一致,否则sql无法运行
4、在Mapper中映射使用
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult"> select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u left join sys_dept d on u.dept_id = d.dept_id where u.del_flag = '0' <!-- 数据范围过滤 --> ${params.dataScope}</select>
以上就是通过注解方式实现数据权限控制的方式。