Mybatis-plus数据权限DataPermissionInterceptor实现(二)

Mybatis-plus数据权限实现方案

前一篇
Mybatis-plus数据权限DataPermissionInterceptor实现


前言

上篇中,会发现,注解是放在mapper的方法上的,所以不能使用Mybatis-plus自带的17个方法,只能自己手写。而且针对于删改查3个功能,需要写3个查询方法,只是注解的操作类型不同,每一个要控制权限的对象都需要额外定义查询,都是重复工作量。


一、实现思路

1、自己实现Mybatis-plus的BaseMapper,增加用于 删、改、查 权限的默认查询,不用每个对象都定义一遍
2、DataPermissionHandler中根据mappedStatementId直接判断判断默认的查询,区分出删、改、查 权限,拼接上对应的权限sql
3、这样不需要写mapper的方法了,怎么区分控制什么权限呢,总不能也根据包名+类名确定吧,所以可以在mapper上加上自定义注解

二、实现细节

1.加强Mybatis-plus的BaseMapper

mybatis-plus官方样例

就三步

  • 自定义全局方法类,需继承com.baomidou.mybatisplus.core.injector.AbstractMethod
  • 注册,将自定义的全局方法注册到mp,继承com.baomidou.mybatisplus.core.injector.DefaultSqlInjector
  • 定义自己的BaseMapper,继承原本的BaseMapper
  1. 自定义全局方法类
    官方样例这样写的

    public class DeleteAll extends AbstractMethod {
        @Override
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            /* 执行 SQL ,动态 SQL 参考类 SqlMethod */
            String sql = "delete from " + tableInfo.getTableName();
            /* mapper 接口方法名一致 */
            String method = "deleteAll";
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
        }
    }
    

    这一步是核心,在这里定义的方法名、sql语句。我们要增加的是根据id查询,所以我们直接继承本身的SelectById,然后把方法改成我们自定义的方法名就行了,先看看SelectById咋实现的,SelectById类如下

    public class SelectById extends AbstractMethod {
        public SelectById() {
            super(SqlMethod.SELECT_BY_ID.getMethod());
        }
        public SelectById(String name) {
            super(name);
        }
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
            SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class);
            return this.addSelectMappedStatementForTable(mapperClass, this.getMethod(sqlMethod), sqlSource, tableInfo);
        }
    }
    

    可以看到里面有两个构造方法,官方的样例没有这个,咱们也把他忽略,直接看injectMappedStatement。比官方的样例的DeleteAll多了个SqlMethod类,点进去看看这个是啥,如下

    public enum SqlMethod {
        INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>\nINSERT INTO %s %s VALUES %s\n</script>"),
        ...
        SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s} %s"),
        ...
        private final String method;
        private final String desc;
        private final String sql;
        // getter
    }
    

    就是个枚举类,定义了方法名、sql结构。
    SqlMethod被使用两次,sqlMethod.getSql()和this.getMethod(sqlMethod),
    sqlMethod.getSql()是获取sql,this.getMethod(sqlMethod)是获取方法名,所以我们直接照搬过来,将this.getMethod(sqlMethod)替换成自己的方法名即可。

    我们要增加selectByIdForFind、selectByIdForEdit、selectByIdForRemove三个方法
    所以新增类SelectByIdForEdit,继承自SelectById,把SelectById的injectMappedStatement方法直接复制过来,加上个@Override,然后把this.getMethod(sqlMethod)换成"selectByIdForFind"就行了,我是把"selectByIdForFind"放到一个常量类里去了

    	public class SelectByIdForEdit extends SelectById {
        @Override
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
            SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class);
            return this.addSelectMappedStatementForTable(mapperClass, SqlMethodConstants.SELECT_BY_ID_FOR_EDIT, sqlSource, tableInfo);
        }
    }
    
  2. 注册
    直接复制的样例里的,把刚刚自己定义的方法类加入进去

     	public class MyLogicSqlInjector extends DefaultSqlInjector {
        /**
         * 如果只需增加方法,保留MP自带方法
         * 可以super.getMethodList() 再add
         * @return
         */
        @Override
        public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
            List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
            methodList.add(new SelectByIdForEdit());
            methodList.add(new SelectByIdForFind());
            methodList.add(new SelectByIdForRemove());
            return methodList;
        }
    }
    

    最后要将这个注入到spring里,定义一个configuration

    @Configuration
    public class MybatisPlusConfiguration {
    	@Bean
        public MyLogicSqlInjector myLogicSqlInjector() {
            return new MyLogicSqlInjector();
        }
    }
    
  3. 定义自己的BaseMapper

    public interface MyBaseMapper<T> extends BaseMapper<T> {
        T selectByIdForFind(Serializable id);
        T selectByIdForEdit(Serializable id);
        T selectByIdForRemove(Serializable id);
    }
    

