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" /> -