记一次mybatis插件开发数据权限支持

摘要

做数据权限,支持自定义sql的实现,不可避免的要进行mybatis的插件开发,这里记录一下

环境:

mybatis-plus+oracle

思路

  1. 调用接口的时候,准备处理好的条件。
  2. 拦截指定的方法,进行参数校验
  3. 校验的相关的参数,进行sql的分析处理。
  4. 把处理好的sql,封装回去。

实现

/**
 * <p>
 * mybatis插件开发
 * </p>
 *
 * @author caoqibei
 * @Dare: 2021/4/23 9:54
 */
@Intercepts({
		// 拦截的对象,对象的方法,方法的参数
        @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
// 加入到spring容器中,自动生效,mybatis插件机制,会自动扫描所有的Interceptor 实现类。
@Component	
@Log4j2
public class DataInterceptor implements Interceptor {

    static int MAPPED_STATEMENT_INDEX = 0;
    static int PARAMETER_INDEX = 1;
    static int ROW_BOUNDS_INDEX = 2;
    static int RESULT_HANDLER_INDEX = 3;
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        DataInfo info = findDataInfo(args[PARAMETER_INDEX]);

        if (info != null){
            log.info("此方法开启数据过滤");
            // 需要数据过滤
            final MappedStatement ms = (MappedStatement)args[MAPPED_STATEMENT_INDEX];
            final Object parameter = args[PARAMETER_INDEX];
            final BoundSql boundSql = ms.getBoundSql(parameter);

            log.info("sql处理前:" + boundSql.getSql());
            String sql = boundSql.getSql().trim();
            String dataSql = getDataSql(sql, info);
            log.info("sql处理后:" + dataSql);
            MappedStatement mappedStatement = mappedStatement(ms, dataSql, boundSql);
            invocation.getArgs()[MAPPED_STATEMENT_INDEX] = mappedStatement;
        }

        return invocation.proceed();
    }

    /**
     * 创建新的MappedStatement,进行替换
     * @param ms
     *  原MappedStatement对象
     * @param dataSql
     *  处理后的sql
     * @param boundSql
     *  原boundSql对象
     * @return
     *  新的MappedStatement
     */
    public static MappedStatement mappedStatement(final MappedStatement ms, final String dataSql, final BoundSql boundSql ){
        BoundSql newBoundSql = 
        new BoundSql(ms.getConfiguration(), dataSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
        MappedStatement.Builder builder = 
        new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), new BoundSqlSqlSource(newBoundSql), ms.getSqlCommandType());
        builder.resource(ms.getResource())
                .fetchSize(ms.getFetchSize())
                .statementType(ms.getStatementType())
                .keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties()!=null){
            for (String keyProperty : ms.getKeyProperties()) {
                builder.keyProperty(keyProperty);
            }
        }
       return builder.timeout(ms.getTimeout())
                .parameterMap(ms.getParameterMap())
                .resultMaps(ms.getResultMaps())
                .cache(ms.getCache())
                .build();
    }

    /**
     * 在方法参数中查找,数据过滤需要对象
     * @param params
     *  Mapper方法中的参数
     * @return
     *  有此参数返回
     *  无返回空
     */
    private DataInfo findDataInfo(Object params) {
        if (params == null){
            return null;
        }
        // 单个参数
        if (DataInfo.class.isAssignableFrom(params.getClass())){
            return (DataInfo) params;
        }
        // 多参数
        else if (params instanceof ParamMap){
            ParamMap<Object> paramMap = (ParamMap<Object>) params;
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                Object value = entry.getValue();
                if (value != null && DataInfo.class.isAssignableFrom(value.getClass())){
                    return (DataInfo) value;
                }
            }
        }

        return null;
    }

    /**
     * SqlSource 接口实现
     */
    public static class BoundSqlSqlSource implements SqlSource{
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql){
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object o) {
            return this.boundSql;
        }
    }

    @Override
    public Object plugin(Object target){
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties){}
}

sql处理部分参考

后续bug

  1. 上面代码导致,foreach标签失效
  2. 经过检测,是因为,foreach生成的动态参数,没有完整复制过来
  3. 解决方案:
    1. 反射,修改 BoundSql 对象,把原来的 BoundSql 对象,通过反射拿到动态参数,塞到新的里面。(因为 BoundSql 对象没提供任何访问动态参数的方式)
    2. 修改过滤器接口位置,很明显,系统不希望我们修改 BoundSql 对象,所以更改切入位置。
  • 方案1参考文章
  • 方案2:but,会影响分页。。。所以采用1。
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
@Log4j2
public class DataInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 目标对象
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        // 获取元数据
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        // 获取参数对象
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        // 数据权限对象
        DataInfo info = findDataInfo(boundSql.getParameterObject());

        if (info != null){
            log.info("表:" + info.getTableName() + " 开启数据过滤");
            // 需要数据过滤
            log.info("sql处理前:" + boundSql.getSql());
            String sql = boundSql.getSql().trim();
            String dataSql = getDataSql(sql, info);
            log.info("sql处理后:" + dataSql);
            metaObject.setValue("delegate.boundSql.sql", dataSql);
        }

        return invocation.proceed();
    }

    /**
     * 在方法参数中查找,数据过滤需要对象
     * @param params
     *  Mapper方法中的参数
     * @return
     *  有此参数返回
     *  无返回空
     */
    private DataInfo findDataInfo(Object params) {

        if (params == null){
            return null;
        }

        // 单个参数
        if (DataInfo.class.isAssignableFrom(params.getClass())){
            return (DataInfo) params;
        }

        // 多参数
        else if (params instanceof ParamMap){
            ParamMap<Object> paramMap = (ParamMap<Object>) params;
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                Object value = entry.getValue();
                if (value != null && DataInfo.class.isAssignableFrom(value.getClass())){
                    return (DataInfo) value;
                }
            }
        }

        return null;
    }

    @Override
    public Object plugin(Object target){
        return Plugin.wrap(target, this);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值