2.调整DataPermissionHandler

代码如下:

	@Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        try {
            Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));
            String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
            Method[] methods = clazz.getDeclaredMethods();
            //判断mapper上是否有注解
            CustomDataPermission classAnnotation = clazz.getAnnotation(CustomDataPermission.class);
            if (classAnnotation == null) {
                return where;
            }
            if (methodName.equals(SqlMethodConstants.SELECT_BY_ID_FOR_FIND) || methodName.equals(SqlMethodConstants.SELECT_BY_ID_FOR_EDIT) || methodName.equals(SqlMethodConstants.SELECT_BY_ID_FOR_REMOVE)) {
                return handle(classAnnotation, where, methodName);
            } else {
            	//保留方法上的自定义注解
                for (Method method : methods) {
                    if (!methodName.equals(method.getName())) {
                        continue;
                    }
                    // 获取自定义注解
                    CustomDataPermission annotation = method.getAnnotation(CustomDataPermission.class);
                    if (annotation == null) {
                        continue;
                    }
                    return handle(annotation, where, methodName);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return where;
    }
	private Expression handle(CustomDataPermission classAnnotation, Expression where, String methodName) {
		//...jsqlparser处理sql,可参照前一篇
	}

3.使用

mapper

@Mapper
@CustomDataPermission(
            billType = BillTypeEnum.APPLY_BILL)
public interface ApplyHMapper extends MyBaseMapper<ApplyHPo> {
    @CustomDataPermission(
            billType = BillTypeEnum.APPLY_BILL,
            operation = OperationTypeEnum.SELECT)
    Page<ApplyHPo> selectApplyHs(IPage<ApplyHPo> page, @Param(Constants.WRAPPER) QueryWrapper<ApplyHPo> queryWrapper);
}

service

@Service
public class ApplyHServiceImpl implements ApplyHService {
    @Autowired
    private ApplyHMapper applyHMapper;

	public void update(ApplyH applyH) {
		// 先校验单据是否存在
		ApplyH oldApplyH = applyHMapper.selectById(applyH.getId());
		if(Objects.isNull(oldApplyH)) {
			throw new ResourceNotFoundException("申请单【" + applyH.getBillNo() + "】不存在");
		}
		// 再校验是否存在修改权限
		oldApplyH = applyHMapper.selectByIdForEdit(applyH.getId());
		if(Objects.isNull(oldApplyH)) {
			throw new NoAccessException("没有修改申请单【" + applyH.getBillNo() + "】的权限");
		}
		// ...其他校验   
		// ResourceNotFoundException和BizException为我们自定义的异常类,有全局异常进行处理
	}
}

4.新增权限控制

删改查的权限控制实际上就是在sql后面拼接条件,

比如要控制门店字段orgcode,原本的sql为 select * from t_org where status = true
会处理成select * from t_org where status = true and orgcode in (…)

但是insert语句是没办法添加where条件的,就不能使用这个方法处理了,我们的思路是使用aop拦截自定义的注解,取到参数进行校验,这要求参数必须规范,大家有什么其他思路,欢迎交流


总结

对上一篇进行了加强,简化了开发,优化了权限控制逻辑

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿狸尬多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值