依赖jar包

引入包 版本
jdk 1.8
spring boot 2.7.2
mybatis-plus-extension 3.5.3.1
mybatis-plus 3.5.3.1
mybatis-plus-boot-starter 3.5.3.1
spring-boot-starter-web 2.7.9

使用

添加依赖

<dependency>
  <groupId>cn.allbs</groupId>
  <artifactId>allbs-mybatis</artifactId>
  <version>2.0.3</version>
</dependency>
implementation 'cn.allbs:allbs-mybatis:2.0.3'
implementation("cn.allbs:allbs-mybatis:2.0.3")

配置示例

mybatis-plus:
  mapper-locations: classpath*:mapper/*/*.xml
  global-config:
    banner: false
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0
# 此处注释是为了使用该包中自定义打印的sql日志,如果放开会打印两次sql日志,只是格式不同
#  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-handlers-package: com.allbs.allbsjwt.config.handler
  # 这边是为了打印自定义格式的sql日志,比如参数自动填充,结果按照类似表格输出。如果不需要或者生成环境此处设为false
  show-sql: true
  # 此处是为了审计自动填充,因为不同表中字段不一样所以逻辑删除、创建者、创建时间、更新者、更新时间字段自定义。有些系统创建者和更新者使用的是id则与本系统不兼容,此处默认的是插入spring security中的用户名
   meta-custom:
    del-flg: delFlag
    create-name: createId
    update-name: updateId
  # 设置中文字符占其他字符的比例,取巧尽量让打印出来的格式工整些,比如某些字体占两个英文的宽度就设置为2
  chine-rate: 1.5
  # 是否开启权限过滤字段
  data-pms: true

日志打印示例

image-20230327111534230

image-20230327111549175

审计字段自动插入

image-20230327111732400

权限过滤

开启

mybatis-plus.data-pms 设置为true,看上方配置示例最后一个配置

使用说明

实现DataPmsHandler后写详细的逻辑即可,比如:

@ScopeField是用于跟表关联的实体类上的注解,用于标记改表中权限过滤的字段是哪个,以下类举例:下文中DEFAULT_FILTER_FIELD默认的是ent_id指数据库表中以该字段作为区分,如果有张表突然设置的是unit_id而不是ent_id则在对应的实体上设置@ScopeField("unit_id")

import cn.allbs.allbsjwt.config.utils.SecurityUtils;
import cn.allbs.allbsjwt.config.vo.SysUser;
import cn.allbs.common.constant.StringPool;
import cn.allbs.mybatis.datascope.DataPmsHandler;
import cn.allbs.mybatis.datascope.ScopeField;
import cn.allbs.mybatis.utils.PluginUtils;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import org.springframework.stereotype.Component;

import java.util.Optional;
import java.util.Set;

/**
 * 类 CustomPermissionHandler
 *
 * @author ChenQi
 * @date 2023/3/28
 */
@Slf4j
@Component
public class CustomPermissionHandler implements DataPmsHandler {

    private final static String DEFAULT_FILTER_FIELD = "ent_id";

    /**
     * 获取数据权限 SQL 片段
     *
     * @param where             待执行 SQL Where 条件表达式
     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
     * @return JSqlParser 条件表达式
     */
    @Override
    public Expression getSqlSegment(final Table table, Expression where, String mappedStatementId) {

        SysUser sysUser = SecurityUtils.getUser();
        // 如果非权限用户则不往下执行,执行原sql
        if (sysUser == null) {
            return where;
        }
        // 在有权限的情况下查询用户所关联的企业列表
        Set<Long> permissionEntList = sysUser.getEntIdList();
//        if (permissionEntList.size() == 0) {
//            return where;
//        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(table.getName());
        String fieldName = Optional.ofNullable(tableInfo.getEntityType().getAnnotation(ScopeField.class)).map(ScopeField::value).orElse(DEFAULT_FILTER_FIELD);
        String finalFieldName = Optional.of(table).map(Table::getAlias).map(a -> a.getName() + StringPool.DOT + fieldName).orElse(fieldName);

        if (permissionEntList.size() > 1) {
            // 把集合转变为 JSQLParser需要的元素列表
            InExpression inExpression = new InExpression(new Column(finalFieldName), PluginUtils.getItemList(permissionEntList));

            // 组装sql
            return where == null ? inExpression : new AndExpression(where, inExpression);
        }
        // 设置where
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(new Column(finalFieldName));
        equalsTo.setRightExpression(new LongValue(permissionEntList.stream().findFirst().orElse(0L)));
        return where == null ? equalsTo : new AndExpression(where, equalsTo);
    }
}
效果

image-20230330102020985

image-20230330102109077

忽略权限拦截的方法
  • 自定义sql情况下忽略:

在对应的dao指定方法上添加注解@InterceptorIgnore

  • 使用mybatis plus 内置sdk的情况下忽略:

dao继承的BaseMapper修改为PmsMapper

image-20230330104127313

  • 指定表所有数据都不经过过滤

    在对应的dao上添加注解@InterceptorIgnore

新增、修改、批量新增、批量修改时的越权判断

实现DataPmsHandler中的updateParameterinsertParameter

package cn.allbs.allbsjwt.config.datascope.mapper;

import cn.allbs.allbsjwt.config.exception.UserOverreachException;
import cn.allbs.allbsjwt.config.utils.SecurityUtils;
import cn.allbs.allbsjwt.config.vo.SysUser;
import cn.allbs.common.constant.StringPool;
import cn.allbs.mybatis.datascope.DataPmsHandler;
import cn.allbs.mybatis.datascope.ScopeField;
import cn.allbs.mybatis.utils.PluginUtils;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * 类 CustomPermissionHandler
 *
 * @author ChenQi
 * @date 2023/3/28
 */
@Slf4j
@Component
public class CustomPermissionHandler implements DataPmsHandler {

    private final static String DEFAULT_FILTER_FIELD = "ent_id";

    /**
     * 获取数据权限 SQL 片段
     *
     * @param where             待执行 SQL Where 条件表达式
     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
     * @return JSqlParser 条件表达式
     */
    @Override
    public Expression getSqlSegment(final Table table, Expression where, String mappedStatementId) {

        // 在有权限的情况下查询用户所关联的企业列表
        SysUser sysUser = SecurityUtils.getUser();
        // 如果非权限用户则不往下执行,执行原sql
        if (sysUser == null) {
            return where;
        }
        Set<Long> permissionEntList = sysUser.getEntIdList();
//        if (permissionEntList.size() == 0) {
//            return where;
//        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(table.getName());
        String fieldName = tableInfo.getFieldList().stream()
                .filter(a -> a.getField().getAnnotation(ScopeField.class) != null)
                .map(a -> a.getField().getAnnotation(ScopeField.class).value())
                .findFirst()
                .orElse(DEFAULT_FILTER_FIELD);
        Alias fromItemAlias = table.getAlias();
        String finalFieldName = Optional.ofNullable(fromItemAlias).map(a -> a.getName() + StringPool.DOT + fieldName).orElse(fieldName);

        if (permissionEntList.size() > 1) {
            // 把集合转变为 JSQLParser需要的元素列表
            InExpression inExpression = new InExpression(new Column(finalFieldName), PluginUtils.getItemList(permissionEntList));

            // 组装sql
            return where == null ? inExpression : new AndExpression(where, inExpression);
        }
        // 设置where
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(new Column(finalFieldName));
        equalsTo.setRightExpression(new LongValue(permissionEntList.stream().findFirst().orElse(0L)));
        return where == null ? equalsTo : new AndExpression(where, equalsTo);
    }

    @Override
    public void updateParameter(Update updateStmt, MappedStatement mappedStatement, BoundSql boundSql) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(updateStmt.getTable().getName());
        parameterHandler(tableInfo.getFieldList(), boundSql);
    }

    @Override
    public void insertParameter(Insert insertStmt, BoundSql boundSql) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(insertStmt.getTable().getName());
        parameterHandler(tableInfo.getFieldList(), boundSql);
    }

    private void parameterHandler(List<TableFieldInfo> fieldList, BoundSql boundSql) {
        // 过滤数据
        SysUser sysUser = SecurityUtils.getUser();
        // 如果当前用户是超级管理员,不处理,根据自己系统实际情况去判断
        if (sysUser.getId() == 1L) {
            return;
        }
        // 获取当前用户所具备的ent_id
        Set<Long> permissionEntList = sysUser.getEntIdList();

        // 获取当前表中需要权限过滤的字段名称
        String fieldName = fieldList.stream()
                .filter(a -> a.getField().getAnnotation(ScopeField.class) != null)
                .map(a -> a.getField().getAnnotation(ScopeField.class).value())
                .findFirst()
                .orElse(DEFAULT_FILTER_FIELD);

        MetaObject metaObject = SystemMetaObject.forObject(boundSql.getParameterObject());

        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
            String propertyName = parameterMapping.getProperty();
            if (propertyName.startsWith("ew.paramNameValuePairs")) {
                continue;
            }
            String[] arr = propertyName.split("\\.");
            String propertyNameTrim = arr[arr.length - 1].replace("_", "").toUpperCase();
            if (fieldName.replaceAll("[._\\-$]", "").toUpperCase().equals(propertyNameTrim)) {
                if (!Optional.ofNullable(metaObject.getValue(propertyName)).isPresent()) {
                    return;
                }
                long currentEntId = Long.parseLong(metaObject.getValue(propertyName).toString());
                // 判断是否在权限范围内
                if (permissionEntList.contains(currentEntId)) {
                    metaObject.setValue(propertyName, currentEntId);
                } else {
                    // 我这边是直接抛出异常,所有sql语句会直接回滚可以选择其他办法如: 使用当前用户的ent_id 替换插入值 or 直接忽略当前插入sql但不抛出异常
                    throw new UserOverreachException();
                }
            }

        }
    }
}

源码

github