用户关联了角色(用户可以关联多个角色),给角色设置数据权限分类,数据权限分类有如下5种:
使用aop的注解作为切入点,找到需要做数据权限限制的service方法,并且要提供此service方法中所调用的mapper的sql中所使用的用户表和部门表别名,然后在切面的增强方法中按不同的数据权限分类拼接出对应的sql,放入BaseEntity的params这个map中,接着,在mapper.xml的sql中手动拼接此前params存入的sql。
/**
* 数据权限过滤注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
/**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
*/
public String permission() default "";
}
/**
* 数据过滤处理
*
* @author ruoyi
*/
@Aspect
@Component
public 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 void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(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 void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
StringBuilder sqlString = new StringBuilder();
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;
}
// 用户可能有多个角色, 其中有一个角色有权限访问当前这个接口, 另外一个角色没有角色访问这个接口, 这样显然这个用户是能访问这个接口的,
// 但是, 这2个角色所设置的数据权限不同, 那么没有接口权限访问这个接口的角色所设置的数据权限 就不应该生效
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
// 拥有所有的数据权限(啥也不拼接, 记录下)
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
// 1. 如果用户的当前角色所设置的数据权限是 自定义数据权限, 那就说明用户一定给这个角色配置了这个角色所能关联的部门集合,
// 接下来查数据, 就只需要通过这个角色关联到的部门 来过滤数据
// 2. 放个OR 在这里是因为一旦用户有多个角色, 那么使用OR就相当于取并集数据(最前面的OR肯定是要被删掉的)
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ",
deptAlias,
role.getRoleId()));
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
// 仅查询本部门数据: 那么直接以本部门作为条件拼接
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
// 本部门及以下数据权限:
// 1. 将数据权限限制在用户所处的当前部门
// 2. 用户所处的当前部门下的所有子部门,
// 它这里确定子部门的方法是:在维护部门表的时候, 每个部门都会把当前部门的所有父级部门按顺序以逗号分隔拼接的结果作为ancestors字段,
// 这样查询任何一个部门下的子部门, 这个子部门的ancestors字段中一定可以找到这个部门, 因此使用find_in_set
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()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
{
// 仅本人数据:
// 如果当前角色设置的数据权限是仅查询当前本人数据, 那么就肯定需要拿用户本人的用户id作为查询条件, 那么就肯定要提供用户表别名,
// 如果提供了用户表别名, 那就直接添加用户id作为条件。
// 如果没有提供用户表别名, 那对程序来说就不知道怎么拼接sql了, 所以干脆就直接设置dept_id为0 这样不可能的条件作为条件,因此这将不会查询任何数据
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));
}
// sql已经拼接好了, 就要放入BaseEntity的params这个map中
if (StringUtils.isNotBlank(sqlString.toString()))
{
// 拿到第一个参数
Object params = joinPoint.getArgs()[0];
// 因此第一个参数必须继承自BaseEntity
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
// 把最前面的 OR 去掉, 使用AND拼接作为条件
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
/**
* 拼接权限sql前先清空params.dataScope参数防止注入
*/
private void clearDataScope(final JoinPoint joinPoint)
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}
public class BaseEntity implements Serializable
{
private static final long serialVersionUID = 1L;
/** 搜索值 */
@JsonIgnore
private String searchValue;
/** 创建者 */
private String createBy;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 更新者 */
private String updateBy;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/** 备注 */
private String remark;
/** 请求参数 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, Object> params;
}
public class SysUser extends BaseEntity
{
// ...
}
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController
/**
* 获取用户列表
*/
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
}
@Service
public class SysUserServiceImpl implements ISysUserService
{
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
}
<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'
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
if>
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
if>
<if test="status != null and status != ''">
AND u.status = #{status}
if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
if>
<if test="params.beginTime != null and params.beginTime != ''">
AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
if>
<if test="params.endTime != null and params.endTime != ''">
AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
if>
${params.dataScope}
select>