diff --git a/pom.xml b/pom.xml
index d892b1a13..b26332668 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,11 +16,11 @@
yudao-module-member
yudao-module-system
yudao-module-infra
-
+ yudao-module-pay
-
+ yudao-module-mall
yudao-example
diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql
index 9d0cc1088..07c34cba9 100644
--- a/sql/mysql/ruoyi-vue-pro.sql
+++ b/sql/mysql/ruoyi-vue-pro.sql
@@ -1637,18 +1637,18 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2209, '秒杀活动', '', 2, 3, 2030, 'seckill', 'ep:place', '', '', 0, b'1', b'1', b'1', '1', '2023-06-24 17:39:13', '1', '2023-06-24 18:55:15', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2262, '会员中心', '', 1, 55, 0, '/member', 'date-range', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-06-10 00:42:03', '1', '2023-06-28 22:52:34', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2275, '积分配置', '', 2, 0, 2299, 'config', '', 'member/point/config/index', 'PointConfig', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 22:50:59', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '积分设置查询', 'point:config:query', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '积分设置创建', 'point:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '积分设置查询', 'member:point:config:get', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '积分设置创建', 'member:point:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2281, '签到配置', '', 2, 2, 2300, 'sign-in-config', '', 'member/signin/config/index', 'SignInConfig', 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-07-02 15:04:15', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2282, '积分签到规则查询', 'point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2283, '积分签到规则创建', 'point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2284, '积分签到规则更新', 'point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2285, '积分签到规则删除', 'point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2282, '签到规则查询', 'member:point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2283, '签到规则创建', 'member:point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2284, '签到规则更新', 'member:point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2285, '签到规则删除', 'member:point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2286, '签到规则获取', 'member:point:sign-in-config:get', 3, 5, 2281, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-08-19 09:48:32', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2287, '积分记录', '', 2, 1, 2299, 'record', '', 'member/point/record/index', 'PointRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-06-27 22:51:07', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '用户积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '用户积分记录查询', 'member:point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2293, '签到记录', '', 2, 3, 2300, 'sign-in-record', '', 'member/signin/record/index', 'SignInRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '1', '2023-07-02 15:04:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2294, '用户签到积分查询', 'point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2297, '用户签到积分删除', 'point:sign-in-record:delete', 3, 4, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2294, '签到记录查询', 'member:point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2299, '会员积分', '', 1, 1, 2262, 'point', '', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:48:51', '1', '2023-06-27 22:48:51', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2300, '会员签到', '', 1, 2, 2262, 'signin', '', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:49:53', '1', '2023-06-27 22:49:53', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2301, '回调通知', '', 2, 4, 1117, 'notify', 'example', 'pay/notify/index', 'PayNotify', 0, b'1', b'1', b'1', '', '2023-07-20 04:41:32', '1', '2023-07-20 13:45:08', b'0');
diff --git a/yudao-framework/yudao-common/pom.xml b/yudao-framework/yudao-common/pom.xml
index 760160330..877da7582 100644
--- a/yudao-framework/yudao-common/pom.xml
+++ b/yudao-framework/yudao-common/pom.xml
@@ -133,6 +133,11 @@
transmittable-thread-local
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
index 8c9f37b02..15fa4e03b 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -5,12 +5,11 @@ import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.ImmutableMap;
import java.util.*;
-import java.util.function.BinaryOperator;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
+import java.util.function.*;
import java.util.stream.Collectors;
+import static java.util.Arrays.asList;
+
/**
* Collection 工具类
*
@@ -19,13 +18,17 @@ import java.util.stream.Collectors;
public class CollectionUtils {
public static boolean containsAny(Object source, Object... targets) {
- return Arrays.asList(targets).contains(source);
+ return asList(targets).contains(source);
}
public static boolean isAnyEmpty(Collection>... collections) {
return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
}
+ public static boolean anyMatch(Collection from, Predicate predicate) {
+ return from.stream().anyMatch(predicate);
+ }
+
public static List filterList(Collection from, Predicate predicate) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
@@ -149,6 +152,46 @@ public class CollectionUtils {
return builder.build();
}
+ /**
+ * 对比老、新两个列表,找出新增、修改、删除的数据
+ *
+ * @param oldList 老列表
+ * @param newList 新列表
+ * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
+ * 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
+ * @return [新增列表、修改列表、删除列表]
+ */
+ public static List> diffList(Collection oldList, Collection newList,
+ BiFunction sameFunc) {
+ List createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
+ List updateList = new ArrayList<>();
+ List deleteList = new ArrayList<>();
+
+ // 通过以 oldList 为主遍历,找出 updateList 和 deleteList
+ for (T oldObj : oldList) {
+ // 1. 寻找是否有匹配的
+ T foundObj = null;
+ for (Iterator iterator = createList.iterator(); iterator.hasNext(); ) {
+ T newObj = iterator.next();
+ // 1.1 不匹配,则直接跳过
+ if (!sameFunc.apply(oldObj, newObj)) {
+ continue;
+ }
+ // 1.2 匹配,则移除,并结束寻找
+ iterator.remove();
+ foundObj = newObj;
+ break;
+ }
+ // 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
+ if (foundObj != null) {
+ updateList.add(foundObj);
+ } else {
+ deleteList.add(oldObj);
+ }
+ }
+ return asList(createList, updateList, deleteList);
+ }
+
public static boolean containsAny(Collection> source, Collection> candidates) {
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
}
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
index c2d069a59..deedb127b 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
@@ -23,6 +23,8 @@ public class DateUtils {
*/
public static final long SECOND_MILLIS = 1000;
+ public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
+
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
index a55af427c..feca25411 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.Duration;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@@ -62,23 +63,18 @@ public class LocalDateTimeUtils {
}
/**
- * 检查时间重叠 不包含日期
+ * 判断时间段是否重叠
*
- * @param startTime1 需要校验的开始时间
- * @param endTime1 需要校验的结束时间
- * @param startTime2 校验所需的开始时间
- * @param endTime2 校验所需的结束时间
- * @return 是否重叠
+ * @param startTime1 开始 time1
+ * @param endTime1 结束 time1
+ * @param startTime2 开始 time2
+ * @param endTime2 结束 time2
+ * @return 重叠:true 不重叠:false
*/
- // TODO @puhui999:LocalDateTimeUtil.isOverlap() 是不是可以满足呀?
- public static boolean checkTimeOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
- // 判断时间是否重叠
- // 开始时间在已配置时段的结束时间之前 且 结束时间在已配置时段的开始时间之后 []
- return startTime1.isBefore(endTime2) && endTime1.isAfter(startTime2)
- // 开始时间在已配置时段的开始时间之前 且 结束时间在已配置时段的开始时间之后 (] 或 ()
- || startTime1.isBefore(startTime2) && endTime1.isAfter(startTime2)
- // 开始时间在已配置时段的结束时间之前 且 结束时间在已配值时段的结束时间之后 [) 或 ()
- || startTime1.isBefore(endTime2) && endTime1.isAfter(endTime2);
+ public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
+ LocalDate nowDate = LocalDate.now();
+ return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),
+ LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
}
}
diff --git a/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtilsTest.java b/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtilsTest.java
new file mode 100644
index 000000000..0e44645bc
--- /dev/null
+++ b/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtilsTest.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.framework.common.util.collection;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CollectionUtils} 的单元测试
+ */
+public class CollectionUtilsTest {
+
+ @Data
+ @AllArgsConstructor
+ private static class Dog {
+
+ private Integer id;
+ private String name;
+ private String code;
+
+ }
+
+ @Test
+ public void testDiffList() {
+ // 准备参数
+ Collection oldList = Arrays.asList(
+ new Dog(1, "花花", "hh"),
+ new Dog(2, "旺财", "wc")
+ );
+ Collection newList = Arrays.asList(
+ new Dog(null, "花花2", "hh"),
+ new Dog(null, "小白", "xb")
+ );
+ BiFunction sameFunc = (oldObj, newObj) -> {
+ boolean same = oldObj.getCode().equals(newObj.getCode());
+ // 如果相等的情况下,需要设置下 id,后续好更新
+ if (same) {
+ newObj.setId(oldObj.getId());
+ }
+ return same;
+ };
+
+ // 调用
+ List> result = CollectionUtils.diffList(oldList, newList, sameFunc);
+ // 断言
+ assertEquals(result.size(), 3);
+ // 断言 create
+ assertEquals(result.get(0).size(), 1);
+ assertEquals(result.get(0).get(0), new Dog(null, "小白", "xb"));
+ // 断言 update
+ assertEquals(result.get(1).size(), 1);
+ assertEquals(result.get(1).get(0), new Dog(1, "花花2", "hh"));
+ // 断言 delete
+ assertEquals(result.get(2).size(), 1);
+ assertEquals(result.get(2).get(0), new Dog(2, "旺财", "wc"));
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
index e92544729..5e52e8e8e 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
@@ -148,7 +148,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient {
+ t.setServerUrl(randomURL());
+ t.setMode(MODE_PUBLIC_KEY);
+ t.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
+ t.setAppCertContent("");
+ t.setAlipayPublicCertContent("");
+ t.setRootCertContent("");
+ });
@InjectMocks
- AlipayQrPayClient client = new AlipayQrPayClient(10L,config);
+ AlipayQrPayClient client = new AlipayQrPayClient(randomLongId(), config);
@Mock
private DefaultAlipayClient defaultAlipayClient;
@Test
- public void testDoInit(){
+ public void test_do_init() {
client.doInit();
assertNotSame(defaultAlipayClient, ReflectUtil.getFieldValue(client, "defaultAlipayClient"));
}
@Test
- @Disabled // TODO 芋艿:临时禁用
- public void create() throws AlipayApiException {
- // TODO @tina:参数可以尽量随机一点,使用随机方法。这样的好处是,避免对固定参数的依赖,导致可能仅仅满足固定参数的结果
- // 这里,设置可以直接随机整个对象。
- Long shopOrderId = System.currentTimeMillis();
- PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
- reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis()));
- reqDTO.setPrice(1);
- reqDTO.setBody("内容:" + shopOrderId);
- reqDTO.setSubject("标题:"+shopOrderId);
- String notify="http://niubi.natapp1.cc/api/pay/order/notify";
- reqDTO.setNotifyUrl(notify);
-
- AlipayTradePrecreateResponse response=randomPojo(AlipayTradePrecreateResponse.class,o->o.setQrCode("success"));
-
- when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request ->{
- assertEquals(notify,request.getNotifyUrl());
+ @DisplayName("支付包扫描支付下单成功")
+ public void test_unified_order_success() throws AlipayApiException {
+ // 准备返回对象
+ String notifyUrl = randomURL();
+ String qrCode = randomString();
+ AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> {
+ o.setQrCode(qrCode);
+ o.setSubCode("");
+ });
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> {
+ assertEquals(notifyUrl, request.getNotifyUrl());
return true;
}))).thenReturn(response);
+ // 准备请求参数
+ String outTradeNo = randomString();
+ PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+ PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+ // 断言
+ assertEquals(WAITING.getStatus(), resp.getStatus());
+ assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
+ assertEquals(outTradeNo, resp.getOutTradeNo());
+ assertEquals(qrCode, resp.getDisplayContent());
+ assertSame(response, resp.getRawData());
+ }
-// PayCommonResult result = client.doUnifiedOrder(reqDTO);
-// // 断言
-// assertEquals(response.getCode(), result.getApiCode());
-// assertEquals(response.getMsg(), result.getApiMsg());
-// // TODO @tina:这个断言木有过?
-// assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
-// assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+ @Test
+ @DisplayName("支付包扫描支付,渠道返回失败")
+ public void test_unified_order_channel_failed() throws AlipayApiException {
+ String notifyUrl = randomURL();
+ String subCode = randomString();
+ String subMsg = randomString();
+ AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> {
+ o.setSubCode(subCode);
+ o.setSubMsg(subMsg);
+ });
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> {
+ assertEquals(notifyUrl, request.getNotifyUrl());
+ return true;
+ }))).thenReturn(response);
+ // 准备请求参数
+ String outTradeNo = randomString();
+ PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+ PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+ // 断言
+ assertEquals(CLOSED.getStatus(), resp.getStatus());
+ assertEquals(subCode, resp.getChannelErrorCode());
+ assertEquals(subMsg, resp.getChannelErrorMsg());
+ assertSame(response, resp.getRawData());
+ }
+
+ @Test
+ @DisplayName("支付包扫描支付,抛出系统异常")
+ public void test_unified_order_throw_pay_exception() throws AlipayApiException {
+ // 准备请求参数
+ String outTradeNo = randomString();
+ String notifyUrl = randomURL();
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> {
+ assertEquals(notifyUrl, request.getNotifyUrl());
+ return true;
+ }))).thenThrow(new RuntimeException("系统异常"));
+ // 准备请求参数
+ PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+ // 断言
+ assertThrows(PayException.class, () -> client.unifiedOrder(reqDTO));
+ }
+
+ @Test
+ @DisplayName("支付包扫描支付,抛出业务异常")
+ public void test_unified_order_throw_service_exception() throws AlipayApiException {
+ // 准备请求参数
+ String outTradeNo = randomString();
+ String notifyUrl = randomURL();
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> {
+ assertEquals(notifyUrl, request.getNotifyUrl());
+ return true;
+ }))).thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
+ // 准备请求参数
+ PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+ // 断言
+ assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
+ }
+
+ @Test
+ @DisplayName("支付包扫描支付,参数校验不通过")
+ public void test_unified_order_param_validate() {
+ // 准备请求参数
+ String outTradeNo = randomString();
+ String notifyUrl = randomURL();
+ PayOrderUnifiedReqDTO reqDTO = randomPojo(PayOrderUnifiedReqDTO.class, o -> {
+ o.setOutTradeNo(outTradeNo);
+ o.setNotifyUrl(notifyUrl);
+ });
+ // 断言
+ assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO));
+ }
+
+ private PayOrderUnifiedReqDTO buildOrderUnifiedReqDTO(String notifyUrl, String outTradeNo) {
+ return randomPojo(PayOrderUnifiedReqDTO.class, o -> {
+ o.setOutTradeNo(outTradeNo);
+ o.setNotifyUrl(notifyUrl);
+ o.setSubject(RandomUtil.randomString(32));
+ o.setBody(RandomUtil.randomString(32));
+ });
+ }
+
+ @Test
+ @DisplayName("支付包扫描退款成功")
+ public void test_unified_refund_success() throws AlipayApiException {
+ // 准备返回对象
+ String notifyUrl = randomURL();
+ Date refundTime = randomDate();
+ String outRefundNo = randomString();
+ String outTradeNo = randomString();
+ Integer refundAmount = randomInteger();
+ AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> {
+ o.setSubCode("");
+ o.setGmtRefundPay(refundTime);
+ });
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> {
+ assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel());
+ AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel();
+ assertEquals(outRefundNo, bizModel.getOutRequestNo());
+ assertEquals(outTradeNo, bizModel.getOutTradeNo());
+ assertEquals(String.valueOf(refundAmount / 100.0), bizModel.getRefundAmount());
+ return true;
+ }))).thenReturn(response);
+ // 准备请求参数
+ PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+ o.setOutRefundNo(outRefundNo);
+ o.setOutTradeNo(outTradeNo);
+ o.setNotifyUrl(notifyUrl);
+ o.setRefundPrice(refundAmount);
+ });
+ PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO);
+ // 断言
+ assertEquals(PayRefundStatusRespEnum.SUCCESS.getStatus(), resp.getStatus());
+ assertNull(resp.getChannelRefundNo());
+ assertEquals(LocalDateTimeUtil.of(refundTime), resp.getSuccessTime());
+ assertEquals(outRefundNo, resp.getOutRefundNo());
+ assertSame(response, resp.getRawData());
+ }
+
+ @Test
+ @DisplayName("支付包扫描退款,渠道返回失败")
+ public void test_unified_refund_channel_failed() throws AlipayApiException {
+ // 准备返回对象
+ String notifyUrl = randomURL();
+ String subCode = randomString();
+ String subMsg = randomString();
+ AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> {
+ o.setSubCode(subCode);
+ o.setSubMsg(subMsg);
+ });
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> {
+ assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel());
+ return true;
+ }))).thenReturn(response);
+ // 准备请求参数
+ String outRefundNo = randomString();
+ String outTradeNo = randomString();
+ PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+ o.setOutRefundNo(outRefundNo);
+ o.setOutTradeNo(outTradeNo);
+ o.setNotifyUrl(notifyUrl);
+ });
+ PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO);
+ // 断言
+ assertEquals(PayRefundStatusRespEnum.FAILURE.getStatus(), resp.getStatus());
+ assertNull(resp.getChannelRefundNo());
+ assertEquals(subCode, resp.getChannelErrorCode());
+ assertEquals(subMsg, resp.getChannelErrorMsg());
+ assertNull(resp.getSuccessTime());
+ assertEquals(outRefundNo, resp.getOutRefundNo());
+ assertSame(response, resp.getRawData());
+ }
+
+ @Test
+ @DisplayName("支付包扫描退款,参数校验不通过")
+ public void test_unified_refund_param_validate() {
+ // 准备请求参数
+ String notifyUrl = randomURL();
+ PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+ o.setOutTradeNo("");
+ o.setNotifyUrl(notifyUrl);
+ });
+ // 断言
+ assertThrows(ConstraintViolationException.class, () -> client.unifiedRefund(refundReqDTO));
+ }
+
+ @Test
+ @DisplayName("支付包扫描退款,抛出业务异常")
+ public void test_unified_refund_throw_service_exception() throws AlipayApiException {
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true)))
+ .thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
+ // 准备请求参数
+ String notifyUrl = randomURL();
+ PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+ o.setNotifyUrl(notifyUrl);
+ });
+ // 断言
+ assertThrows(ServiceException.class, () -> client.unifiedRefund(refundReqDTO));
+ }
+ @Test
+ @DisplayName("支付包扫描退款,抛出系统异常")
+ public void test_unified_refund_throw_pay_exception() throws AlipayApiException {
+ // mock
+ when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true)))
+ .thenThrow(new RuntimeException("系统异常"));
+ // 准备请求参数
+ String notifyUrl = randomURL();
+ PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+ o.setNotifyUrl(notifyUrl);
+ });
+ // 断言
+ assertThrows(PayException.class, () -> client.unifiedRefund(refundReqDTO));
}
}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
index 9eed6d0e0..839b5abb8 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
@@ -44,6 +44,12 @@ public interface BaseMapperX extends BaseMapper {
return selectOne(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2));
}
+ default T selectOne(SFunction field1, Object value1, SFunction field2, Object value2,
+ SFunction field3, Object value3) {
+ return selectOne(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2)
+ .eq(field3, value3));
+ }
+
default Long selectCount() {
return selectCount(new QueryWrapper());
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java
index f4cb71330..53c40254b 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.jackson.core.databind;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
@@ -20,7 +19,7 @@ public class LocalDateTimeDeserializer extends JsonDeserializer {
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
@Override
- public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
}
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java
deleted file mode 100644
index f9ff37511..000000000
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.framework.jackson.core.databind;
-
-import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
-import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
-
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;
-
-public class LocalTimeJson {
-
- public static final LocalTimeSerializer SERIALIZER = new LocalTimeSerializer(DateTimeFormatter
- .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
- .withZone(ZoneId.systemDefault()));
-
- public static final LocalTimeDeserializer DESERIALIZABLE = new LocalTimeDeserializer(DateTimeFormatter
- .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
- .withZone(ZoneId.systemDefault()));
-
-}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java
new file mode 100644
index 000000000..62755fc68
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.infra.controller.app.file;
+
+import cn.hutool.core.io.IoUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO;
+import cn.iocoder.yudao.module.infra.service.file.FileService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 App - 文件存储")
+@RestController
+@RequestMapping("/infra/file")
+@Validated
+@Slf4j
+public class AppFileController {
+
+ @Resource
+ private FileService fileService;
+
+ @PostMapping("/upload")
+ @Operation(summary = "上传文件")
+ public CommonResult uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
+ MultipartFile file = uploadReqVO.getFile();
+ String path = uploadReqVO.getPath();
+ return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
+ }
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java
new file mode 100644
index 000000000..04666c14f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.infra.controller.app.file.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "用户 App - 上传文件 Request VO")
+@Data
+public class AppFileUploadReqVO {
+
+ @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "文件附件不能为空")
+ private MultipartFile file;
+
+ @Schema(description = "文件附件", example = "yudaoyuanma.png")
+ private String path;
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
index 1e98abe13..67b51d412 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
@@ -120,7 +120,6 @@
-
#foreach($column in $columns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
@@ -142,7 +141,7 @@
#else
-
+
#end
#end
#end
@@ -180,7 +179,7 @@
<${simpleClassName}Form ref="formRef" @success="getList" />
-