【Mybits-Plus】拦截器的学习和使用,以及如何实现数据权限

常规处理数据权限的话Mybits需要对Mybits\Mybits-plus拦截器了解

1.基础知识学习

(请自行学习如下内容,后续才能根据各种需求灵活调整满足场景的合适方案)

Mybatis——拦截器Interceptor

MyBatis 插件之拦截器(Interceptor)

Mybatis——执行流程及关键代码走读

Mybatis-Plus入门系列(3)- MybatisPlus之数据权限插件DataPermissionInterceptor

2.各种场景–实战案例

场景——数据加密(二)Mybatis拦截器
MyBatis实现SQL占位符替换

1. 数据权限控制–若依 AOP方案:详情见若依github工程

        方案缺点:会被信息安全扫描判定为sql注入

在这里插入图片描述

2. 数据权限控制–我公司方案:

结合若依DataScope注解和 MP组建的DataPermissionInterceptor进行扩展:
核心如下:

package mscp.boot.starter.data.permission.annotation;

import java.lang.annotation.*;


/**
 * 数据权限过滤注解
 * 常用在Mapper的sql方法注解上
 *
 * @author
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
    /**
     * 查询组织主体的别名
     */
    public String orgSubjectAlias() default "";

    /**
     * 组织字段的别名
     */
    public String orgFieldAlias() default "";

    /**
     * 查询个人主体的别名
     */
    public String userSubjectAlias() default "";

    /**
     * 用户字段的别名
     */
    public String[] userFieldAlias() default {};


}

package mscp.boot.starter.data.permission.handler;

import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.sf.mscp.system.service.framework.api.SsFrameworkUtils;
import lombok.extern.slf4j.Slf4j;
import mscp.boot.starter.data.permission.annotation.DataPermission;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.schema.Column;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 编写数据权限处理逻辑,对SQL进行拦截处理
 *
 * @author 01392068
 * @date 2023/07/13 14:00
 **/
@Slf4j
public class MSCPDataPermissionHandler implements DataPermissionHandler {


    private static final String COUNT_SUFFIX = "_COUNT";


    /**
     * @param where             原SQL Where 条件表达式
     * @param mappedStatementId Mapper接口方法ID
     *                          <p>
     *                          eg:id=com.sf.nmrm.manage.common.mapper.NmrmDictionaryMapper.selectList
     * @return
     */
    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        log.info("=========================== start MyDataPermissionHandler");
        // 1. 模拟获取登录用户、系统模块信息

        //TODO 替换获取当前系统和人员信息
        String empCode = "002321";
        String sysType = "mscp_service";

        // 2.一级判定逻辑
        // 无当前用户相关信息间接认为是:识别开放接口、job任务不做数据权限拦截
        if (StringUtils.isAnyBlank(empCode, sysType)) {
            return null;
        }
        // 超管和所有组织 不做拦截
        if (SsFrameworkUtils.isSuperAdmin(empCode, sysType) || SsFrameworkUtils.isUserDataScopeContainsAll(empCode, sysType)) {
            return null;
        }


        // 3. 二级拦截逻辑
        // 3.1通过登录用户,从用户信息中获取ORG_ID
        //(1) user->sys_user_role->role_id (多个)
        //(2) role_id->sys_role->data_scope (多个)
        //(3) role_id->sys_role_org->org_id (多个)
        Set<Long> permissionOrgIds = SsFrameworkUtils.getUserDataPermissionOrgIds(empCode, sysType);

        List<String> dataScopes = Arrays.asList("1", "2", "3", "4");

        // 3.2. 通过 id 获取到 Dao 层类的全限定名称,然后反射获取 Class 对象 和Method

        String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
        // 分页插件会生成一个count语句,这个语句的参数也要做处理
        if (methodName.endsWith(COUNT_SUFFIX)) {
            methodName = methodName.substring(0, methodName.lastIndexOf(COUNT_SUFFIX));
        }
        // 动态加载类并获取类中的方法
        Method[] methods = new Method[0];
        try {
            methods = Class.forName(className).getMethods();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }


        // 4.根据注解添加过滤逻辑
        // (1). 遍历 Dao 层类的方法
        // 遍历类的所有方法并找到此次调用的方法,拼装permissionExpressions

        List<Expression> permissionExpressions = getExpressionsFromClassMethod(empCode, permissionOrgIds, methodName, methods);
        //(2)转换 permissionExpressions(List) to  OrExprssion
        if (CollectionUtils.isNotEmpty(permissionExpressions)){
            Expression orExpression = permissionExpressions.stream()
                    .reduce((e1, e2) -> new OrExpression(e1, e2))
                    .orElse(null);
            return new AndExpression(where, orExpression);
        }else{
            //TODO warning
            return where;
        }

    }

    /**
     * 根据注解和个人信息获取List Expression
     *
     * @param empCode 工号
     * @param permissionOrgIds 组织集合
     * @param methodName
     * @param methods
     * @return java.util.List<net.sf.jsqlparser.expression.Expression>
     */
    @NotNull
    private List<Expression> getExpressionsFromClassMethod(String empCode, Set<Long> permissionOrgIds, String methodName, Method[] methods) {
        List<Expression> permissionExpressions = new ArrayList<Expression>();
        for (Method method : methods) {
            if (method.getName().equals(methodName) && method.isAnnotationPresent(DataPermission.class)) {

                // 获取方法上的注解以及注解对应的参数
                DataPermission permissionAnnotation = method.getAnnotation(DataPermission.class);

                // 支持数据权限过滤
                String orgSubjectAlias = permissionAnnotation.orgSubjectAlias();
                String orgFieldAlias = permissionAnnotation.orgFieldAlias();
                String userSubjectAlias = permissionAnnotation.userSubjectAlias();
                String[] userFieldAlias = permissionAnnotation.userFieldAlias();
                if (CollectionUtils.isNotEmpty(permissionOrgIds) && StringUtils.isNotBlank(orgFieldAlias)) {
                    Column orgColumnInfo = null;
                    if (StringUtils.isNotBlank(orgSubjectAlias)) {
                        orgColumnInfo = new Column(String.format("%s.%s", orgSubjectAlias, orgFieldAlias));
                    } else {
                        orgColumnInfo = new Column(orgFieldAlias);
                    }
                    //  order_tbl.dept_id IN ('2', '3', '4', '5')
                    InExpression inExpression = new InExpression(orgColumnInfo,
                            (ItemsList) permissionOrgIds);
                    permissionExpressions.add(inExpression);
                }
                if (ArrayUtils.isEmpty(userFieldAlias)) {
                    for (String empColumn : userFieldAlias) {
                        Column empColumnInfo = null;
                        if (StringUtils.isNotBlank(userSubjectAlias)) {
                            empColumnInfo = new Column(String.format("%s.%s", userSubjectAlias, empColumn));
                        } else {
                            empColumnInfo = new Column(empColumn);
                        }
                        // order_tbl.user_code = 'xxxxx'
                        EqualsTo equalsTo = new EqualsTo();
                        equalsTo.setLeftExpression(empColumnInfo);
                        equalsTo.setRightExpression(new StringValue(empCode));
                        permissionExpressions.add(equalsTo);
                    }
                }
                break;
            }
        }
        return permissionExpressions;
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值