前言:
mybatis-plus版本:3.5+
springboot版本:2.4.2
1.引入springboot环境依赖
省略
2.引入mybatis-plus依赖,引入数据库依赖,德鲁伊
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--mysql数据库链接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mybati plus-boot-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--hutool开发工具-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
实现 InnerInterceptor 接口,重写核心方法 beforeQuery,通过 boundSql.getSql() 方法拿到准备执行的 select语句做处理。
代码:
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.tz4cloud.common.core.exception.ServiceException;
import com.tz4cloud.common.core.obj.TzUser;
import com.tz4cloud.common.core.utils.TzUserInfoUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.apache.ibatis.executor.Executor;
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 org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
/**
* mybatis-plus sql拦截器
*
* @author wrx
* @since 2022/9/21 17:42
*/
@Slf4j
public class DataScopeInterceptor implements InnerInterceptor {
private static final List<String> IGNORE_TABLES = new ArrayList<>();
@Autowired
TzUserInfoUtil tzUserInfoUtil;
static {
//不需要权限过滤公司id的表
IGNORE_TABLES.add("base_customs_params");
IGNORE_TABLES.add("base_hs_element");
}
//拿到当前拦截的表名
private String tableName = StrUtil.EMPTY;
/**
* 查询语句的前置解析操作
*
* @param
* @return
* @author wrx
* @date 2022/9/22 14:23
*/
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
String buildSql = boundSql.getSql();
try {
Statement statement = CCJSqlParserUtil.parse(buildSql);
Select select = (Select) statement;
processSelectBody(select.getSelectBody());
} catch (JSQLParserException e) {
throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e.getCause(), buildSql);
} catch (ServiceException be) {
log.info(be.getMessage());
return;
}
TzUser user = tzUserInfoUtil.getUser();
if (user != null) {
//sql拼接根据当前客户id处理。 缺点:sql查出的列一定要有creater_firm_id
buildSql = "select t1.* from (" + buildSql + ") t1 where t1.creater_firm_id = '" + user.getFirmId() + "'";
}
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
mpBoundSql.sql(buildSql);
}
private void processPlainSelect(PlainSelect plainSelect) {
FromItem fromItem = plainSelect.getFromItem();
if (fromItem instanceof Table) {
Table fromTable = (Table) fromItem;
if (IGNORE_TABLES.contains(fromTable.getName())) {
log.info(StrBuilder.create(fromTable.getName()).append("表已被忽略 不进行数据权限过滤").toString());
throw new ServiceException();
}
tableName = fromTable.getName();
} else {
processFromItem(fromItem);
}
}
private void processSelectBody(SelectBody selectBody) {
if (selectBody == null) {
return;
}
if (selectBody instanceof PlainSelect) {
processPlainSelect((PlainSelect) selectBody);
} else if (selectBody instanceof WithItem) {
WithItem withItem = (WithItem) selectBody;
processSelectBody(withItem.getSubSelect().getSelectBody());
} else {
SetOperationList operationList = (SetOperationList) selectBody;
List<SelectBody> selectBodys = operationList.getSelects();
if (CollectionUtils.isNotEmpty(selectBodys)) {
for (SelectBody body : selectBodys) {
processSelectBody(body);
}
}
}
}
private void processFromItem(FromItem fromItem) {
if (fromItem instanceof SubJoin) {
SubJoin subJoin = (SubJoin) fromItem;
if (subJoin.getJoinList() != null) {
for (Join join : subJoin.getJoinList()) {
processJoin(join);
}
}
if (subJoin.getLeft() != null) {
processFromItem(subJoin.getLeft());
}
} else if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
} else if (fromItem instanceof ValuesList) {
log.info("Perform a subquery, if you do not give us feedback");
} else if (fromItem instanceof LateralSubSelect) {
LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
if (lateralSubSelect.getSubSelect() != null) {
SubSelect subSelect = lateralSubSelect.getSubSelect();
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
}
}
}
private void processJoin(Join join) {
if (join.getRightItem() instanceof Table) {
Table fromTable = (Table) join.getRightItem();
if (IGNORE_TABLES.contains(fromTable.getName())) {
log.info(StrBuilder.create(fromTable.getName()).append("表已被忽略 不进行数据权限过滤").toString());
throw new ServiceException();
}
tableName = fromTable.getName();
}
}
}
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.tz4cloud.common.core.obj.TzUser;
import com.tz4cloud.common.core.utils.TzUserInfoUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
/**
* 公共字段自动填充
*
* @author wrx
* @date 2022/7/19 10:52
*/
@Slf4j
public class TzMetaObjectHandler implements MetaObjectHandler {
@Autowired
TzUserInfoUtil tzUserInfoUtil;
/**
* 判断字段没有值才进行设置填充
*
* @param fieldName 数据库字段名称
* @param fieldVal 要填充的字段值
* @param metaObject 对象元数据信息
*/
private void autoFill(String fieldName, Object fieldVal, MetaObject metaObject) {
Object value = this.getFieldValByName(fieldName, metaObject);
if (ObjectUtil.isNull(value) || StrUtil.isEmpty(String.valueOf(value))) {
this.setFieldValByName(fieldName, fieldVal, metaObject);
}
}
@Override
public void insertFill(MetaObject metaObject) {
log.debug("新增操作自动填充......");
this.autoFill("createTime", new Date(), metaObject);
TzUser user = tzUserInfoUtil.getUser();
if (user != null) {
this.autoFill("createrName", user.getUserCnName(), metaObject);
this.autoFill("createrId", user.getUserId(), metaObject);
this.autoFill("createrFirmId", user.getFirmId(), metaObject);
this.autoFill("createrFirmName", user.getFirmName(), metaObject);
}
// 看业务要求,修改字段在新增时也要填充?
this.updateFill(metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.debug("更新操作自动填充......");
this.setFieldValByName("modifyTime", new Date(), metaObject);
TzUser user = tzUserInfoUtil.getUser();
if (user != null) {
this.setFieldValByName("modifyName", user.getUserCnName(), metaObject);
}
}
}
/**
* mybatis-plus 全局配置
*
* @author wrx
* @date 2022/7/19 10:52
*/
@Configuration
public class MybatisPlusConfig {
/**
* 插件配置:1.阻止恶意或误操作的全表更新删除 2.分页插件
*
* @return MybatisPlusInterceptor
* @author wrx
* @date 2022/7/19 10:53
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//阻止恶意或误操作的全表更新删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
//权限过滤
interceptor.addInnerInterceptor(dataScopeInterceptor());
//分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
pageInterceptor.setMaxLimit(500L);
// 开启 count 的 join 优化,只针对部分 left join
pageInterceptor.setOptimizeJoin(true);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
@Bean
public DataScopeInterceptor dataScopeInterceptor(){
return new DataScopeInterceptor();
}
/**
* 自动填充基础创建,修改人信息等
*
* @author wrx
* @date 2022/7/19 10:53
*/
@Bean
public TzMetaObjectHandler tzMetaObjectHandler() {
return new TzMetaObjectHandler();
}
}
/**
* 德鲁伊内置监控页面配置 http://ip:端口/druid/index.html
*
* @author wrx
* @date 2022/7/18 14:56
*/
@Configuration
public class DruidConfiguration {
@Value("${spring.datasource.druid.config.loginUsername:admin}")
private String loginUsername;
@Value("${spring.datasource.druid.config.loginPassword:tz123456}")
private String loginPassword;
@Value("${spring.datasource.druid.config.resetEnable:true}")
private String resetEnable;
/**
* druid
* 注册一个StatViewServlet
*
* @return
*/
@Bean
public ServletRegistrationBean druidStatViewServlet() {
//org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//白名单:
servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
//IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
//servletRegistrationBean.addInitParameter("deny","192.168.1.x");
//登录查看信息的账号密码.
servletRegistrationBean.addInitParameter("loginUsername", loginUsername);
servletRegistrationBean.addInitParameter("loginPassword", loginPassword);
//客户端的重置数据按钮是否有效
servletRegistrationBean.addInitParameter("resetEnable", resetEnable);
return servletRegistrationBean;
}
/**
* druid过滤器
* 注册一个:filterRegistrationBean
*
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则.
filterRegistrationBean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}