commit
e73311d074
4
pom.xml
4
pom.xml
|
@ -32,8 +32,8 @@
|
|||
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
|
||||
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
|
||||
<!-- 看看咋放到 bom 里 -->
|
||||
<lombok.version>1.18.20</lombok.version>
|
||||
<mapstruct.version>1.4.1.Final</mapstruct.version>
|
||||
<lombok.version>1.18.24</lombok.version>
|
||||
<mapstruct.version>1.5.3.Final</mapstruct.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
|
|
|
@ -16,19 +16,19 @@
|
|||
<properties>
|
||||
<revision>1.6.2-snapshot</revision>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>2.6.9</spring.boot.version>
|
||||
<spring.boot.version>2.6.13</spring.boot.version>
|
||||
<spring.cloud.version>2021.0.1</spring.cloud.version>
|
||||
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
|
||||
<!-- Web 相关 -->
|
||||
<knife4j.version>3.0.3</knife4j.version>
|
||||
<swagger-annotations.version>1.6.6</swagger-annotations.version>
|
||||
<swagger-annotations.version>1.6.7</swagger-annotations.version>
|
||||
<servlet.versoin>2.5</servlet.versoin>
|
||||
<!-- DB 相关 -->
|
||||
<druid.version>1.2.11</druid.version>
|
||||
<mybatis-plus.version>3.4.3.4</mybatis-plus.version>
|
||||
<druid.version>1.2.14</druid.version>
|
||||
<mybatis-plus.version>3.5.2</mybatis-plus.version>
|
||||
<mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
|
||||
<dynamic-datasource.version>3.5.0</dynamic-datasource.version>
|
||||
<redisson.version>3.17.3</redisson.version>
|
||||
<dynamic-datasource.version>3.5.2</dynamic-datasource.version>
|
||||
<redisson.version>3.17.7</redisson.version>
|
||||
<!-- RPC 相关 -->
|
||||
<dubbo.version>2.7.15</dubbo.version>
|
||||
<!-- Config 配置中心相关 -->
|
||||
|
@ -36,37 +36,38 @@
|
|||
<!-- Job 定时任务相关 -->
|
||||
<xxl-job.version>2.3.1</xxl-job.version>
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.0</lock4j.version>
|
||||
<lock4j.version>2.2.2</lock4j.version>
|
||||
<resilience4j.version>1.7.1</resilience4j.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>8.7.0</skywalking.version>
|
||||
<spring-boot-admin.version>2.6.7</spring-boot-admin.version>
|
||||
<opentracing.version>0.31.0</opentracing.version>
|
||||
<skywalking.version>8.12.0</skywalking.version>
|
||||
<spring-boot-admin.version>2.6.9</spring-boot-admin.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<!-- Test 测试相关 -->
|
||||
<podam.version>7.2.6.RELEASE</podam.version>
|
||||
<jedis-mock.version>0.1.16</jedis-mock.version>
|
||||
<mockito-inline.version>4.0.0</mockito-inline.version>
|
||||
<podam.version>7.2.9.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.0.4</jedis-mock.version>
|
||||
<mockito-inline.version>4.8.0</mockito-inline.version>
|
||||
<!-- Bpm 工作流相关 -->
|
||||
<flowable.version>6.7.2</flowable.version>
|
||||
<!-- 工具类相关 -->
|
||||
<jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version>
|
||||
<lombok.version>1.18.20</lombok.version>
|
||||
<mapstruct.version>1.4.1.Final</mapstruct.version>
|
||||
<hutool.version>5.8.5</hutool.version>
|
||||
<easyexcel.verion>2.2.7</easyexcel.verion>
|
||||
<lombok.version>1.18.24</lombok.version>
|
||||
<mapstruct.version>1.5.3.Final</mapstruct.version>
|
||||
<hutool.version>5.8.9</hutool.version>
|
||||
<easyexcel.verion>3.1.2</easyexcel.verion>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<screw.version>1.0.5</screw.version>
|
||||
<fastjson.version>2.0.5</fastjson.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<guava.version>30.1.1-jre</guava.version>
|
||||
<guice.version>5.1.0</guice.version>
|
||||
<transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
|
||||
<transmittable-thread-local.version>2.14.0</transmittable-thread-local.version>
|
||||
<commons-net.version>3.8.0</commons-net.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<tika-core.version>2.4.1</tika-core.version>
|
||||
<tika-core.version>2.5.0</tika-core.version>
|
||||
<aj-captcha.version>1.3.0</aj-captcha.version>
|
||||
<netty-all.version>4.1.82.Final</netty-all.version>
|
||||
<!-- 三方云服务相关 -->
|
||||
<minio.version>8.2.2</minio.version>
|
||||
<aliyun-java-sdk-core.version>4.6.0</aliyun-java-sdk-core.version>
|
||||
<aliyun-java-sdk-core.version>4.6.2</aliyun-java-sdk-core.version>
|
||||
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
|
||||
<tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version>
|
||||
<yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
|
||||
|
@ -563,6 +564,12 @@
|
|||
<version>${jsch.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>${netty-all.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 三方云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
|
|
|
@ -105,6 +105,11 @@
|
|||
<artifactId>jackson-core</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
|
|
@ -12,8 +12,9 @@ import lombok.Getter;
|
|||
@AllArgsConstructor
|
||||
public enum DocumentEnum {
|
||||
|
||||
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档");
|
||||
|
||||
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"),
|
||||
TENANT("https://doc.iocoder.cn", "SaaS 多租户文档");
|
||||
|
||||
private final String url;
|
||||
private final String memo;
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.common.util.date;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.*;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
|
@ -25,6 +26,34 @@ public class DateUtils {
|
|||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 将 LocalDateTime 转换成 Date
|
||||
*
|
||||
* @param date LocalDateTime
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static Date of(LocalDateTime date) {
|
||||
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
||||
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
||||
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return Date.from(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Date 转换成 LocalDateTime
|
||||
*
|
||||
* @param date Date
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static LocalDateTime of(Date date) {
|
||||
// 转为时间戳
|
||||
Instant instant = date.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public static Date addTime(Duration duration) {
|
||||
return new Date(System.currentTimeMillis() + duration.toMillis());
|
||||
}
|
||||
|
@ -33,6 +62,11 @@ public class DateUtils {
|
|||
return System.currentTimeMillis() > time.getTime();
|
||||
}
|
||||
|
||||
public static boolean isExpired(LocalDateTime time) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return now.isAfter(time);
|
||||
}
|
||||
|
||||
public static long diff(Date endTime, Date startTime) {
|
||||
return endTime.getTime() - startTime.getTime();
|
||||
}
|
||||
|
@ -40,24 +74,28 @@ public class DateUtils {
|
|||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int mouth, int day) {
|
||||
return buildTime(year, mouth, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static LocalDateTime buildLocalDateTime(int year, int mouth, int day) {
|
||||
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @param hour 小时
|
||||
* @param minute 分钟
|
||||
* @param second 秒
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @param hour 小时
|
||||
* @param minute 分钟
|
||||
* @param second 秒
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int mouth, int day,
|
||||
|
@ -83,6 +121,16 @@ public class DateUtils {
|
|||
return a.compareTo(b) > 0 ? a : b;
|
||||
}
|
||||
|
||||
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
return a.isAfter(b) ? a : b;
|
||||
}
|
||||
|
||||
public static boolean beforeNow(Date date) {
|
||||
return date.getTime() < System.currentTimeMillis();
|
||||
}
|
||||
|
@ -91,6 +139,10 @@ public class DateUtils {
|
|||
return date.getTime() >= System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static boolean afterNow(LocalDateTime localDateTime) {
|
||||
return localDateTime.isAfter(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当期时间相差的日期
|
||||
*
|
||||
|
@ -135,4 +187,14 @@ public class DateUtils {
|
|||
return DateUtil.isSameDay(date, new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否今天
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isToday(LocalDateTime date) {
|
||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -28,6 +29,7 @@ public class JsonUtils {
|
|||
|
||||
static {
|
||||
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,6 @@ 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.expression.operators.relational.ItemsList;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.delete.Delete;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
|
@ -37,7 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
/**
|
||||
* 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现
|
||||
* 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, Table)} 方法
|
||||
* 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, List)} 方法
|
||||
*
|
||||
* 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。
|
||||
* 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更!
|
||||
|
@ -53,8 +52,7 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||
private final MappedStatementCache mappedStatementCache = new MappedStatementCache();
|
||||
|
||||
@Override // SELECT 场景
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
|
||||
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
|
||||
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)) { // 如果无需重写,则跳过
|
||||
|
@ -68,12 +66,14 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||
// 处理 SQL
|
||||
mpBs.sql(parserSingle(mpBs.sql(), null));
|
||||
} finally {
|
||||
// 添加是否需要重写的缓存
|
||||
addMappedStatementCache(ms);
|
||||
// 清空上下文
|
||||
ContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景
|
||||
@Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限)
|
||||
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
|
||||
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
|
||||
MappedStatement ms = mpSh.mappedStatement();
|
||||
|
@ -92,7 +92,9 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||
// 处理 SQL
|
||||
mpBs.sql(parserMulti(mpBs.sql(), null));
|
||||
} finally {
|
||||
// 添加是否需要重写的缓存
|
||||
addMappedStatementCache(ms);
|
||||
// 清空上下文
|
||||
ContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
@ -107,24 +109,6 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||
}
|
||||
}
|
||||
|
||||
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> selectBodys = operationList.getSelects();
|
||||
if (CollectionUtils.isNotEmpty(selectBodys)) {
|
||||
selectBodys.forEach(this::processSelectBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update 语句处理
|
||||
*/
|
||||
|
@ -142,28 +126,77 @@ public class DataPermissionDatabaseInterceptor extends JsqlParserSupport impleme
|
|||
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) {
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
Expression where = plainSelect.getWhere();
|
||||
processWhereSubSelect(where);
|
||||
if (fromItem instanceof Table) {
|
||||
Table fromTable = (Table) fromItem;
|
||||
plainSelect.setWhere(builderExpression(where, fromTable));
|
||||
} else {
|
||||
processFromItem(fromItem);
|
||||
}
|
||||
//#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)) {
|
||||
processJoins(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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
|||
* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
|
||||
* <p> fixed gitee pulls/141</p>
|
||||
*
|
||||
* @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<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 joins join 集合
|
||||
* @param mainTables 可以为 null
|
||||
* @param joins join 集合
|
||||
* @return List<Table> 右连接查询的 Table 列表
|
||||
*/
|
||||
private void processJoins(List<Join> joins) {
|
||||
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<Table> tables = new LinkedList<>();
|
||||
Deque<List<Table>> 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<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) {
|
||||
processJoin(join);
|
||||
if (originOnExpressions.size() == 1 && onTables != null) {
|
||||
List<Expression> 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<Expression> onExpressions = new LinkedList<>();
|
||||
for (Expression originOnExpression : originOnExpressions) {
|
||||
Table currentTable = tables.poll();
|
||||
onExpressions.add(builderExpression(originOnExpression, currentTable));
|
||||
List<Table> 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<Table> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) 未返回数据权限",
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
|
|||
|
||||
@Override
|
||||
public Set<String> 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 执行时长,单位:毫秒
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 通知的原始数据
|
||||
|
|
|
@ -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;
|
||||
|
||||
// ========== 拓展参数 ==========
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
|
||||
|
|
|
@ -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<AlipayPayCl
|
|||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
|
||||
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
|
||||
.tradeStatus(params.get("trade_status"))
|
||||
.successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.successTime(LocalDateTimeUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.data(data.getBody()).build();
|
||||
}
|
||||
|
||||
|
@ -72,7 +71,7 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
.tradeNo(params.get("out_trade_no"))
|
||||
.reqNo(params.get("out_biz_no"))
|
||||
.status(PayNotifyRefundStatusEnum.SUCCESS)
|
||||
.refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.refundSuccessTime(LocalDateTimeUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
|
||||
.build();
|
||||
return notifyDTO;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.domain.AlipayTradePagePayModel;
|
||||
import com.alipay.api.request.AlipayTradePagePayRequest;
|
||||
import com.alipay.api.response.AlipayTradePagePayResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* 支付宝【PC网站支付】的 PayClient 实现类
|
||||
* 文档:https://opendocs.alipay.com/open/270/105898
|
||||
*
|
||||
* @author XGD
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlipayPcPayClient extends AbstractAlipayClient {
|
||||
|
||||
public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<AlipayTradePagePayResponse> 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);
|
||||
}
|
||||
}
|
|
@ -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<WXPayClientConfig> {
|
|||
.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<WXPayClientConfig> {
|
|||
.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();
|
||||
|
||||
|
|
|
@ -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<WXPayClientConfig> {
|
|||
.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<WXPayClientConfig> {
|
|||
.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();
|
||||
|
||||
|
|
|
@ -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<WXPayClientConfig> {
|
|||
.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<WXPayClientConfig> {
|
|||
.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();
|
||||
|
||||
|
|
|
@ -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 发送返回的序号
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
* 是否接收成功
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 国家(或地区)码
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<String, Object> redisTemplate,
|
||||
RedisCacheConfiguration redisCacheConfiguration) {
|
||||
// 创建 RedisCacheWriter 对象
|
||||
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
||||
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
// 创建 TenantRedisCacheManager 对象
|
||||
return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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<Object> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
|
||||
public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
// 使用字典解析
|
||||
String type = getType(contentProperty);
|
||||
String label = cellData.getStringValue();
|
||||
String label = readCellData.getStringValue();
|
||||
String value = DictFrameworkUtils.parseDictDataValue(type, label);
|
||||
if (value == null) {
|
||||
log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label);
|
||||
|
@ -45,11 +46,11 @@ public class DictConvert implements Converter<Object> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
// 空时,返回空
|
||||
if (object == null) {
|
||||
return new CellData<>("");
|
||||
return new WriteCellData<>("");
|
||||
}
|
||||
|
||||
// 使用字典格式化
|
||||
|
@ -58,10 +59,10 @@ public class DictConvert implements Converter<Object> {
|
|||
String label = DictFrameworkUtils.getDictDataLabel(type, value);
|
||||
if (label == null) {
|
||||
log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, value);
|
||||
return new CellData<>("");
|
||||
return new WriteCellData<>("");
|
||||
}
|
||||
// 生成 Excel 小表格
|
||||
return new CellData<>(label);
|
||||
return new WriteCellData<>(label);
|
||||
}
|
||||
|
||||
private static String getType(ExcelContentProperty contentProperty) {
|
||||
|
|
|
@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.excel.core.convert;
|
|||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
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.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
|
||||
/**
|
||||
|
@ -25,15 +25,10 @@ public class JsonConvert implements Converter<Object> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
|
||||
throw new UnsupportedOperationException("暂不支持,也不需要");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellData<String> convertToExcelData(Object value, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
public WriteCellData<String> convertToExcelData(Object value, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
// 生成 Excel 小表格
|
||||
return new CellData<>(JsonUtils.toJsonString(value));
|
||||
return new WriteCellData<>(JsonUtils.toJsonString(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import lombok.Data;
|
|||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体对象
|
||||
|
@ -21,12 +21,12 @@ public abstract class BaseDO implements Serializable {
|
|||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 最后更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 创建者,目前使用 SysUser 的 id 编号
|
||||
*
|
||||
|
|
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
|||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
|||
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
|
||||
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
|
||||
|
||||
Date current = new Date();
|
||||
LocalDateTime current = LocalDateTime.now();
|
||||
// 创建时间为空,则以当前时间为插入时间
|
||||
if (Objects.isNull(baseDO.getCreateTime())) {
|
||||
baseDO.setCreateTime(current);
|
||||
|
@ -49,7 +49,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
|||
// 更新时间为空,则以当前时间为更新时间
|
||||
Object modifyTime = getFieldValByName("updateTime", metaObject);
|
||||
if (Objects.isNull(modifyTime)) {
|
||||
setFieldValByName("updateTime", new Date(), metaObject);
|
||||
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
|
||||
}
|
||||
|
||||
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||
|
|
|
@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
|||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
@ -33,7 +34,7 @@ public class MyBatisUtils {
|
|||
// 排序字段
|
||||
if (!CollectionUtil.isEmpty(sortingFields)) {
|
||||
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
|
||||
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
|
||||
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return page;
|
||||
|
@ -78,7 +79,10 @@ public class MyBatisUtils {
|
|||
* @return Column 对象
|
||||
*/
|
||||
public static Column buildColumn(String tableName, Alias tableAlias, String column) {
|
||||
return new Column(tableAlias != null ? tableAlias.getName() + "." + column : column);
|
||||
if (tableAlias != null) {
|
||||
tableName = tableAlias.getName();
|
||||
}
|
||||
return new Column(tableName + StringPool.DOT + column);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant
|
|||
import com.baomidou.lock.LockFailureStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 自定义获取锁失败策略,抛出 {@link ServiceException} 异常
|
||||
*/
|
||||
|
@ -12,9 +14,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||
public class DefaultLockFailureStrategy implements LockFailureStrategy {
|
||||
|
||||
@Override
|
||||
public void onLockFailure(String key, long acquireTimeout, int acquireCount) {
|
||||
log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取超时时长:{} ms]", Thread.currentThread().getName(), key, acquireTimeout);
|
||||
public void onLockFailure(String key, Method method, Object[] arguments) {
|
||||
log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取失败:{} ]", Thread.currentThread().getName(), key, arguments);
|
||||
throw new ServiceException(GlobalErrorCodeConstants.LOCKED);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,10 @@
|
|||
<artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
@ -13,6 +14,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
|||
* Cache 配置类,基于 Redis 实现
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({CacheProperties.class})
|
||||
@EnableCaching
|
||||
public class YudaoCacheAutoConfiguration {
|
||||
|
||||
|
|
|
@ -127,7 +127,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
|||
.antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
|
||||
// 基于 yudao.security.permit-all-urls 无需认证
|
||||
.antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
|
||||
.antMatchers("/captcha/get", "/captcha/check").permitAll()
|
||||
// 设置 App API 无需认证
|
||||
.antMatchers(buildAppApi("/**")).permitAll()
|
||||
// ②:每个项目的自定义规则
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.test.core.util;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
@ -8,6 +9,7 @@ import uk.co.jemos.podam.api.PodamFactory;
|
|||
import uk.co.jemos.podam.api.PodamFactoryImpl;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -76,6 +78,10 @@ public class RandomUtils {
|
|||
return RandomUtil.randomDay(0, RANDOM_DATE_MAX);
|
||||
}
|
||||
|
||||
public static LocalDateTime randomLocalDateTime() {
|
||||
return LocalDateTimeUtil.of(randomDate());
|
||||
}
|
||||
|
||||
public static Short randomShort() {
|
||||
return (short) RandomUtil.randomInt(0, Short.MAX_VALUE);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.filter;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
|
@ -7,7 +8,6 @@ import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLog;
|
|||
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
|
@ -20,7 +20,8 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -47,7 +48,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
|
|||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
// 获得开始时间
|
||||
Date beginTim = new Date();
|
||||
LocalDateTime beginTime = LocalDateTime.now();
|
||||
// 提前获得参数,避免 XssFilter 过滤处理
|
||||
Map<String, String> queryString = ServletUtil.getParamMap(request);
|
||||
String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtil.getBody(request) : null;
|
||||
|
@ -56,15 +57,15 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
|
|||
// 继续过滤器
|
||||
filterChain.doFilter(request, response);
|
||||
// 正常执行,记录日志
|
||||
createApiAccessLog(request, beginTim, queryString, requestBody, null);
|
||||
createApiAccessLog(request, beginTime, queryString, requestBody, null);
|
||||
} catch (Exception ex) {
|
||||
// 异常执行,记录日志
|
||||
createApiAccessLog(request, beginTim, queryString, requestBody, ex);
|
||||
createApiAccessLog(request, beginTime, queryString, requestBody, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private void createApiAccessLog(HttpServletRequest request, Date beginTime,
|
||||
private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime,
|
||||
Map<String, String> queryString, String requestBody, Exception ex) {
|
||||
ApiAccessLog accessLog = new ApiAccessLog();
|
||||
try {
|
||||
|
@ -75,7 +76,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
|
|||
}
|
||||
}
|
||||
|
||||
private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, Date beginTime,
|
||||
private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, LocalDateTime beginTime,
|
||||
Map<String, String> queryString, String requestBody, Exception ex) {
|
||||
// 处理用户信息
|
||||
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
|
||||
|
@ -103,8 +104,8 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
|
|||
accessLog.setUserIp(ServletUtil.getClientIP(request));
|
||||
// 持续时间
|
||||
accessLog.setBeginTime(beginTime);
|
||||
accessLog.setEndTime(new Date());
|
||||
accessLog.setDuration((int) DateUtils.diff(accessLog.getEndTime(), accessLog.getBeginTime()));
|
||||
accessLog.setEndTime(LocalDateTime.now());
|
||||
accessLog.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.apilog.core.service;
|
|||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 访问日志
|
||||
|
@ -61,12 +61,12 @@ public class ApiAccessLog {
|
|||
* 开始请求时间
|
||||
*/
|
||||
@NotNull(message = "开始请求时间不能为空")
|
||||
private Date beginTime;
|
||||
private LocalDateTime beginTime;
|
||||
/**
|
||||
* 结束请求时间
|
||||
*/
|
||||
@NotNull(message = "结束请求时间不能为空")
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 执行时长,单位:毫秒
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.apilog.core.service;
|
|||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 错误日志
|
||||
|
@ -61,7 +61,7 @@ public class ApiErrorLog {
|
|||
* 异常时间
|
||||
*/
|
||||
@NotNull(message = "异常时间不能为空")
|
||||
private Date exceptionTime;
|
||||
private LocalDateTime exceptionTime;
|
||||
/**
|
||||
* 异常名
|
||||
*/
|
||||
|
|
|
@ -30,7 +30,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
|
||||
|
@ -267,7 +268,7 @@ public class GlobalExceptionHandler {
|
|||
errorLog.setRequestMethod(request.getMethod());
|
||||
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
|
||||
errorLog.setUserIp(ServletUtil.getClientIP(request));
|
||||
errorLog.setExceptionTime(new Date());
|
||||
errorLog.setExceptionTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.web.core.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
|
@ -124,4 +125,14 @@ public class WebFrameworkUtils {
|
|||
return servletRequestAttributes.getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 RPC 请求
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 是否为 RPC 请求
|
||||
*/
|
||||
public static boolean isRpcRequest(HttpServletRequest request) {
|
||||
return request.getRequestURI().startsWith(RpcConstants.RPC_API_PREFIX);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,20 @@
|
|||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 远程调用相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
|
@ -63,19 +77,6 @@
|
|||
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -87,7 +88,7 @@
|
|||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.6.7</version> <!-- 如果 spring.boot.version 版本修改,则这里也要跟着修改 -->
|
||||
<version>2.6.13</version> <!-- 如果 spring.boot.version 版本修改,则这里也要跟着修改 -->
|
||||
<configuration>
|
||||
<fork>true</fork>
|
||||
</configuration>
|
||||
|
|
|
@ -2,58 +2,10 @@ package cn.iocoder.yudao.gateway;
|
|||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.cors.reactive.CorsUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@SpringBootApplication
|
||||
public class GatewayServerApplication {
|
||||
|
||||
// ----------------------------- 解决跨域 Begin -----------------------------
|
||||
|
||||
private static final String ALL = "*";
|
||||
private static final String MAX_AGE = "3600L";
|
||||
|
||||
|
||||
@Bean
|
||||
public WebFilter corsFilter() {
|
||||
return (ServerWebExchange ctx, WebFilterChain chain) -> {
|
||||
ServerHttpRequest request = ctx.getRequest();
|
||||
if (!CorsUtils.isCorsRequest(request)) {
|
||||
return chain.filter(ctx);
|
||||
}
|
||||
HttpHeaders requestHeaders = request.getHeaders();
|
||||
ServerHttpResponse response = ctx.getResponse();
|
||||
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
|
||||
HttpHeaders headers = response.getHeaders();
|
||||
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
|
||||
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
|
||||
if (requestMethod != null) {
|
||||
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
|
||||
}
|
||||
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
|
||||
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
|
||||
if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
return Mono.empty();
|
||||
}
|
||||
return chain.filter(ctx);
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------- 解决跨域 End -----------------------------
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 启动 Spring Boot 应用
|
||||
SpringApplication.run(GatewayServerApplication.class, args);
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package cn.iocoder.yudao.gateway.filter.cors;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.cors.reactive.CorsUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 跨域 Filter
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class CorsFilter implements WebFilter {
|
||||
|
||||
private static final String ALL = "*";
|
||||
private static final String MAX_AGE = "3600L";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
// 非跨域请求,直接放行
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
if (!CorsUtils.isCorsRequest(request)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 设置跨域响应头
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
HttpHeaders headers = response.getHeaders();
|
||||
headers.add("Access-Control-Allow-Origin", ALL);
|
||||
headers.add("Access-Control-Allow-Methods", ALL);
|
||||
headers.add("Access-Control-Allow-Headers", ALL);
|
||||
headers.add("Access-Control-Max-Age", MAX_AGE);
|
||||
if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
return Mono.empty();
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,8 +5,7 @@ import org.springframework.cloud.gateway.route.Route;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 网关的访问日志
|
||||
|
@ -80,11 +79,11 @@ public class AccessLog {
|
|||
/**
|
||||
* 开始请求时间
|
||||
*/
|
||||
private Date startTime;
|
||||
private LocalDateTime startTime;
|
||||
/**
|
||||
* 结束请求时间
|
||||
*/
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 执行时长,单位:毫秒
|
||||
*/
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.gateway.filter.logging;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
|
@ -38,12 +39,13 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMAT;
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
|
||||
|
||||
/**
|
||||
* 网关的访问日志过滤器
|
||||
|
@ -89,8 +91,8 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
|
|||
values.put("responseHeaders", gatewayLog.getResponseHeaders() != null ?
|
||||
JsonUtils.toJsonString(gatewayLog.getResponseHeaders().toSingleValueMap()) : null);
|
||||
values.put("httpStatus", gatewayLog.getHttpStatus());
|
||||
values.put("startTime", DateUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMAT));
|
||||
values.put("endTime", DateUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMAT));
|
||||
values.put("startTime", LocalDateTimeUtil.format(gatewayLog.getStartTime(), NORM_DATETIME_MS_FORMATTER));
|
||||
values.put("endTime", LocalDateTimeUtil.format(gatewayLog.getEndTime(), NORM_DATETIME_MS_FORMATTER));
|
||||
values.put("duration", gatewayLog.getDuration() != null ? gatewayLog.getDuration() + " ms" : null);
|
||||
log.info("[writeAccessLog][网关日志:{}]", JsonUtils.toJsonPrettyString(values));
|
||||
}
|
||||
|
@ -112,7 +114,7 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
|
|||
gatewayLog.setRequestUrl(request.getURI().getRawPath());
|
||||
gatewayLog.setQueryParams(request.getQueryParams());
|
||||
gatewayLog.setRequestHeaders(request.getHeaders());
|
||||
gatewayLog.setStartTime(new Date());
|
||||
gatewayLog.setStartTime(LocalDateTime.now());
|
||||
gatewayLog.setUserIp(WebFrameworkUtils.getClientIP(exchange));
|
||||
|
||||
// 继续 filter 过滤
|
||||
|
@ -179,8 +181,9 @@ public class AccessLogFilter implements GlobalFilter, Ordered {
|
|||
if (body instanceof Flux) {
|
||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
||||
// 计算执行时间
|
||||
gatewayLog.setEndTime(new Date());
|
||||
gatewayLog.setDuration((int) DateUtils.diff(gatewayLog.getEndTime(), gatewayLog.getStartTime()));
|
||||
gatewayLog.setEndTime(LocalDateTime.now());
|
||||
gatewayLog.setDuration((int) (LocalDateTimeUtil.between(gatewayLog.getStartTime(),
|
||||
gatewayLog.getEndTime()).toMillis()));
|
||||
// 设置其它字段
|
||||
gatewayLog.setUserId(SecurityFrameworkUtils.getLoginUserId(exchange));
|
||||
gatewayLog.setUserType(SecurityFrameworkUtils.getLoginUserType(exchange));
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package cn.iocoder.yudao.gateway.swagger;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
@ -9,55 +8,47 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
import springfox.documentation.swagger.web.SecurityConfiguration;
|
||||
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
import springfox.documentation.swagger.web.UiConfiguration;
|
||||
import springfox.documentation.swagger.web.UiConfigurationBuilder;
|
||||
import springfox.documentation.swagger.web.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Swagger Controller
|
||||
*
|
||||
* @author zxliu
|
||||
* @create 2022-10-25 11:24
|
||||
* @date 2022-10-25 11:24
|
||||
*/
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/swagger-resources")
|
||||
public class SwaggerHandler {
|
||||
|
||||
|
||||
@Resource
|
||||
private SwaggerResourcesProvider swaggerResources;
|
||||
|
||||
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // 只有 @Autowired 可以实现可选注入
|
||||
@Autowired(required = false)
|
||||
private SecurityConfiguration securityConfiguration;
|
||||
|
||||
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") // 只有 @Autowired 可以实现可选注入
|
||||
@Autowired(required = false)
|
||||
private UiConfiguration uiConfiguration;
|
||||
|
||||
private final SwaggerResourcesProvider swaggerResources;
|
||||
|
||||
|
||||
@Autowired
|
||||
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
|
||||
this.swaggerResources = swaggerResources;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/configuration/security")
|
||||
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
|
||||
HttpStatus.OK));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/configuration/ui")
|
||||
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@GetMapping("")
|
||||
public Mono<ResponseEntity> swaggerResources() {
|
||||
public Mono<ResponseEntity<List<SwaggerResource>>> swaggerResources() {
|
||||
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
|
||||
}
|
||||
|
||||
@GetMapping("/configuration/security")
|
||||
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(Optional.ofNullable(securityConfiguration)
|
||||
.orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("/configuration/ui")
|
||||
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(Optional.ofNullable(uiConfiguration)
|
||||
.orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,48 +1,70 @@
|
|||
package cn.iocoder.yudao.gateway.swagger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.support.NameUtils;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
|
||||
/**
|
||||
* @author zxliu
|
||||
* @create 2022-10-25 11:23
|
||||
*/
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Swagger 资源的 Provider 实现类
|
||||
*
|
||||
* @author zxliu
|
||||
* @date 2022-10-25 11:23
|
||||
*/
|
||||
@Component
|
||||
@Primary
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class SwaggerProvider implements SwaggerResourcesProvider {
|
||||
|
||||
private final RouteLocator routeLocator;
|
||||
private final GatewayProperties gatewayProperties;
|
||||
|
||||
@Resource
|
||||
private GatewayProperties gatewayProperties;
|
||||
|
||||
/**
|
||||
* 获得 SwaggerResource 列表
|
||||
*
|
||||
* @return SwaggerResource 列表
|
||||
*/
|
||||
@Override
|
||||
public List<SwaggerResource> get() {
|
||||
// 将 RouteDefinition 转换成 SwaggerResource
|
||||
List<SwaggerResource> resources = new ArrayList<>();
|
||||
List<String> routes = new ArrayList<>();
|
||||
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
|
||||
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
|
||||
.forEach(route -> route.getPredicates().stream()
|
||||
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
|
||||
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
|
||||
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
|
||||
.replace("**", "v2/api-docs"))))
|
||||
);
|
||||
Set<String> serviceNames = new HashSet<>(); // 已处理的服务名,避免重复
|
||||
gatewayProperties.getRoutes().forEach(route -> {
|
||||
// 已存在的服务,直接忽略
|
||||
String serviceName = route.getUri().getHost();
|
||||
if (StrUtil.isEmpty(serviceName)) {
|
||||
return;
|
||||
}
|
||||
if (!serviceNames.add(serviceName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获得 Path PredicateDefinition
|
||||
String path = getRoutePath(route);
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重要:构建最终的 SwaggerResource 对象
|
||||
resources.add(buildSwaggerResource(serviceName, path));
|
||||
});
|
||||
return resources;
|
||||
}
|
||||
|
||||
|
||||
private SwaggerResource swaggerResource(String name, String location) {
|
||||
private SwaggerResource buildSwaggerResource(String name, String location) {
|
||||
SwaggerResource swaggerResource = new SwaggerResource();
|
||||
swaggerResource.setName(name);
|
||||
swaggerResource.setLocation(location);
|
||||
|
@ -50,4 +72,31 @@ public class SwaggerProvider implements SwaggerResourcesProvider {
|
|||
return swaggerResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得路由的 Path
|
||||
*
|
||||
* ① 输入:
|
||||
* predicates:
|
||||
* - Path=/admin-api/system/**
|
||||
* ② 输出:
|
||||
* /admin-api/system/v2/api-docs
|
||||
*
|
||||
* @param route 路由
|
||||
* @return 路由
|
||||
*/
|
||||
private String getRoutePath(RouteDefinition route) {
|
||||
PredicateDefinition pathDefinition = CollUtil.findOne(route.getPredicates(),
|
||||
predicateDefinition -> predicateDefinition.getName().equals("Path"));
|
||||
if (pathDefinition == null) {
|
||||
log.info("[get][Route({}) 没有 Path 条件,忽略接口文档]", route.getId());
|
||||
return null;
|
||||
}
|
||||
String path = pathDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0");
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
log.info("[get][Route({}) Path 的值为空,忽略接口文档]", route.getId());
|
||||
return null;
|
||||
}
|
||||
return path.replace("/**", "/v2/api-docs");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,20 +5,14 @@ spring:
|
|||
cloud:
|
||||
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
|
||||
gateway:
|
||||
discovery:
|
||||
locator:
|
||||
# gateway开启服务注册和发现的功能,
|
||||
enabled: true
|
||||
# 将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了)
|
||||
lowerCaseServiceId: true
|
||||
# 路由配置项,对应 RouteDefinition 数组
|
||||
routes:
|
||||
- id: system-admin-api # 路由的编号
|
||||
uri: grayLb://system-server
|
||||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
||||
- Path=/admin-api/system/**,/captcha/**
|
||||
- Path=/admin-api/system/**
|
||||
filters:
|
||||
- RewritePath=/admin-api/system/v2/api-docs, /v2/api-docs
|
||||
- RewritePath=/admin-api/system/v2/api-docs, /v2/api-docs # 配置,保证转发到 /v2/api-docs
|
||||
- id: system-app-api # 路由的编号
|
||||
uri: grayLb://system-server
|
||||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
||||
|
@ -30,7 +24,7 @@ spring:
|
|||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
||||
- Path=/admin-api/infra/**
|
||||
filters:
|
||||
- RewritePath=/admin-api/infram/v2/api-docs, /v2/api-docs
|
||||
- RewritePath=/admin-api/infra/v2/api-docs, /v2/api-docs
|
||||
- id: infra-app-api # 路由的编号
|
||||
uri: grayLb://infra-server
|
||||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
||||
|
|
|
@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel("管理后台 - 动态表单 Response VO")
|
||||
|
@ -28,6 +28,6 @@ public class BpmFormRespVO extends BpmFormBaseVO {
|
|||
private List<String> fields;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 用户组分页 Request VO")
|
||||
@Data
|
||||
|
@ -25,6 +25,6 @@ public class BpmUserGroupPageReqVO extends PageParam {
|
|||
|
||||
@DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
private Date[] createTime;
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import io.swagger.annotations.*;
|
||||
|
||||
|
@ -14,6 +16,6 @@ public class BpmUserGroupRespVO extends BpmUserGroupBaseVO {
|
|||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 流程模型的分页的每一项 Response VO")
|
||||
@Data
|
||||
|
@ -21,7 +21,7 @@ public class BpmModelPageItemRespVO extends BpmModelBaseVO {
|
|||
private String formName;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 最新部署的流程定义
|
||||
|
@ -39,7 +39,7 @@ public class BpmModelPageItemRespVO extends BpmModelBaseVO {
|
|||
private Integer version;
|
||||
|
||||
@ApiModelProperty(value = "部署时间", required = true)
|
||||
private Date deploymentTime;
|
||||
private LocalDateTime deploymentTime;
|
||||
|
||||
@ApiModelProperty(value = "中断状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
|
||||
private Integer suspensionState;
|
||||
|
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 流程模型的创建 Request VO")
|
||||
@Data
|
||||
|
@ -21,6 +21,6 @@ public class BpmModelRespVO extends BpmModelBaseVO {
|
|||
private String bpmnXml;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 流程定义的分页的每一项 Response VO")
|
||||
@Data
|
||||
|
@ -18,6 +18,6 @@ public class BpmProcessDefinitionPageItemRespVO extends BpmProcessDefinitionResp
|
|||
private String formName;
|
||||
|
||||
@ApiModelProperty(value = "部署时间", required = true)
|
||||
private Date deploymentTime;
|
||||
private LocalDateTime deploymentTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.annotations.*;
|
||||
import javax.validation.constraints.*;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
/**
|
||||
|
@ -18,11 +19,11 @@ public class BpmOALeaveBaseVO {
|
|||
@ApiModelProperty(value = "请假的开始时间", required = true)
|
||||
@NotNull(message = "开始时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date startTime;
|
||||
private LocalDateTime startTime;
|
||||
@ApiModelProperty(value = "请假的结束时间", required = true)
|
||||
@NotNull(message = "结束时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@ApiModelProperty(value = "请假类型", required = true, example = "1", notes = "参见 bpm_oa_type 枚举")
|
||||
private Integer type;
|
||||
|
|
|
@ -15,7 +15,8 @@ public class BpmOALeaveCreateReqVO extends BpmOALeaveBaseVO {
|
|||
|
||||
@AssertTrue(message = "结束时间,需要在开始时间之后")
|
||||
public boolean isEndTimeValid() {
|
||||
return !getEndTime().before(getStartTime());
|
||||
return !getEndTime().isBefore(getStartTime());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.swagger.annotations.*;
|
|||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
|
@ -24,7 +24,7 @@ public class BpmOALeaveRespVO extends BpmOALeaveBaseVO {
|
|||
@ApiModelProperty(value = "申请时间", required = true)
|
||||
@NotNull(message = "申请时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@ApiModelProperty(value = "流程id")
|
||||
private String processInstanceId;
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
|
|||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 流程活动的 Response VO")
|
||||
@Data
|
||||
|
@ -16,9 +16,9 @@ public class BpmActivityRespVO {
|
|||
private String type;
|
||||
|
||||
@ApiModelProperty(value = "流程活动的开始时间", required = true)
|
||||
private Date startTime;
|
||||
private LocalDateTime startTime;
|
||||
@ApiModelProperty(value = "流程活动的结束时间", required = true)
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@ApiModelProperty(value = "关联的流程任务的编号", example = "2048", notes = "关联的流程任务,只有 UserTask 等类型才有")
|
||||
private String taskId;
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
|
@ -35,6 +35,6 @@ public class BpmProcessInstanceMyPageReqVO extends PageParam {
|
|||
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date[] createTime;
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
|
|||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel("管理后台 - 流程实例的分页 Item Response VO")
|
||||
|
@ -30,10 +30,10 @@ public class BpmProcessInstancePageItemRespVO {
|
|||
private Integer result;
|
||||
|
||||
@ApiModelProperty(value = "提交时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@ApiModelProperty(value = "结束时间", required = true)
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 当前任务
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
|
|||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -28,10 +28,10 @@ public class BpmProcessInstanceRespVO {
|
|||
private Integer result;
|
||||
|
||||
@ApiModelProperty(value = "提交时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@ApiModelProperty(value = "结束时间", required = true)
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@ApiModelProperty(value = "提交的表单值", required = true)
|
||||
private Map<String, Object> formVariables;
|
||||
|
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 流程任务的 Done 已完成的分页项 Response VO")
|
||||
@Data
|
||||
|
@ -15,7 +15,7 @@ import java.util.Date;
|
|||
public class BpmTaskDonePageItemRespVO extends BpmTaskTodoPageItemRespVO {
|
||||
|
||||
@ApiModelProperty(value = "结束时间", required = true)
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
@ApiModelProperty(value = "持续时间", required = true, example = "1000")
|
||||
private Long durationInMillis;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
|
@ -23,10 +23,10 @@ public class BpmTaskDonePageReqVO extends PageParam {
|
|||
|
||||
@ApiModelProperty(value = "开始的创建收间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date beginCreateTime;
|
||||
private LocalDateTime beginCreateTime;
|
||||
|
||||
@ApiModelProperty(value = "结束的创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date endCreateTime;
|
||||
private LocalDateTime endCreateTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.swagger.annotations.ApiModel;
|
|||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 流程任务的 Running 进行中的分页项 Response VO")
|
||||
@Data
|
||||
|
@ -17,10 +17,10 @@ public class BpmTaskTodoPageItemRespVO {
|
|||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "接收时间", required = true)
|
||||
private Date claimTime;
|
||||
private LocalDateTime claimTime;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@ApiModelProperty(value = "激活状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
|
||||
private Integer suspensionState;
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
|
@ -23,10 +23,10 @@ public class BpmTaskTodoPageReqVO extends PageParam {
|
|||
|
||||
@ApiModelProperty(value = "开始的创建收间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date beginCreateTime;
|
||||
private LocalDateTime beginCreateTime;
|
||||
|
||||
@ApiModelProperty(value = "结束的创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date endCreateTime;
|
||||
private LocalDateTime endCreateTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package cn.iocoder.yudao.module.bpm.convert.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSimpleRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.definition;
|
|||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
|
@ -44,7 +45,7 @@ public interface BpmModelConvert {
|
|||
default BpmModelPageItemRespVO convert(Model model, BpmFormDO form, Deployment deployment, ProcessDefinition processDefinition) {
|
||||
BpmModelPageItemRespVO modelRespVO = new BpmModelPageItemRespVO();
|
||||
modelRespVO.setId(model.getId());
|
||||
modelRespVO.setCreateTime(model.getCreateTime());
|
||||
modelRespVO.setCreateTime(DateUtils.of(model.getCreateTime()));
|
||||
// 通用 copy
|
||||
copyTo(model, modelRespVO);
|
||||
// Form
|
||||
|
@ -57,7 +58,7 @@ public interface BpmModelConvert {
|
|||
if (modelRespVO.getProcessDefinition() != null) {
|
||||
modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
|
||||
SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
|
||||
modelRespVO.getProcessDefinition().setDeploymentTime(deployment.getDeploymentTime());
|
||||
modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
|
||||
}
|
||||
return modelRespVO;
|
||||
}
|
||||
|
@ -65,7 +66,7 @@ public interface BpmModelConvert {
|
|||
default BpmModelRespVO convert(Model model) {
|
||||
BpmModelRespVO modelRespVO = new BpmModelRespVO();
|
||||
modelRespVO.setId(model.getId());
|
||||
modelRespVO.setCreateTime(model.getCreateTime());
|
||||
modelRespVO.setCreateTime(DateUtils.of(model.getCreateTime()));
|
||||
// 通用 copy
|
||||
copyTo(model, modelRespVO);
|
||||
return modelRespVO;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.bpm.convert.definition;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
|
||||
|
@ -67,7 +68,7 @@ public interface BpmProcessDefinitionConvert {
|
|||
BpmProcessDefinitionPageItemRespVO respVO = convert(bean);
|
||||
respVO.setSuspensionState(bean.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
|
||||
if (deployment != null) {
|
||||
respVO.setDeploymentTime(deployment.getDeploymentTime());
|
||||
respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime()));
|
||||
}
|
||||
if (form != null) {
|
||||
respVO.setFormName(form.getName());
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
package cn.iocoder.yudao.module.bpm.convert.definition;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户组 Convert
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.convert.message;
|
|||
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.Map;
|
||||
|
@ -11,6 +12,10 @@ public interface BpmMessageConvert {
|
|||
|
||||
BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class);
|
||||
|
||||
@Mapping(target = "mobile", ignore = true)
|
||||
@Mapping(source = "userId", target = "userId")
|
||||
@Mapping(source = "templateCode", target = "templateCode")
|
||||
@Mapping(source = "templateParams", target = "templateParams")
|
||||
SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package cn.iocoder.yudao.module.bpm.convert.oa;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.bpm.convert.task;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
|
||||
|
@ -151,7 +152,7 @@ public interface BpmTaskConvert {
|
|||
BpmTaskExtDO taskExtDO = new BpmTaskExtDO().setTaskId(task.getId())
|
||||
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setName(task.getName())
|
||||
.setProcessDefinitionId(task.getProcessDefinitionId()).setProcessInstanceId(task.getProcessInstanceId());
|
||||
taskExtDO.setCreateTime(task.getCreateTime());
|
||||
taskExtDO.setCreateTime(LocalDateTimeUtil.of(task.getCreateTime()));
|
||||
return taskExtDO;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
|
|||
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
@ -46,11 +48,11 @@ public class BpmOALeaveDO extends BaseDO {
|
|||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private Date startTime;
|
||||
private LocalDateTime startTime;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
|||
import lombok.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
@ -80,14 +80,14 @@ public class BpmActivityDO {
|
|||
*/
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private Date startTime;
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 任务结束时间
|
||||
*/
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
private Integer transactionOrder;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -79,7 +79,7 @@ public class BpmProcessInstanceExtDO extends BaseDO {
|
|||
*
|
||||
* 冗余 HistoricProcessInstance 的 endTime 属性
|
||||
*/
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 提交的表单值
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Bpm 流程任务的拓展表
|
||||
|
@ -67,7 +67,7 @@ public class BpmTaskExtDO extends BaseDO {
|
|||
*
|
||||
* 冗余 HistoricTaskInstance 的 endTime 属性
|
||||
*/
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 流程实例的编号
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.module.bpm.service.oa;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
|
||||
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
||||
|
@ -46,7 +47,7 @@ public class BpmOALeaveServiceImpl implements BpmOALeaveService {
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createLeave(Long userId, BpmOALeaveCreateReqVO createReqVO) {
|
||||
// 插入 OA 请假单
|
||||
long day = DateUtil.betweenDay(createReqVO.getStartTime(), createReqVO.getEndTime(), false);
|
||||
long day = LocalDateTimeUtil.between(createReqVO.getStartTime(), createReqVO.getEndTime()).toDays();
|
||||
BpmOALeaveDO leave = BpmOALeaveConvert.INSTANCE.convert(createReqVO).setUserId(userId).setDay(day)
|
||||
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
|
||||
leaveMapper.insert(leave);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.service.task;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
|
||||
|
@ -32,6 +33,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
@ -74,10 +76,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
|
||||
}
|
||||
if (pageVO.getBeginCreateTime() != null) {
|
||||
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
|
||||
taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getBeginCreateTime()));
|
||||
}
|
||||
if (pageVO.getEndCreateTime() != null) {
|
||||
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
|
||||
taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getEndCreateTime()));
|
||||
}
|
||||
// 执行查询
|
||||
List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||
|
@ -106,10 +108,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
|
||||
}
|
||||
if (pageVO.getBeginCreateTime() != null) {
|
||||
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
|
||||
taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getBeginCreateTime()));
|
||||
}
|
||||
if (pageVO.getEndCreateTime() != null) {
|
||||
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
|
||||
taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getEndCreateTime()));
|
||||
}
|
||||
// 执行查询
|
||||
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||
|
@ -205,7 +207,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
// 更新任务拓展表为不通过
|
||||
taskExtMapper.updateByTaskId(
|
||||
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
|
||||
.setEndTime(new Date()).setReason(reqVO.getReason()));
|
||||
.setEndTime(LocalDateTime.now()).setReason(reqVO.getReason()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -249,7 +251,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
public void updateTaskExtComplete(Task task) {
|
||||
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task)
|
||||
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) // 不设置也问题不大,因为 Complete 一般是审核通过,已经设置
|
||||
.setEndTime(new Date());
|
||||
.setEndTime(LocalDateTime.now());
|
||||
taskExtMapper.updateByTaskId(taskExtDO);
|
||||
}
|
||||
|
||||
|
@ -280,7 +282,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
|
||||
// 更新任务
|
||||
taskExtMapper.updateById(new BpmTaskExtDO().setId(taskExt.getId()).setResult(BpmProcessInstanceResultEnum.CANCEL.getResult())
|
||||
.setEndTime(new Date()).setReason(BpmProcessInstanceDeleteReasonEnum.translateReason(task.getDeleteReason())));
|
||||
.setEndTime(LocalDateTime.now()).setReason(BpmProcessInstanceDeleteReasonEnum.translateReason(task.getDeleteReason())));
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ spring:
|
|||
# Jackson 配置项
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
|
||||
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
|
||||
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
|
||||
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
|
||||
fail-on-empty-beans: false # 允许序列化无属性的 Bean
|
||||
|
|
|
@ -41,6 +41,8 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
|
|||
// mock 方法(startUser)
|
||||
AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L));
|
||||
when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser));
|
||||
// mock 方法(getStartUserDept)没有部门
|
||||
when(deptApi.getDept(eq(10L))).thenReturn(success(null));
|
||||
|
||||
// 调用
|
||||
Set<Long> result = script.calculateTaskCandidateUsers(execution);
|
||||
|
@ -57,7 +59,9 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
|
|||
when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser));
|
||||
DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L)
|
||||
.setLeaderUserId(20L));
|
||||
// mock 方法(getDept)
|
||||
when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept));
|
||||
when(deptApi.getDept(eq(100L))).thenReturn(success(null));
|
||||
|
||||
// 调用
|
||||
Set<Long> result = script.calculateTaskCandidateUsers(execution);
|
||||
|
|
|
@ -17,8 +17,9 @@ import org.springframework.context.annotation.Import;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildLocalDateTime;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
|
||||
|
@ -106,7 +107,7 @@ public class BpmUserGroupServiceTest extends BaseDbUnitTest {
|
|||
BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class, o -> { // 等会查询到
|
||||
o.setName("芋道源码");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(DateUtils.buildTime(2021, 11, 11));
|
||||
o.setCreateTime(buildLocalDateTime(2021, 11, 11));
|
||||
});
|
||||
userGroupMapper.insert(dbUserGroup);
|
||||
// 测试 name 不匹配
|
||||
|
@ -114,12 +115,13 @@ public class BpmUserGroupServiceTest extends BaseDbUnitTest {
|
|||
// 测试 status 不匹配
|
||||
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(DateUtils.buildTime(2021, 12, 12))));
|
||||
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(buildLocalDateTime(2021, 12, 12))));
|
||||
// 准备参数
|
||||
BpmUserGroupPageReqVO reqVO = new BpmUserGroupPageReqVO();
|
||||
reqVO.setName("源码");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime((new Date[]{buildTime(2021, 11, 10),buildTime(2021, 11, 12)}));
|
||||
reqVO.setCreateTime((new LocalDateTime[]{buildLocalDateTime(2021, 11, 10),
|
||||
buildLocalDateTime(2021, 11, 12)}));
|
||||
|
||||
// 调用
|
||||
PageResult<BpmUserGroupDO> pageResult = userGroupService.getUserGroupPage(reqVO);
|
||||
|
|
|
@ -23,6 +23,9 @@ spring:
|
|||
mybatis-plus:
|
||||
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
|
||||
type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: AUTO # H2 主键递增
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
|
|||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("RPC 服务 - API 访问日志创建 Request DTO")
|
||||
@Data
|
||||
|
@ -40,10 +40,10 @@ public class ApiAccessLogCreateReqDTO {
|
|||
|
||||
@ApiModelProperty(value = "开始时间", required = true)
|
||||
@NotNull(message = "开始请求时间不能为空")
|
||||
private Date beginTime;
|
||||
private LocalDateTime beginTime;
|
||||
@ApiModelProperty(value = "结束时间", required = true)
|
||||
@NotNull(message = "结束请求时间不能为空")
|
||||
private Date endTime;
|
||||
private LocalDateTime endTime;
|
||||
@ApiModelProperty(value = "执行时长,单位:毫秒", required = true)
|
||||
@NotNull(message = "执行时长不能为空")
|
||||
private Integer duration;
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
|
|||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("RPC 服务 - API 错误日志创建 Request DTO")
|
||||
@Data
|
||||
|
@ -40,7 +40,7 @@ public class ApiErrorLogCreateReqDTO {
|
|||
|
||||
@ApiModelProperty(value = "异常时间", required = true)
|
||||
@NotNull(message = "异常时间不能为空")
|
||||
private Date exceptionTime;
|
||||
private LocalDateTime exceptionTime;
|
||||
@ApiModelProperty(value = "异常名", required = true)
|
||||
@NotNull(message = "异常名不能为空")
|
||||
private String exceptionName;
|
||||
|
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 代码生成字段定义 Response VO")
|
||||
@Data
|
||||
|
@ -18,6 +18,6 @@ public class CodegenColumnRespVO extends CodegenColumnBaseVO {
|
|||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
|
@ -26,6 +26,6 @@ public class CodegenTablePageReqVO extends PageParam {
|
|||
|
||||
@ApiModelProperty(value = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date[] createTime;
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ApiModel("管理后台 - 代码生成表定义 Response VO")
|
||||
@Data
|
||||
|
@ -21,9 +21,9 @@ public class CodegenTableRespVO extends CodegenTableBaseVO {
|
|||
private Integer dataSourceConfigId;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@ApiModelProperty(value = "更新时间", required = true)
|
||||
private Date updateTime;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cn.iocoder.yudao.module.infra.controller.admin.db.vo;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import io.swagger.annotations.*;
|
||||
|
||||
|
@ -14,6 +16,6 @@ public class DataSourceConfigRespVO extends DataSourceConfigBaseVO {
|
|||
private Integer id;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
|
@ -26,6 +26,6 @@ public class FileConfigPageReqVO extends PageParam {
|
|||
|
||||
@ApiModelProperty(value = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private Date[] createTime;
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue