依赖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.5</version>
</dependency>
implementation 'cn.allbs:allbs-mybatis:2.0.5'
implementation("cn.allbs:allbs-mybatis:2.0.5")
配置示例
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
日志打印示例
审计字段自动插入
权限过滤
开启
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);
}
}
效果
忽略权限拦截的方法
-
自定义sql情况下忽略:
在对应的dao指定方法上添加注解@InterceptorIgnore
-
使用mybatis plus 内置sdk的情况下忽略:
dao继承的BaseMapper修改为PmsMapper
-
指定表所有数据都不经过过滤
在对应的dao上添加注解
@InterceptorIgnore
新增、修改、批量新增、批量修改时的越权判断
实现DataPmsHandler
中的updateParameter
和insertParameter
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();
}
}
}
}
}
源码
https://github.com/chenqi92/allbs-mybatis
原文始发于微信公众号(询于刍荛):针对数据权限封装了一个工具包
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/280911.html