使用MyBatis-Plus 及 注解 做数据权限

本文介绍了如何在MyBatis-plus项目中结合若依框架实现数据权限处理,包括添加DataScope注解、创建DataScopeAspect处理器以及定制DataPermissionHandler拦截器,确保满足不同角色的权限需求。
摘要由CSDN通过智能技术生成

主要参考了若依的数据权限处理,但是考虑到使用了MyBatis-plus,就试着结合了一下子。下面是具体代码。

1.注解

这里和若依的注解一致,增加了一些自己业务的东西。考虑到可能自定义权限有多个,所以增加了customType用来指定自定义权限方法。将注解放在dao层方法上就可以了。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
    /**
     * 机构表别名
     * */
    String orgAlias() default "";

    /**
     * 部门表别名
     * */
    String deptAlias() default "";

    /**
     * 用户表别名
     * */
    String userAlias() default "";

    /**
     * 权限字符(用于多个角色匹配符合要求的权限)多个权限用逗号分隔开来
     */
    String permission() default "";

    /**
     * 自定义权限方法
     */
    String customType() default "";
}

2.处理器

这里dataScopeFilter方法就是去处理数据权限,根据业务就好了。需要注意的是在存储到线程的时候,需要把你最后生成的sql的首位 and 或者 or删除去除掉,这是因为再后面mybatis-plus再拼接sql时,他会有自己and。如果不删除,可能会出现net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: ,这个问题也会因为mybatis版本冲突产生。

@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 final static ThreadLocal<String> threadLocal = new ThreadLocal<>();


    @Before(value = "@annotation(dataScope)")
    public void doBefore(JoinPoint joinPoint, DataScope dataScope){
        // 非系统管理员
        if(!SaUserUtil.isAdmin()){
            dataScopeFilter(dataScope.orgAlias(), dataScope.deptAlias(), dataScope.userAlias(), dataScope.customType());
        }
    }

    /**
     * 执行完方法后,将线程删除
     * */
    @After("@annotation(dataScope)")
    public void doAfter(DataScope dataScope){
        threadLocal.remove();
    }

    /**
     * 出现异常时,线程删除
     * */
    @AfterThrowing("@annotation(dataScope)")
    public void doAfterThrowing(DataScope dataScope){
        threadLocal.remove();
    }

    public static void dataScopeFilter(String orgAlias,String deptAlias,String userAlias,String customType){
        // 获取当前用户信息
        SysUser sysUser = SaUserUtil.getSysUser();
        // 获取当前登录用户角色信息
        List<SysRole> sysRoles = SaUserUtil.getSysRoles();
        // 所有权限标识集合
        List<String> conditions = new ArrayList<>();
        // sql数据
        StringBuilder sqlString = new StringBuilder();
        for (SysRole role : sysRoles) {
            String roleScope = role.getDataScope();
            if (!DATA_SCOPE_CUSTOM.equals(roleScope) && conditions.contains(roleScope))
            {
                continue;
            }
            if (DATA_SCOPE_ALL.equals(roleScope))
            {
                sqlString = new StringBuilder();
                conditions.add(roleScope);
                break;
            }
            else if (DATA_SCOPE_CUSTOM.equals(roleScope)){

            }
            else if (DATA_SCOPE_DEPT.equals(roleScope)){

            }
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(roleScope)){

            }
            else if (DATA_SCOPE_SELF.equals(roleScope))
            {
                if (StringUtil.isNotBlank(userAlias))
                {
                    sqlString.append(StringUtil.format(" or {}.user_id = {} ", userAlias, sysUser.getUserId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名
                    sqlString.append(StringUtil.format(" or user_id= {} ", sysUser.getUserId()));
                }
            }
            conditions.add(roleScope);
        }

        // sql存储
        if (StringUtil.isNotBlank(sqlString.toString()))
        {
            // 去除sql 首位的 and 或 or,根据业务去除。
            threadLocal.set(sqlString.substring(4));
        }
        // 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
        if (StringUtil.isEmpty(conditions))
        {
            threadLocal.set(sqlString.append(" limit 0 ").toString());
        }

    }

}

3.拦截器

实现mybatis-plus的数据权限接口,将处理器中生成的sql拼接到执行sql中。

AndExpression()方法会在拼接sql时提供and。
@Slf4j
public class CustomDataPermissionHandler implements DataPermissionHandler {

    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {

        try {
            String sql = DataScopeAspect.threadLocal.get();
           
            // 加入sql
            if(StringUtil.isNotEmpty(sql)){
                Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sql);
                where =  new AndExpression(where, sqlSegmentExpression);
            }
            return where;
        } catch (JSQLParserException e) {
            log.error("数据权限失败!,{}",e.getMessage());
            throw new ServiceException(ResultEnum.SELECT_FAIL);
        }
    }

}

4.注册拦截器

将拦截器注册到mybatis-plus中。需要注意如果用到分页插件,需在分页插件前进行注册。

@Component
public class MyBatisPlusConfig implements MetaObjectHandler {

    /**
     * 添加插件(官网最新)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 权限
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new CustomDataPermissionHandler()));
        // 分页
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    @Override
    public void insertFill(MetaObject metaObject) {
        // 起始版本 3.3.0(推荐使用)
        this.strictInsertFill(metaObject,"status",String.class,"1");
        this.strictInsertFill(metaObject,"delFlag",String.class,"1");
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 起始版本 3.3.0(推荐使用)
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值