【依赖】mybatis-plus from 3.5.5 to 3.5.7
【代码优化】全局:复用 MyBatis Plus 数据权限插件,简化项目的数据权限实现pull/126/head
parent
670a2bfad0
commit
76e4586e20
|
@ -17,7 +17,7 @@
|
||||||
<revision>2.1.0-snapshot</revision>
|
<revision>2.1.0-snapshot</revision>
|
||||||
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
||||||
<!-- 统一依赖管理 -->
|
<!-- 统一依赖管理 -->
|
||||||
<spring.boot.version>3.3.1</spring.boot.version>
|
<spring.boot.version>3.2.7</spring.boot.version>
|
||||||
<spring.cloud.version>2023.0.0</spring.cloud.version>
|
<spring.cloud.version>2023.0.0</spring.cloud.version>
|
||||||
<spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>
|
<spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>
|
||||||
<!-- Web 相关 -->
|
<!-- Web 相关 -->
|
||||||
|
@ -25,10 +25,11 @@
|
||||||
<knife4j.version>4.3.0</knife4j.version>
|
<knife4j.version>4.3.0</knife4j.version>
|
||||||
<!-- DB 相关 -->
|
<!-- DB 相关 -->
|
||||||
<druid.version>1.2.23</druid.version>
|
<druid.version>1.2.23</druid.version>
|
||||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
<mybatis.version>3.5.16</mybatis.version>
|
||||||
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>
|
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||||
|
<mybatis-plus-generator.version>3.5.7</mybatis-plus-generator.version>
|
||||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||||
<mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>
|
<mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
|
||||||
<easy-trans.version>2.2.11</easy-trans.version>
|
<easy-trans.version>2.2.11</easy-trans.version>
|
||||||
<redisson.version>3.26.0</redisson.version>
|
<redisson.version>3.26.0</redisson.version>
|
||||||
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
|
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
|
||||||
|
@ -196,6 +197,11 @@
|
||||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||||
<version>${druid.version}</version>
|
<version>${druid.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mybatis</groupId>
|
||||||
|
<artifactId>mybatis</artifactId>
|
||||||
|
<version>${mybatis.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package cn.iocoder.yudao.framework.datapermission.config;
|
package cn.iocoder.yudao.framework.datapermission.config;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
|
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionDatabaseInterceptor;
|
import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionRuleHandler;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
@ -26,14 +27,15 @@ public class YudaoDataPermissionAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
|
public DataPermissionRuleHandler dataPermissionRuleHandler(MybatisPlusInterceptor interceptor,
|
||||||
DataPermissionRuleFactory ruleFactory) {
|
DataPermissionRuleFactory ruleFactory) {
|
||||||
// 创建 DataPermissionDatabaseInterceptor 拦截器
|
// 创建 DataPermissionInterceptor 拦截器
|
||||||
DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);
|
DataPermissionRuleHandler handler = new DataPermissionRuleHandler(ruleFactory);
|
||||||
|
DataPermissionInterceptor inner = new DataPermissionInterceptor(handler);
|
||||||
// 添加到 interceptor 中
|
// 添加到 interceptor 中
|
||||||
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
|
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
|
||||||
MyBatisUtils.addInterceptor(interceptor, inner, 0);
|
MyBatisUtils.addInterceptor(interceptor, inner, 0);
|
||||||
return inner;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -1,641 +0,0 @@
|
||||||
package cn.iocoder.yudao.framework.datapermission.core.db;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
|
||||||
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import net.sf.jsqlparser.expression.*;
|
|
||||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
|
||||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
|
||||||
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
|
|
||||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
|
||||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
|
||||||
import net.sf.jsqlparser.schema.Table;
|
|
||||||
import net.sf.jsqlparser.statement.delete.Delete;
|
|
||||||
import net.sf.jsqlparser.statement.select.*;
|
|
||||||
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.mapping.SqlCommandType;
|
|
||||||
import org.apache.ibatis.session.ResultHandler;
|
|
||||||
import org.apache.ibatis.session.RowBounds;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现
|
|
||||||
* 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, List)} 方法
|
|
||||||
*
|
|
||||||
* 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。
|
|
||||||
* 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更!
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {
|
|
||||||
|
|
||||||
private final DataPermissionRuleFactory ruleFactory;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final MappedStatementCache mappedStatementCache = new MappedStatementCache();
|
|
||||||
|
|
||||||
@Override // SELECT 场景
|
|
||||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
|
|
||||||
// 获得 Mapper 对应的数据权限的规则
|
|
||||||
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());
|
|
||||||
if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
|
|
||||||
try {
|
|
||||||
// 初始化上下文
|
|
||||||
ContextHolder.init(rules);
|
|
||||||
// 处理 SQL
|
|
||||||
mpBs.sql(parserSingle(mpBs.sql(), null));
|
|
||||||
} finally {
|
|
||||||
// 添加是否需要重写的缓存
|
|
||||||
addMappedStatementCache(ms);
|
|
||||||
// 清空上下文
|
|
||||||
ContextHolder.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限)
|
|
||||||
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
|
|
||||||
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
|
|
||||||
MappedStatement ms = mpSh.mappedStatement();
|
|
||||||
SqlCommandType sct = ms.getSqlCommandType();
|
|
||||||
if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
|
|
||||||
// 获得 Mapper 对应的数据权限的规则
|
|
||||||
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());
|
|
||||||
if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
|
||||||
try {
|
|
||||||
// 初始化上下文
|
|
||||||
ContextHolder.init(rules);
|
|
||||||
// 处理 SQL
|
|
||||||
mpBs.sql(parserMulti(mpBs.sql(), null));
|
|
||||||
} finally {
|
|
||||||
// 添加是否需要重写的缓存
|
|
||||||
addMappedStatementCache(ms);
|
|
||||||
// 清空上下文
|
|
||||||
ContextHolder.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void processSelect(Select select, int index, String sql, Object obj) {
|
|
||||||
processSelectBody(select.getSelectBody());
|
|
||||||
List<WithItem> withItemsList = select.getWithItemsList();
|
|
||||||
if (!CollectionUtils.isEmpty(withItemsList)) {
|
|
||||||
withItemsList.forEach(this::processSelectBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update 语句处理
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void processUpdate(Update update, int index, String sql, Object obj) {
|
|
||||||
final Table table = update.getTable();
|
|
||||||
update.setWhere(this.builderExpression(update.getWhere(), table));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* delete 语句处理
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
|
||||||
delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 和 TenantLineInnerInterceptor 一致的逻辑 ==========
|
|
||||||
|
|
||||||
protected 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> selectBodyList = operationList.getSelects();
|
|
||||||
if (CollectionUtils.isNotEmpty(selectBodyList)) {
|
|
||||||
selectBodyList.forEach(this::processSelectBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 PlainSelect
|
|
||||||
*/
|
|
||||||
protected void processPlainSelect(PlainSelect plainSelect) {
|
|
||||||
//#3087 github
|
|
||||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
|
||||||
if (CollectionUtils.isNotEmpty(selectItems)) {
|
|
||||||
selectItems.forEach(this::processSelectItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 where 中的子查询
|
|
||||||
Expression where = plainSelect.getWhere();
|
|
||||||
processWhereSubSelect(where);
|
|
||||||
|
|
||||||
// 处理 fromItem
|
|
||||||
FromItem fromItem = plainSelect.getFromItem();
|
|
||||||
List<Table> list = processFromItem(fromItem);
|
|
||||||
List<Table> mainTables = new ArrayList<>(list);
|
|
||||||
|
|
||||||
// 处理 join
|
|
||||||
List<Join> joins = plainSelect.getJoins();
|
|
||||||
if (CollectionUtils.isNotEmpty(joins)) {
|
|
||||||
mainTables = processJoins(mainTables, joins);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当有 mainTable 时,进行 where 条件追加
|
|
||||||
if (CollectionUtils.isNotEmpty(mainTables)) {
|
|
||||||
plainSelect.setWhere(builderExpression(where, mainTables));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Table> processFromItem(FromItem fromItem) {
|
|
||||||
// 处理括号括起来的表达式
|
|
||||||
while (fromItem instanceof ParenthesisFromItem) {
|
|
||||||
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Table> mainTables = new ArrayList<>();
|
|
||||||
// 无 join 时的处理逻辑
|
|
||||||
if (fromItem instanceof Table) {
|
|
||||||
Table fromTable = (Table) fromItem;
|
|
||||||
mainTables.add(fromTable);
|
|
||||||
} else if (fromItem instanceof SubJoin) {
|
|
||||||
// SubJoin 类型则还需要添加上 where 条件
|
|
||||||
List<Table> tables = processSubJoin((SubJoin) fromItem);
|
|
||||||
mainTables.addAll(tables);
|
|
||||||
} else {
|
|
||||||
// 处理下 fromItem
|
|
||||||
processOtherFromItem(fromItem);
|
|
||||||
}
|
|
||||||
return mainTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理where条件内的子查询
|
|
||||||
* <p>
|
|
||||||
* 支持如下:
|
|
||||||
* 1. in
|
|
||||||
* 2. =
|
|
||||||
* 3. >
|
|
||||||
* 4. <
|
|
||||||
* 5. >=
|
|
||||||
* 6. <=
|
|
||||||
* 7. <>
|
|
||||||
* 8. EXISTS
|
|
||||||
* 9. NOT EXISTS
|
|
||||||
* <p>
|
|
||||||
* 前提条件:
|
|
||||||
* 1. 子查询必须放在小括号中
|
|
||||||
* 2. 子查询一般放在比较操作符的右边
|
|
||||||
*
|
|
||||||
* @param where where 条件
|
|
||||||
*/
|
|
||||||
protected void processWhereSubSelect(Expression where) {
|
|
||||||
if (where == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (where instanceof FromItem) {
|
|
||||||
processOtherFromItem((FromItem) where);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (where.toString().indexOf("SELECT") > 0) {
|
|
||||||
// 有子查询
|
|
||||||
if (where instanceof BinaryExpression) {
|
|
||||||
// 比较符号 , and , or , 等等
|
|
||||||
BinaryExpression expression = (BinaryExpression) where;
|
|
||||||
processWhereSubSelect(expression.getLeftExpression());
|
|
||||||
processWhereSubSelect(expression.getRightExpression());
|
|
||||||
} else if (where instanceof InExpression) {
|
|
||||||
// in
|
|
||||||
InExpression expression = (InExpression) where;
|
|
||||||
Expression inExpression = expression.getRightExpression();
|
|
||||||
if (inExpression instanceof SubSelect) {
|
|
||||||
processSelectBody(((SubSelect) inExpression).getSelectBody());
|
|
||||||
}
|
|
||||||
} else if (where instanceof ExistsExpression) {
|
|
||||||
// exists
|
|
||||||
ExistsExpression expression = (ExistsExpression) where;
|
|
||||||
processWhereSubSelect(expression.getRightExpression());
|
|
||||||
} else if (where instanceof NotExpression) {
|
|
||||||
// not exists
|
|
||||||
NotExpression expression = (NotExpression) where;
|
|
||||||
processWhereSubSelect(expression.getExpression());
|
|
||||||
} else if (where instanceof Parenthesis) {
|
|
||||||
Parenthesis expression = (Parenthesis) where;
|
|
||||||
processWhereSubSelect(expression.getExpression());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processSelectItem(SelectItem selectItem) {
|
|
||||||
if (selectItem instanceof SelectExpressionItem) {
|
|
||||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
|
||||||
if (selectExpressionItem.getExpression() instanceof SubSelect) {
|
|
||||||
processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
|
|
||||||
} else if (selectExpressionItem.getExpression() instanceof Function) {
|
|
||||||
processFunction((Function) selectExpressionItem.getExpression());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理函数
|
|
||||||
* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
|
|
||||||
* <p> fixed gitee pulls/141</p>
|
|
||||||
*
|
|
||||||
* @param function
|
|
||||||
*/
|
|
||||||
protected void processFunction(Function function) {
|
|
||||||
ExpressionList parameters = function.getParameters();
|
|
||||||
if (parameters != null) {
|
|
||||||
parameters.getExpressions().forEach(expression -> {
|
|
||||||
if (expression instanceof SubSelect) {
|
|
||||||
processSelectBody(((SubSelect) expression).getSelectBody());
|
|
||||||
} else if (expression instanceof Function) {
|
|
||||||
processFunction((Function) expression);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理子查询等
|
|
||||||
*/
|
|
||||||
protected void processOtherFromItem(FromItem fromItem) {
|
|
||||||
// 去除括号
|
|
||||||
while (fromItem instanceof ParenthesisFromItem) {
|
|
||||||
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromItem instanceof SubSelect) {
|
|
||||||
SubSelect subSelect = (SubSelect) fromItem;
|
|
||||||
if (subSelect.getSelectBody() != null) {
|
|
||||||
processSelectBody(subSelect.getSelectBody());
|
|
||||||
}
|
|
||||||
} else if (fromItem instanceof ValuesList) {
|
|
||||||
logger.debug("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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 sub join
|
|
||||||
*
|
|
||||||
* @param subJoin subJoin
|
|
||||||
* @return Table subJoin 中的主表
|
|
||||||
*/
|
|
||||||
private List<Table> processSubJoin(SubJoin subJoin) {
|
|
||||||
List<Table> mainTables = new ArrayList<>();
|
|
||||||
if (subJoin.getJoinList() != null) {
|
|
||||||
List<Table> list = processFromItem(subJoin.getLeft());
|
|
||||||
mainTables.addAll(list);
|
|
||||||
mainTables = processJoins(mainTables, subJoin.getJoinList());
|
|
||||||
}
|
|
||||||
return mainTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理 joins
|
|
||||||
*
|
|
||||||
* @param mainTables 可以为 null
|
|
||||||
* @param joins join 集合
|
|
||||||
* @return List<Table> 右连接查询的 Table 列表
|
|
||||||
*/
|
|
||||||
private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
|
|
||||||
// join 表达式中最终的主表
|
|
||||||
Table mainTable = null;
|
|
||||||
// 当前 join 的左表
|
|
||||||
Table leftTable = null;
|
|
||||||
|
|
||||||
if (mainTables == null) {
|
|
||||||
mainTables = new ArrayList<>();
|
|
||||||
} else if (mainTables.size() == 1) {
|
|
||||||
mainTable = mainTables.get(0);
|
|
||||||
leftTable = mainTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
//对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
|
|
||||||
Deque<List<Table>> onTableDeque = new LinkedList<>();
|
|
||||||
for (Join join : joins) {
|
|
||||||
// 处理 on 表达式
|
|
||||||
FromItem joinItem = join.getRightItem();
|
|
||||||
|
|
||||||
// 获取当前 join 的表,subJoint 可以看作是一张表
|
|
||||||
List<Table> joinTables = null;
|
|
||||||
if (joinItem instanceof Table) {
|
|
||||||
joinTables = new ArrayList<>();
|
|
||||||
joinTables.add((Table) joinItem);
|
|
||||||
} else if (joinItem instanceof SubJoin) {
|
|
||||||
joinTables = processSubJoin((SubJoin) joinItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (joinTables != null) {
|
|
||||||
|
|
||||||
// 如果是隐式内连接
|
|
||||||
if (join.isSimple()) {
|
|
||||||
mainTables.addAll(joinTables);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当前表是否忽略
|
|
||||||
Table joinTable = joinTables.get(0);
|
|
||||||
|
|
||||||
List<Table> onTables = null;
|
|
||||||
// 如果不要忽略,且是右连接,则记录下当前表
|
|
||||||
if (join.isRight()) {
|
|
||||||
mainTable = joinTable;
|
|
||||||
if (leftTable != null) {
|
|
||||||
onTables = Collections.singletonList(leftTable);
|
|
||||||
}
|
|
||||||
} else if (join.isLeft()) {
|
|
||||||
onTables = Collections.singletonList(joinTable);
|
|
||||||
} else if (join.isInner()) {
|
|
||||||
if (mainTable == null) {
|
|
||||||
onTables = Collections.singletonList(joinTable);
|
|
||||||
} else {
|
|
||||||
onTables = Arrays.asList(mainTable, joinTable);
|
|
||||||
}
|
|
||||||
mainTable = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mainTables = new ArrayList<>();
|
|
||||||
if (mainTable != null) {
|
|
||||||
mainTables.add(mainTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 join 尾缀的 on 表达式列表
|
|
||||||
Collection<Expression> originOnExpressions = join.getOnExpressions();
|
|
||||||
// 正常 join on 表达式只有一个,立刻处理
|
|
||||||
if (originOnExpressions.size() == 1 && onTables != null) {
|
|
||||||
List<Expression> onExpressions = new LinkedList<>();
|
|
||||||
onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
|
|
||||||
join.setOnExpressions(onExpressions);
|
|
||||||
leftTable = joinTable;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 表名压栈,忽略的表压入 null,以便后续不处理
|
|
||||||
onTableDeque.push(onTables);
|
|
||||||
// 尾缀多个 on 表达式的时候统一处理
|
|
||||||
if (originOnExpressions.size() > 1) {
|
|
||||||
Collection<Expression> onExpressions = new LinkedList<>();
|
|
||||||
for (Expression originOnExpression : originOnExpressions) {
|
|
||||||
List<Table> currentTableList = onTableDeque.poll();
|
|
||||||
if (CollectionUtils.isEmpty(currentTableList)) {
|
|
||||||
onExpressions.add(originOnExpression);
|
|
||||||
} else {
|
|
||||||
onExpressions.add(builderExpression(originOnExpression, currentTableList));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
join.setOnExpressions(onExpressions);
|
|
||||||
}
|
|
||||||
leftTable = joinTable;
|
|
||||||
} else {
|
|
||||||
processOtherFromItem(joinItem);
|
|
||||||
leftTable = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 和 TenantLineInnerInterceptor 存在差异的逻辑:关键,实现权限条件的拼接 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理条件
|
|
||||||
*
|
|
||||||
* @param currentExpression 当前 where 条件
|
|
||||||
* @param table 单个表
|
|
||||||
*/
|
|
||||||
protected Expression builderExpression(Expression currentExpression, Table table) {
|
|
||||||
return this.builderExpression(currentExpression, Collections.singletonList(table));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理条件
|
|
||||||
*
|
|
||||||
* @param currentExpression 当前 where 条件
|
|
||||||
* @param tables 多个表
|
|
||||||
*/
|
|
||||||
protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
|
|
||||||
// 没有表需要处理直接返回
|
|
||||||
if (CollectionUtils.isEmpty(tables)) {
|
|
||||||
return currentExpression;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第一步,获得 Table 对应的数据权限条件
|
|
||||||
Expression dataPermissionExpression = null;
|
|
||||||
for (Table table : tables) {
|
|
||||||
// 构建每个表的权限 Expression 条件
|
|
||||||
Expression expression = buildDataPermissionExpression(table);
|
|
||||||
if (expression == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 合并到 dataPermissionExpression 中
|
|
||||||
dataPermissionExpression = dataPermissionExpression == null ? expression
|
|
||||||
: new AndExpression(dataPermissionExpression, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二步,合并多个 Expression 条件
|
|
||||||
if (dataPermissionExpression == null) {
|
|
||||||
return currentExpression;
|
|
||||||
}
|
|
||||||
if (currentExpression == null) {
|
|
||||||
return dataPermissionExpression;
|
|
||||||
}
|
|
||||||
// ① 如果表达式为 Or,则需要 (currentExpression) AND dataPermissionExpression
|
|
||||||
if (currentExpression instanceof OrExpression) {
|
|
||||||
return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression);
|
|
||||||
}
|
|
||||||
// ② 如果表达式为 And,则直接返回 where AND dataPermissionExpression
|
|
||||||
return new AndExpression(currentExpression, dataPermissionExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建指定表的数据权限的 Expression 过滤条件
|
|
||||||
*
|
|
||||||
* @param table 表
|
|
||||||
* @return Expression 过滤条件
|
|
||||||
*/
|
|
||||||
private Expression buildDataPermissionExpression(Table table) {
|
|
||||||
// 生成条件
|
|
||||||
Expression allExpression = null;
|
|
||||||
for (DataPermissionRule rule : ContextHolder.getRules()) {
|
|
||||||
// 判断表名是否匹配
|
|
||||||
String tableName = MyBatisUtils.getTableName(table);
|
|
||||||
if (!rule.getTableNames().contains(tableName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 如果有匹配的规则,说明可重写。
|
|
||||||
// 为什么不是有 allExpression 非空才重写呢?在生成 column = value 过滤条件时,会因为 value 不存在,导致未重写。
|
|
||||||
// 这样导致第一次无 value,被标记成无需重写;但是第二次有 value,此时会需要重写。
|
|
||||||
ContextHolder.setRewrite(true);
|
|
||||||
|
|
||||||
// 单条规则的条件
|
|
||||||
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
|
|
||||||
if (oneExpress == null){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 拼接到 allExpression 中
|
|
||||||
allExpression = allExpression == null ? oneExpress
|
|
||||||
: new AndExpression(allExpression, oneExpress);
|
|
||||||
}
|
|
||||||
|
|
||||||
return allExpression;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断 SQL 是否重写。如果没有重写,则添加到 {@link MappedStatementCache} 中
|
|
||||||
*
|
|
||||||
* @param ms MappedStatement
|
|
||||||
*/
|
|
||||||
private void addMappedStatementCache(MappedStatement ms) {
|
|
||||||
if (ContextHolder.getRewrite()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 无重写,进行添加
|
|
||||||
mappedStatementCache.addNoRewritable(ms, ContextHolder.getRules());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL 解析上下文,方便透传 {@link DataPermissionRule} 规则
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
static final class ContextHolder {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 该 {@link MappedStatement} 对应的规则
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<List<DataPermissionRule>> RULES = ThreadLocal.withInitial(Collections::emptyList);
|
|
||||||
/**
|
|
||||||
* SQL 是否进行重写
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<Boolean> REWRITE = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
|
||||||
|
|
||||||
public static void init(List<DataPermissionRule> rules) {
|
|
||||||
RULES.set(rules);
|
|
||||||
REWRITE.set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
RULES.remove();
|
|
||||||
REWRITE.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getRewrite() {
|
|
||||||
return REWRITE.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setRewrite(boolean rewrite) {
|
|
||||||
REWRITE.set(rewrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<DataPermissionRule> getRules() {
|
|
||||||
return RULES.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link MappedStatement} 缓存
|
|
||||||
* 目前主要用于,记录 {@link DataPermissionRule} 是否对指定 {@link MappedStatement} 无效
|
|
||||||
* 如果无效,则可以避免 SQL 的解析,加快速度
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
static final class MappedStatementCache {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指定数据权限规则,对指定 MappedStatement 无需重写(不生效)的缓存
|
|
||||||
*
|
|
||||||
* value:{@link MappedStatement#getId()} 编号
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否无需重写
|
|
||||||
* ps:虽然有点中文式英语,但是容易读懂即可
|
|
||||||
*
|
|
||||||
* @param ms MappedStatement
|
|
||||||
* @param rules 数据权限规则数组
|
|
||||||
* @return 是否无需重写
|
|
||||||
*/
|
|
||||||
public boolean noRewritable(MappedStatement ms, List<DataPermissionRule> rules) {
|
|
||||||
// 如果规则为空,说明无需重写
|
|
||||||
if (CollUtil.isEmpty(rules)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// 任一规则不在 noRewritableMap 中,则说明可能需要重写
|
|
||||||
for (DataPermissionRule rule : rules) {
|
|
||||||
Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());
|
|
||||||
if (!CollUtil.contains(mappedStatementIds, ms.getId())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加无需重写的 MappedStatement
|
|
||||||
*
|
|
||||||
* @param ms MappedStatement
|
|
||||||
* @param rules 数据权限规则数组
|
|
||||||
*/
|
|
||||||
public void addNoRewritable(MappedStatement ms, List<DataPermissionRule> rules) {
|
|
||||||
for (DataPermissionRule rule : rules) {
|
|
||||||
Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());
|
|
||||||
if (CollUtil.isNotEmpty(mappedStatementIds)) {
|
|
||||||
mappedStatementIds.add(ms.getId());
|
|
||||||
} else {
|
|
||||||
noRewritableMappedStatements.put(rule.getClass(), SetUtils.asSet(ms.getId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空缓存
|
|
||||||
* 目前主要提供给单元测试
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
noRewritableMappedStatements.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package cn.iocoder.yudao.framework.datapermission.core.db;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||||
|
import net.sf.jsqlparser.schema.Table;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 {@link DataPermissionRule} 的数据权限处理器
|
||||||
|
*
|
||||||
|
* 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>
|
||||||
|
* 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
|
||||||
|
|
||||||
|
private final DataPermissionRuleFactory ruleFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
|
||||||
|
// 获得 Mapper 对应的数据权限的规则
|
||||||
|
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);
|
||||||
|
if (CollUtil.isEmpty(rules)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成条件
|
||||||
|
Expression allExpression = null;
|
||||||
|
for (DataPermissionRule rule : rules) {
|
||||||
|
// 判断表名是否匹配
|
||||||
|
String tableName = MyBatisUtils.getTableName(table);
|
||||||
|
if (!rule.getTableNames().contains(tableName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单条规则的条件
|
||||||
|
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
|
||||||
|
if (oneExpress == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 拼接到 allExpression 中
|
||||||
|
allExpression = allExpression == null ? oneExpress
|
||||||
|
: new AndExpression(allExpression, oneExpress);
|
||||||
|
}
|
||||||
|
return allExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -156,7 +156,8 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||||
}
|
}
|
||||||
// 拼接条件
|
// 拼接条件
|
||||||
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
|
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
|
||||||
new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
|
// Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
|
||||||
|
new Parenthesis(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
|
private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
|
||||||
|
|
|
@ -1,190 +0,0 @@
|
||||||
package cn.iocoder.yudao.framework.datapermission.core.db;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
|
||||||
import net.sf.jsqlparser.expression.Alias;
|
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
|
||||||
import net.sf.jsqlparser.expression.LongValue;
|
|
||||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
|
||||||
import net.sf.jsqlparser.schema.Column;
|
|
||||||
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.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockedStatic;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link DataPermissionDatabaseInterceptor} 的单元测试
|
|
||||||
* 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
|
|
||||||
* 和 {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
|
|
||||||
* 以及在这个过程中,ContextHolder 和 MappedStatementCache
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private DataPermissionDatabaseInterceptor interceptor;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private DataPermissionRuleFactory ruleFactory;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() {
|
|
||||||
// 清理上下文
|
|
||||||
DataPermissionDatabaseInterceptor.ContextHolder.clear();
|
|
||||||
// 清空缓存
|
|
||||||
interceptor.getMappedStatementCache().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test // 不存在规则,且不匹配
|
|
||||||
public void testBeforeQuery_withoutRule() {
|
|
||||||
try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
|
|
||||||
// 准备参数
|
|
||||||
MappedStatement mappedStatement = mock(MappedStatement.class);
|
|
||||||
BoundSql boundSql = mock(BoundSql.class);
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
|
|
||||||
// 断言
|
|
||||||
pluginUtilsMock.verify(() -> PluginUtils.mpBoundSql(boundSql), never());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test // 存在规则,且不匹配
|
|
||||||
public void testBeforeQuery_withMatchRule() {
|
|
||||||
try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
|
|
||||||
// 准备参数
|
|
||||||
MappedStatement mappedStatement = mock(MappedStatement.class);
|
|
||||||
BoundSql boundSql = mock(BoundSql.class);
|
|
||||||
// mock 方法(数据权限)
|
|
||||||
when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))
|
|
||||||
.thenReturn(singletonList(new DeptDataPermissionRule()));
|
|
||||||
// mock 方法(MPBoundSql)
|
|
||||||
PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);
|
|
||||||
pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);
|
|
||||||
// mock 方法(SQL)
|
|
||||||
String sql = "select * from t_user where id = 1";
|
|
||||||
when(mpBs.sql()).thenReturn(sql);
|
|
||||||
// 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
|
|
||||||
// 断言
|
|
||||||
verify(mpBs, times(1)).sql(
|
|
||||||
eq("SELECT * FROM t_user WHERE id = 1 AND t_user.dept_id = 100"));
|
|
||||||
// 断言缓存
|
|
||||||
assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test // 存在规则,但不匹配
|
|
||||||
public void testBeforeQuery_withoutMatchRule() {
|
|
||||||
try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
|
|
||||||
// 准备参数
|
|
||||||
MappedStatement mappedStatement = mock(MappedStatement.class);
|
|
||||||
BoundSql boundSql = mock(BoundSql.class);
|
|
||||||
// mock 方法(数据权限)
|
|
||||||
when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))
|
|
||||||
.thenReturn(singletonList(new DeptDataPermissionRule()));
|
|
||||||
// mock 方法(MPBoundSql)
|
|
||||||
PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);
|
|
||||||
pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);
|
|
||||||
// mock 方法(SQL)
|
|
||||||
String sql = "select * from t_role where id = 1";
|
|
||||||
when(mpBs.sql()).thenReturn(sql);
|
|
||||||
// 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
|
|
||||||
// 断言
|
|
||||||
verify(mpBs, times(1)).sql(
|
|
||||||
eq("SELECT * FROM t_role WHERE id = 1"));
|
|
||||||
// 断言缓存
|
|
||||||
assertFalse(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddNoRewritable() {
|
|
||||||
// 准备参数
|
|
||||||
MappedStatement ms = mock(MappedStatement.class);
|
|
||||||
List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());
|
|
||||||
// mock 方法
|
|
||||||
when(ms.getId()).thenReturn("selectById");
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
interceptor.getMappedStatementCache().addNoRewritable(ms, rules);
|
|
||||||
// 断言
|
|
||||||
Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements =
|
|
||||||
interceptor.getMappedStatementCache().getNoRewritableMappedStatements();
|
|
||||||
assertEquals(1, noRewritableMappedStatements.size());
|
|
||||||
assertEquals(SetUtils.asSet("selectById"), noRewritableMappedStatements.get(DeptDataPermissionRule.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNoRewritable() {
|
|
||||||
// 准备参数
|
|
||||||
MappedStatement ms = mock(MappedStatement.class);
|
|
||||||
// mock 方法
|
|
||||||
when(ms.getId()).thenReturn("selectById");
|
|
||||||
// mock 数据
|
|
||||||
List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());
|
|
||||||
interceptor.getMappedStatementCache().addNoRewritable(ms, rules);
|
|
||||||
|
|
||||||
// 场景一,rules 为空
|
|
||||||
assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, null));
|
|
||||||
// 场景二,rules 非空,可重写
|
|
||||||
assertFalse(interceptor.getMappedStatementCache().noRewritable(ms, singletonList(new EmptyDataPermissionRule())));
|
|
||||||
// 场景三,rule 非空,不可重写
|
|
||||||
assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, rules));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DeptDataPermissionRule implements DataPermissionRule {
|
|
||||||
|
|
||||||
private static final String COLUMN = "dept_id";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getTableNames() {
|
|
||||||
return SetUtils.asSet("t_user");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
|
||||||
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
|
|
||||||
LongValue value = new LongValue(100L);
|
|
||||||
return new EqualsTo(column, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class EmptyDataPermissionRule implements DataPermissionRule {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getTableNames() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -4,9 +4,11 @@ import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||||
import net.sf.jsqlparser.expression.Alias;
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import net.sf.jsqlparser.expression.LongValue;
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.Parenthesis;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||||
|
@ -21,24 +23,30 @@ import java.util.Set;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link DataPermissionDatabaseInterceptor} 的单元测试
|
* {@link DataPermissionRuleHandler} 的单元测试
|
||||||
* 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试
|
* 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试
|
||||||
* 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~
|
* 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest {
|
public class DataPermissionRuleHandlerTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private DataPermissionDatabaseInterceptor interceptor;
|
private DataPermissionRuleHandler handler;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private DataPermissionRuleFactory ruleFactory;
|
private DataPermissionRuleFactory ruleFactory;
|
||||||
|
|
||||||
|
private DataPermissionInterceptor interceptor;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
interceptor = new DataPermissionInterceptor(handler);
|
||||||
|
|
||||||
// 租户的数据权限规则
|
// 租户的数据权限规则
|
||||||
DataPermissionRule tenantRule = new DataPermissionRule() {
|
DataPermissionRule tenantRule = new DataPermissionRule() {
|
||||||
|
|
||||||
|
@ -71,14 +79,14 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
|
||||||
@Override
|
@Override
|
||||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||||
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
|
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
|
||||||
ExpressionList values = new ExpressionList(new LongValue(10L),
|
ExpressionList<LongValue> values = new ExpressionList<>(new LongValue(10L),
|
||||||
new LongValue(20L));
|
new LongValue(20L));
|
||||||
return new InExpression(column, values);
|
return new InExpression(column, new Parenthesis((values)));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
// 设置到上下文,保证
|
// 设置到上下文
|
||||||
DataPermissionDatabaseInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule));
|
when(ruleFactory.getDataPermissionRule(any())).thenReturn(Arrays.asList(tenantRule, deptRule));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -262,7 +270,7 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
|
||||||
"right join entity2 e2 on e1.id = e2.id",
|
"right join entity2 e2 on e1.id = e2.id",
|
||||||
"SELECT * FROM entity e " +
|
"SELECT * FROM entity e " +
|
||||||
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
|
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
|
||||||
"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
|
"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 " +
|
||||||
"WHERE e2.tenant_id = 1");
|
"WHERE e2.tenant_id = 1");
|
||||||
|
|
||||||
assertSql("SELECT * FROM entity e " +
|
assertSql("SELECT * FROM entity e " +
|
||||||
|
@ -447,7 +455,6 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
|
||||||
assertEquals(targetSql, interceptor.parserSingle(sql, null));
|
assertEquals(targetSql, interceptor.parserSingle(sql, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========== 额外的测试 ==========
|
// ========== 额外的测试 ==========
|
||||||
|
|
||||||
@Test
|
@Test
|
|
@ -185,7 +185,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
||||||
return Db.updateBatchById(entities, size);
|
return Db.updateBatchById(entities, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
default Boolean insertOrUpdate(T entity) {
|
default boolean insertOrUpdate(T entity) {
|
||||||
return Db.saveOrUpdate(entity);
|
return Db.saveOrUpdate(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package cn.iocoder.yudao.framework.mybatis.core.type;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
||||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考 {@link com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler} 实现
|
|
||||||
* 在我们将字符串反序列化为 Set 并且泛型为 Long 时,如果每个元素的数值太小,会被处理成 Integer 类型,导致可能存在隐性的 BUG。
|
|
||||||
*
|
|
||||||
* 例如说哦,SysUserDO 的 postIds 属性
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler<Object> {
|
|
||||||
|
|
||||||
private static final TypeReference<Set<Long>> TYPE_REFERENCE = new TypeReference<Set<Long>>(){};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object parse(String json) {
|
|
||||||
return JsonUtils.parseObject(json, TYPE_REFERENCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String toJson(Object obj) {
|
|
||||||
return JsonUtils.toJsonString(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cn.iocoder.yudao.module.ai.dal.dataobject.image;
|
package cn.iocoder.yudao.module.ai.dal.dataobject.image;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
|
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
|
||||||
import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
|
import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
|
||||||
|
@ -10,7 +9,6 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
|
||||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||||
|
@ -107,7 +105,7 @@ public class AiImageDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* mj buttons 按钮
|
* mj buttons 按钮
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = ButtonTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<MidjourneyApi.Button> buttons;
|
private List<MidjourneyApi.Button> buttons;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,19 +115,5 @@ public class AiImageDO extends BaseDO {
|
||||||
*/
|
*/
|
||||||
private String taskId;
|
private String taskId;
|
||||||
|
|
||||||
public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object parse(String json) {
|
|
||||||
return JsonUtils.parseArray(json, MidjourneyApi.Button.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String toJson(Object obj) {
|
|
||||||
return JsonUtils.toJsonString(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -47,7 +47,7 @@ public class BpmUserGroupDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 成员用户编号数组
|
* 成员用户编号数组
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = JsonLongSetTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private Set<Long> userIds;
|
private Set<Long> userIds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件配置表
|
* 文件配置表
|
||||||
*
|
*
|
||||||
|
@ -65,8 +67,16 @@ public class FileConfigDO extends BaseDO {
|
||||||
|
|
||||||
public static class FileClientConfigTypeHandler extends AbstractJsonTypeHandler<Object> {
|
public static class FileClientConfigTypeHandler extends AbstractJsonTypeHandler<Object> {
|
||||||
|
|
||||||
|
public FileClientConfigTypeHandler(Class<?> type) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileClientConfigTypeHandler(Class<?> type, Field field) {
|
||||||
|
super(type, field);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object parse(String json) {
|
public Object parse(String json) {
|
||||||
FileClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<>() {});
|
FileClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<>() {});
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
return config;
|
return config;
|
||||||
|
@ -92,7 +102,7 @@ public class FileConfigDO extends BaseDO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String toJson(Object obj) {
|
public String toJson(Object obj) {
|
||||||
return JsonUtils.toJsonString(obj);
|
return JsonUtils.toJsonString(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ public class ProductCommentDO extends BaseDO {
|
||||||
*
|
*
|
||||||
* 关联 {@link ProductSkuDO#getProperties()}
|
* 关联 {@link ProductSkuDO#getProperties()}
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = ProductSkuDO.PropertyTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<ProductSkuDO.Property> skuProperties;
|
private List<ProductSkuDO.Property> skuProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cn.iocoder.yudao.module.product.dal.dataobject.sku;
|
package cn.iocoder.yudao.module.product.dal.dataobject.sku;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
|
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
|
||||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
|
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
|
||||||
|
@ -9,7 +8,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -43,7 +42,7 @@ public class ProductSkuDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 属性数组,JSON 格式
|
* 属性数组,JSON 格式
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = PropertyTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<Property> properties;
|
private List<Property> properties;
|
||||||
/**
|
/**
|
||||||
* 商品价格,单位:分
|
* 商品价格,单位:分
|
||||||
|
@ -131,21 +130,6 @@ public class ProductSkuDO extends BaseDO {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @芋艿:可以找一些新的思路
|
|
||||||
public static class PropertyTypeHandler extends AbstractJsonTypeHandler<Object> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object parse(String json) {
|
|
||||||
return JsonUtils.parseArray(json, Property.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String toJson(Object obj) {
|
|
||||||
return JsonUtils.toJsonString(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO 芋艿:integral from y
|
// TODO 芋艿:integral from y
|
||||||
// TODO 芋艿:pinkPrice from y
|
// TODO 芋艿:pinkPrice from y
|
||||||
// TODO 芋艿:seckillPrice from y
|
// TODO 芋艿:seckillPrice from y
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
|
package cn.iocoder.yudao.module.promotion.dal.dataobject.reward;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
|
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
|
||||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
|
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
|
||||||
|
@ -10,7 +9,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@ -76,7 +75,7 @@ public class RewardActivityDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 优惠规则的数组
|
* 优惠规则的数组
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = RuleTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<Rule> rules;
|
private List<Rule> rules;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,19 +114,4 @@ public class RewardActivityDO extends BaseDO {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @芋艿:可以找一些新的思路
|
|
||||||
public static class RuleTypeHandler extends AbstractJsonTypeHandler<List<Rule>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Rule> parse(String json) {
|
|
||||||
return JsonUtils.parseArray(json, Rule.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String toJson(List<Rule> obj) {
|
|
||||||
return JsonUtils.toJsonString(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ public class AfterSaleDO extends BaseDO {
|
||||||
*
|
*
|
||||||
* 冗余 {@link TradeOrderItemDO#getProperties()}
|
* 冗余 {@link TradeOrderItemDO#getProperties()}
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = TradeOrderItemDO.PropertyTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<TradeOrderItemDO.Property> properties;
|
private List<TradeOrderItemDO.Property> properties;
|
||||||
/**
|
/**
|
||||||
* 商品图片
|
* 商品图片
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package cn.iocoder.yudao.module.trade.dal.dataobject.order;
|
package cn.iocoder.yudao.module.trade.dal.dataobject.order;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
|
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
|
||||||
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
|
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
|
||||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
|
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
@ -74,7 +73,7 @@ public class TradeOrderItemDO extends BaseDO {
|
||||||
*
|
*
|
||||||
* 冗余 ProductSkuDO 的 properties 字段
|
* 冗余 ProductSkuDO 的 properties 字段
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = PropertyTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<Property> properties;
|
private List<Property> properties;
|
||||||
/**
|
/**
|
||||||
* 商品图片
|
* 商品图片
|
||||||
|
@ -210,20 +209,5 @@ public class TradeOrderItemDO extends BaseDO {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @芋艿:可以找一些新的思路
|
|
||||||
public static class PropertyTypeHandler extends AbstractJsonTypeHandler<List<Property>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Property> parse(String json) {
|
|
||||||
return JsonUtils.parseArray(json, Property.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String toJson(List<Property> obj) {
|
|
||||||
return JsonUtils.toJsonString(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
|
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Schema(description = "管理后台 - 公众号消息 Response VO")
|
@Schema(description = "管理后台 - 公众号消息 Response VO")
|
||||||
|
@ -81,7 +79,6 @@ public class MpMessageRespVO {
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
|
|
||||||
private List<MpMessageDO.Article> articles;
|
private List<MpMessageDO.Article> articles;
|
||||||
|
|
||||||
@Schema(description = "音乐链接 消息类型为 music 时,才有值", example = "https://www.iocoder.cn/xxx.mp3")
|
@Schema(description = "音乐链接 消息类型为 music 时,才有值", example = "https://www.iocoder.cn/xxx.mp3")
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
@ -163,7 +164,7 @@ public class MpMenuDO extends BaseDO {
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<MpMessageDO.Article> replyArticles;
|
private List<MpMessageDO.Article> replyArticles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
@ -143,7 +144,7 @@ public class MpAutoReplyDO extends BaseDO {
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<MpMessageDO.Article> responseArticles;
|
private List<MpMessageDO.Article> responseArticles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cn.iocoder.yudao.module.mp.dal.dataobject.message;
|
package cn.iocoder.yudao.module.mp.dal.dataobject.message;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
|
||||||
|
@ -9,12 +8,14 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.*;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
import me.chanjar.weixin.mp.builder.kefu.NewsBuilder;
|
import me.chanjar.weixin.mp.builder.kefu.NewsBuilder;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -173,7 +174,7 @@ public class MpMessageDO extends BaseDO {
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = ArticleTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<Article> articles;
|
private List<Article> articles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,18 +239,4 @@ public class MpMessageDO extends BaseDO {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @芋艿:可以找一些新的思路
|
|
||||||
public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Article> parse(String json) {
|
|
||||||
return JsonUtils.parseArray(json, Article.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String toJson(List<Article> obj) {
|
|
||||||
return JsonUtils.toJsonString(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package cn.iocoder.yudao.module.system.dal.dataobject.permission;
|
package cn.iocoder.yudao.module.system.dal.dataobject.permission;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
|
|
||||||
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
|
||||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||||
|
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
||||||
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ public class RoleDO extends TenantBaseDO {
|
||||||
*
|
*
|
||||||
* 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时
|
* 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = JsonLongSetTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private Set<Long> dataScopeDeptIds;
|
private Set<Long> dataScopeDeptIds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.system.dal.dataobject.tenant;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
|
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -46,7 +46,7 @@ public class TenantPackageDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 关联的菜单编号
|
* 关联的菜单编号
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = JsonLongSetTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private Set<Long> menuIds;
|
private Set<Long> menuIds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package cn.iocoder.yudao.module.system.dal.dataobject.user;
|
package cn.iocoder.yudao.module.system.dal.dataobject.user;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
|
|
||||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||||
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
|
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ public class AdminUserDO extends TenantBaseDO {
|
||||||
/**
|
/**
|
||||||
* 岗位编号数组
|
* 岗位编号数组
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = JsonLongSetTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private Set<Long> postIds;
|
private Set<Long> postIds;
|
||||||
/**
|
/**
|
||||||
* 用户邮箱
|
* 用户邮箱
|
||||||
|
|
Loading…
Reference in New Issue