tables = processSubJoin((SubJoin) fromItem);
+ mainTables.addAll(tables);
+ } else {
+ // 处理下 fromItem
+ processOtherFromItem(fromItem);
+ }
+ return mainTables;
}
/**
@@ -191,7 +224,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
return;
}
if (where instanceof FromItem) {
- processFromItem((FromItem) where);
+ processOtherFromItem((FromItem) where);
return;
}
if (where.toString().indexOf("SELECT") > 0) {
@@ -204,9 +237,9 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
} else if (where instanceof InExpression) {
// in
InExpression expression = (InExpression) where;
- ItemsList itemsList = expression.getRightItemsList();
- if (itemsList instanceof SubSelect) {
- processSelectBody(((SubSelect) itemsList).getSelectBody());
+ Expression inExpression = expression.getRightExpression();
+ if (inExpression instanceof SubSelect) {
+ processSelectBody(((SubSelect) inExpression).getSelectBody());
}
} else if (where instanceof ExistsExpression) {
// exists
@@ -239,7 +272,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
* 支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)
*
fixed gitee pulls/141
*
- * @param function 函数
+ * @param function
*/
protected void processFunction(Function function) {
ExpressionList parameters = function.getParameters();
@@ -257,22 +290,19 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
/**
* 处理子查询等
*/
- protected void processFromItem(FromItem fromItem) {
- if (fromItem instanceof SubJoin) {
- SubJoin subJoin = (SubJoin) fromItem;
- if (subJoin.getJoinList() != null) {
- processJoins(subJoin.getJoinList());
- }
- if (subJoin.getLeft() != null) {
- processFromItem(subJoin.getLeft());
- }
- } else if (fromItem instanceof SubSelect) {
+ 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");
+ 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) {
@@ -284,75 +314,176 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
}
}
+ /**
+ * 处理 sub join
+ *
+ * @param subJoin subJoin
+ * @return Table subJoin 中的主表
+ */
+ private List processSubJoin(SubJoin subJoin) {
+ List mainTables = new ArrayList<>();
+ if (subJoin.getJoinList() != null) {
+ List list = processFromItem(subJoin.getLeft());
+ mainTables.addAll(list);
+ mainTables = processJoins(mainTables, subJoin.getJoinList());
+ }
+ return mainTables;
+ }
+
/**
* 处理 joins
*
- * @param joins join 集合
+ * @param mainTables 可以为 null
+ * @param joins join 集合
+ * @return List 右连接查询的 Table 列表
*/
- private void processJoins(List joins) {
+ private List processJoins(List mainTables, List 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 tables = new LinkedList<>();
+ Deque> onTableDeque = new LinkedList<>();
for (Join join : joins) {
// 处理 on 表达式
- FromItem fromItem = join.getRightItem();
- if (fromItem instanceof Table) {
- Table fromTable = (Table) fromItem;
+ FromItem joinItem = join.getRightItem();
+
+ // 获取当前 join 的表,subJoint 可以看作是一张表
+ List 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 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 originOnExpressions = join.getOnExpressions();
// 正常 join on 表达式只有一个,立刻处理
- if (originOnExpressions.size() == 1) {
- processJoin(join);
+ if (originOnExpressions.size() == 1 && onTables != null) {
+ List onExpressions = new LinkedList<>();
+ onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
+ join.setOnExpressions(onExpressions);
+ leftTable = joinTable;
continue;
}
- tables.push(fromTable);
+ // 表名压栈,忽略的表压入 null,以便后续不处理
+ onTableDeque.push(onTables);
// 尾缀多个 on 表达式的时候统一处理
if (originOnExpressions.size() > 1) {
Collection onExpressions = new LinkedList<>();
for (Expression originOnExpression : originOnExpressions) {
- Table currentTable = tables.poll();
- onExpressions.add(builderExpression(originOnExpression, currentTable));
+ List currentTableList = onTableDeque.poll();
+ if (CollectionUtils.isEmpty(currentTableList)) {
+ onExpressions.add(originOnExpression);
+ } else {
+ onExpressions.add(builderExpression(originOnExpression, currentTableList));
+ }
}
join.setOnExpressions(onExpressions);
}
+ leftTable = joinTable;
} else {
- // 处理右边连接的子表达式
- processFromItem(fromItem);
+ processOtherFromItem(joinItem);
+ leftTable = null;
}
}
+
+ return mainTables;
}
+ // ========== 和 TenantLineInnerInterceptor 存在差异的逻辑:关键,实现权限条件的拼接 ==========
+
/**
- * 处理联接语句
+ * 处理条件
+ *
+ * @param currentExpression 当前 where 条件
+ * @param table 单个表
*/
- protected void processJoin(Join join) {
- if (join.getRightItem() instanceof Table) {
- Table fromTable = (Table) join.getRightItem();
- Expression originOnExpression = CollUtil.getFirst(join.getOnExpressions());
- originOnExpression = builderExpression(originOnExpression, fromTable);
- join.setOnExpressions(CollUtil.newArrayList(originOnExpression));
- }
+ protected Expression builderExpression(Expression currentExpression, Table table) {
+ return this.builderExpression(currentExpression, Collections.singletonList(table));
}
/**
* 处理条件
+ *
+ * @param currentExpression 当前 where 条件
+ * @param tables 多个表
*/
- protected Expression builderExpression(Expression currentExpression, Table table) {
- // 获得 Table 对应的数据权限条件
- Expression equalsTo = buildDataPermissionExpression(table);
- if (equalsTo == null) { // 如果没条件,则返回 currentExpression 默认
+ protected Expression builderExpression(Expression currentExpression, List tables) {
+ // 没有表需要处理直接返回
+ if (CollectionUtils.isEmpty(tables)) {
return currentExpression;
}
- // 表达式为空,则直接返回 equalsTo
+ // 第一步,获得 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 equalsTo;
+ return dataPermissionExpression;
}
- // 如果表达式为 Or,则需要 (currentExpression) AND equalsTo
+ // ① 如果表达式为 Or,则需要 (currentExpression) AND dataPermissionExpression
if (currentExpression instanceof OrExpression) {
- return new AndExpression(new Parenthesis(currentExpression), equalsTo);
+ return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression);
}
- // 如果表达式为 And,则直接返回 currentExpression AND equalsTo
- return new AndExpression(currentExpression, equalsTo);
+ // ② 如果表达式为 And,则直接返回 where AND dataPermissionExpression
+ return new AndExpression(currentExpression, dataPermissionExpression);
}
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
index 85067439e..0e4b91691 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
@@ -106,7 +105,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
// 从上下文中拿不到,则调用逻辑进行获取
if (deptDataPermission == null) {
- deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId()).getData();
+ deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId()).getCheckedData();
if (deptDataPermission == null) {
log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java
index 4c1494aca..145360789 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java
@@ -87,7 +87,7 @@ public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
// 断言
verify(mpBs, times(1)).sql(
- eq("SELECT * FROM t_user WHERE id = 1 AND dept_id = 100"));
+ eq("SELECT * FROM t_user WHERE id = 1 AND t_user.dept_id = 100"));
// 断言缓存
assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java
index 8c0772f1a..6e42d52f4 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java
@@ -46,7 +46,7 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
@Override
public Set getTableNames() {
- return asSet("entity", "entity1", "entity2", "t1", "t2", // 支持 MyBatis Plus 的单元测试
+ return asSet("entity", "entity1", "entity2", "entity3", "t1", "t2", "sys_dict_item", // 支持 MyBatis Plus 的单元测试
"t_user", "t_role"); // 满足自己的单元测试
}
@@ -84,30 +84,30 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
@Test
void delete() {
assertSql("delete from entity where id = ?",
- "DELETE FROM entity WHERE id = ? AND tenant_id = 1");
+ "DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1");
}
@Test
void update() {
assertSql("update entity set name = ? where id = ?",
- "UPDATE entity SET name = ? WHERE id = ? AND tenant_id = 1");
+ "UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1");
}
@Test
void selectSingle() {
// 单表
assertSql("select * from entity where id = ?",
- "SELECT * FROM entity WHERE id = ? AND tenant_id = 1");
+ "SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1");
assertSql("select * from entity where id = ? or name = ?",
- "SELECT * FROM entity WHERE (id = ? OR name = ?) AND tenant_id = 1");
+ "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
assertSql("SELECT * FROM entity WHERE (id = ? OR name = ?)",
- "SELECT * FROM entity WHERE (id = ? OR name = ?) AND tenant_id = 1");
+ "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
/* not */
assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
- "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND tenant_id = 1");
+ "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1");
}
@Test
@@ -167,10 +167,12 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
assertSql("SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
+
/* <= */
assertSql("SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
+
/* <> */
assertSql("SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
@@ -204,6 +206,14 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+
+ assertSql("SELECT * FROM entity e " +
+ "left join entity1 e1 on e1.id = e.id " +
+ "left join entity2 e2 on e1.id = e2.id",
+ "SELECT * FROM entity e " +
+ "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+ "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
+ "WHERE e.tenant_id = 1");
}
@Test
@@ -212,17 +222,125 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
assertSql("SELECT * FROM entity e " +
"right join entity1 e1 on e1.id = e.id",
"SELECT * FROM entity e " +
- "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE e.tenant_id = 1");
+ "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+ "WHERE e1.tenant_id = 1");
+
+ assertSql("SELECT * FROM with_as_1 e " +
+ "right join entity1 e1 on e1.id = e.id",
+ "SELECT * FROM with_as_1 e " +
+ "RIGHT JOIN entity1 e1 ON e1.id = e.id " +
+ "WHERE e1.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"right join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM entity e " +
- "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+ "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+ "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
+
+ assertSql("SELECT * FROM entity e " +
+ "right join entity1 e1 on e1.id = e.id " +
+ "right join entity2 e2 on e1.id = e2.id ",
+ "SELECT * FROM entity e " +
+ "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+ "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
+ "WHERE e2.tenant_id = 1");
}
+ @Test
+ void selectMixJoin() {
+ assertSql("SELECT * FROM entity e " +
+ "right join entity1 e1 on e1.id = e.id " +
+ "left join entity2 e2 on e1.id = e2.id",
+ "SELECT * FROM entity e " +
+ "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
+ "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
+ "WHERE e1.tenant_id = 1");
+
+ assertSql("SELECT * FROM entity e " +
+ "left join entity1 e1 on e1.id = e.id " +
+ "right join entity2 e2 on e1.id = e2.id",
+ "SELECT * FROM entity e " +
+ "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 " +
+ "WHERE e2.tenant_id = 1");
+
+ assertSql("SELECT * FROM entity e " +
+ "left join entity1 e1 on e1.id = e.id " +
+ "inner join entity2 e2 on e1.id = e2.id",
+ "SELECT * FROM entity e " +
+ "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
+ "INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1");
+ }
+
+
+ @Test
+ void selectJoinSubSelect() {
+ assertSql("select * from (select * from entity) e1 " +
+ "left join entity2 e2 on e1.id = e2.id",
+ "SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 " +
+ "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1");
+
+ assertSql("select * from entity1 e1 " +
+ "left join (select * from entity2) e2 " +
+ "on e1.id = e2.id",
+ "SELECT * FROM entity1 e1 " +
+ "LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 " +
+ "ON e1.id = e2.id " +
+ "WHERE e1.tenant_id = 1");
+ }
+
+ @Test
+ void selectSubJoin() {
+
+ assertSql("select * FROM " +
+ "(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)",
+ "SELECT * FROM " +
+ "(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
+ "WHERE e2.tenant_id = 1");
+
+ assertSql("select * FROM " +
+ "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)",
+ "SELECT * FROM " +
+ "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+ "WHERE e1.tenant_id = 1");
+
+
+ assertSql("select * FROM " +
+ "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) " +
+ "right join entity3 e3 on e1.id = e3.id",
+ "SELECT * FROM " +
+ "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+ "RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 " +
+ "WHERE e3.tenant_id = 1");
+
+
+ assertSql("select * FROM entity e " +
+ "LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) " +
+ "on e.id = e2.id",
+ "SELECT * FROM entity e " +
+ "LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
+ "ON e.id = e2.id AND e2.tenant_id = 1 " +
+ "WHERE e.tenant_id = 1");
+
+ assertSql("select * FROM entity e " +
+ "LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
+ "on e.id = e2.id",
+ "SELECT * FROM entity e " +
+ "LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+ "ON e.id = e2.id AND e1.tenant_id = 1 " +
+ "WHERE e.tenant_id = 1");
+
+ assertSql("select * FROM entity e " +
+ "RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
+ "on e.id = e2.id",
+ "SELECT * FROM entity e " +
+ "RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
+ "ON e.id = e2.id AND e.tenant_id = 1 " +
+ "WHERE e1.tenant_id = 1");
+ }
+
+
@Test
void selectLeftJoinMultipleTrailingOn() {
// 多个 on 尾缀的
@@ -256,51 +374,97 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
"inner join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM entity e " +
- "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+ "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
+ "WHERE e.id = ? OR e.name = ?");
assertSql("SELECT * FROM entity e " +
"inner join entity1 e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM entity e " +
- "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+ "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
+ "WHERE (e.id = ? OR e.name = ?)");
+
+ // 隐式内连接
+ assertSql("SELECT * FROM entity,entity1 " +
+ "WHERE entity.id = entity1.id",
+ "SELECT * FROM entity, entity1 " +
+ "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
+
+ // 隐式内连接
+ assertSql("SELECT * FROM entity a, with_as_entity1 b " +
+ "WHERE a.id = b.id",
+ "SELECT * FROM entity a, with_as_entity1 b " +
+ "WHERE a.id = b.id AND a.tenant_id = 1");
+
+ assertSql("SELECT * FROM with_as_entity a, with_as_entity1 b " +
+ "WHERE a.id = b.id",
+ "SELECT * FROM with_as_entity a, with_as_entity1 b " +
+ "WHERE a.id = b.id");
+
+ // SubJoin with 隐式内连接
+ assertSql("SELECT * FROM (entity,entity1) " +
+ "WHERE entity.id = entity1.id",
+ "SELECT * FROM (entity, entity1) " +
+ "WHERE entity.id = entity1.id " +
+ "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
+
+ assertSql("SELECT * FROM ((entity,entity1),entity2) " +
+ "WHERE entity.id = entity1.id and entity.id = entity2.id",
+ "SELECT * FROM ((entity, entity1), entity2) " +
+ "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
+ "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
+
+ assertSql("SELECT * FROM (entity,(entity1,entity2)) " +
+ "WHERE entity.id = entity1.id and entity.id = entity2.id",
+ "SELECT * FROM (entity, (entity1, entity2)) " +
+ "WHERE entity.id = entity1.id AND entity.id = entity2.id " +
+ "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
+
+ // 沙雕的括号写法
+ assertSql("SELECT * FROM (((entity,entity1))) " +
+ "WHERE entity.id = entity1.id",
+ "SELECT * FROM (((entity, entity1))) " +
+ "WHERE entity.id = entity1.id " +
+ "AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
- // 垃圾 inner join todo
-// assertSql("SELECT * FROM entity,entity1 " +
-// "WHERE entity.id = entity1.id",
-// "SELECT * FROM entity e " +
-// "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-// "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
}
+
@Test
void selectWithAs() {
assertSql("with with_as_A as (select * from entity) select * from with_as_A",
- "WITH with_as_A AS (SELECT * FROM entity WHERE tenant_id = 1) SELECT * FROM with_as_A");
+ "WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A");
+ }
+
+
+ @Test
+ void selectIgnoreTable() {
+ assertSql(" SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)",
+ "SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)");
}
private void assertSql(String sql, String targetSql) {
assertEquals(targetSql, interceptor.parserSingle(sql, null));
}
+
// ========== 额外的测试 ==========
@Test
public void testSelectSingle() {
// 单表
assertSql("select * from t_user where id = ?",
- "SELECT * FROM t_user WHERE id = ? AND tenant_id = 1 AND dept_id IN (10, 20)");
+ "SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
assertSql("select * from t_user where id = ? or name = ?",
- "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+ "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
assertSql("SELECT * FROM t_user WHERE (id = ? OR name = ?)",
- "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+ "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
/* not */
assertSql("SELECT * FROM t_user WHERE not (id = ? OR name = ?)",
- "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND tenant_id = 1 AND dept_id IN (10, 20)");
+ "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
}
@Test
@@ -329,16 +493,16 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
"right join t_role e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM t_user e " +
- "RIGHT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+ "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
+ "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
// 条件 e.id = ? OR e.name = ? 带括号
assertSql("SELECT * FROM t_user e " +
"right join t_role e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM t_user e " +
- "RIGHT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+ "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
+ "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
}
@Test
@@ -348,23 +512,22 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
"inner join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM t_user e " +
- "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+ "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
+ "WHERE e.id = ? OR e.name = ?");
// 条件 e.id = ? OR e.name = ? 带括号
assertSql("SELECT * FROM t_user e " +
- "inner join t_role e1 on e1.id = e.id " +
+ "inner join entity1 e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM t_user e " +
- "INNER JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
- "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
+ "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
+ "WHERE (e.id = ? OR e.name = ?)");
- // 垃圾 inner join todo
-// assertSql("SELECT * FROM entity,entity1 " +
-// "WHERE entity.id = entity1.id",
-// "SELECT * FROM entity e " +
-// "INNER JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
-// "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+ // 没有 On 的 inner join
+ assertSql("SELECT * FROM entity,entity1 " +
+ "WHERE entity.id = entity1.id",
+ "SELECT * FROM entity, entity1 " +
+ "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
}
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java
index bfbb5f9da..b9622fa0f 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java
@@ -3,13 +3,12 @@ package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
-import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
-import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import org.junit.jupiter.api.BeforeEach;
@@ -25,6 +24,7 @@ import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataP
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
@@ -75,6 +75,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
+ // mock 方法(permissionApi 返回 null)
+ when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(success(null));
// 调用
NullPointerException exception = assertThrows(NullPointerException.class,
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java
index da8a60152..870c74f11 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/core/loader/ErrorCodeLoaderImpl.java
@@ -10,7 +10,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
-import java.util.Date;
+import java.time.LocalDateTime;
import java.util.List;
/**
@@ -41,7 +41,7 @@ public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
/**
* 缓存错误码的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
- private Date maxUpdateTime;
+ private LocalDateTime maxUpdateTime;
@EventListener(ApplicationReadyEvent.class)
public void loadErrorCodes() {
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java
index d3b94d25a..75819cbe1 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/aop/OperateLogAspect.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.operatelog.core.aop;
+import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
@@ -32,6 +33,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
+import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.IntStream;
@@ -94,7 +96,7 @@ public class OperateLogAspect {
}
// 记录开始时间
- Date startTime = new Date();
+ LocalDateTime startTime = LocalDateTime.now();
try {
// 执行原有方法
Object result = joinPoint.proceed();
@@ -128,7 +130,7 @@ public class OperateLogAspect {
private void log(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
ApiOperation apiOperation,
- Date startTime, Object result, Throwable exception) {
+ LocalDateTime startTime, Object result, Throwable exception) {
try {
// 判断不记录的情况
if (!isLogEnable(joinPoint, operateLog)) {
@@ -145,7 +147,7 @@ public class OperateLogAspect {
private void log0(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
ApiOperation apiOperation,
- Date startTime, Object result, Throwable exception) {
+ LocalDateTime startTime, Object result, Throwable exception) {
OperateLog operateLogObj = new OperateLog();
// 补全通用字段
operateLogObj.setTraceId(TracerUtils.getTraceId());
@@ -226,7 +228,7 @@ public class OperateLogAspect {
private static void fillMethodFields(OperateLog operateLogObj,
ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
- Date startTime, Object result, Throwable exception) {
+ LocalDateTime startTime, Object result, Throwable exception) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
operateLogObj.setJavaMethod(methodSignature.toString());
if (operateLog == null || operateLog.logArgs()) {
@@ -235,7 +237,7 @@ public class OperateLogAspect {
if (operateLog == null || operateLog.logResultData()) {
operateLogObj.setResultData(obtainResultData(result));
}
- operateLogObj.setDuration((int) (System.currentTimeMillis() - startTime.getTime()));
+ operateLogObj.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
// (正常)处理 resultCode 和 resultMsg 字段
if (result instanceof CommonResult) {
CommonResult> commonResult = (CommonResult>) result;
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLog.java b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLog.java
index 7bce1c6bd..1e3b8c8a8 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLog.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/OperateLog.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.operatelog.core.service;
import lombok.Data;
-import java.util.Date;
+import java.time.LocalDateTime;
import java.util.Map;
/**
@@ -85,7 +85,7 @@ public class OperateLog {
/**
* 开始时间
*/
- private Date startTime;
+ private LocalDateTime startTime;
/**
* 执行时长,单位:毫秒
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java
index a5c24c24e..91f3b2a37 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java
@@ -5,7 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
-import java.util.Date;
+import java.time.LocalDateTime;
/**
* 支付通知 Response DTO
@@ -33,7 +33,7 @@ public class PayOrderNotifyRespDTO {
/**
* 支付成功时间
*/
- private Date successTime;
+ private LocalDateTime successTime;
/**
* 通知的原始数据
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
index 6f99bb80d..ea3628931 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
@@ -7,7 +7,7 @@ import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
-import java.util.Date;
+import java.time.LocalDateTime;
import java.util.Map;
/**
@@ -68,7 +68,7 @@ public class PayOrderUnifiedReqDTO {
* 支付过期时间
*/
@NotNull(message = "支付过期时间不能为空")
- private Date expireTime;
+ private LocalDateTime expireTime;
// ========== 拓展参数 ==========
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java
index 05fcff3c1..edb593d82 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java
@@ -5,7 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.ToString;
-import java.util.Date;
+import java.time.LocalDateTime;
/**
* 从渠道返回数据中解析得到的支付退款通知的Notify DTO
@@ -57,7 +57,7 @@ public class PayRefundNotifyDTO {
/**
* 退款成功时间
*/
- private Date refundSuccessTime;
+ private LocalDateTime refundSuccessTime;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
index d2e280ae5..cedf0020d 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.framework.pay.core.client.dto;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
index aefb3ba3b..421dbfe7d 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPcPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXLitePayClient;
@@ -69,7 +70,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
case ALIPAY_WAP: return (AbstractPayClient) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_QR: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_APP: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
- case ALIPAY_PC: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
+ case ALIPAY_PC: return (AbstractPayClient) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
index 41c473be7..680d80d77 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
@@ -1,8 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.http.HttpUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
@@ -61,7 +60,7 @@ public abstract class AbstractAlipayClient extends AbstractPayClient doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+ // 构建 AlipayTradePagePayModel 请求
+ AlipayTradePagePayModel model = new AlipayTradePagePayModel();
+ // 构建 AlipayTradePagePayRequest
+ AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
+ request.setBizModel(model);
+ JSONObject bizContent = new JSONObject();
+ // 参数说明可查看: https://opendocs.alipay.com/open/028r8t?scene=22
+ bizContent.put("out_trade_no", reqDTO.getMerchantOrderId());
+ bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
+ bizContent.put("subject", reqDTO.getSubject());
+ bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
+ // PC扫码支付的方式:支持前置模式和跳转模式。4: 订单码-可定义宽度的嵌入式二维码
+ bizContent.put("qr_pay_mode", "4");
+ // 自定义二维码宽度
+ bizContent.put("qrcode_width", "150");
+ request.setBizContent(bizContent.toJSONString());
+ request.setNotifyUrl(reqDTO.getNotifyUrl());
+ request.setReturnUrl("");
+ // 执行请求
+ AlipayTradePagePayResponse response;
+ try {
+ response = client.pageExecute(request);
+ } catch (AlipayApiException e) {
+ log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
+ return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
+ }
+ // 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
+ return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java
index c9b81ace0..52bdde62d 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
@@ -167,7 +168,7 @@ public class WXLitePayClient extends AbstractPayClient {
.builder()
.orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
- .successTime(DateUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+ .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build();
}
@@ -181,7 +182,7 @@ public class WXLitePayClient extends AbstractPayClient {
.orderExtensionNo(notifyResult.getOutTradeNo())
.channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid())
- .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
+ .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build();
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java
index 20ad91c82..f9ccd4629 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
@@ -150,7 +151,7 @@ public class WXNativePayClient extends AbstractPayClient {
.builder()
.orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
- .successTime(DateUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+ .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build();
}
@@ -164,7 +165,7 @@ public class WXNativePayClient extends AbstractPayClient {
.orderExtensionNo(notifyResult.getOutTradeNo())
.channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid())
- .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
+ .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build();
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
index 238344759..84e3064c3 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
@@ -161,7 +162,7 @@ public class WXPubPayClient extends AbstractPayClient {
.builder()
.orderExtensionNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
- .successTime(DateUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
+ .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
.data(data.getBody())
.build();
}
@@ -175,7 +176,7 @@ public class WXPubPayClient extends AbstractPayClient {
.orderExtensionNo(notifyResult.getOutTradeNo())
.channelOrderNo(notifyResult.getTransactionId())
.channelUserId(notifyResult.getOpenid())
- .successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
+ .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
.data(data.getBody())
.build();
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/dto/SmsReceiveRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/dto/SmsReceiveRespDTO.java
index 8c841ebea..4def4a25c 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/dto/SmsReceiveRespDTO.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/dto/SmsReceiveRespDTO.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.sms.core.client.dto;
import lombok.Data;
-import java.util.Date;
+import java.time.LocalDateTime;
/**
* 消息接收 Response DTO
@@ -32,7 +32,7 @@ public class SmsReceiveRespDTO {
/**
* 用户接收时间
*/
- private Date receiveTime;
+ private LocalDateTime receiveTime;
/**
* 短信 API 发送返回的序号
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java
index ae93a88b6..59723c4c9 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java
@@ -4,6 +4,8 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
@@ -11,8 +13,6 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.AcsResponse;
import com.aliyuncs.DefaultAcsClient;
@@ -28,7 +28,7 @@ import com.google.common.annotations.VisibleForTesting;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
-import java.util.Date;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
@@ -166,13 +166,13 @@ public class AliyunSmsClient extends AbstractSmsClient {
*/
@JsonProperty("send_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
- private Date sendTime;
+ private LocalDateTime sendTime;
/**
* 状态报告时间
*/
@JsonProperty("report_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
- private Date reportTime;
+ private LocalDateTime reportTime;
/**
* 是否接收成功
*/
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java
index 23bb01a02..743c1aed8 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClient.java
@@ -22,7 +22,7 @@ import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.*;
import lombok.Data;
-import java.util.Date;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -240,7 +240,7 @@ public class TencentSmsClient extends AbstractSmsClient {
*/
@JsonProperty("user_receive_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
- private Date receiveTime;
+ private LocalDateTime receiveTime;
/**
* 国家(或地区)码
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java
index 12cb6635c..c5d1f88d0 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
@@ -12,7 +13,6 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
@@ -23,6 +23,7 @@ import com.yunpian.sdk.model.Template;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
+import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -198,7 +199,7 @@ public class YunpianSmsClient extends AbstractSmsClient {
*/
@JsonProperty("user_receive_time")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
- private Date userReceiveTime;
+ private LocalDateTime userReceiveTime;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java
index e65e23db7..4b02c5121 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java
@@ -11,7 +11,6 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.IAcsClient;
@@ -27,6 +26,7 @@ import org.mockito.ArgumentMatcher;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Function;
@@ -125,7 +125,7 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
assertEquals("DELIVERED", statuses.get(0).getErrorCode());
assertEquals("用户接收成功", statuses.get(0).getErrorMsg());
assertEquals("13900000001", statuses.get(0).getMobile());
- assertEquals(DateUtils.buildTime(2017, 2, 2, 22, 23, 24), statuses.get(0).getReceiveTime());
+ assertEquals(LocalDateTime.of(2017, 2, 2, 22, 23, 24), statuses.get(0).getReceiveTime());
assertEquals("12345", statuses.get(0).getSerialNo());
assertEquals(67890L, statuses.get(0).getLogId());
}
@@ -181,7 +181,7 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
when(client.getAcsResponse(any(AcsRequest.class))).thenThrow(ex);
// 调用,并断言异常
- SmsCommonResult> result = smsClient.invoke(request,null);
+ SmsCommonResult> result = smsClient.invoke(request, null);
// 断言
assertEquals(ex.getErrCode(), result.getApiCode());
assertEquals(ex.getErrMsg(), result.getApiMsg());
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java
index 64305c7a0..9269de724 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/tencent/TencentSmsClientTest.java
@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
@@ -25,6 +24,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -146,7 +146,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
assertEquals("DELIVRD", statuses.get(0).getErrorCode());
assertEquals("用户短信送达成功", statuses.get(0).getErrorMsg());
assertEquals("13900000001", statuses.get(0).getMobile());
- assertEquals(DateUtils.buildTime(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
+ assertEquals(LocalDateTime.of(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
assertEquals("12345", statuses.get(0).getSerialNo());
assertEquals(67890L, statuses.get(0).getLogId());
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java
index 8271c0d7b..a28745e6c 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-sms/src/test/java/cn/iocoder/yudao/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
-import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import com.google.common.collect.Lists;
import com.yunpian.sdk.YunpianClient;
import com.yunpian.sdk.api.SmsApi;
@@ -23,6 +22,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -115,7 +115,7 @@ public class YunpianSmsClientTest extends BaseMockitoUnitTest {
assertEquals("", statuses.get(0).getErrorCode());
assertNull(statuses.get(0).getErrorMsg());
assertEquals("15205201314", statuses.get(0).getMobile());
- assertEquals(DateUtils.buildTime(2014, 3, 17, 22, 55, 21), statuses.get(0).getReceiveTime());
+ assertEquals(LocalDateTime.of(2014, 3, 17, 22, 55, 21), statuses.get(0).getReceiveTime());
assertEquals("9527", statuses.get(0).getSerialNo());
assertEquals(1024L, statuses.get(0).getLogId());
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
index e338e2c73..e9ec9772c 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantChannelInterceptor;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantFunctionAroundWrapper;
+import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkServiceImpl;
@@ -25,8 +26,16 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.integration.config.GlobalChannelInterceptor;
+import java.util.Objects;
+
@Configuration
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
@EnableConfigurationProperties(TenantProperties.class)
@@ -122,4 +131,17 @@ public class YudaoTenantAutoConfiguration {
return new TenantJobAspect(tenantFrameworkService);
}
+ // ========== Redis ==========
+
+ @Bean
+ @Primary // 引入租户时,tenantRedisCacheManager 为主 Bean
+ public RedisCacheManager tenantRedisCacheManager(RedisTemplate redisTemplate,
+ RedisCacheConfiguration redisCacheConfiguration) {
+ // 创建 RedisCacheWriter 对象
+ RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
+ RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
+ // 创建 TenantRedisCacheManager 对象
+ return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration);
+ }
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
index a42acc41c..3b15398d6 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.tenant.core.context;
+import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
import com.alibaba.ttl.TransmittableThreadLocal;
/**
@@ -36,7 +37,8 @@ public class TenantContextHolder {
public static Long getRequiredTenantId() {
Long tenantId = getTenantId();
if (tenantId == null) {
- throw new NullPointerException("TenantContextHolder 不存在租户编号"); // TODO 芋艿:增加文档链接
+ throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:"
+ + DocumentEnum.TENANT.getUrl());
}
return tenantId;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java
new file mode 100644
index 000000000..8058b2f54
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.framework.tenant.core.redis;
+
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.Cache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+
+/**
+ * 多租户的 {@link RedisCacheManager} 实现类
+ *
+ * 操作指定 name 的 {@link Cache} 时,自动拼接租户后缀,格式为 name + ":" + tenantId + 后缀
+ *
+ * @author airhead
+ */
+@Slf4j
+public class TenantRedisCacheManager extends RedisCacheManager {
+
+ public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
+ RedisCacheConfiguration defaultCacheConfiguration) {
+ super(cacheWriter, defaultCacheConfiguration);
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ // 如果开启多租户,则 name 拼接租户后缀
+ if (!TenantContextHolder.isIgnore()
+ && TenantContextHolder.getTenantId() != null) {
+ name = name + ":" + TenantContextHolder.getTenantId();
+ }
+
+ // 继续基于父方法
+ return super.getCache(name);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
index 7e1f1dc5a..803e541c5 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
@@ -65,6 +66,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
Long tenantId = TenantContextHolder.getTenantId();
+ boolean isRpcRequest = !WebFrameworkUtils.isRpcRequest(request);
// 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。
LoginUser user = SecurityFrameworkUtils.getLoginUser();
if (user != null) {
@@ -73,7 +75,8 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
tenantId = user.getTenantId();
TenantContextHolder.setTenantId(tenantId);
// 如果传递了租户编号,则进行比对租户编号,避免越权问题
- } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {
+ } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())
+ && !isRpcRequest) { // Cloud 特殊逻辑:如果是 RPC 请求,就不校验了。主要考虑,一些场景下,会调用 TenantUtils 去切换租户
log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]",
user.getTenantId(), user.getId(), user.getUserType(),
TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
index 7698cf4a6..182c83a72 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
@@ -5,8 +5,9 @@ import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
@@ -29,11 +30,11 @@ public class DictConvert implements Converter