升级mybatis-plus

This commit is contained in:
2024-03-28 10:34:31 +08:00
parent 86ca404dbf
commit 646a26b754
20 changed files with 1092 additions and 664 deletions

View File

@@ -1,132 +0,0 @@
package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import com.ruoyi.common.utils.StringUtils;
/**
* Mybatis支持*匹配扫描包
*
* @author ruoyi
*/
@Configuration
public class MyBatisConfig
{
@Autowired
private Environment env;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
public static String setTypeAliasesPackage(String typeAliasesPackage)
{
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
List<String> allResult = new ArrayList<String>();
try
{
for (String aliasesPackage : typeAliasesPackage.split(","))
{
List<String> result = new ArrayList<String>();
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = resolver.getResources(aliasesPackage);
if (resources != null && resources.length > 0)
{
MetadataReader metadataReader = null;
for (Resource resource : resources)
{
if (resource.isReadable())
{
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try
{
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
}
if (result.size() > 0)
{
HashSet<String> hashResult = new HashSet<String>(result);
allResult.addAll(hashResult);
}
}
if (allResult.size() > 0)
{
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
}
else
{
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
}
}
catch (IOException e)
{
e.printStackTrace();
}
return typeAliasesPackage;
}
public Resource[] resolveMapperLocations(String[] mapperLocations)
{
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null)
{
for (String mapperLocation : mapperLocations)
{
try
{
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
}
catch (IOException e)
{
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
{
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
}

View File

@@ -0,0 +1,67 @@
package com.ruoyi.framework.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.ruoyi.framework.interceptor.DataScopeIntercept;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Mybatis Plus 配置
*
* @author ruoyi
*/
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig
{
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限控制插件
DataScopeIntercept userDataScopeIntercept = new DataScopeIntercept();
interceptor.addInnerInterceptor(userDataScopeIntercept);
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 阻断插件
interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
return interceptor;
}
/**
* 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html
*/
public PaginationInnerInterceptor paginationInnerInterceptor()
{
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置数据库类型为mysql
paginationInnerInterceptor.setDbType(DbType.MYSQL);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor()
{
return new OptimisticLockerInnerInterceptor();
}
/**
* 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html
*/
public BlockAttackInnerInterceptor blockAttackInnerInterceptor()
{
return new BlockAttackInnerInterceptor();
}
}

View File

@@ -0,0 +1,234 @@
package com.ruoyi.framework.interceptor;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.context.PermissionContextHolder;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.HexValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.ruoyi.framework.aspectj.DataScopeAspect.*;
@Slf4j
public class DataScopeIntercept extends JsqlParserSupport implements InnerInterceptor {
/**
* 主要处理查询
*/
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
String mapperId = ms.getId();
//获取mapper名称
String className = mapperId.substring(0, mapperId.lastIndexOf("."));
//获取方法名
String methodName = mapperId.substring(mapperId.lastIndexOf(".") + 1);
try {
Method[] methods = Class.forName(className).getMethods();
//遍历判断mapper 的所以方法,判断方法上是否有 UserDataScope
for (Method m : methods) {
if (Objects.equals(m.getName(), methodName)) {
DataScope controllerDataScope = m.getAnnotation(DataScope.class);
if(controllerDataScope!=null){
String originalSql = boundSql.getSql();
// 检查SQL是否包含count()函数
if (!originalSql.toLowerCase().contains("count(")) {
// 如果不包含count()函数则进行SQL解析和修改
PluginUtils.MPBoundSql mp = PluginUtils.mpBoundSql(boundSql);
mp.sql(parserSingle(mp.sql(), ms.getId()));
}
// 如果包含count()函数,则不进行任何操作,直接放行
}
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 操作前置处理可以在这里改改sql啥的
*/
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
InnerInterceptor.super.beforePrepare(sh, connection, transactionTimeout);
}
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
super.processDelete(delete, index, sql, obj);
}
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
super.processUpdate(update, index, sql, obj);
}
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
SelectBody selectBody = select.getSelectBody();
try {
// 单个sql
if (selectBody instanceof PlainSelect) {
this.setWhere((PlainSelect) selectBody, obj.toString());
} else if (selectBody instanceof SetOperationList) {
// 多个sql用;号隔开一般不会用到。例如select * from user;select * from role;
SetOperationList setOperationList = (SetOperationList) selectBody;
List<SelectBody> selects = setOperationList.getSelects();
selects.forEach(s -> this.setWhere((PlainSelect) s, obj.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected void setWhere(PlainSelect plainSelect, String mapperId) {
Expression sqlSegment = getSqlSegment(plainSelect, mapperId);
if (null != sqlSegment) {
plainSelect.setWhere(sqlSegment);
}
}
private Expression getSqlSegment(PlainSelect plainSelect, String mapperId) {
// 待执行 SQL Where 条件表达式
Expression where = plainSelect.getWhere();
log.info("开始进行权限过滤,where: {},mapperId: {}", where, mapperId);
//获取mapper名称
String className = mapperId.substring(0, mapperId.lastIndexOf("."));
//获取方法名
String methodName = mapperId.substring(mapperId.lastIndexOf(".") + 1);
Table fromItem = (Table) plainSelect.getFromItem();
// 有别名用别名,无别名用表名,防止字段冲突报错
// Alias fromItemAlias = fromItem.getAlias();
// String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName();
//获取当前mapper 的方法
try {
Method[] methods = Class.forName(className).getMethods();
//遍历判断mapper 的所以方法,判断方法上是否有 UserDataScope
for (Method m : methods) {
if (Objects.equals(m.getName(), methodName)) {
DataScope controllerDataScope = m.getAnnotation(DataScope.class);
if (controllerDataScope == null) {
return where;
}
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser)) {
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
// 构建查询条件
String sql = buildDataFilter(currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission);
if ("".equals(sql)) {
return where;
}
try {
Expression expression = CCJSqlParserUtil.parseExpression(sql);
// 数据权限使用单独的括号 防止与其他条件冲突
Parenthesis parenthesis = new Parenthesis(expression);
if (null != where) {
return new AndExpression(where, parenthesis);
} else {
return parenthesis;
}
} catch (Exception e) {
throw new RuntimeException("数据权限解析异常 => " + e.getMessage());
}
}
}
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return where;
}
/**
* 数据范围过滤
*
* @param user 用户
* @param deptAlias 部门别名
* @param userAlias 用户别名
* @param permission 权限字符
*/
public String buildDataFilter(SysUser user, String deptAlias, String userAlias, String permission) {
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
for (SysRole role : user.getRoles()) {
String dataScope = role.getDataScope();
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {
continue;
}
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope)) {
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
} else if (DATA_SCOPE_DEPT.equals(dataScope)) {
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
} else if (DATA_SCOPE_SELF.equals(dataScope)) {
if (StringUtils.isNotBlank(userAlias)) {
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
} else {
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
conditions.add(dataScope);
}
// 多角色情况下所有角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions)) {
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
return sqlString.toString();
}
}