使用AOP切面,自定义注解,自定义mybatisplus拦截器,使用 JSqlParser 自定拼接where条件。
1、自定义注解@DataScope;注解一般用于Service层或者DAO层(Mapper)
import java.lang.annotation.*;
/**
* 数据权限过滤注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 用户表的别名
*
* @return
*/
String userAlias() default "";
/**
* 用户字段名
*
* @return
*/
String userColumn() default "";
/**
* 店铺表的别名
*
* @return
*/
String shopAlias() default "";
/**
* 店铺字段名
*
* @return
*/
String shopColumn() default "";
}
2、DataScopeAspect 定义切面
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 数据权限切面
*/
@Aspect
@Component
public class DataScopeAspect {
@Before("@annotation(datascope)")
public void doBefore(JoinPoint point, DataScope datascope) throws Throwable {
resetContextHolders();
initContextHolders(datascope);
}
protected void initContextHolders(DataScope dataScope) {
AbstractDataScopeContextHolder.set(dataScope);
}
private void resetContextHolders() {
AbstractDataScopeContextHolder.remove();
}
}
3、AbstractDataScopeContextHolder 存储和获取dataScpoe上下文对象
/**
* DataScope上下文对象
*/
public abstract class AbstractDataScopeContextHolder {
private static final ThreadLocal<DataScope> CONTEXT = new InheritableThreadLocal<>();
public static void set(DataScope dataScope) {
CONTEXT.set(dataScope);
}
public static DataScope get() {
return CONTEXT.get();
}
public static void remove() {
CONTEXT.remove();
}
}
4、DataScopeHandler 数据权限dataScope业务处理接口
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.statement.select.PlainSelect;
/**
* 数据权限处理接口
*/
public interface DataScopeHandler {
/**
* 处理数据权限sql
*
* @param plainSelect sql解析器
* @param dataScope 数据范围注解
* @throws JSQLParserException SQL解析异常
*/
void handlerDataScopeSql(PlainSelect plainSelect, DataScope dataScope) throws JSQLParserException;
}
5、DataScopeHandlerAutoConfigure 实现 DataScopeHandler 数据权限业务处理类;代码参考:mybatis-mate-datascope/src/main/java/mybatis/mate/datascope/config/DataScopeConfig.java · baomidou/mybatis-mate-examples - Gitee.com
/**
* 实现数据权限处理
*
*/
@Configuration
public class DataScopeHandlerAutoConfigure {
@Bean
DataScopeHandler dataScopeHandler() {
return new DataScopeHandler() {
/**
* 拼接到where条件
*
* @param plainSelect
* @param expression
*/
private void setWhere(PlainSelect plainSelect, Expression expression) {
Expression where = plainSelect.getWhere();
if (where == null) {
// 不存在 where 条件
plainSelect.setWhere(new Parenthesis(expression));
} else {
// 存在 where 条件 and 处理
plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), expression));
}
}
/**
* 处理数据权限sql
* @param plainSelect sql解析器
* @param dataScope 数据范围注解
* @throws JSQLParserException
*/
@Override
public void handlerDataScopeSql(PlainSelect plainSelect, DataScope dataScope) throws JSQLParserException {
// todo 从登录用户中获取数据权限
User user = getUser();
// 1、仅限本人查看数据权限
String userColumn = dataScope.userColumn();
if (StrUtil.isNotEmpty(userColumn)) {
String userAlias = dataScope.userAlias();
String column;
if (StrUtil.isEmpty(userAlias)) {
column = String.format("%s", userColumn);
} else {
column = String.format("%s.%s", userAlias, userColumn);
}
EqualsTo expression = new EqualsTo();
expression.setLeftExpression(new Column(column));
expression.setRightExpression(new StringValue(user.getUserName()));
this.setWhere(plainSelect, expression);
}
// 2、店铺权限
String shopColumn = dataScope.shopColumn();
if (StrUtil.isNotEmpty(shopColumn)) {
String shopAlias = dataScope.shopAlias();
String column;
if (StrUtil.isEmpty(shopAlias)) {
column = String.format("%s", shopColumn);
} else {
column = String.format("%s.%s", shopAlias, shopColumn);
}
// 数据权限数据组装in条件
List<String> shops = user.getShops();
// 把集合转变为JSQLParser需要的元素列表
ItemsList itemsList = new ExpressionList(shops.stream().map(StringValue::new).collect(Collectors.toList()));
// 创建in表达式对象,传入列名及in范围列表
InExpression inExpression = new InExpression(new Column(column), itemsList);
this.setWhere(plainSelect, inExpression);
}
// 3、其他数据权限处理where条件 todo
}
};
}
}
6、DataScopeInnerInterceptor 定义Mybatis-Plus拦截器
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.log4j.Log4j2;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import org.apache.commons.collections4.CollectionUtils;
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 java.util.List;
/**
* Mybatis-Plus数据权限拦截器插件
*/
@Log4j2
public class DataScopeInnerInterceptor implements InnerInterceptor {
private final DataScopeHandler dataScopeHandler;
public DataScopeInnerInterceptor(DataScopeHandler dataScopeHandler) {
this.dataScopeHandler = dataScopeHandler;
}
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 获取登录用户,或获取其他条件,不执行数据权限 todo
User user = getUser();
if (null == user) {
if (log.isInfoEnabled()) {
log.info("未登录无user不执行数据权限");
return;
}
}
if (user.getAdmin()) {
if (log.isInfoEnabled()) {
log.info("管理员不执行数据权限");
}
return;
}
// todo ........
if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
return;
}
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
// 原始SQL
String originalSql = mpBs.sql();
if (log.isInfoEnabled()) {
log.warn("Original SQL: " + originalSql);
}
try {
Statement statement = CCJSqlParserUtil.parse(originalSql);
if (statement instanceof Select) {
Select select = (Select) statement;
// 解析SQL
this.processSelect(select);
final String parserSql = statement.toString();
mpBs.sql(parserSql);
if (log.isInfoEnabled()) {
log.warn("parser SQL: " + parserSql);
}
}
} catch (JSQLParserException e) {
log.error("Failed to process, Error SQL: {}", originalSql, e);
throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e, originalSql);
}
}
/**
* 解析sql
*
* @param select
*/
protected void processSelect(Select select) {
// 处理sqlBody
this.processSelectBody(select.getSelectBody());
List<WithItem> withItemsList = select.getWithItemsList();
if (!CollectionUtils.isEmpty(withItemsList)) {
withItemsList.forEach(this::processSelectBody);
}
}
/**
* 处理sqlBody
*
* @param selectBody
*/
protected void processSelectBody(SelectBody selectBody) {
if (selectBody == null) {
return;
}
if (selectBody instanceof PlainSelect) {
// 处理 PlainSelect
this.processPlainSelect((PlainSelect) selectBody);
} else if (selectBody instanceof WithItem) {
// With关键字
WithItem withItem = (WithItem) selectBody;
/**
* jsqlparser 4.3版本 使用 {@code withItem.getSubSelect().getSelectBody())} 代替 {@code withItem.getSelectBody()}
*/
processSelectBody(withItem.getSubSelect().getSelectBody());
} else {
// 集合操作 UNION(并集) MINUS(差集)
SetOperationList operationList = (SetOperationList) selectBody;
List<SelectBody> selectBodyList = operationList.getSelects();
if (CollectionUtils.isNotEmpty(selectBodyList)) {
selectBodyList.forEach(this::processSelectBody);
}
}
}
/**
* 处理 PlainSelect
*
* @param plainSelect
*/
protected void processPlainSelect(PlainSelect plainSelect) {
DataScope dataScope = AbstractDataScopeContextHolder.get();
if (dataScope != null) {
try {
dataScopeHandler.handlerDataScopeSql(plainSelect, dataScope);
} catch (JSQLParserException e) {
throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e);
}
}
}
}
7、MybatisPlusAutoConfigure 把自定义的拦截器添加到MybatisPlus
@Configuration
public class MybatisPlusAutoConfigure {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(
DataScopeHandler dataScopeHandler) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(
new DataScopeInnerInterceptor(dataScopeHandler)
);
return interceptor;
}
}
8、测试使用MybatisPlus写法,同样支持Mybatis 的xml中自己写sql,自己试一下吧。我自己都在用没问题
@DataScope(userColumn = "user_id")
public Map<String, Object> queryPage() {
IPage<OperateLog> page = new Page<>(pageNo, pageSize);
// 打印sql 查看,sql已经拼接 and user_id = '123'
IPage<User> pageData = this.lambdaQuery().page(page);
Map<String, Object> map = new HashMap<>();
map.put("total", pageData.getTotal());
map.put("list", pageData.getRecords());
return map;
}
没有啰嗦理论,直接上干货,请提出宝贵意见共同改变世界^_^