MyBatis中添加拦截器实现权限控制

有一个@AuthData,用它能够在MyBatis查询的时候增加一些权限

mybatis可以被拦截的类型有四种:

  • Excutor(拦截执行器的方法)
  • ParameterHandler(拦截参数的处理),基本
  • StatementHandler(拦截sql语句构建的处理)
  • ResultHandler(拦截结果集的处理)

详情看下面的类的具体实现

// (1) 数据权限拦截器的实现类
AuthDataIntercepter

// (2) 配合该注解实现,用在controller层的方法上
@AuthData

// (3) 在写SQL语句时可以获取到注入的参数数据
<if test="auth != null and auth.authType == 2">
    WHERE org_id IN
    <foreach collection="auth.departIds" item="did" open="(" separator="," close=")">
        #{did}
    </foreach>
</if>

// (4) 在启动类种注册拦截器
public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(ModuleFinanceServiceApplication.class, args);
    SpringUtils.setApplicationContext(context);
    //这种方式添加mybatis拦截器保证在pageHelper前执行
    context.getBean(SqlSessionFactory.class).getConfiguration().addInterceptor(new AuthDataIntercepter());
    context.getBean(SqlSessionFactory.class).getConfiguration().addInterceptor(new SignComAuthDataIntercept());
}
业务开发只需要在写SQL时从指定的变量中获取权限的数据,并将其拼接到SQL语句中即可。

拦截器实现代码

前面的注解@Intercepts的两个参数,好像也没人介绍为什么要这样写。

基本的意思就是,执行具体的update或者query的SQL的时候会触发这些逻辑。

第一个接口AuthDataIntercepter


@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),})
public class AuthDataIntercepter implements Interceptor {

    private final Logger logger = LoggerFactory.getLogger(AuthDataIntercepter.class);

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Object intercept(Invocation invocation) throws Throwable {

        AuthDataBo authDataBo = AuthDataUtils.getAuthData();

        Object[] args = invocation.getArgs();
        Object paramObject = args[1];
        if (Objects.isNull(authDataBo)) {
            if (paramObject instanceof MapperMethod.ParamMap) {
                MapperMethod.ParamMap paramObject1 = (MapperMethod.ParamMap) paramObject;
                paramObject1.put("auth", authDataBo);
                args[1] = paramObject1;
            }
            return invocation.proceed();
        }
        if (paramObject instanceof Map) {
            if (MapUtils.isNotEmpty((Map<String, Object>) paramObject)) {
                ((Map<Object, Object>) paramObject).put("auth", authDataBo);
            }
        } else if (paramObject == null) {
            // 如果mapper参数列表为空而又需要auth
            HashMap<String, Object> params = new HashMap<>();
            params.put("auth", authDataBo);
            args[1] = params;
        } else {
            Class<?> clazz = paramObject.getClass();
            if (logger.isDebugEnabled()) {
                logger.debug("mybatis参数类型不为Map:{}", clazz);
            }

            Map<String, Object> beamMap = getMethod(args);
            beamMap.put("auth", authDataBo);
            args[1] = beamMap;
        }
        if (logger.isDebugEnabled()) {
            logger.info("mybatis添加数据权限后的参数:{}", JsonUtils.toJson(args[1]));
        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {
        //do Nothing
    }

    public Map<String, Object> getMethod(Object[] args) throws Throwable {
        MappedStatement arg = (MappedStatement) args[0];
        String string = arg.getId();
        Object paramObject = args[1];
        String method = string.substring(string.lastIndexOf(".") + 1);
        String clazz = string.substring(0, string.lastIndexOf("."));
        Class<?> aClass = Class.forName(clazz);
        Method[] methods = aClass.getMethods();
        Map<String, Object> beamMap = ObjectUtil.objectToMap(paramObject);
        for (Method method1 : methods) {
            if (!method1.getName().equals(method)) {
                continue;
            }
            //if (method1.isAnnotationPresent(Param.class)) continue;
            Parameter[] ps = method1.getParameters();
            for (Parameter p : ps) {
                String name = p.getName();
                beamMap.put(name, paramObject);
            }
            args[1] = beamMap;
        }
        return beamMap;
    }

}

另一个拦截器的代码是这样的

另一个接口SignComAuthDataIntercept

/**
 * 签约主体划分数据权限Mybatis拦截器
 *
 */
@Intercepts({
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
                        CacheKey.class, BoundSql.class}),})
public class SignComAuthDataIntercept implements Interceptor {

