笔者最近在努力的分析开源项目若依框架,今天看到了若依对数据权限进行控制的部分,自定义注解+AOP+动态SQL的注入,看的我是眼花缭乱,然后我又认真的复盘了一遍整个的实现过程,不由得感叹一句,若依YYDS~~

简单猜测

除了我们平时都知道的 路由权限(即对接口的访问权限外),在日常生产开发中,我们还应该有对数据的访问权限。

在若依这个框架中,通过角色中的数据范围这个属性来对数据权限进行控制。

对应实体类:

深入分析

一个用户肯定是 有一种角色的,也肯定是隶属于一个部门的。

这里咱们就以用户在查询用户时,即 selectUserList时所做的数据权限为例进行分析。

若依在进行数据权限的访问时,持久层(Mapper层)中对数据进行处理,根据用户角色的权限对数据进行过滤。

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">   select u.user_id, u.dept_id, u.login_name, u.user_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.salt, 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="loginName != null and loginName != ''">      AND u.login_name like concat('%', #{loginName}, '%')   </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') &gt;= 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') &lt;= 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>

我们可以看到倒数第二行${params.dataScope}就是对数据范围进行过滤

其中,params指的是parameterType=”SysUser”传来的参数 SysUse的一个属性,然后这个属性的dataScope属性。

思考:既然params.dataScope通过占位符嵌入在这里,那么他肯定是一个sql语句。我们返回到sysUser实体类中,发现sysUser中并没有params 这个属性。

这里我们可以看到SysUser继承了BaseEntity,果然我们在BaseEntity这个类中发现了 params 这个属性

public class BaseEntity implements Serializable{    private static final long serialVersionUID = 1L;    /** 搜索值 */    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;    /** 请求参数 */    private Map<String, Object> params;    public String getSearchValue()    {        return searchValue;    }    public void setSearchValue(String searchValue)    {        this.searchValue = searchValue;    }    public String getCreateBy()    {        return createBy;    }    public void setCreateBy(String createBy)    {        this.createBy = createBy;    }    public Date getCreateTime()    {        return createTime;    }    public void setCreateTime(Date createTime)    {        this.createTime = createTime;    }    public String getUpdateBy()    {        return updateBy;    }    public void setUpdateBy(String updateBy)    {        this.updateBy = updateBy;    }    public Date getUpdateTime()    {        return updateTime;    }    public void setUpdateTime(Date updateTime)    {        this.updateTime = updateTime;    }    public String getRemark()    {        return remark;    }    public void setRemark(String remark)    {        this.remark = remark;    }    public Map<String, Object> getParams()    {        if (params == null)        {            params = new HashMap<>();        }        return params;    }    public void setParams(Map<String, Object> params)    {        this.params = params;    }}

到这里 我们已经知道有 params这个属性的存在,并且能够肯定 params.dataScope 这是一个sql语句。 所以我们下一步就是要确定这个sql语句是什么时候注入的,怎么注入的?

高阶玩法

我们能够发现若依自定义了一个注解 @DataScope

/** * 数据权限过滤注解 *  * @author ruoyi */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataScope{    /**     * 部门表的别名     */    public String deptAlias() default "";    /**     * 用户表的别名     */    public String userAlias() default "";}

我们继续探究这个注解是怎么执行的

我们可以发现在 SysUserServiceImpl Service业务层实现类中发现 @DataScope 的使用

@Override@DataScope(deptAlias = "d", userAlias = "u")public List<SysUser> selectUserList(SysUser user){    return userMapper.selectUserList(user);}

我们现在来观察DataScopeAspect 这个切面类,具体实现步骤我都在旁边进行注解了

/** * 数据过滤处理 *  * @author ruoyi */@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)")    // 这个controllerDataScope是一个形参    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable    {        clearDataScope(point);        handleDataScope(point, controllerDataScope);    }    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)    {        // 获取当前的用户        SysUser currentUser = ShiroUtils.getSysUser();        if (currentUser != null)        {            // 如果是超级管理员,则不过滤数据            if (!currentUser.isAdmin())            {                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),                        controllerDataScope.userAlias());            }        }    }    /**     * 数据范围过滤     *      * @param joinPoint 切点     * @param user 用户     * @param deptAlias 部门别名     * @param userAlias 用户别名     */    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)    {        StringBuilder sqlString = new StringBuilder();        for (SysRole role : user.getRoles())   //获取当前用户的角色        {            String dataScope = role.getDataScope();   //获取角色的数据权限            // 若这个角色的数据权限是  全部数据权限            if (DATA_SCOPE_ALL.equals(dataScope))            {                sqlString = new StringBuilder();                break;            }            //自定义数据权限            else if (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()));            }            //本部门数据权限            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))            {                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))            {                if (StringUtils.isNotBlank(userAlias))                {                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));                }                else                {                    // 数据权限为仅本人且没有userAlias别名不查询任何数据                    sqlString.append(" OR 1=0 ");                }            }        }        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属性中 这个属性是一个Map            }        }    }    /**     * 拼接权限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, "");        }    }}

!!!!完结撒花!!!

如果你对这个过程看懂了的话,相信你也能够数据库中 sys_role_dept这个表的含义了吧,笔者当时也是不明白这个到底是个什么意思。其实这张表就是存放那些 自定义数据权限的角色和它对应的部门数据权限。