    /**
     * 需要注入参数的key值
     */
    private static final String SIGN_COM_KEY = "signComAuth";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 从线程本地变量中获取数据权限对象
        SignComVo signComAuthVo = SignComAuthDataAspect.getSignComAuthVo();
        Object[] args = invocation.getArgs(); // 这里获取的是@Signature中args
        Object paramObject = args[1]; // 数据结构为:MapperMethod.ParamMap

        if (Objects.isNull(signComAuthVo)) {
            if (MapperMethod.ParamMap.class.isInstance(paramObject)) {
                MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) paramObject;
                // 对map追加一个元素然后放回 args[1]
                paramMap.put(SIGN_COM_KEY, null);
                args[1] = paramMap;
            }
            return invocation.proceed();
        }

        if (paramObject instanceof Map) {
            if (MapUtils.isNotEmpty((Map<String, Object>) paramObject)) {
                ((Map<String, Object>) paramObject).put(SIGN_COM_KEY, signComAuthVo);
            }
        } else if (paramObject == null) {
            // 如果mapper参数列表为空而又需要auth
            HashMap<String, Object> params = new HashMap<>();
            params.put(SIGN_COM_KEY, signComAuthVo);
            args[1] = params;
        } else {
            Map<String, Object> beamMap = ObjectUtil.objectToMap(paramObject);
            beamMap.put(SIGN_COM_KEY, signComAuthVo);
            args[1] = beamMap;
        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {
        // do nothing
    }

}

总结

以前设计权限系统的时候,所有的权限都是在代码中实现的,这样其实是会增加代码量。

这是一个很不错的思路

### MyBatis-Plus 拦截器实现数据权限控制 #### 使用自定义注解与拦截器相结合的方式 为了实现MyBatis Plus中对查询结果的数据权限过滤,可以采用自定义注解配合拦截器的方式来完成。这种方式不仅能够简化开发流程,还能有效减少SQL解析的风险。 当开发者希望在不修改原有业务逻辑的前提下增加数据访问层的安全机制时,可以通过创建特定场景下的注解来标记需要应用此安全措施的服务接口或方法[^2]。例如: ```java @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface DataPermission { String value(); } ``` 接着,在配置类中注册一个实现了`Interceptor`接口的组件,并重写其内的`intercept()`函数用于实际处理逻辑。在此过程中,可以从上下文中获取由上述自定义注解传递过来的信息并据此调整最终发出给数据库引擎执行的SQL语句[^1]。 对于具体的应用实例来说,则是在目标Service层的方法上添加之前定义好的注解,指定相应的条件表达式或者其他必要的参数。之后每当触发带有该注解标注的操作时,系统便会自动调用对应的拦截器来进行额外校验工作,从而达到预期效果。 下面是一个简单的例子展示如何设置这样的环境以及编写相关代码片段: ```java // 定义数据权限注解 import java.lang.annotation.*; @Documented @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface DataScope {} // 创建拦截器类 @Component @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})) class DynamicDataSourceInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement)invocation.getArgs()[0]; Object parameterObject = invocation.getArgs()[1]; Method currentMethod = ReflectUtil.getMethodByClass(ms.getId(), parameterObject.getClass()); if(null != currentMethod && currentMethod.isAnnotationPresent(DataScope.class)){ // 获取当前登录用户的部门id或其他信息 Long deptId = SecurityContextHolder.getContext().getAuthentication().getName(); BoundSql boundSql = ms.getBoundSql(parameterObject); String sql = boundSql.getSql(); // 动态拼接sql,这里只是简单示范,实际情况可能更复杂 StringBuilder newSqlBuilder = new StringBuilder(sql.length() + 100).append(sql); // 假设表中有dept_id字段用来存储所属部门编号 int index = newSqlBuilder.lastIndexOf("WHERE"); if(index >= 0){ newSqlBuilder.insert(index + 5," AND t.dept_id = #{deptId}"); }else{ newSqlBuilder.append(" WHERE t.dept_id = #{deptId}"); } // 更新boundSql对象中的原始sql字符串和其他属性... MetaObject metaObject = SystemMetaObject.forObject(boundSql); metaObject.setValue("sql",newSqlBuilder.toString()); // 将新的参数加入到原参数列表里以便后续使用 Map<String,Object> paramsMap = (Map<String,Object>)parameterObject; paramsMap.put("deptId",deptId); } return invocation.proceed(); } } // 应用至服务端点处 @Service public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements IUserService { @DataScope @Override public List<UserEntity> listUsers(){ return baseMapper.selectList(new QueryWrapper<>()); } } ``` 以上就是关于如何利用MyBatis Plus自带的拦截器特性结合自定义注解达成较为灵活高效的数据权限管理方案的一个基本介绍和实践案例说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值