Commit 057952bdeb6262abc46fd698e4b449dea77ed19f

Authored by YunaiV
2 parents b74169ac abfebebd

Merge branch 'feature/mall_product' of https://gitee.com/CrazyWorld/ruoyi-vue-pr…

…o into feature/mall_product

# Conflicts:
#	yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
Showing 85 changed files with 1578 additions and 497 deletions
sql/mysql/member.sql 0 → 100644
  1 +-- 查询上级菜单、排序
  2 +SELECT parent_id, sort
  3 +INTO @parentId, @sort
  4 +FROM system_menu
  5 +WHERE name = '用户等级修改'
  6 +LIMIT 1;
  7 +-- 新增 按钮权限
  8 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  9 +VALUES ('用户积分修改', 'member:user:update-point', 3, @sort + 1, @parentId, '', '', '', 0);
  10 +INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
  11 +VALUES ('用户余额修改', 'member:user:update-balance', 3, @sort + 2, @parentId, '', '', '', 0);
... ...
sql/mysql/ruoyi-vue-pro.sql
... ... @@ -805,7 +805,7 @@ CREATE TABLE `member_level_record` (
805 805 `user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户编号',
806 806 `level_id` bigint NOT NULL DEFAULT 0 COMMENT '等级编号',
807 807 `level` int NOT NULL DEFAULT 0 COMMENT '会员等级',
808   - `discount_percent` tinyint NOT NULL DEFAULT 100 COMMENT '享受折扣',
  808 + `discount_percent` int NOT NULL DEFAULT 100 COMMENT '享受折扣',
809 809 `experience` int NOT NULL DEFAULT 0 COMMENT '升级经验',
810 810 `user_experience` int NOT NULL DEFAULT 0 COMMENT '会员此时的经验',
811 811 `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
... ... @@ -827,15 +827,15 @@ BEGIN;
827 827 COMMIT;
828 828  
829 829 -- ----------------------------
830   --- Table structure for member_point_config
  830 +-- Table structure for member_config
831 831 -- ----------------------------
832   -DROP TABLE IF EXISTS `member_point_config`;
833   -CREATE TABLE `member_point_config` (
  832 +DROP TABLE IF EXISTS `member_config`;
  833 +CREATE TABLE `member_config` (
834 834 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
835   - `trade_deduct_enable` bit(1) NOT NULL COMMENT '是否开启积分抵扣',
836   - `trade_deduct_unit_price` int NOT NULL COMMENT '积分抵扣(单位:分)',
837   - `trade_deduct_max_price` int NULL DEFAULT NULL COMMENT '积分抵扣最大值',
838   - `trade_give_point` bigint NULL DEFAULT NULL COMMENT '1 元赠送多少分',
  835 + `point_trade_deduct_enable` bit(1) NOT NULL COMMENT '是否开启积分抵扣',
  836 + `point_trade_deduct_unit_price` int NOT NULL COMMENT '积分抵扣(单位:分)',
  837 + `point_trade_deduct_max_price` int NULL DEFAULT NULL COMMENT '积分抵扣最大值',
  838 + `point_trade_give_point` bigint NULL DEFAULT NULL COMMENT '1 元赠送多少分',
839 839 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
840 840 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
841 841 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
... ... @@ -843,13 +843,13 @@ CREATE TABLE `member_point_config` (
843 843 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
844 844 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
845 845 PRIMARY KEY (`id`) USING BTREE
846   -) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员积分配置表';
  846 +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员配置表';
847 847  
848 848 -- ----------------------------
849   --- Records of member_point_config
  849 +-- Records of member_config
850 850 -- ----------------------------
851 851 BEGIN;
852   -INSERT INTO `member_point_config` (`id`, `trade_deduct_enable`, `trade_deduct_unit_price`, `trade_deduct_max_price`, `trade_give_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, b'1', 100, 2, 3, '1', '2023-08-20 09:54:42', '1', '2023-08-20 09:54:42', b'0', 1);
  852 +INSERT INTO `member_config` (`id`, `point_trade_deduct_enable`, `point_trade_deduct_unit_price`, `point_trade_deduct_max_price`, `point_trade_give_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, b'1', 100, 2, 3, '1', '2023-08-20 09:54:42', '1', '2023-08-20 09:54:42', b'0', 1);
853 853 COMMIT;
854 854  
855 855 -- ----------------------------
... ... @@ -915,7 +915,8 @@ DROP TABLE IF EXISTS `member_sign_in_config`;
915 915 CREATE TABLE `member_sign_in_config` (
916 916 `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
917 917 `day` int NOT NULL COMMENT '第几天',
918   - `point` int NOT NULL COMMENT '奖励积分',
  918 + `point` int NOT NULL DEFAULT 0 COMMENT '奖励积分',
  919 + `experience` int NOT NULL DEFAULT 0 COMMENT '奖励经验',
919 920 `status` tinyint NOT NULL COMMENT '状态',
920 921 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
921 922 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
... ... @@ -930,18 +931,18 @@ CREATE TABLE `member_sign_in_config` (
930 931 -- Records of member_sign_in_config
931 932 -- ----------------------------
932 933 BEGIN;
933   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 10, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
934   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 20, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
935   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 7, 1001, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
936   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 6, 12121, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
937   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 2, 12, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
938   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 1, 10, 0, '1', '2023-08-20 19:20:42', '1', '2023-08-20 19:20:56', b'0', 1);
939   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 7, 22, 0, '1', '2023-08-20 19:20:48', '1', '2023-08-20 19:20:48', b'0', 1);
940   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 2, 3, 0, '1', '2023-08-21 20:22:44', '1', '2023-08-21 20:22:44', b'0', 1);
941   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 3, 4, 0, '1', '2023-08-21 20:22:48', '1', '2023-08-21 20:22:48', b'0', 1);
942   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 4, 5, 0, '1', '2023-08-21 20:22:51', '1', '2023-08-21 20:22:51', b'0', 1);
943   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 5, 6, 0, '1', '2023-08-21 20:22:56', '1', '2023-08-21 20:22:56', b'0', 1);
944   -INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 6, 7, 0, '1', '2023-08-21 20:22:59', '1', '2023-08-21 20:22:59', b'0', 1);
  934 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 10, 10, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
  935 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 20, 20, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
  936 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 7, 1001, 1001, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
  937 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 6, 12121, 12121, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
  938 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 3, 12, 12, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
  939 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 1, 10, 10, 0, '1', '2023-08-20 19:20:42', '1', '2023-08-20 19:20:56', b'0', 1);
  940 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 7, 22, 22, 0, '1', '2023-08-20 19:20:48', '1', '2023-08-20 19:20:48', b'0', 1);
  941 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 2, 3, 3, 0, '1', '2023-08-21 20:22:44', '1', '2023-08-21 20:22:44', b'0', 1);
  942 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 3, 4, 4, 0, '1', '2023-08-21 20:22:48', '1', '2023-08-21 20:22:48', b'0', 1);
  943 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 4, 5, 5, 0, '1', '2023-08-21 20:22:51', '1', '2023-08-21 20:22:51', b'0', 1);
  944 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 5, 6, 6, 0, '1', '2023-08-21 20:22:56', '1', '2023-08-21 20:22:56', b'0', 1);
  945 +INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 6, 7, 7, 0, '1', '2023-08-21 20:22:59', '1', '2023-08-21 20:22:59', b'0', 1);
945 946 COMMIT;
946 947  
947 948 -- ----------------------------
... ... @@ -952,7 +953,8 @@ CREATE TABLE `member_sign_in_record` (
952 953 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '签到自增id',
953 954 `user_id` int NULL DEFAULT NULL COMMENT '签到用户',
954 955 `day` int NULL DEFAULT NULL COMMENT '第几天签到',
955   - `point` int NULL DEFAULT NULL COMMENT '签到的分数',
  956 + `point` int NOT NULL DEFAULT 0 COMMENT '签到的积分',
  957 + `experience` int NOT NULL DEFAULT 0 COMMENT '签到的经验',
956 958 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
957 959 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
958 960 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
... ... @@ -962,6 +964,8 @@ CREATE TABLE `member_sign_in_record` (
962 964 PRIMARY KEY (`id`) USING BTREE
963 965 ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '签到记录';
964 966  
  967 +CREATE INDEX user_id_index ON member_sign_in_record (user_id);
  968 +
965 969 -- ----------------------------
966 970 -- Records of member_sign_in_record
967 971 -- ----------------------------
... ... @@ -2013,20 +2017,19 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
2013 2017 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 (2184, '自提门店导出', 'trade:delivery:pick-up-store:export', 3, 5, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0');
2014 2018 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');
2015 2019 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', 'ep:bicycle', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-06-10 00:42:03', '1', '2023-08-20 09:23:56', b'0');
2016   -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', 'fa:archive', 'member/point/config/index', 'PointConfig', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-08-20 12:01:20', b'0');
2017   -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');
2018   -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');
  2020 +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, 2262, 'config', 'fa:archive', 'member/config/index', 'MemberConfig', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-08-20 12:01:20', b'0');
  2021 +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: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');
  2022 +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: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');
2019 2023 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, 'config', 'ep:calendar', 'member/signin/config/index', 'SignInConfig', 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-08-20 19:25:51', b'0');
2020 2024 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');
2021 2025 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');
2022 2026 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');
2023 2027 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');
2024   -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', 'fa:asterisk', 'member/point/record/index', 'PointRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-08-20 12:01:42', b'0');
2025   -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');
  2028 +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, 2262, 'point', 'ep:pointer', 'member/point/record/index', 'PointRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-08-20 12:01:42', b'0');
  2029 +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');
2026 2030 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, 'record', 'ep:chicken', 'member/signin/record/index', 'SignInRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '1', '2023-08-20 19:26:02', b'0');
2027 2031 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');
2028 2032 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');
2029   -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, 10, 2262, 'point', 'ep:pointer', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:48:51', '1', '2023-08-20 09:23:35', b'0');
2030 2033 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, 11, 2262, 'signin', 'ep:alarm-clock', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:49:53', '1', '2023-08-20 09:23:48', b'0');
2031 2034 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');
2032 2035 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 (2302, '支付通知查询', 'pay:notify:query', 3, 1, 2301, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-07-20 04:41:32', '', '2023-07-20 04:41:32', b'0');
... ...
sql/mysql/trade_order.sql 0 → 100644
  1 +-- 增加字段
  2 +ALTER TABLE trade_order
  3 + ADD COLUMN brokerage_user_id bigint NULL COMMENT '推广人编号' AFTER comment_status;
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
... ... @@ -6,6 +6,7 @@ import java.time.Duration;
6 6 import java.time.LocalDate;
7 7 import java.time.LocalDateTime;
8 8 import java.time.LocalTime;
  9 +import java.time.temporal.TemporalAdjusters;
9 10  
10 11 /**
11 12 * 时间工具类,用于 {@link java.time.LocalDateTime}
... ... @@ -137,4 +138,29 @@ public class LocalDateTimeUtils {
137 138  
138 139 }
139 140  
  141 + /**
  142 + * 获取指定日期所在的月份的开始时间
  143 + * 例如:2023-09-30 00:00:00,000
  144 + *
  145 + * @param date 日期
  146 + * @return 月份的开始时间
  147 + */
  148 + public static LocalDateTime beginOfMonth(LocalDateTime date) {
  149 + return date
  150 + .with(TemporalAdjusters.firstDayOfMonth())
  151 + .with(LocalTime.MIN);
  152 + }
  153 +
  154 + /**
  155 + * 获取指定日期所在的月份的最后时间
  156 + * 例如:2023-09-30 23:59:59,999
  157 + *
  158 + * @param date 日期
  159 + * @return 月份的结束时间
  160 + */
  161 + public static LocalDateTime endOfMonth(LocalDateTime date) {
  162 + return date
  163 + .with(TemporalAdjusters.lastDayOfMonth())
  164 + .with(LocalTime.MAX);
  165 + }
140 166 }
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.category;
  2 +
  3 +import java.util.Collection;
  4 +
  5 +/**
  6 + * 商品分类 API 接口
  7 + *
  8 + * @author owen
  9 + */
  10 +public interface ProductCategoryApi {
  11 +
  12 + /**
  13 + * 校验商品分类是否有效。如下情况,视为无效:
  14 + * 1. 商品分类编号不存在
  15 + * 2. 商品分类被禁用
  16 + *
  17 + * @param ids 商品分类编号数组
  18 + */
  19 + void validateCategoryList(Collection<Long> ids);
  20 +}
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
... ... @@ -22,10 +22,26 @@ public interface ProductSpuApi {
22 22 List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
23 23  
24 24 /**
  25 + * 批量查询 SPU 数组,并且校验是否 SPU 是否有效
  26 + *
  27 + * @param ids SPU 编号列表
  28 + * @return SPU 数组
  29 + */
  30 + List<ProductSpuRespDTO> getSpuListAndValidate(Collection<Long> ids);
  31 +
  32 + /**
25 33 * 获得 SPU
26 34 *
27 35 * @return SPU
28 36 */
29 37 ProductSpuRespDTO getSpu(Long id);
30 38  
  39 + /**
  40 + * 校验商品是否有效。如下情况,视为无效:
  41 + * 1. 商品编号不存在
  42 + * 2. 商品被禁用
  43 + *
  44 + * @param ids 商品编号数组
  45 + */
  46 + void validateSpuList(Collection<Long> ids);
31 47 }
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
... ... @@ -34,7 +34,7 @@ public interface ErrorCodeConstants {
34 34 // ========== 商品 SPU 1-008-005-000 ==========
35 35 ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
36 36 ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
37   - ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU 不处于上架状态");
  37 + ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU【[]】不处于上架状态");
38 38 ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_003, "商品 SPU 不处于回收站状态");
39 39  
40 40 // ========== 商品 SKU 1-008-006-000 ==========
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.category;
  2 +
  3 +import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
  4 +import org.springframework.stereotype.Service;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +import javax.annotation.Resource;
  8 +import java.util.Collection;
  9 +
  10 +/**
  11 + * 商品分类 API 接口实现类
  12 + *
  13 + * @author owen
  14 + */
  15 +@Service
  16 +@Validated
  17 +public class ProductCategoryApiImpl implements ProductCategoryApi {
  18 +
  19 + @Resource
  20 + private ProductCategoryService productCategoryService;
  21 +
  22 + @Override
  23 + public void validateCategoryList(Collection<Long> ids) {
  24 + productCategoryService.validateCategoryList(ids);
  25 + }
  26 +
  27 +}
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
... ... @@ -3,9 +3,6 @@ package cn.iocoder.yudao.module.product.api.spu;
3 3 import cn.hutool.core.collection.CollectionUtil;
4 4 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
5 5 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
6   -import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
7   -import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
8   -import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
9 6 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
10 7 import org.springframework.stereotype.Service;
11 8 import org.springframework.validation.annotation.Validated;
... ... @@ -37,8 +34,18 @@ public class ProductSpuApiImpl implements ProductSpuApi {
37 34 }
38 35  
39 36 @Override
  37 + public List<ProductSpuRespDTO> getSpuListAndValidate(Collection<Long> ids) {
  38 + return ProductSpuConvert.INSTANCE.convertList2(spuService.validateSpuList(ids));
  39 + }
  40 +
  41 + @Override
40 42 public ProductSpuRespDTO getSpu(Long id) {
41 43 return ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id));
42 44 }
43 45  
  46 + @Override
  47 + public void validateSpuList(Collection<Long> ids) {
  48 + spuService.validateSpuList(ids);
  49 + }
  50 +
44 51 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryService.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCateg
6 6 import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
7 7  
8 8 import javax.validation.Valid;
  9 +import java.util.Collection;
9 10 import java.util.List;
10 11  
11 12 /**
... ... @@ -75,4 +76,12 @@ public interface ProductCategoryService {
75 76 */
76 77 List<ProductCategoryDO> getEnableCategoryList();
77 78  
  79 + /**
  80 + * 校验商品分类是否有效。如下情况,视为无效:
  81 + * 1. 商品分类编号不存在
  82 + * 2. 商品分类被禁用
  83 + *
  84 + * @param ids 商品分类编号数组
  85 + */
  86 + void validateCategoryList(Collection<Long> ids);
78 87 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java
1 1 package cn.iocoder.yudao.module.product.service.category;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 6 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;
5 7 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
6 8 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;
... ... @@ -13,7 +15,9 @@ import org.springframework.stereotype.Service;
13 15 import org.springframework.validation.annotation.Validated;
14 16  
15 17 import javax.annotation.Resource;
  18 +import java.util.Collection;
16 19 import java.util.List;
  20 +import java.util.Map;
17 21 import java.util.Objects;
18 22  
19 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -100,6 +104,26 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
100 104 }
101 105  
102 106 @Override
  107 + public void validateCategoryList(Collection<Long> ids) {
  108 + if (CollUtil.isEmpty(ids)) {
  109 + return;
  110 + }
  111 + // 获得商品分类信息
  112 + List<ProductCategoryDO> categoryList = productCategoryMapper.selectBatchIds(ids);
  113 + Map<Long, ProductCategoryDO> categoryMap = CollectionUtils.convertMap(categoryList, ProductCategoryDO::getId);
  114 + // 校验
  115 + ids.forEach(id -> {
  116 + ProductCategoryDO category = categoryMap.get(id);
  117 + if (category == null) {
  118 + throw exception(CATEGORY_NOT_EXISTS);
  119 + }
  120 + if (!CommonStatusEnum.ENABLE.getStatus().equals(category.getStatus())) {
  121 + throw exception(CATEGORY_DISABLED, category.getName());
  122 + }
  123 + });
  124 + }
  125 +
  126 + @Override
103 127 public ProductCategoryDO getCategory(Long id) {
104 128 return productCategoryMapper.selectById(id);
105 129 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
... ... @@ -136,4 +136,14 @@ public interface ProductSpuService {
136 136 */
137 137 Long getSpuCountByCategoryId(Long categoryId);
138 138  
  139 +
  140 + /**
  141 + * 校验商品是否有效。如下情况,视为无效:
  142 + * 1. 商品编号不存在
  143 + * 2. 商品被禁用
  144 + *
  145 + * @param ids 商品编号数组
  146 + * @return 商品 SPU 列表
  147 + */
  148 + List<ProductSpuDO> validateSpuList(Collection<Long> ids);
139 149 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
1 1 package cn.iocoder.yudao.module.product.service.spu;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.util.ObjectUtil;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
... ... @@ -140,6 +141,28 @@ public class ProductSpuServiceImpl implements ProductSpuService {
140 141 }
141 142  
142 143 @Override
  144 + public List<ProductSpuDO> validateSpuList(Collection<Long> ids) {
  145 + if (CollUtil.isEmpty(ids)) {
  146 + return Collections.emptyList();
  147 + }
  148 + // 获得商品信息
  149 + List<ProductSpuDO> spuList = productSpuMapper.selectBatchIds(ids);
  150 + Map<Long, ProductSpuDO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuDO::getId);
  151 + // 校验
  152 + ids.forEach(id -> {
  153 + ProductSpuDO spu = spuMap.get(id);
  154 + if (spu == null) {
  155 + throw exception(SPU_NOT_EXISTS);
  156 + }
  157 + if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
  158 + throw exception(SPU_NOT_ENABLE, spu.getName());
  159 + }
  160 + });
  161 +
  162 + return spuList;
  163 + }
  164 +
  165 + @Override
143 166 @Transactional(rollbackFor = Exception.class)
144 167 public void deleteSpu(Long id) {
145 168 // 校验存在
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
2 2  
3   -import cn.hutool.core.util.ObjUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
... ... @@ -25,11 +24,9 @@ import org.springframework.web.bind.annotation.RequestParam;
25 24 import org.springframework.web.bind.annotation.RestController;
26 25  
27 26 import javax.annotation.Resource;
28   -import java.time.LocalDateTime;
29 27 import java.util.*;
30 28  
31 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
32   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
33 30 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
34 31  
35 32 @Tag(name = "用户 App - 优惠劵模板")
... ... @@ -46,7 +43,6 @@ public class AppCouponTemplateController {
46 43 @Resource
47 44 private ProductSpuApi productSpuApi;
48 45  
49   - // TODO 疯狂:这里应该还有个 list 接口哈;获得优惠劵模版列表,用于首页、商品页的优惠劵
50 46 @GetMapping("/list")
51 47 @Operation(summary = "获得优惠劵模版列表")
52 48 @Parameters({
... ... @@ -56,46 +52,28 @@ public class AppCouponTemplateController {
56 52 })
57 53 public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(
58 54 @RequestParam(value = "spuId", required = false) Long spuId,
59   - @RequestParam(value = "useType", required = false) Integer useType,
  55 + @RequestParam(value = "productScope", required = false) Integer productScope,
60 56 @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
61   - List<AppCouponTemplateRespVO> list = new ArrayList<>();
62   - Random random = new Random();
63   - for (int i = 0; i < 10; i++) {
64   - AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
65   - vo.setId(i + 1L);
66   - vo.setName("优惠劵" + (i + 1));
67   - vo.setTakeLimitCount(random.nextInt(10) + 1);
68   - vo.setUsePrice(random.nextInt(100) * 100);
69   - vo.setValidityType(random.nextInt(2) + 1);
70   - if (vo.getValidityType() == 1) {
71   - vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
72   - vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
73   - } else {
74   - vo.setFixedStartTerm(random.nextInt(10));
75   - vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
76   - }
77   - vo.setDiscountType(random.nextInt(2) + 1);
78   - if (vo.getDiscountType() == 1) {
79   - vo.setDiscountPercent(null);
80   - vo.setDiscountPrice(random.nextInt(50) * 100);
81   - vo.setDiscountLimitPrice(null);
82   - } else {
83   - vo.setDiscountPercent(random.nextInt(90) + 10);
84   - vo.setDiscountPrice(null);
85   - vo.setDiscountLimitPrice(random.nextInt(200) * 100);
86   - }
87   - // TODO @疯狂:是否已领取,要不在 TemplateService 搞个 static 方法,让它基于 countMap 这种去计算,这样好点?
88   - vo.setTakeStatus(random.nextBoolean());
89   - list.add(vo);
90   - }
91   - return success(list);
  57 + // 1.1 处理查询条件:商品范围编号
  58 + Long productScopeValue = getProductScopeValue(productScope, spuId);
  59 + // 1.2 处理查询条件:领取方式 = 直接领取
  60 + List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
  61 +
  62 + // 2. 查询
  63 + List<CouponTemplateDO> list = couponTemplateService.getCouponTemplateList(canTakeTypes, productScope,
  64 + productScopeValue, count);
  65 +
  66 + // 3.1 领取数量
  67 + Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), list);
  68 + // 3.2 拼接返回
  69 + return success(CouponTemplateConvert.INSTANCE.convertAppList(list, canCanTakeMap));
92 70 }
93 71  
94 72 @GetMapping("/page")
95 73 @Operation(summary = "获得优惠劵模版分页")
96 74 public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
97 75 // 1.1 处理查询条件:商品范围编号
98   - Long productScopeValue = getProductScopeValue(pageReqVO);
  76 + Long productScopeValue = getProductScopeValue(pageReqVO.getProductScope(), pageReqVO.getSpuId());
99 77 // 1.2 处理查询条件:领取方式 = 直接领取
100 78 List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
101 79  
... ... @@ -104,35 +82,30 @@ public class AppCouponTemplateController {
104 82 CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
105 83  
106 84 // 3.1 领取数量
107   - Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
108   - Long userId = getLoginUserId();
109   - if (userId != null) {
110   - List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId,
111   - t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置“每人限领个数”的
112   - couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId);
113   - }
  85 + Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), pageResult.getList());
114 86 // 3.2 拼接返回
115   - return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
  87 + return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, canCanTakeMap));
116 88 }
117 89  
118 90 /**
119   - * 获得分页查询的商品范围
  91 + * 获得商品的使用范围编号
120 92 *
121   - * @param pageReqVO 分页查询
122   - * @return 商品范围
  93 + * @param productScope 商品范围
  94 + * @param spuId 商品 SPU 编号
  95 + * @return 商品范围编号
123 96 */
124   - private Long getProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
125   - // 通用券:清除商品范围
126   - if (pageReqVO.getProductScope() == null || ObjectUtils.equalsAny(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope(), null)) {
  97 + private Long getProductScopeValue(Integer productScope, Long spuId) {
  98 + // 通用券:没有商品范围
  99 + if (productScope == null || ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) {
127 100 return null;
128 101 }
129   - // 品类券:查询商品的品类
130   - if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
131   - return Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
  102 + // 品类券:查询商品的品类编号
  103 + if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) {
  104 + return Optional.ofNullable(productSpuApi.getSpu(spuId))
132 105 .map(ProductSpuRespDTO::getCategoryId).orElse(null);
133 106 }
134 107 // 商品卷:直接返回
135   - return pageReqVO.getSpuId();
  108 + return spuId;
136 109 }
137 110  
138 111 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
... ... @@ -55,7 +55,7 @@ public class AppCouponTemplateRespVO {
55 55  
56 56 // ========== 用户相关字段 ==========
57 57  
58   - @Schema(description = "是否已领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
59   - private Boolean takeStatus;
  58 + @Schema(description = "是否可以领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  59 + private Boolean canTake;
60 60  
61 61 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
... ... @@ -37,21 +37,25 @@ public interface CouponTemplateConvert {
37 37  
38 38 PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult);
39 39  
40   - default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) {
  40 + List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list);
  41 +
  42 + default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Boolean> userCanTakeMap) {
41 43 PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult);
42   - if (MapUtil.isEmpty(couponTakeCountMap)) {
43   - return result;
44   - }
45   - for (AppCouponTemplateRespVO template : result.getList()) {
46   - // 每人领取数量无限制
47   - if (template.getTakeLimitCount() == -1) {
48   - template.setTakeStatus(false);
49   - continue;
50   - }
  44 + copyTo(result.getList(), userCanTakeMap);
  45 + return result;
  46 + }
  47 +
  48 + default List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list, Map<Long, Boolean> userCanTakeMap) {
  49 + List<AppCouponTemplateRespVO> result = convertAppList(list);
  50 + copyTo(result, userCanTakeMap);
  51 + return result;
  52 + }
  53 +
  54 + default void copyTo(List<AppCouponTemplateRespVO> list, Map<Long, Boolean> userCanTakeMap) {
  55 + for (AppCouponTemplateRespVO template : list) {
51 56 // 检查已领取数量是否超过限领数量
52   - template.setTakeStatus(MapUtil.getInt(couponTakeCountMap, template.getId(), 0) >= template.getTakeLimitCount());
  57 + template.setCanTake(MapUtil.getBool(userCanTakeMap, template.getId(), false));
53 58 }
54   - return result;
55 59 }
56 60  
57 61 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
1 1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
2 2  
3   -import cn.hutool.core.bean.BeanUtil;
  3 +import cn.hutool.core.map.MapUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 8 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
9 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
10 10 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
11   -import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
12 11 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
13 12 import com.github.yulichang.toolkit.MPJWrappers;
14 13 import org.apache.ibatis.annotations.Mapper;
... ... @@ -16,9 +15,12 @@ import org.apache.ibatis.annotations.Mapper;
16 15 import java.time.LocalDateTime;
17 16 import java.util.Collection;
18 17 import java.util.List;
  18 +import java.util.Map;
19 19 import java.util.function.Function;
20 20 import java.util.stream.Collectors;
21 21  
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  23 +
22 24 /**
23 25 * 优惠劵 Mapper
24 26 *
... ... @@ -70,14 +72,16 @@ public interface CouponMapper extends BaseMapperX&lt;CouponDO&gt; {
70 72 );
71 73 }
72 74  
73   - // TODO @疯狂:这个是不是搞个 Map 就可以呀?
74   - default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
75   - return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
76   - .select(CouponDO::getTemplateId)
77   - .selectCount(CouponDO::getId, CouponTakeCountBO::getCount)
  75 + default Map<Long, Integer> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
  76 + String templateIdAlias = "templateId";
  77 + String countAlias = "count";
  78 + List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
  79 + .selectAs(CouponDO::getTemplateId, templateIdAlias)
  80 + .selectCount(CouponDO::getId, countAlias)
78 81 .eq(CouponDO::getUserId, userId)
79 82 .in(CouponDO::getTemplateId, templateIds)
80   - .groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class);
  83 + .groupBy(CouponDO::getTemplateId));
  84 + return convertMap(list, map -> MapUtil.getLong(map, templateIdAlias), map -> MapUtil.getInt(map, countAlias));
81 85 }
82 86  
83 87 default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
... ... @@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
12 12 import org.apache.ibatis.annotations.Param;
13 13  
14 14 import java.time.LocalDateTime;
  15 +import java.util.List;
15 16 import java.util.function.Consumer;
16 17  
17 18 /**
... ... @@ -23,16 +24,8 @@ import java.util.function.Consumer;
23 24 public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
24 25  
25 26 default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
26   - // 构建可领取的查询条件, 好啰嗦 ( ╯-_-)╯┴—┴
27   - Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
28   - if (CollUtil.isNotEmpty(reqVO.getCanTakeTypes())) {
29   - canTakeConsumer = w ->
30   - w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
31   - .in(CouponTemplateDO::getTakeType, reqVO.getCanTakeTypes()) // 2. 领取方式一致
32   - .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
33   - .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
34   - .apply(" take_count < total_count "); // 4. 剩余数量大于 0
35   - }
  27 + // 构建可领取的查询条件
  28 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(reqVO.getCanTakeTypes());
36 29 // 执行分页查询
37 30 return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
38 31 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
... ... @@ -48,4 +41,33 @@ public interface CouponTemplateMapper extends BaseMapperX&lt;CouponTemplateDO&gt; {
48 41  
49 42 void updateTakeCount(@Param("id") Long id, @Param("incrCount") Integer incrCount);
50 43  
  44 + default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
  45 + return selectList(CouponTemplateDO::getTakeType, takeType);
  46 + }
  47 +
  48 + default List<CouponTemplateDO> selectList(List<Integer> canTakeTypes, Integer productScope, Long productScopeValue, Integer count) {
  49 + // 构建可领取的查询条件
  50 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes);
  51 + return selectList(new LambdaQueryWrapperX<CouponTemplateDO>()
  52 + .eqIfPresent(CouponTemplateDO::getProductScope, productScope)
  53 + .and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)",
  54 + productScopeValue))
  55 + .and(canTakeConsumer != null, canTakeConsumer)
  56 + .last(" LIMIT " + count)
  57 + .orderByDesc(CouponTemplateDO::getId));
  58 + }
  59 +
  60 + static Consumer<LambdaQueryWrapper<CouponTemplateDO>> buildCanTakeQueryConsumer(List<Integer> canTakeTypes) {
  61 + Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
  62 + if (CollUtil.isNotEmpty(canTakeTypes)) {
  63 + canTakeConsumer = w ->
  64 + w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
  65 + .in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致
  66 + .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
  67 + .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
  68 + .apply(" take_count < total_count "); // 4. 剩余数量大于 0
  69 + }
  70 + return canTakeConsumer;
  71 + }
  72 +
51 73 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/consumer/coupon/RegisterCouponSendConsumer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.mq.consumer.coupon;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
  4 +import cn.iocoder.yudao.module.member.mq.message.user.RegisterCouponSendMessage;
  5 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link RegisterCouponSendMessage} 的消费者
  13 + *
  14 + * @author owen
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class RegisterCouponSendConsumer extends AbstractStreamMessageListener<RegisterCouponSendMessage> {
  19 +
  20 + @Resource
  21 + private CouponService couponService;
  22 +
  23 + @Override
  24 + public void onMessage(RegisterCouponSendMessage message) {
  25 + log.info("[onMessage][消息内容({})]", message);
  26 + couponService.takeCouponByRegister(message.getUserId());
  27 + }
  28 +
  29 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
1 1 package cn.iocoder.yudao.module.promotion.service.coupon;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
6 7 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
7 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  9 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
8 10 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
9   -import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
10 11  
11 12 import java.util.*;
12 13  
13   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
14   -
15 14 /**
16 15 * 优惠劵 Service 接口
17 16 *
... ... @@ -119,12 +118,9 @@ public interface CouponService {
119 118 /**
120 119 * 【系统】给用户发送新人券
121 120 *
122   - * @param templateId 优惠券模板编号
123   - * @param userId 用户编号列表
  121 + * @param userId 用户编号
124 122 */
125   - default void takeCouponByRegister(Long templateId, Long userId) {
126   - takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
127   - }
  123 + void takeCouponByRegister(Long userId);
128 124  
129 125 /**
130 126 * 获取会员领取指定优惠券的数量
... ... @@ -134,11 +130,8 @@ public interface CouponService {
134 130 * @return 领取优惠券的数量
135 131 */
136 132 default Integer getTakeCount(Long templateId, Long userId) {
137   - return CollUtil.emptyIfNull(getTakeCountListByTemplateIds(Collections.singleton(templateId), userId))
138   - .stream()
139   - .findFirst()
140   - .map(CouponTakeCountBO::getCount)
141   - .orElse(0);
  133 + Map<Long, Integer> map = getTakeCountMapByTemplateIds(Collections.singleton(templateId), userId);
  134 + return MapUtil.getInt(map, templateId, 0);
142 135 }
143 136  
144 137 /**
... ... @@ -148,19 +141,7 @@ public interface CouponService {
148 141 * @param userId 用户编号
149 142 * @return 领取优惠券的数量
150 143 */
151   - default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
152   - return convertMap(getTakeCountListByTemplateIds(templateIds, userId),
153   - CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
154   - }
155   -
156   - /**
157   - * 统计会员领取优惠券的数量
158   - *
159   - * @param templateIds 优惠券模板编号列表
160   - * @param userId 用户编号
161   - * @return 领取优惠券的数量
162   - */
163   - List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId);
  144 + Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId);
164 145  
165 146 /**
166 147 * 获取用户匹配的优惠券列表
... ... @@ -178,4 +159,13 @@ public interface CouponService {
178 159 */
179 160 int expireCoupon();
180 161  
  162 + /**
  163 + * 获取用户是否可以领取优惠券
  164 + *
  165 + * @param userId 用户编号
  166 + * @param templates 优惠券列表
  167 + * @return 是否可以领取
  168 + */
  169 + Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates);
  170 +
181 171 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
2 2  
3 3 import cn.hutool.core.collection.CollStreamUtil;
4 4 import cn.hutool.core.collection.CollUtil;
5   -import cn.hutool.core.collection.ListUtil;
6 5 import cn.hutool.core.map.MapUtil;
7 6 import cn.hutool.core.util.ObjectUtil;
8 7 import cn.hutool.core.util.StrUtil;
... ... @@ -21,7 +20,6 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
21 20 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
22 21 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
23 22 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
24   -import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
25 23 import lombok.extern.slf4j.Slf4j;
26 24 import org.springframework.stereotype.Service;
27 25 import org.springframework.transaction.annotation.Transactional;
... ... @@ -29,18 +27,14 @@ import org.springframework.validation.annotation.Validated;
29 27  
30 28 import javax.annotation.Resource;
31 29 import java.time.LocalDateTime;
32   -import java.util.Collection;
33   -import java.util.List;
34   -import java.util.Map;
35   -import java.util.Set;
  30 +import java.util.*;
36 31 import java.util.stream.Collectors;
37 32  
38 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
39   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
40 35 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
41 36 import static java.util.Arrays.asList;
42 37  
43   -// TODO @疯狂:注册时,赠送用户优惠劵;为了解耦,可以考虑注册时发个 MQ 消息;然后营销这里监听后消费;
44 38 /**
45 39 * 优惠劵 Service 实现类
46 40 *
... ... @@ -184,9 +178,17 @@ public class CouponServiceImpl implements CouponService {
184 178 }
185 179  
186 180 @Override
187   - public List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId) {
  181 + public void takeCouponByRegister(Long userId) {
  182 + List<CouponTemplateDO> templates = couponTemplateService.getCouponTemplateByTakeType(CouponTakeTypeEnum.REGISTER);
  183 + for (CouponTemplateDO template : templates) {
  184 + takeCoupon(template.getId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
  185 + }
  186 + }
  187 +
  188 + @Override
  189 + public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
188 190 if (CollUtil.isEmpty(templateIds)) {
189   - return ListUtil.empty();
  191 + return Collections.emptyMap();
190 192 }
191 193 return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
192 194 }
... ... @@ -222,6 +224,29 @@ public class CouponServiceImpl implements CouponService {
222 224 return count;
223 225 }
224 226  
  227 + @Override
  228 + public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
  229 + Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
  230 + // 未登录时,都显示可以领取
  231 + if (userId == null) {
  232 + return userCanTakeMap;
  233 + }
  234 +
  235 + // 过滤领取数量无限制的
  236 + Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
  237 +
  238 + // 检查用户领取的数量是否超过限制
  239 + if (CollUtil.isNotEmpty(templateIds)) {
  240 + Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
  241 + for (CouponTemplateDO template : templates) {
  242 + Integer takeCount = couponTakeCountMap.get(template.getId());
  243 + userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
  244 + }
  245 + }
  246 +
  247 + return userCanTakeMap;
  248 + }
  249 +
225 250 /**
226 251 * 过期单个优惠劵
227 252 *
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java
... ... @@ -5,8 +5,10 @@ import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.Cou
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
7 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
  8 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
8 9  
9 10 import javax.validation.Valid;
  11 +import java.util.List;
10 12  
11 13 /**
12 14 * 优惠劵模板 Service 接口
... ... @@ -69,4 +71,24 @@ public interface CouponTemplateService {
69 71 */
70 72 void updateCouponTemplateTakeCount(Long id, int incrCount);
71 73  
  74 + /**
  75 + * 获得指定领取方式的优惠券模板
  76 + *
  77 + * @param takeType 领取方式
  78 + * @return 优惠券模板列表
  79 + */
  80 + List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType);
  81 +
  82 + /**
  83 + * 获得优惠券模板列表
  84 + *
  85 + * @param canTakeTypes 可领取的类型列表
  86 + * @param productScope 商品使用范围类型
  87 + * @param productScopeValue 商品使用范围编号
  88 + * @param count 查询数量
  89 + * @return 优惠券模板列表
  90 + */
  91 + List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
  92 + Long productScopeValue, Integer count);
  93 +
72 94 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
... ... @@ -2,16 +2,22 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
  6 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
5 7 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
6 8 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
7 9 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
8 10 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
9 11 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
10 12 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
  13 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
  14 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
11 15 import org.springframework.stereotype.Service;
12 16 import org.springframework.validation.annotation.Validated;
13 17  
14 18 import javax.annotation.Resource;
  19 +import java.util.List;
  20 +import java.util.Objects;
15 21  
16 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
17 23 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
... ... @@ -29,9 +35,15 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
29 35 @Resource
30 36 private CouponTemplateMapper couponTemplateMapper;
31 37  
32   - // TODO @疯狂:新增/修改时,需要校验对应的商品、分类是否存在
  38 + @Resource
  39 + private ProductCategoryApi productCategoryApi;
  40 + @Resource
  41 + private ProductSpuApi productSpuApi;
  42 +
33 43 @Override
34 44 public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
  45 + // 校验商品范围
  46 + validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
35 47 // 插入
36 48 CouponTemplateDO couponTemplate = CouponTemplateConvert.INSTANCE.convert(createReqVO)
37 49 .setStatus(CommonStatusEnum.ENABLE.getStatus());
... ... @@ -48,6 +60,8 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
48 60 if (updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
49 61 throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
50 62 }
  63 + // 校验商品范围
  64 + validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues());
51 65  
52 66 // 更新
53 67 CouponTemplateDO updateObj = CouponTemplateConvert.INSTANCE.convert(updateReqVO);
... ... @@ -78,6 +92,14 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
78 92 return couponTemplate;
79 93 }
80 94  
  95 + private void validateProductScope(Integer productScope, List<Long> productScopeValues) {
  96 + if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) {
  97 + productSpuApi.validateSpuList(productScopeValues);
  98 + } else if (Objects.equals(PromotionProductScopeEnum.CATEGORY.getScope(), productScope)) {
  99 + productCategoryApi.validateCategoryList(productScopeValues);
  100 + }
  101 + }
  102 +
81 103 @Override
82 104 public CouponTemplateDO getCouponTemplate(Long id) {
83 105 return couponTemplateMapper.selectById(id);
... ... @@ -93,4 +115,15 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
93 115 couponTemplateMapper.updateTakeCount(id, incrCount);
94 116 }
95 117  
  118 + @Override
  119 + public List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType) {
  120 + return couponTemplateMapper.selectListByTakeType(takeType.getValue());
  121 + }
  122 +
  123 + @Override
  124 + public List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
  125 + Long productScopeValue, Integer count) {
  126 + return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count);
  127 + }
  128 +
96 129 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java deleted
1   -package cn.iocoder.yudao.module.promotion.service.coupon.bo;
2   -
3   -import lombok.Data;
4   -
5   -/**
6   - * 优惠券领取数量 BO
7   - *
8   - * @author owen
9   - */
10   -@Data
11   -public class CouponTakeCountBO {
12   -
13   - /**
14   - * 优惠劵模板编号
15   - */
16   - private Long templateId;
17   - /**
18   - * 领取数量
19   - */
20   - private Integer count;
21   -
22   -}
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/TradeStatisticsController.java
... ... @@ -32,7 +32,6 @@ public class TradeStatisticsController {
32 32 @Resource
33 33 private TradeStatisticsService tradeStatisticsService;
34 34  
35   - // TODO @疯狂:要不这个就是 /trend/summary 的特例,前端自己查询两次?
36 35 @GetMapping("/summary")
37 36 @Operation(summary = "获得交易统计")
38 37 @PreAuthorize("@ss.hasPermission('statistics:trade:query')")
... ... @@ -40,7 +39,6 @@ public class TradeStatisticsController {
40 39 return success(tradeStatisticsService.getTradeSummaryComparison());
41 40 }
42 41  
43   - // TODO @疯狂:直接 comparison?主要 trend 和 comparison 二选一,一个是数据趋势,一个是数据对比哈;
44 42 @GetMapping("/trend/summary")
45 43 @Operation(summary = "获得交易状况统计")
46 44 @PreAuthorize("@ss.hasPermission('statistics:trade:query')")
... ...
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java
1 1 package cn.iocoder.yudao.module.statistics.service.trade;
2 2  
3 3 import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
4 5 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
5 6 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
6 7 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
... ... @@ -13,8 +14,6 @@ import org.springframework.validation.annotation.Validated;
13 14 import javax.annotation.Resource;
14 15 import java.time.Duration;
15 16 import java.time.LocalDateTime;
16   -import java.time.LocalTime;
17   -import java.time.temporal.TemporalAdjusters;
18 17 import java.util.List;
19 18  
20 19 /**
... ... @@ -79,18 +78,9 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
79 78 * @return 交易数据
80 79 */
81 80 private TradeSummaryRespBO getTradeSummaryByMonths(int months) {
82   - // TODO @疯狂:可以在 LocalDateUtils 封装方法;获得月份的开始;以及结束两个方法;然后这里就可以直接调用了
83   - // 月份开始时间
84   - LocalDateTime beginOfMonth = LocalDateTime.now()
85   - .plusMonths(months)
86   - .with(TemporalAdjusters.firstDayOfMonth())
87   - .with(LocalTime.MIN);
88   - // 月份截止时间
89   - LocalDateTime endOfToday = LocalDateTime.now()
90   - .plusMonths(months)
91   - .with(TemporalAdjusters.lastDayOfMonth())
92   - .with(LocalTime.MAX);
93   - return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(beginOfMonth, endOfToday);
  81 + LocalDateTime monthDate = LocalDateTime.now().plusMonths(months);
  82 + return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(
  83 + LocalDateTimeUtils.beginOfMonth(monthDate), LocalDateTimeUtils.endOfMonth(monthDate));
94 84 }
95 85  
96 86 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
... ... @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageRecordConvert;
11 11 import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert;
12 12 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
13 13 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  14 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
14 15 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
15 16 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
16 17 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
... ... @@ -84,7 +85,7 @@ public class AppBrokerageUserController {
84 85 LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
85 86 LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
86 87 Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(),
87   - BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime);
  88 + BrokerageRecordBizTypeEnum.ORDER, BrokerageRecordStatusEnum.SETTLEMENT, beginTime, endTime);
88 89 // 统计用户提现的佣金
89 90 Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryListByUserId(Collections.singleton(brokerageUser.getId()),
90 91 BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream()
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
... ... @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera
11 11 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
12 12 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
13 13 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
14   -import com.baomidou.mybatisplus.core.metadata.IPage;
15 14 import org.mapstruct.Mapper;
16 15 import org.mapstruct.MappingTarget;
17 16 import org.mapstruct.factory.Mappers;
... ... @@ -88,8 +87,8 @@ public interface BrokerageUserConvert {
88 87 return respVO;
89 88 }
90 89  
91   - default void copyTo(IPage<AppBrokerageUserChildSummaryRespVO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
92   - for (AppBrokerageUserChildSummaryRespVO vo : pageResult.getRecords()) {
  90 + default void copyTo(List<AppBrokerageUserChildSummaryRespVO> list, Map<Long, MemberUserRespDTO> userMap) {
  91 + for (AppBrokerageUserChildSummaryRespVO vo : list) {
93 92 Optional.ofNullable(userMap.get(vo.getId())).ifPresent(user ->
94 93 vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
95 94 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  6 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
5 7 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
6 8 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
7 9 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
... ... @@ -105,7 +107,12 @@ public class TradeOrderDO extends BaseDO {
105 107 */
106 108 private Boolean commentStatus;
107 109  
108   - // TODO @疯狂:加一个推广人编号;
  110 + /**
  111 + * 推广人编号
  112 + * {@link BrokerageUserDO#getId()}
  113 + * {@link MemberUserRespDTO#getId()}
  114 + */
  115 + private Long brokerageUserId;
109 116  
110 117 // ========== 价格 + 支付基本信息 ==========
111 118  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
... ... @@ -80,10 +80,11 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
80 80 }
81 81  
82 82 @Select("SELECT SUM(price) FROM trade_brokerage_record " +
83   - "WHERE user_id = #{userId} AND biz_type = #{bizType} " +
84   - "AND create_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
  83 + "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} " +
  84 + "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
85 85 Integer selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(@Param("userId") Long userId,
86 86 @Param("bizType") Integer bizType,
  87 + @Param("status") Integer status,
87 88 @Param("beginTime") LocalDateTime beginTime,
88 89 @Param("endTime") LocalDateTime endTime);
89 90  
... ... @@ -98,4 +99,13 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
98 99 @Param("beginTime") LocalDateTime beginTime,
99 100 @Param("endTime") LocalDateTime endTime);
100 101  
  102 + @Select("SELECT COUNT(1) FROM trade_brokerage_record " +
  103 + "WHERE biz_type = #{bizType} AND status = #{status} AND deleted = FALSE " +
  104 + "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " +
  105 + "GROUP BY user_id HAVING SUM(price) > #{brokeragePrice}")
  106 + Integer selectCountByPriceGt(@Param("brokeragePrice") Integer brokeragePrice,
  107 + @Param("bizType") Integer bizType,
  108 + @Param("status") Integer status,
  109 + @Param("beginTime") LocalDateTime beginTime,
  110 + @Param("endTime") LocalDateTime endTime);
101 111 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageUserMapper.java
... ... @@ -138,13 +138,35 @@ public interface BrokerageUserMapper extends BaseMapperX&lt;BrokerageUserDO&gt; {
138 138 @Param("beginTime") LocalDateTime beginTime,
139 139 @Param("endTime") LocalDateTime endTime);
140 140  
  141 + /**
  142 + * 下级分销统计(分页)
  143 + *
  144 + * @param bizType 业务类型
  145 + * @param status 状态
  146 + * @param bindUserIds 绑定用户编号列表
  147 + * @param sortingField 排序字段
  148 + * @return 下级分销统计分页列表
  149 + */
141 150 IPage<AppBrokerageUserChildSummaryRespVO> selectSummaryPageByUserId(Page<?> page,
142   - @Param("ids") List<Long> ids, // BrokerageUser 的 ids 数组
143 151 @Param("bizType") Integer bizType,
144 152 @Param("status") Integer status,
145 153 @Param("bindUserIds") List<Long> bindUserIds,
146 154 @Param("sortingField") SortingField sortingField);
147 155  
  156 + /**
  157 + * 下级分销统计(不分页)
  158 + *
  159 + * @param bizType 业务类型
  160 + * @param status 状态
  161 + * @param bindUserIds 绑定用户编号列表
  162 + * @param sortingField 排序字段
  163 + * @return 下级分销统计列表
  164 + */
  165 + List<AppBrokerageUserChildSummaryRespVO> selectSummaryListByUserId(@Param("bizType") Integer bizType,
  166 + @Param("status") Integer status,
  167 + @Param("bindUserIds") List<Long> bindUserIds,
  168 + @Param("sortingField") SortingField sortingField);
  169 +
148 170 default List<BrokerageUserDO> selectListByBindUserId(Long bindUserId) {
149 171 return selectList(BrokerageUserDO::getBindUserId, bindUserId);
150 172 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera
7 7 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO;
8 8 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
9 9 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
  10 +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
10 11 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
11 12 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
12 13  
... ... @@ -120,11 +121,13 @@ public interface BrokerageRecordService {
120 121 *
121 122 * @param userId 用户编号
122 123 * @param bizType 业务类型
  124 + * @param status 状态
123 125 * @param beginTime 开始时间
124 126 * @param endTime 截止时间
125 127 * @return 用户佣金合计
126 128 */
127   - Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime);
  129 + Integer getSummaryPriceByUserId(Long userId, BrokerageRecordBizTypeEnum bizType, BrokerageRecordStatusEnum status,
  130 + LocalDateTime beginTime, LocalDateTime endTime);
128 131  
129 132 /**
130 133 * 获得用户佣金排行分页列表(基于佣金总数)
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
... ... @@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.trade.service.brokerage;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
5   -import cn.hutool.core.util.ArrayUtil;
6   -import cn.hutool.core.util.BooleanUtil;
7   -import cn.hutool.core.util.ObjectUtil;
8   -import cn.hutool.core.util.StrUtil;
  5 +import cn.hutool.core.util.*;
9 6 import cn.hutool.extra.spring.SpringUtil;
10 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 8 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
... ... @@ -265,9 +262,10 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
265 262 }
266 263  
267 264 @Override
268   - public Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime) {
269   - return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId, bizType,
270   - beginTime, endTime);
  265 + public Integer getSummaryPriceByUserId(Long userId, BrokerageRecordBizTypeEnum bizType, BrokerageRecordStatusEnum status,
  266 + LocalDateTime beginTime, LocalDateTime endTime) {
  267 + return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId,
  268 + bizType.getType(), status.getStatus(), beginTime, endTime);
271 269 }
272 270  
273 271 @Override
... ... @@ -279,17 +277,18 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
279 277 return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
280 278 }
281 279  
282   - // TODO @疯狂:这个要不我们先做精准的?先查询自己的推广金额,然后查询比该金额多的有多少人?
283 280 @Override
284 281 public Integer getUserRankByPrice(Long userId, LocalDateTime[] times) {
285   - AppBrokerageUserRankPageReqVO pageParam = new AppBrokerageUserRankPageReqVO().setTimes(times);
286   - // 取前 100 名
287   - pageParam.setPageSize(100);
288   - PageResult<AppBrokerageUserRankByPriceRespVO> pageResult = getBrokerageUserChildSummaryPageByPrice(pageParam);
289   - // 获得索引
290   - int index = CollUtil.indexOf(pageResult.getList(), user -> Objects.equals(userId, user.getId()));
  282 + // 用户的推广金额
  283 + Integer price = brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId,
  284 + BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
  285 + ArrayUtil.get(times, 0), ArrayUtil.get(times, 1));
  286 + // 排在用户前面的人数
  287 + Integer greaterCount = brokerageRecordMapper.selectCountByPriceGt(price,
  288 + BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
  289 + ArrayUtil.get(times, 0), ArrayUtil.get(times, 1));
291 290 // 获得排名
292   - return index + 1;
  291 + return ObjUtil.defaultIfNull(greaterCount, 0) + 1;
293 292 }
294 293  
295 294 @Override
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
... ... @@ -31,6 +31,7 @@ import org.springframework.validation.annotation.Validated;
31 31 import javax.annotation.Resource;
32 32 import java.time.LocalDateTime;
33 33 import java.util.*;
  34 +import java.util.stream.Collectors;
34 35  
35 36 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 37 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -225,25 +226,66 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
225 226  
226 227 @Override
227 228 public PageResult<AppBrokerageUserChildSummaryRespVO> getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId) {
228   - // 1.1 根据昵称过滤用户
229   - List<Long> ids = StrUtil.isBlank(pageReqVO.getNickname())
230   - ? Collections.emptyList()
231   - : convertList(memberUserApi.getUserListByNickname(pageReqVO.getNickname()), MemberUserRespDTO::getId);
232   - // 1.2 生成推广员编号列表
233   - // TODO @疯狂:是不是可以先 1.2 查询出来,然后查询对应的昵称,进行过滤?避免昵称过滤返回的 id 过多;
  229 + // 生成推广员编号列表
234 230 List<Long> bindUserIds = buildBindUserIdsByLevel(userId, pageReqVO.getLevel());
235 231  
236   - // 2. 分页查询
237   - IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
238   - MyBatisUtils.buildPage(pageReqVO), ids, BrokerageRecordBizTypeEnum.ORDER.getType(),
239   - BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
  232 + // 情况一:没有昵称过滤条件时,直接使用数据库的分页查询
  233 + if (StrUtil.isBlank(pageReqVO.getNickname())) {
  234 + // 1.1 分页查询
  235 + IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
  236 + MyBatisUtils.buildPage(pageReqVO), BrokerageRecordBizTypeEnum.ORDER.getType(),
  237 + BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
  238 + );
  239 +
  240 + // 1.2 拼接数据并返回
  241 + List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
  242 + Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
  243 + BrokerageUserConvert.INSTANCE.copyTo(pageResult.getRecords(), userMap);
  244 + return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
  245 + }
  246 +
  247 + // 情况二:有昵称过滤条件时,需要跨模块(Member)过滤
  248 + // 2.1 查询所有匹配的分销用户
  249 + List<AppBrokerageUserChildSummaryRespVO> list = brokerageUserMapper.selectSummaryListByUserId(
  250 + BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
  251 + bindUserIds, pageReqVO.getSortingField()
240 252 );
  253 + if (CollUtil.isEmpty(list)) {
  254 + return PageResult.empty();
  255 + }
241 256  
242   - // 3. 拼接数据并返回
243   - List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
244   - Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
245   - BrokerageUserConvert.INSTANCE.copyTo(pageResult, userMap);
246   - return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
  257 + // 2.2 查出对应的用户信息
  258 + List<MemberUserRespDTO> users = memberUserApi.getUserList(convertList(list, AppBrokerageUserChildSummaryRespVO::getId));
  259 + if (CollUtil.isEmpty(users)) {
  260 + return PageResult.empty();
  261 + }
  262 +
  263 + // 2.3 根据昵称过滤出用户编号
  264 + Map<Long, MemberUserRespDTO> userMap = users.stream()
  265 + .filter(user -> StrUtil.contains(user.getNickname(), pageReqVO.getNickname()))
  266 + .collect(Collectors.toMap(MemberUserRespDTO::getId, dto -> dto));
  267 + if (CollUtil.isEmpty(userMap)) {
  268 + return PageResult.empty();
  269 + }
  270 +
  271 + // 2.4 根据用户编号过滤结果
  272 + list.removeIf(vo -> !userMap.containsKey(vo.getId()));
  273 + if (CollUtil.isEmpty(list)) {
  274 + return PageResult.empty();
  275 + }
  276 +
  277 + // 2.5 处理分页
  278 + List<AppBrokerageUserChildSummaryRespVO> result = list.stream()
  279 + .skip((long) (pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize())
  280 + .limit(pageReqVO.getPageSize())
  281 + .collect(Collectors.toList());
  282 + if (CollUtil.isEmpty(result)) {
  283 + return PageResult.empty();
  284 + }
  285 +
  286 + // 2.6 拼接数据并返回
  287 + BrokerageUserConvert.INSTANCE.copyTo(result, userMap);
  288 + return new PageResult<>(result, (long) list.size());
247 289 }
248 290  
249 291 private boolean isUserCanBind(BrokerageUserDO user) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -40,6 +40,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
40 40 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
41 41 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
42 42 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
  43 +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
43 44 import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
44 45 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
45 46 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
... ... @@ -54,6 +55,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
54 55 import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
55 56 import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
56 57 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
  58 +import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
57 59 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
58 60 import cn.iocoder.yudao.module.trade.service.cart.CartService;
59 61 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
... ... @@ -108,6 +110,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
108 110 private DeliveryExpressService deliveryExpressService;
109 111 @Resource
110 112 private TradeMessageService tradeMessageService;
  113 + @Resource
  114 + private BrokerageUserService brokerageUserService;
  115 + @Resource
  116 + private BrokerageRecordService brokerageRecordService;
111 117  
112 118 @Resource
113 119 private ProductSpuApi productSpuApi;
... ... @@ -130,8 +136,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
130 136 @Resource
131 137 private MemberPointApi memberPointApi;
132 138 @Resource
133   - private BrokerageRecordService brokerageRecordService;
134   - @Resource
135 139 private ProductCommentApi productCommentApi;
136 140  
137 141 @Resource
... ... @@ -301,6 +305,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
301 305 // 6. 插入订单日志
302 306 TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
303 307  
  308 + // 7. 设置订单推广人
  309 + BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
  310 + if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
  311 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setBrokerageUserId(brokerageUser.getBindUserId()));
  312 + }
  313 +
304 314 // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
305 315 }
306 316  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
4 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 5 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
6 6 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
7   -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
8 7 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
9 8 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
10 9 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
... ... @@ -20,7 +19,8 @@ import java.util.Map;
20 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
22 21 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
23   -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
  22 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
  23 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
24 24 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
25 25  
26 26 /**
... ... @@ -82,18 +82,7 @@ public class TradePriceServiceImpl implements TradePriceService {
82 82  
83 83 private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
84 84 // 获得商品 SPU 数组
85   - List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
86   -
87   - // 校验商品 SPU
88   - spus.forEach(spu -> {
89   - if (spu == null) {
90   - throw exception(SPU_NOT_EXISTS);
91   - }
92   - if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
93   - throw exception(SPU_NOT_ENABLE);
94   - }
95   - });
96   - return spus;
  85 + return productSpuApi.getSpuListAndValidate(convertSet(skuList, ProductSkuRespDTO::getSpuId));
97 86 }
98 87  
99 88 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
... ... @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.util.BooleanUtil;
4 4 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
5   -import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
6   -import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
  5 +import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
  6 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
7 7 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
8 8 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
9 9 import lombok.extern.slf4j.Slf4j;
... ... @@ -16,7 +16,6 @@ import java.util.Optional;
16 16  
17 17 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
18 18  
19   -// TODO @疯狂:这个可以搞个单测;
20 19 /**
21 20 * 赠送积分的 {@link TradePriceCalculator} 实现类
22 21 *
... ... @@ -28,14 +27,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
28 27 public class TradePointGiveCalculator implements TradePriceCalculator {
29 28  
30 29 @Resource
31   - private MemberPointApi memberPointApi;
  30 + private MemberConfigApi memberConfigApi;
32 31  
33 32 @Override
34 33 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
35 34 // 1.1 校验积分功能是否开启
36   - int givePointPerYuan = Optional.ofNullable(memberPointApi.getConfig())
37   - .filter(config -> BooleanUtil.isTrue(config.getTradeDeductEnable()))
38   - .map(MemberPointConfigRespDTO::getTradeGivePoint)
  35 + int givePointPerYuan = Optional.ofNullable(memberConfigApi.getConfig())
  36 + .filter(config -> BooleanUtil.isTrue(config.getPointTradeDeductEnable()))
  37 + .map(MemberConfigRespDTO::getPointTradeGivePoint)
39 38 .orElse(0);
40 39 if (givePointPerYuan <= 0) {
41 40 return;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
... ... @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.util.BooleanUtil;
4 4 import cn.hutool.core.util.StrUtil;
5   -import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
6   -import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
  5 +import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
  6 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
7 7 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
8 8 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
9 9 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
... ... @@ -20,7 +20,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
20 20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
21 21 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
22 22  
23   -// TODO @疯狂:搞个单测,嘿嘿;
24 23 /**
25 24 * 使用积分的 {@link TradePriceCalculator} 实现类
26 25 *
... ... @@ -32,19 +31,21 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCU
32 31 public class TradePointUsePriceCalculator implements TradePriceCalculator {
33 32  
34 33 @Resource
35   - private MemberPointApi memberPointApi;
  34 + private MemberConfigApi memberConfigApi;
36 35 @Resource
37 36 private MemberUserApi memberUserApi;
38 37  
39 38 @Override
40 39 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
  40 + // 默认使用积分为 0
  41 + result.setUsePoint(0);
41 42 // 1.1 校验是否使用积分
42 43 if (!BooleanUtil.isTrue(param.getPointStatus())) {
43 44 result.setUsePoint(0);
44 45 return;
45 46 }
46 47 // 1.2 校验积分抵扣是否开启
47   - MemberPointConfigRespDTO config = memberPointApi.getConfig();
  48 + MemberConfigRespDTO config = memberConfigApi.getConfig();
48 49 if (!isDeductPointEnable(config)) {
49 50 return;
50 51 }
... ... @@ -76,20 +77,20 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
76 77 TradePriceCalculatorHelper.recountAllPrice(result);
77 78 }
78 79  
79   - private boolean isDeductPointEnable(MemberPointConfigRespDTO config) {
  80 + private boolean isDeductPointEnable(MemberConfigRespDTO config) {
80 81 return config != null &&
81   - !BooleanUtil.isTrue(config.getTradeDeductEnable()) && // 积分功能是否启用
82   - config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分
  82 + BooleanUtil.isTrue(config.getPointTradeDeductEnable()) && // 积分功能是否启用
  83 + config.getPointTradeDeductUnitPrice() != null && config.getPointTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分
83 84 }
84 85  
85   - private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
  86 + private Integer calculatePointPrice(MemberConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
86 87 // 每个订单最多可以使用的积分数量
87   - if (config.getTradeDeductMaxPrice() != null && config.getTradeDeductMaxPrice() > 0) {
88   - usePoint = Math.min(usePoint, config.getTradeDeductMaxPrice());
  88 + if (config.getPointTradeDeductMaxPrice() != null && config.getPointTradeDeductMaxPrice() > 0) {
  89 + usePoint = Math.min(usePoint, config.getPointTradeDeductMaxPrice());
89 90 }
90 91 // TODO @疯狂:这里应该是,抵扣到只剩下 0.01;
91 92 // 积分优惠金额(分)
92   - int pointPrice = usePoint * config.getTradeDeductUnitPrice();
  93 + int pointPrice = usePoint * config.getPointTradeDeductUnitPrice();
93 94 if (result.getPrice().getPayPrice() <= pointPrice) {
94 95 // 禁止 0 元购
95 96 throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
... ... @@ -99,7 +100,7 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
99 100 // pointPrice = result.getPrice().getPayPrice();
100 101 // // 反推需要扣除的积分
101 102 // usePoint = NumberUtil.toBigDecimal(pointPrice)
102   -// .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
  103 +// .divide(NumberUtil.toBigDecimal(config.getPointTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
103 104 // .intValue();
104 105 // }
105 106 // 记录使用的积分
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml
... ... @@ -2,8 +2,7 @@
2 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3 3 <mapper namespace="cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper">
4 4  
5   - <select id="selectSummaryPageByUserId"
6   - resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
  5 + <sql id="selectSummaryListByUserId">
7 6 SELECT bu.id, bu.bind_user_time AS brokerageTime,
8 7 (SELECT SUM(price) FROM trade_brokerage_record r
9 8 WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = FALSE) AS brokeragePrice,
... ... @@ -14,12 +13,6 @@
14 13 FROM trade_brokerage_user AS bu
15 14 <where>
16 15 bu.deleted = false
17   - <if test="ids != null and ids.size() > 0">
18   - and bu.id in
19   - <foreach collection="ids" open="(" item="id" separator="," close=")">
20   - #{id}
21   - </foreach>
22   - </if>
23 16 <if test="bindUserIds != null and bindUserIds.size() > 0">
24 17 and bu.bind_user_id in
25 18 <foreach collection="bindUserIds" open="(" item="bindUserId" separator="," close=")">
... ... @@ -41,6 +34,15 @@
41 34 ORDER BY bu.bind_user_time DESC
42 35 </otherwise>
43 36 </choose>
  37 + </sql>
  38 +
  39 + <select id="selectSummaryPageByUserId"
  40 + resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
  41 + <include refid="selectSummaryListByUserId" />
  42 + </select>
  43 + <select id="selectSummaryListByUserId"
  44 + resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
  45 + <include refid="selectSummaryListByUserId" />
44 46 </select>
45 47  
46 48 </mapper>
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.price.calculator;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
  5 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
  6 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
  7 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  8 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  9 +import org.junit.jupiter.api.Test;
  10 +import org.mockito.InjectMocks;
  11 +import org.mockito.Mock;
  12 +
  13 +import java.util.ArrayList;
  14 +
  15 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  16 +import static java.util.Arrays.asList;
  17 +import static org.junit.jupiter.api.Assertions.assertEquals;
  18 +import static org.mockito.Mockito.when;
  19 +
  20 +/**
  21 + * {@link TradePointGiveCalculator} 的单元测试类
  22 + *
  23 + * @author owen
  24 + */
  25 +public class TradePointGiveCalculatorTest extends BaseMockitoUnitTest {
  26 +
  27 + @InjectMocks
  28 + private TradePointGiveCalculator tradePointGiveCalculator;
  29 +
  30 + @Mock
  31 + private MemberConfigApi memberConfigApi;
  32 +
  33 + @Test
  34 + public void testCalculate() {
  35 +
  36 + // 准备参数
  37 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  38 + .setUserId(233L)
  39 + .setItems(asList(
  40 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 全局积分
  41 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 全局积分 + SKU 积分
  42 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false), // 全局积分,但是未选中
  43 + new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 全局积分 + SKU 积分,但是未选中
  44 + ));
  45 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  46 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  47 + .setPrice(new TradePriceCalculateRespBO.Price())
  48 + .setPromotions(new ArrayList<>())
  49 + .setItems(asList(
  50 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  51 + .setPrice(100).setSpuId(1L).setGivePoint(0),
  52 + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
  53 + .setPrice(50).setSpuId(2L).setGivePoint(100),
  54 + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(false)
  55 + .setPrice(30).setSpuId(3L).setGivePoint(0),
  56 + new TradePriceCalculateRespBO.OrderItem().setSkuId(40L).setCount(5).setSelected(false)
  57 + .setPrice(60).setSpuId(1L).setGivePoint(100)
  58 + ));
  59 + // 保证价格被初始化上
  60 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  61 + TradePriceCalculatorHelper.recountAllPrice(result);
  62 +
  63 + // mock 方法(积分配置 信息)
  64 + MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
  65 + o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
  66 + .setPointTradeGivePoint(100)); // 1 元赠送多少分
  67 + when(memberConfigApi.getConfig()).thenReturn(memberConfig);
  68 +
  69 + // 调用
  70 + tradePointGiveCalculator.calculate(param, result);
  71 + // 断言:Price 部分
  72 + assertEquals(result.getGivePoint(), 2 * 100 + 3 * 50 + 100);
  73 + // 断言:SKU 1
  74 + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
  75 + assertEquals(orderItem01.getSkuId(), 10L);
  76 + assertEquals(orderItem01.getCount(), 2);
  77 + assertEquals(orderItem01.getPrice(), 100);
  78 + assertEquals(orderItem01.getGivePoint(), 2 * 100); // 全局积分
  79 + // 断言:SKU 2
  80 + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
  81 + assertEquals(orderItem02.getSkuId(), 20L);
  82 + assertEquals(orderItem02.getCount(), 3);
  83 + assertEquals(orderItem02.getPrice(), 50);
  84 + assertEquals(orderItem02.getGivePoint(), 3 * 50 + 100); // 全局积分 + SKU 积分
  85 + // 断言:SKU 3
  86 + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
  87 + assertEquals(orderItem03.getSkuId(), 30L);
  88 + assertEquals(orderItem03.getCount(), 4);
  89 + assertEquals(orderItem03.getPrice(), 30);
  90 + assertEquals(orderItem03.getGivePoint(), 0); // 全局积分,但是未选中
  91 + // 断言:SKU 4
  92 + TradePriceCalculateRespBO.OrderItem orderItem04 = result.getItems().get(3);
  93 + assertEquals(orderItem04.getSkuId(), 40L);
  94 + assertEquals(orderItem04.getCount(), 5);
  95 + assertEquals(orderItem04.getPrice(), 60);
  96 + assertEquals(orderItem04.getGivePoint(), 100); // 全局积分 + SKU 积分,但是未选中
  97 + }
  98 +}
0 99 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.price.calculator;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
  5 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
  6 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  7 +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  8 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  9 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
  10 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  11 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.mockito.InjectMocks;
  14 +import org.mockito.Mock;
  15 +
  16 +import java.util.ArrayList;
  17 +
  18 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  19 +import static java.util.Arrays.asList;
  20 +import static org.junit.jupiter.api.Assertions.assertEquals;
  21 +import static org.junit.jupiter.api.Assertions.assertTrue;
  22 +import static org.mockito.Mockito.when;
  23 +
  24 +/**
  25 + * {@link TradePointUsePriceCalculator } 的单元测试类
  26 + *
  27 + * @author owen
  28 + */
  29 +public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
  30 +
  31 + @InjectMocks
  32 + private TradePointUsePriceCalculator tradePointUsePriceCalculator;
  33 +
  34 + @Mock
  35 + private MemberConfigApi memberConfigApi;
  36 + @Mock
  37 + private MemberUserApi memberUserApi;
  38 +
  39 + @Test
  40 + public void testCalculate_success() {
  41 + // 准备参数
  42 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  43 + .setUserId(233L).setPointStatus(true) // 是否使用积分
  44 + .setItems(asList(
  45 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
  46 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
  47 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
  48 + ));
  49 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  50 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  51 + .setPrice(new TradePriceCalculateRespBO.Price())
  52 + .setPromotions(new ArrayList<>())
  53 + .setItems(asList(
  54 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  55 + .setPrice(100).setSpuId(1L),
  56 + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
  57 + .setPrice(50).setSpuId(2L),
  58 + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
  59 + .setPrice(30).setSpuId(3L)
  60 + ));
  61 + // 保证价格被初始化上
  62 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  63 + TradePriceCalculatorHelper.recountAllPrice(result);
  64 +
  65 + // mock 方法(积分配置 信息)
  66 + MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
  67 + o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
  68 + .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分)
  69 + .setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值
  70 + when(memberConfigApi.getConfig()).thenReturn(memberConfig);
  71 + // mock 方法(会员 信息)
  72 + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
  73 + when(memberUserApi.getUser(user.getId())).thenReturn(user);
  74 +
  75 + // 调用
  76 + tradePointUsePriceCalculator.calculate(param, result);
  77 + // 断言:使用了多少积分
  78 + assertEquals(result.getUsePoint(), 100);
  79 + // 断言:Price 部分
  80 + TradePriceCalculateRespBO.Price price = result.getPrice();
  81 + assertEquals(price.getTotalPrice(), 350);
  82 + assertEquals(price.getPayPrice(), 250);
  83 + assertEquals(price.getPointPrice(), 100);
  84 + // 断言:SKU 1
  85 + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
  86 + assertEquals(orderItem01.getSkuId(), 10L);
  87 + assertEquals(orderItem01.getCount(), 2);
  88 + assertEquals(orderItem01.getPrice(), 100);
  89 + assertEquals(orderItem01.getPointPrice(), 57);
  90 + assertEquals(orderItem01.getPayPrice(), 143);
  91 + // 断言:SKU 2
  92 + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
  93 + assertEquals(orderItem02.getSkuId(), 20L);
  94 + assertEquals(orderItem02.getCount(), 3);
  95 + assertEquals(orderItem02.getPrice(), 50);
  96 + assertEquals(orderItem02.getPointPrice(), 43);
  97 + assertEquals(orderItem02.getPayPrice(), 107);
  98 + // 断言:SKU 3
  99 + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
  100 + assertEquals(orderItem03.getSkuId(), 30L);
  101 + assertEquals(orderItem03.getCount(), 5);
  102 + assertEquals(orderItem03.getPrice(), 30);
  103 + assertEquals(orderItem03.getPointPrice(), 0);
  104 + assertEquals(orderItem03.getPayPrice(), 150);
  105 + // 断言:Promotion 部分
  106 + assertEquals(result.getPromotions().size(), 1);
  107 + TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
  108 + assertEquals(promotion01.getId(), user.getId());
  109 + assertEquals(promotion01.getName(), "积分抵扣");
  110 + assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType());
  111 + assertEquals(promotion01.getTotalPrice(), 350);
  112 + assertEquals(promotion01.getDiscountPrice(), 100);
  113 + assertTrue(promotion01.getMatch());
  114 + assertEquals(promotion01.getDescription(), "积分抵扣:省 1.00 元");
  115 + assertEquals(promotion01.getItems().size(), 2);
  116 + TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
  117 + assertEquals(promotionItem011.getSkuId(), 10L);
  118 + assertEquals(promotionItem011.getTotalPrice(), 200);
  119 + assertEquals(promotionItem011.getDiscountPrice(), 57);
  120 + TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
  121 + assertEquals(promotionItem012.getSkuId(), 20L);
  122 + assertEquals(promotionItem012.getTotalPrice(), 150);
  123 + assertEquals(promotionItem012.getDiscountPrice(), 43);
  124 + }
  125 +
  126 + /**
  127 + * 当用户积分充足时,抵扣的金额为:配置表的“积分抵扣最大值”
  128 + */
  129 + @Test
  130 + public void testCalculate_TradeDeductMaxPrice() {
  131 + // 准备参数
  132 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  133 + .setUserId(233L).setPointStatus(true) // 是否使用积分
  134 + .setItems(asList(
  135 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
  136 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
  137 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
  138 + ));
  139 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  140 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  141 + .setPrice(new TradePriceCalculateRespBO.Price())
  142 + .setPromotions(new ArrayList<>())
  143 + .setItems(asList(
  144 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  145 + .setPrice(100).setSpuId(1L),
  146 + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
  147 + .setPrice(50).setSpuId(2L),
  148 + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
  149 + .setPrice(30).setSpuId(3L)
  150 + ));
  151 + // 保证价格被初始化上
  152 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  153 + TradePriceCalculatorHelper.recountAllPrice(result);
  154 +
  155 + // mock 方法(积分配置 信息)
  156 + MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
  157 + o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
  158 + .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分)
  159 + .setPointTradeDeductMaxPrice(50)); // 积分抵扣最大值
  160 + when(memberConfigApi.getConfig()).thenReturn(memberConfig);
  161 + // mock 方法(会员 信息)
  162 + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
  163 + when(memberUserApi.getUser(user.getId())).thenReturn(user);
  164 +
  165 + // 调用
  166 + tradePointUsePriceCalculator.calculate(param, result);
  167 + // 断言:使用了多少积分
  168 + assertEquals(result.getUsePoint(), 50);
  169 + // 断言:Price 部分
  170 + TradePriceCalculateRespBO.Price price = result.getPrice();
  171 + assertEquals(price.getTotalPrice(), 350);
  172 + assertEquals(price.getPayPrice(), 300);
  173 + assertEquals(price.getPointPrice(), 50);
  174 + // 断言:SKU 1
  175 + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
  176 + assertEquals(orderItem01.getSkuId(), 10L);
  177 + assertEquals(orderItem01.getCount(), 2);
  178 + assertEquals(orderItem01.getPrice(), 100);
  179 + assertEquals(orderItem01.getPointPrice(), 28);
  180 + assertEquals(orderItem01.getPayPrice(), 172);
  181 + // 断言:SKU 2
  182 + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
  183 + assertEquals(orderItem02.getSkuId(), 20L);
  184 + assertEquals(orderItem02.getCount(), 3);
  185 + assertEquals(orderItem02.getPrice(), 50);
  186 + assertEquals(orderItem02.getPointPrice(), 22);
  187 + assertEquals(orderItem02.getPayPrice(), 128);
  188 + // 断言:SKU 3
  189 + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
  190 + assertEquals(orderItem03.getSkuId(), 30L);
  191 + assertEquals(orderItem03.getCount(), 5);
  192 + assertEquals(orderItem03.getPrice(), 30);
  193 + assertEquals(orderItem03.getPointPrice(), 0);
  194 + assertEquals(orderItem03.getPayPrice(), 150);
  195 + // 断言:Promotion 部分
  196 + assertEquals(result.getPromotions().size(), 1);
  197 + TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
  198 + assertEquals(promotion01.getId(), user.getId());
  199 + assertEquals(promotion01.getName(), "积分抵扣");
  200 + assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType());
  201 + assertEquals(promotion01.getTotalPrice(), 350);
  202 + assertEquals(promotion01.getDiscountPrice(), 50);
  203 + assertTrue(promotion01.getMatch());
  204 + assertEquals(promotion01.getDescription(), "积分抵扣:省 0.50 元");
  205 + assertEquals(promotion01.getItems().size(), 2);
  206 + TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
  207 + assertEquals(promotionItem011.getSkuId(), 10L);
  208 + assertEquals(promotionItem011.getTotalPrice(), 200);
  209 + assertEquals(promotionItem011.getDiscountPrice(), 28);
  210 + TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
  211 + assertEquals(promotionItem012.getSkuId(), 20L);
  212 + assertEquals(promotionItem012.getTotalPrice(), 150);
  213 + assertEquals(promotionItem012.getDiscountPrice(), 22);
  214 + }
  215 +
  216 + /**
  217 + * 订单不使用积分,不会产生优惠
  218 + */
  219 + @Test
  220 + public void testCalculate_PointStatusFalse() {
  221 + // 准备参数
  222 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  223 + .setUserId(233L).setPointStatus(false) // 是否使用积分
  224 + .setItems(asList(
  225 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
  226 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
  227 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
  228 + ));
  229 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  230 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  231 + .setPrice(new TradePriceCalculateRespBO.Price())
  232 + .setPromotions(new ArrayList<>())
  233 + .setItems(asList(
  234 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  235 + .setPrice(100).setSpuId(1L),
  236 + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
  237 + .setPrice(50).setSpuId(2L),
  238 + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
  239 + .setPrice(30).setSpuId(3L)
  240 + ));
  241 + // 保证价格被初始化上
  242 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  243 + TradePriceCalculatorHelper.recountAllPrice(result);
  244 +
  245 + // 调用
  246 + tradePointUsePriceCalculator.calculate(param, result);
  247 + // 断言:没有使用积分
  248 + assertNotUsePoint(result);
  249 + }
  250 +
  251 + /**
  252 + * 会员积分不足,不会产生优惠
  253 + */
  254 + @Test
  255 + public void testCalculate_UserPointNotEnough() {
  256 + // 准备参数
  257 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  258 + .setUserId(233L).setPointStatus(true) // 是否使用积分
  259 + .setItems(asList(
  260 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
  261 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
  262 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
  263 + ));
  264 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  265 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  266 + .setPrice(new TradePriceCalculateRespBO.Price())
  267 + .setPromotions(new ArrayList<>())
  268 + .setItems(asList(
  269 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  270 + .setPrice(100).setSpuId(1L),
  271 + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
  272 + .setPrice(50).setSpuId(2L),
  273 + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
  274 + .setPrice(30).setSpuId(3L)
  275 + ));
  276 + // 保证价格被初始化上
  277 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  278 + TradePriceCalculatorHelper.recountAllPrice(result);
  279 +
  280 + // mock 方法(积分配置 信息)
  281 + MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
  282 + o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
  283 + .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分)
  284 + .setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值
  285 + when(memberConfigApi.getConfig()).thenReturn(memberConfig);
  286 + // mock 方法(会员 信息)
  287 + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(0));
  288 + when(memberUserApi.getUser(user.getId())).thenReturn(user);
  289 +
  290 + // 调用
  291 + tradePointUsePriceCalculator.calculate(param, result);
  292 +
  293 + // 断言:没有使用积分
  294 + assertNotUsePoint(result);
  295 + }
  296 +
  297 + /**
  298 + * 断言:没有使用积分
  299 + */
  300 + private static void assertNotUsePoint(TradePriceCalculateRespBO result) {
  301 + // 断言:使用了多少积分
  302 + assertEquals(result.getUsePoint(), 0);
  303 + // 断言:Price 部分
  304 + TradePriceCalculateRespBO.Price price = result.getPrice();
  305 + assertEquals(price.getTotalPrice(), 350);
  306 + assertEquals(price.getPayPrice(), 350);
  307 + assertEquals(price.getPointPrice(), 0);
  308 + // 断言:SKU 1
  309 + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
  310 + assertEquals(orderItem01.getSkuId(), 10L);
  311 + assertEquals(orderItem01.getCount(), 2);
  312 + assertEquals(orderItem01.getPrice(), 100);
  313 + assertEquals(orderItem01.getPointPrice(), 0);
  314 + assertEquals(orderItem01.getPayPrice(), 200);
  315 + // 断言:SKU 2
  316 + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
  317 + assertEquals(orderItem02.getSkuId(), 20L);
  318 + assertEquals(orderItem02.getCount(), 3);
  319 + assertEquals(orderItem02.getPrice(), 50);
  320 + assertEquals(orderItem02.getPointPrice(), 0);
  321 + assertEquals(orderItem02.getPayPrice(), 150);
  322 + // 断言:SKU 3
  323 + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
  324 + assertEquals(orderItem03.getSkuId(), 30L);
  325 + assertEquals(orderItem03.getCount(), 5);
  326 + assertEquals(orderItem03.getPrice(), 30);
  327 + assertEquals(orderItem03.getPointPrice(), 0);
  328 + assertEquals(orderItem03.getPayPrice(), 150);
  329 + // 断言:Promotion 部分
  330 + assertEquals(result.getPromotions().size(), 0);
  331 + }
  332 +}
0 333 \ No newline at end of file
... ...
yudao-module-member/yudao-module-member-api/pom.xml
... ... @@ -22,6 +22,11 @@
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
24 24  
  25 + <dependency>
  26 + <groupId>cn.iocoder.boot</groupId>
  27 + <artifactId>yudao-spring-boot-starter-mq</artifactId>
  28 + <scope>compile</scope>
  29 + </dependency>
25 30 <!-- 参数校验 -->
26 31 <dependency>
27 32 <groupId>org.springframework.boot</groupId>
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.api.config;
  2 +
  3 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
  4 +
  5 +/**
  6 + * 用户配置 API 接口
  7 + *
  8 + * @author owen
  9 + */
  10 +public interface MemberConfigApi {
  11 +
  12 + /**
  13 + * 获得积分配置
  14 + *
  15 + * @return 积分配置
  16 + */
  17 + MemberConfigRespDTO getConfig();
  18 +}
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/dto/MemberPointConfigRespDTO.java renamed to yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/dto/MemberConfigRespDTO.java
1   -package cn.iocoder.yudao.module.member.api.point.dto;
  1 +package cn.iocoder.yudao.module.member.api.config.dto;
2 2  
3 3 import lombok.Data;
4 4  
... ... @@ -8,25 +8,25 @@ import lombok.Data;
8 8 * @author 芋道源码
9 9 */
10 10 @Data
11   -public class MemberPointConfigRespDTO {
  11 +public class MemberConfigRespDTO {
12 12  
13 13 /**
14 14 * 积分抵扣开关
15 15 */
16   - private Boolean tradeDeductEnable;
  16 + private Boolean pointTradeDeductEnable;
17 17 /**
18 18 * 积分抵扣,单位:分
19 19 * <p>
20 20 * 1 积分抵扣多少分
21 21 */
22   - private Integer tradeDeductUnitPrice;
  22 + private Integer pointTradeDeductUnitPrice;
23 23 /**
24 24 * 积分抵扣最大值
25 25 */
26   - private Integer tradeDeductMaxPrice;
  26 + private Integer pointTradeDeductMaxPrice;
27 27 /**
28 28 * 1 元赠送多少分
29 29 */
30   - private Integer tradeGivePoint;
  30 + private Integer pointTradeGivePoint;
31 31  
32 32 }
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
1 1 package cn.iocoder.yudao.module.member.api.point;
2 2  
3   -import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
4 3 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
5 4  
6 5 import javax.validation.constraints.Min;
... ... @@ -12,14 +11,6 @@ import javax.validation.constraints.Min;
12 11 */
13 12 public interface MemberPointApi {
14 13  
15   - // TODO @疯狂:这个我们要不要搞成通用的会员配置?MemberConfig?
16   - /**
17   - * 获得积分配置
18   - *
19   - * @return 积分配置
20   - */
21   - MemberPointConfigRespDTO getConfig();
22   -
23 14 /**
24 15 * 增加用户积分
25 16 *
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
... ... @@ -38,6 +38,7 @@ public interface ErrorCodeConstants {
38 38 //========== 签到配置 1-004-009-000 ==========
39 39 ErrorCode SIGN_IN_CONFIG_NOT_EXISTS = new ErrorCode(1_004_009_000, "签到天数规则不存在");
40 40 ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1_004_009_001, "签到天数规则已存在");
  41 + ErrorCode SIGN_IN_CONFIG_AWARD_EMPTY = new ErrorCode(1_004_009_002, "签到奖励积分和经验不能同时为空");
41 42  
42 43 //========== 签到配置 1-004-010-000 ==========
43 44 ErrorCode SIGN_IN_RECORD_TODAY_EXISTS = new ErrorCode(1_004_010_000,"今日已签到,请勿重复签到");
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
... ... @@ -17,6 +17,7 @@ import java.util.Objects;
17 17 public enum MemberPointBizTypeEnum implements IntArrayValuable {
18 18  
19 19 SIGN(1, "签到", "签到获得 {} 积分", true),
  20 + ADMIN(2, "管理员修改", "管理员修改 {} 积分", true),
20 21 ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分
21 22 ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时,退回积分
22 23 ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时,扣减积分
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/RegisterCouponSendMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.mq.message.user;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +/**
  10 + * 新人券发放消息
  11 + *
  12 + * @author owen
  13 + */
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class RegisterCouponSendMessage extends AbstractStreamMessage {
  17 +
  18 + /**
  19 + * 用户编号
  20 + */
  21 + @NotNull(message = "用户编号不能为空")
  22 + private Long userId;
  23 +
  24 + @Override
  25 + public String getStreamKey() {
  26 + return "member.register-coupon.send";
  27 + }
  28 +
  29 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.api.config;
  2 +
  3 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
  4 +import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
  5 +import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
  6 +import org.springframework.stereotype.Service;
  7 +import org.springframework.validation.annotation.Validated;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 用户配置 API 实现类
  13 + *
  14 + * @author owen
  15 + */
  16 +@Service
  17 +@Validated
  18 +public class MemberConfigApiImpl implements MemberConfigApi {
  19 +
  20 + @Resource
  21 + private MemberConfigService memberConfigService;
  22 +
  23 + @Override
  24 + public MemberConfigRespDTO getConfig() {
  25 + return MemberConfigConvert.INSTANCE.convert01(memberConfigService.getConfig());
  26 + }
  27 +
  28 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
1 1 package cn.iocoder.yudao.module.member.api.point;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4   -import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
5   -import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
6 4 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
7   -import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
8 5 import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
9 6 import org.springframework.stereotype.Service;
10 7 import org.springframework.validation.annotation.Validated;
... ... @@ -25,13 +22,6 @@ public class MemberPointApiImpl implements MemberPointApi {
25 22  
26 23 @Resource
27 24 private MemberPointRecordService memberPointRecordService;
28   - @Resource
29   - private MemberPointConfigService memberPointConfigService;
30   -
31   - @Override
32   - public MemberPointConfigRespDTO getConfig() {
33   - return MemberPointConfigConvert.INSTANCE.convert01(memberPointConfigService.getPointConfig());
34   - }
35 25  
36 26 @Override
37 27 public void addPoint(Long userId, Integer point, Integer bizType, String bizId) {
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/MemberConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.admin.config;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
  5 +import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
  6 +import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
  7 +import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
  8 +import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import org.springframework.security.access.prepost.PreAuthorize;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.*;
  14 +
  15 +import javax.annotation.Resource;
  16 +import javax.validation.Valid;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - 会员设置")
  21 +@RestController
  22 +@RequestMapping("/member/config")
  23 +@Validated
  24 +public class MemberConfigController {
  25 +
  26 + @Resource
  27 + private MemberConfigService memberConfigService;
  28 +
  29 + @PutMapping("/save")
  30 + @Operation(summary = "保存会员配置")
  31 + @PreAuthorize("@ss.hasPermission('member:config:save')")
  32 + public CommonResult<Boolean> saveConfig(@Valid @RequestBody MemberConfigSaveReqVO saveReqVO) {
  33 + memberConfigService.saveConfig(saveReqVO);
  34 + return success(true);
  35 + }
  36 +
  37 + @GetMapping("/get")
  38 + @Operation(summary = "获得会员配置")
  39 + @PreAuthorize("@ss.hasPermission('member:config:query')")
  40 + public CommonResult<MemberConfigRespVO> getConfig() {
  41 + MemberConfigDO config = memberConfigService.getConfig();
  42 + return success(MemberConfigConvert.INSTANCE.convert(config));
  43 + }
  44 +
  45 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigBaseVO.java
1   -package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
  1 +package cn.iocoder.yudao.module.member.controller.admin.config.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
... ... @@ -6,26 +6,26 @@ import lombok.Data;
6 6 import javax.validation.constraints.NotNull;
7 7  
8 8 /**
9   - * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用
  9 + * 会员配置 Base VO,提供给添加、修改、详细的子 VO 使用
10 10 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
11 11 */
12 12 @Data
13   -public class MemberPointConfigBaseVO {
  13 +public class MemberConfigBaseVO {
14 14  
15 15 @Schema(description = "积分抵扣开关", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
16 16 @NotNull(message = "积分抵扣开发不能为空")
17   - private Boolean tradeDeductEnable;
  17 + private Boolean pointTradeDeductEnable;
18 18  
19 19 @Schema(description = "积分抵扣,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "13506")
20 20 @NotNull(message = "积分抵扣不能为空")
21   - private Integer tradeDeductUnitPrice;
  21 + private Integer pointTradeDeductUnitPrice;
22 22  
23 23 @Schema(description = "积分抵扣最大值", requiredMode = Schema.RequiredMode.REQUIRED, example = "32428")
24 24 @NotNull(message = "积分抵扣最大值不能为空")
25   - private Integer tradeDeductMaxPrice;
  25 + private Integer pointTradeDeductMaxPrice;
26 26  
27 27 @Schema(description = "1 元赠送多少分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
28 28 @NotNull(message = "1 元赠送积分不能为空")
29   - private Integer tradeGivePoint;
  29 + private Integer pointTradeGivePoint;
30 30  
31 31 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigRespVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigRespVO.java
1   -package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
  1 +package cn.iocoder.yudao.module.member.controller.admin.config.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5 import lombok.EqualsAndHashCode;
6 6 import lombok.ToString;
7 7  
8   -@Schema(description = "管理后台 - 会员积分配置 Response VO")
  8 +@Schema(description = "管理后台 - 会员配置 Response VO")
9 9 @Data
10 10 @EqualsAndHashCode(callSuper = true)
11 11 @ToString(callSuper = true)
12   -public class MemberPointConfigRespVO extends MemberPointConfigBaseVO {
  12 +public class MemberConfigRespVO extends MemberConfigBaseVO {
13 13  
14 14 @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
15 15 private Long id;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigSaveReqVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigSaveReqVO.java
1   -package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
  1 +package cn.iocoder.yudao.module.member.controller.admin.config.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5 import lombok.EqualsAndHashCode;
6 6 import lombok.ToString;
7 7  
8   -@Schema(description = "管理后台 - 会员积分配置保存 Request VO")
  8 +@Schema(description = "管理后台 - 会员配置保存 Request VO")
9 9 @Data
10 10 @EqualsAndHashCode(callSuper = true)
11 11 @ToString(callSuper = true)
12   -public class MemberPointConfigSaveReqVO extends MemberPointConfigBaseVO {
  12 +public class MemberConfigSaveReqVO extends MemberConfigBaseVO {
13 13 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/MemberPointConfigController.java deleted
1   -package cn.iocoder.yudao.module.member.controller.admin.point;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigRespVO;
5   -import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
6   -import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
7   -import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
8   -import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
9   -import io.swagger.v3.oas.annotations.Operation;
10   -import io.swagger.v3.oas.annotations.tags.Tag;
11   -import org.springframework.security.access.prepost.PreAuthorize;
12   -import org.springframework.validation.annotation.Validated;
13   -import org.springframework.web.bind.annotation.*;
14   -
15   -import javax.annotation.Resource;
16   -import javax.validation.Valid;
17   -
18   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
19   -
20   -@Tag(name = "管理后台 - 会员积分设置")
21   -@RestController
22   -@RequestMapping("/member/point/config")
23   -@Validated
24   -public class MemberPointConfigController {
25   -
26   - @Resource
27   - private MemberPointConfigService memberPointConfigService;
28   -
29   - @PutMapping("/save")
30   - @Operation(summary = "保存会员积分配置")
31   - @PreAuthorize("@ss.hasPermission('point:config:save')")
32   - public CommonResult<Boolean> savePointConfig(@Valid @RequestBody MemberPointConfigSaveReqVO saveReqVO) {
33   - memberPointConfigService.savePointConfig(saveReqVO);
34   - return success(true);
35   - }
36   -
37   - @GetMapping("/get")
38   - @Operation(summary = "获得会员积分配置")
39   - @PreAuthorize("@ss.hasPermission('point:config:query')")
40   - public CommonResult<MemberPointConfigRespVO> getPointConfig() {
41   - MemberPointConfigDO config = memberPointConfigService.getPointConfig();
42   - return success(MemberPointConfigConvert.INSTANCE.convert(config));
43   - }
44   -
45   -}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigBaseVO.java
... ... @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
6 6 import lombok.Data;
7 7  
8 8 import javax.validation.constraints.NotNull;
  9 +import javax.validation.constraints.PositiveOrZero;
9 10  
10 11 /**
11 12 * 签到规则 Base VO,提供给添加、修改、详细的子 VO 使用
... ... @@ -20,8 +21,14 @@ public class MemberSignInConfigBaseVO {
20 21  
21 22 @Schema(description = "奖励积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
22 23 @NotNull(message = "奖励积分不能为空")
  24 + @PositiveOrZero(message = "奖励积分不能小于 0")
23 25 private Integer point;
24 26  
  27 + @Schema(description = "奖励经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  28 + @NotNull(message = "奖励经验不能为空")
  29 + @PositiveOrZero(message = "奖励经验不能小于 0")
  30 + private Integer experience;
  31 +
25 32 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
26 33 @NotNull(message = "状态不能为空")
27 34 @InEnum(CommonStatusEnum.class)
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordRespVO.java
... ... @@ -21,7 +21,7 @@ public class MemberSignInRecordRespVO {
21 21 @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
22 22 private Integer day;
23 23  
24   - @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  24 + @Schema(description = "签到的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
25 25 private Integer point;
26 26  
27 27 @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
... ... @@ -3,17 +3,16 @@ package cn.iocoder.yudao.module.member.controller.admin.user;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
7   -import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserRespVO;
8   -import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateLevelReqVO;
9   -import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.admin.user.vo.*;
10 7 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
11 8 import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
12 9 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
13 10 import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
14 11 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
  12 +import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
15 13 import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
16 14 import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
  15 +import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
17 16 import cn.iocoder.yudao.module.member.service.tag.MemberTagService;
18 17 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
19 18 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -33,6 +32,7 @@ import java.util.stream.Collectors;
33 32  
34 33 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
35 34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  35 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
36 36  
37 37 @Tag(name = "管理后台 - 会员用户")
38 38 @RestController
... ... @@ -48,6 +48,8 @@ public class MemberUserController {
48 48 private MemberLevelService memberLevelService;
49 49 @Resource
50 50 private MemberGroupService memberGroupService;
  51 + @Resource
  52 + private MemberPointRecordService memberPointRecordService;
51 53  
52 54 @PutMapping("/update")
53 55 @Operation(summary = "更新会员用户")
... ... @@ -65,6 +67,23 @@ public class MemberUserController {
65 67 return success(true);
66 68 }
67 69  
  70 + @PutMapping("/update-point")
  71 + @Operation(summary = "更新会员用户积分")
  72 + @PreAuthorize("@ss.hasPermission('member:user:update-point')")
  73 + public CommonResult<Boolean> updateUserPoint(@Valid @RequestBody MemberUserUpdatePointReqVO updateReqVO) {
  74 + memberPointRecordService.createPointRecord(updateReqVO.getId(), updateReqVO.getPoint(),
  75 + MemberPointBizTypeEnum.ADMIN, String.valueOf(getLoginUserId()));
  76 + return success(true);
  77 + }
  78 +
  79 + @PutMapping("/update-balance")
  80 + @Operation(summary = "更新会员用户余额")
  81 + @PreAuthorize("@ss.hasPermission('member:user:update-balance')")
  82 + public CommonResult<Boolean> updateUserBalance(@Valid @RequestBody Long id) {
  83 + // todo @jason:增加一个【修改余额】
  84 + return success(true);
  85 + }
  86 +
68 87 @GetMapping("/get")
69 88 @Operation(summary = "获得会员用户")
70 89 @Parameter(name = "id", description = "编号", required = true, example = "1024")
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.member.controller.admin.user.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6 5 import lombok.ToString;
7 6  
8 7 import javax.validation.constraints.NotBlank;
... ... @@ -10,9 +9,8 @@ import javax.validation.constraints.NotNull;
10 9  
11 10 @Schema(description = "管理后台 - 会员用户 修改等级 Request VO")
12 11 @Data
13   -@EqualsAndHashCode(callSuper = true)
14 12 @ToString(callSuper = true)
15   -public class MemberUserUpdateLevelReqVO extends MemberUserBaseVO {
  13 +public class MemberUserUpdateLevelReqVO {
16 14  
17 15 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
18 16 @NotNull(message = "用户编号不能为空")
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.admin.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.ToString;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "管理后台 - 会员用户 修改积分 Request VO")
  10 +@Data
  11 +@ToString(callSuper = true)
  12 +public class MemberUserUpdatePointReqVO {
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
  15 + @NotNull(message = "用户编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "变动积分,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  19 + @NotNull(message = "变动积分不能为空")
  20 + private Integer point;
  21 +
  22 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java
1 1 package cn.iocoder.yudao.module.member.controller.app.signin;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
5   -import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
6 4 import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
7 5 import io.swagger.v3.oas.annotations.Operation;
8 6 import io.swagger.v3.oas.annotations.tags.Tag;
9 7 import org.springframework.web.bind.annotation.GetMapping;
10   -import org.springframework.web.bind.annotation.PostMapping;
11 8 import org.springframework.web.bind.annotation.RequestMapping;
12 9 import org.springframework.web.bind.annotation.RestController;
13 10  
... ... @@ -29,17 +26,8 @@ public class AppMemberSignInController {
29 26 // TODO @xiaqing:合并到 AppMemberSignInRecordController 的 getSignInRecordSummary 里哈。
30 27 @Operation(summary = "个人签到信息")
31 28 @GetMapping("/get-summary")
32   - public CommonResult getUserSummary(){
  29 + public CommonResult getUserSummary() {
33 30 return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
34 31 }
35 32  
36   - // TODO @xiaqing:泛型:
37   - // TODO @xiaqing:合并到 AppMemberSignInRecordController 的 createSignInRecord 里哈。
38   - @Operation(summary = "会员签到")
39   - @PostMapping("/create")
40   - public CommonResult create(){
41   - MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
42   - return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
43   - }
44   -
45 33 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java
... ... @@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
18 18 import org.springframework.web.bind.annotation.RestController;
19 19  
20 20 import javax.annotation.Resource;
21   -import java.time.LocalDateTime;
22 21  
23 22 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
24 23 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
... ... @@ -50,16 +49,12 @@ public class AppMemberSignInRecordController {
50 49 return success(respVO);
51 50 }
52 51  
53   - // TODO 芋艿:临时 mock => UserSignController.info
54 52 @PostMapping("/create")
55 53 @Operation(summary = "签到")
56 54 @PreAuthenticated
57 55 public CommonResult<AppMemberSignInRecordRespVO> createSignInRecord() {
58   - AppMemberSignInRecordRespVO respVO = new AppMemberSignInRecordRespVO()
59   - .setPoint(10)
60   - .setDay(10)
61   - .setCreateTime(LocalDateTime.now());
62   - return success(respVO);
  56 + MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
  57 + return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
63 58 }
64 59  
65 60 @GetMapping("/page")
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInRecordRespVO.java
... ... @@ -12,9 +12,12 @@ public class AppMemberSignInRecordRespVO {
12 12 @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
13 13 private Integer day;
14 14  
15   - @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  15 + @Schema(description = "签到的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
16 16 private Integer point;
17 17  
  18 + @Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  19 + private Integer experience;
  20 +
18 21 @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
19 22 private LocalDateTime createTime;
20 23  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordRespVO.java
... ... @@ -15,6 +15,9 @@ public class AppMemberSignInRecordRespVO {
15 15 @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
16 16 private Integer point;
17 17  
  18 + @Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  19 + private Integer experience;
  20 +
18 21 @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
19 22 private LocalDateTime createTime;
20 23  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/config/MemberConfigConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.convert.config;
  2 +
  3 +import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
  4 +import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
  5 +import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
  6 +import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +/**
  11 + * 会员配置 Convert
  12 + *
  13 + * @author QingX
  14 + */
  15 +@Mapper
  16 +public interface MemberConfigConvert {
  17 +
  18 + MemberConfigConvert INSTANCE = Mappers.getMapper(MemberConfigConvert.class);
  19 +
  20 + MemberConfigRespVO convert(MemberConfigDO bean);
  21 +
  22 + MemberConfigDO convert(MemberConfigSaveReqVO bean);
  23 +
  24 + MemberConfigRespDTO convert01(MemberConfigDO config);
  25 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/point/MemberPointConfigConvert.java deleted
1   -package cn.iocoder.yudao.module.member.convert.point;
2   -
3   -import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
4   -import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigRespVO;
5   -import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
6   -import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
7   -import org.mapstruct.Mapper;
8   -import org.mapstruct.factory.Mappers;
9   -
10   -/**
11   - * 会员积分配置 Convert
12   - *
13   - * @author QingX
14   - */
15   -@Mapper
16   -public interface MemberPointConfigConvert {
17   -
18   - MemberPointConfigConvert INSTANCE = Mappers.getMapper(MemberPointConfigConvert.class);
19   -
20   - MemberPointConfigRespVO convert(MemberPointConfigDO bean);
21   -
22   - MemberPointConfigDO convert(MemberPointConfigSaveReqVO bean);
23   -
24   - MemberPointConfigRespDTO convert01(MemberPointConfigDO pointConfig);
25   -}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java
1   -package cn.iocoder.yudao.module.member.dal.dataobject.point;
  1 +package cn.iocoder.yudao.module.member.dal.dataobject.config;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import com.baomidou.mybatisplus.annotation.KeySequence;
... ... @@ -7,19 +7,19 @@ import com.baomidou.mybatisplus.annotation.TableName;
7 7 import lombok.*;
8 8  
9 9 /**
10   - * 会员积分配置 DO
  10 + * 会员配置 DO
11 11 *
12 12 * @author QingX
13 13 */
14   -@TableName(value = "member_point_config", autoResultMap = true)
15   -@KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  14 +@TableName(value = "member_config", autoResultMap = true)
  15 +@KeySequence("member_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
16 16 @Data
17 17 @EqualsAndHashCode(callSuper = true)
18 18 @ToString(callSuper = true)
19 19 @Builder
20 20 @NoArgsConstructor
21 21 @AllArgsConstructor
22   -public class MemberPointConfigDO extends BaseDO {
  22 +public class MemberConfigDO extends BaseDO {
23 23  
24 24 /**
25 25 * 自增主键
... ... @@ -29,20 +29,20 @@ public class MemberPointConfigDO extends BaseDO {
29 29 /**
30 30 * 积分抵扣开关
31 31 */
32   - private Boolean tradeDeductEnable;
  32 + private Boolean pointTradeDeductEnable;
33 33 /**
34 34 * 积分抵扣,单位:分
35 35 *
36 36 * 1 积分抵扣多少分
37 37 */
38   - private Integer tradeDeductUnitPrice;
  38 + private Integer pointTradeDeductUnitPrice;
39 39 /**
40 40 * 积分抵扣最大值
41 41 */
42   - private Integer tradeDeductMaxPrice;
  42 + private Integer pointTradeDeductMaxPrice;
43 43 /**
44 44 * 1 元赠送多少分
45 45 */
46   - private Integer tradeGivePoint;
  46 + private Integer pointTradeGivePoint;
47 47  
48 48 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInConfigDO.java
... ... @@ -35,6 +35,10 @@ public class MemberSignInConfigDO extends BaseDO {
35 35 * 奖励积分
36 36 */
37 37 private Integer point;
  38 + /**
  39 + * 奖励经验
  40 + */
  41 + private Integer experience;
38 42  
39 43 /**
40 44 * 状态
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInRecordDO.java
... ... @@ -35,10 +35,12 @@ public class MemberSignInRecordDO extends BaseDO {
35 35 */
36 36 private Integer day;
37 37 /**
38   - * 签到的分数
  38 + * 签到的积分
39 39 */
40 40 private Integer point;
41   -
42   - // TODO 疯狂:签到的经验
  41 + /**
  42 + * 签到的经验
  43 + */
  44 + private Integer experience;
43 45  
44 46 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/point/MemberPointConfigMapper.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/config/MemberConfigMapper.java
1   -package cn.iocoder.yudao.module.member.dal.mysql.point;
  1 +package cn.iocoder.yudao.module.member.dal.mysql.config;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4   -import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
  4 +import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
5 5 import org.apache.ibatis.annotations.Mapper;
6 6  
7 7 /**
... ... @@ -10,5 +10,5 @@ import org.apache.ibatis.annotations.Mapper;
10 10 * @author QingX
11 11 */
12 12 @Mapper
13   -public interface MemberPointConfigMapper extends BaseMapperX<MemberPointConfigDO> {
  13 +public interface MemberConfigMapper extends BaseMapperX<MemberConfigDO> {
14 14 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/RegisterCouponProducer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.mq.producer.user;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
  4 +import cn.iocoder.yudao.module.member.mq.message.user.RegisterCouponSendMessage;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * 新人券发放 Producer
  12 + *
  13 + * @author owen
  14 + */
  15 +@Slf4j
  16 +@Component
  17 +public class RegisterCouponProducer {
  18 +
  19 + @Resource
  20 + private RedisMQTemplate redisMQTemplate;
  21 +
  22 + /**
  23 + * 发送 {@link RegisterCouponSendMessage} 消息
  24 + *
  25 + * @param userId 用户编号
  26 + */
  27 + public void sendMailSendMessage(Long userId) {
  28 + redisMQTemplate.send(new RegisterCouponSendMessage().setUserId(userId));
  29 + }
  30 +
  31 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointConfigService.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigService.java
1   -package cn.iocoder.yudao.module.member.service.point;
  1 +package cn.iocoder.yudao.module.member.service.config;
2 2  
3   -import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
4   -import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
  3 +import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
  4 +import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
5 5  
6 6 import javax.validation.Valid;
7 7  
8 8 /**
9   - * 会员积分配置 Service 接口
  9 + * 会员配置 Service 接口
10 10 *
11 11 * @author QingX
12 12 */
13   -public interface MemberPointConfigService {
  13 +public interface MemberConfigService {
14 14  
15 15 /**
16   - * 保存会员积分配置
  16 + * 保存会员配置
17 17 *
18 18 * @param saveReqVO 更新信息
19 19 */
20   - void savePointConfig(@Valid MemberPointConfigSaveReqVO saveReqVO);
  20 + void saveConfig(@Valid MemberConfigSaveReqVO saveReqVO);
21 21  
22 22 /**
23   - * 获得会员积分配置
  23 + * 获得会员配置
24 24 *
25 25 * @return 积分配置
26 26 */
27   - MemberPointConfigDO getPointConfig();
  27 + MemberConfigDO getConfig();
28 28  
29 29 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointConfigServiceImpl.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigServiceImpl.java
1   -package cn.iocoder.yudao.module.member.service.point;
  1 +package cn.iocoder.yudao.module.member.service.config;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4   -import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
5   -import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
6   -import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
7   -import cn.iocoder.yudao.module.member.dal.mysql.point.MemberPointConfigMapper;
  4 +import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
  5 +import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
  6 +import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
  7 +import cn.iocoder.yudao.module.member.dal.mysql.config.MemberConfigMapper;
8 8 import org.springframework.stereotype.Service;
9 9 import org.springframework.validation.annotation.Validated;
10 10  
... ... @@ -12,32 +12,32 @@ import javax.annotation.Resource;
12 12 import java.util.List;
13 13  
14 14 /**
15   - * 会员积分配置 Service 实现类
  15 + * 会员配置 Service 实现类
16 16 *
17 17 * @author QingX
18 18 */
19 19 @Service
20 20 @Validated
21   -public class MemberPointConfigServiceImpl implements MemberPointConfigService {
  21 +public class MemberConfigServiceImpl implements MemberConfigService {
22 22  
23 23 @Resource
24   - private MemberPointConfigMapper memberPointConfigMapper;
  24 + private MemberConfigMapper memberConfigMapper;
25 25  
26 26 @Override
27   - public void savePointConfig(MemberPointConfigSaveReqVO saveReqVO) {
  27 + public void saveConfig(MemberConfigSaveReqVO saveReqVO) {
28 28 // 存在,则进行更新
29   - MemberPointConfigDO dbConfig = getPointConfig();
  29 + MemberConfigDO dbConfig = getConfig();
30 30 if (dbConfig != null) {
31   - memberPointConfigMapper.updateById(MemberPointConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
  31 + memberConfigMapper.updateById(MemberConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
32 32 return;
33 33 }
34 34 // 不存在,则进行插入
35   - memberPointConfigMapper.insert(MemberPointConfigConvert.INSTANCE.convert(saveReqVO));
  35 + memberConfigMapper.insert(MemberConfigConvert.INSTANCE.convert(saveReqVO));
36 36 }
37 37  
38 38 @Override
39   - public MemberPointConfigDO getPointConfig() {
40   - List<MemberPointConfigDO> list = memberPointConfigMapper.selectList();
  39 + public MemberConfigDO getConfig() {
  40 + List<MemberConfigDO> list = memberConfigMapper.selectList();
41 41 return CollectionUtils.getFirst(list);
42 42 }
43 43  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java
... ... @@ -239,7 +239,8 @@ public class MemberLevelServiceImpl implements MemberLevelService {
239 239  
240 240 // 1. 创建经验记录
241 241 MemberUserDO user = memberUserService.getUser(userId);
242   - int userExperience = NumberUtil.max(user.getExperience() + experience, 0); // 防止扣出负数
  242 + Integer userExperience = ObjUtil.defaultIfNull(user.getExperience(), 0);
  243 + userExperience = NumberUtil.max(userExperience + experience, 0); // 防止扣出负数
243 244 MemberLevelRecordDO levelRecord = new MemberLevelRecordDO()
244 245 .setUserId(user.getId())
245 246 .setExperience(experience)
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointRecordServiceImpl.java
... ... @@ -66,6 +66,9 @@ public class MemberPointRecordServiceImpl implements MemberPointRecordService {
66 66 @Override
67 67 @Transactional(rollbackFor = Exception.class)
68 68 public void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId) {
  69 + if (point == 0) {
  70 + return;
  71 + }
69 72 // 1. 校验用户积分余额
70 73 MemberUserDO user = memberUserService.getUser(userId);
71 74 Integer userPoint = ObjectUtil.defaultIfNull(user.getPoint(), 0);
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInConfigServiceImpl.java
... ... @@ -11,10 +11,10 @@ import org.springframework.validation.annotation.Validated;
11 11 import javax.annotation.Resource;
12 12 import java.util.Comparator;
13 13 import java.util.List;
  14 +import java.util.Objects;
14 15  
15 16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
16   -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_CONFIG_EXISTS;
17   -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_CONFIG_NOT_EXISTS;
  17 +import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
18 18  
19 19 /**
20 20 * 签到规则 Service 实现类
... ... @@ -30,6 +30,8 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
30 30  
31 31 @Override
32 32 public Long createSignInConfig(MemberSignInConfigCreateReqVO createReqVO) {
  33 + // 校验奖励积分、奖励经验
  34 + validatePointAndExperience(createReqVO.getPoint(), createReqVO.getExperience());
33 35 // 判断是否重复插入签到天数
34 36 validateSignInConfigDayDuplicate(createReqVO.getDay(), null);
35 37  
... ... @@ -42,6 +44,8 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
42 44  
43 45 @Override
44 46 public void updateSignInConfig(MemberSignInConfigUpdateReqVO updateReqVO) {
  47 + // 校验奖励积分、奖励经验
  48 + validatePointAndExperience(updateReqVO.getPoint(), updateReqVO.getExperience());
45 49 // 校验存在
46 50 validateSignInConfigExists(updateReqVO.getId());
47 51 // 判断是否重复插入签到天数
... ... @@ -70,7 +74,7 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
70 74 * 校验 day 是否重复
71 75 *
72 76 * @param day 天
73   - * @param id 编号,只有更新的时候会传递
  77 + * @param id 编号,只有更新的时候会传递
74 78 */
75 79 private void validateSignInConfigDayDuplicate(Integer day, Long id) {
76 80 MemberSignInConfigDO config = memberSignInConfigMapper.selectByDay(day);
... ... @@ -84,13 +88,20 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
84 88 }
85 89 }
86 90  
  91 + private void validatePointAndExperience(Integer point, Integer experience) {
  92 + // 奖励积分、经验 至少要配置一个,否则没有意义
  93 + if (Objects.equals(point, 0) && Objects.equals(experience, 0)) {
  94 + throw exception(SIGN_IN_CONFIG_AWARD_EMPTY);
  95 + }
  96 + }
  97 +
87 98 @Override
88 99 public MemberSignInConfigDO getSignInConfig(Long id) {
89 100 return memberSignInConfigMapper.selectById(id);
90 101 }
91 102  
92 103 @Override
93   - public List <MemberSignInConfigDO> getSignInConfigList() {
  104 + public List<MemberSignInConfigDO> getSignInConfigList() {
94 105 List<MemberSignInConfigDO> list = memberSignInConfigMapper.selectList();
95 106 list.sort(Comparator.comparing(MemberSignInConfigDO::getDay));
96 107 return list;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.service.signin;
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
6 7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 8 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
8 9 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
... ... @@ -13,8 +14,13 @@ import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO
13 14 import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInConfigMapper;
14 15 import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper;
15 16 import cn.iocoder.yudao.module.member.enums.ErrorCodeConstants;
  17 +import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
  18 +import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
  19 +import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
  20 +import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
16 21 import org.apache.commons.lang3.StringUtils;
17 22 import org.springframework.stereotype.Service;
  23 +import org.springframework.transaction.annotation.Transactional;
18 24 import org.springframework.util.CollectionUtils;
19 25 import org.springframework.validation.annotation.Validated;
20 26  
... ... @@ -40,20 +46,24 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
40 46 private MemberSignInRecordMapper signInRecordMapper;
41 47 @Resource
42 48 private MemberSignInConfigMapper signInConfigMapper;
  49 + @Resource
  50 + private MemberPointRecordService pointRecordService;
  51 + @Resource
  52 + private MemberLevelService memberLevelService;
43 53  
44 54 @Resource
45 55 private MemberUserApi memberUserApi;
46 56  
47 57 @Override
48 58 public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) {
49   - AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
  59 + AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
50 60 vo.setTotalDay(0);
51 61 vo.setContinuousDay(0);
52 62 vo.setTodaySignIn(false);
53 63 //获取用户签到的记录,按照天数倒序获取
54   - List <MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
  64 + List<MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
55 65 // TODO @xiaqing:if 空的时候,直接 return;这样括号少,逻辑更简洁;
56   - if(!CollectionUtils.isEmpty(signInRecordDOList)){
  66 + if (!CollectionUtils.isEmpty(signInRecordDOList)) {
57 67 //设置总签到天数
58 68 vo.setTotalDay(signInRecordDOList.size()); // TODO @xiaqing:是不是不用读取 signInRecordDOList 所有的,而是 count下,然后另外再读取一条最后一条;
59 69 //判断当天是否有签到复用校验方法
... ... @@ -61,11 +71,11 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
61 71 try {
62 72 validSignDay(signInRecordDOList.get(0));
63 73 vo.setTodaySignIn(false);
64   - }catch (Exception e){
  74 + } catch (Exception e) {
65 75 vo.setTodaySignIn(true);
66 76 }
67 77 //如果当天签到了则说明连续签到天数有意义,否则直接用默认值0
68   - if(vo.getTodaySignIn()){
  78 + if (vo.getTodaySignIn()) {
69 79 //下方计算连续签到从2天开始,此处直接设置一天连续签到
70 80 vo.setContinuousDay(1);
71 81 //判断连续签到天数
... ... @@ -73,10 +83,10 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
73 83 for (int i = 1; i < signInRecordDOList.size(); i++) {
74 84 //前一天减1等于当前天数则说明连续,继续循环
75 85 LocalDate cur = signInRecordDOList.get(i).getCreateTime().toLocalDate();
76   - LocalDate pre = signInRecordDOList.get(i-1).getCreateTime().toLocalDate();
77   - if(1==daysBetween(cur,pre)){
78   - vo.setContinuousDay(i+1);
79   - }else{
  86 + LocalDate pre = signInRecordDOList.get(i - 1).getCreateTime().toLocalDate();
  87 + if (1 == daysBetween(cur, pre)) {
  88 + vo.setContinuousDay(i + 1);
  89 + } else {
80 90 break;
81 91 }
82 92 }
... ... @@ -87,16 +97,16 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
87 97 return vo;
88 98 }
89 99  
90   - private long daysBetween(LocalDate date1,LocalDate date2){
  100 + private long daysBetween(LocalDate date1, LocalDate date2) {
91 101 return ChronoUnit.DAYS.between(date1, date2);
92 102 }
93 103  
94 104 @Override
95   - public PageResult <MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
  105 + public PageResult<MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
96 106 // 根据用户昵称查询出用户ids
97   - Set <Long> userIds = null;
  107 + Set<Long> userIds = null;
98 108 if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
99   - List <MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
  109 + List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
100 110 // 如果查询用户结果为空直接返回无需继续查询
101 111 if (CollectionUtils.isEmpty(users)) {
102 112 return PageResult.empty();
... ... @@ -113,50 +123,63 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
113 123 }
114 124  
115 125 @Override
  126 + @Transactional(rollbackFor = Exception.class)
116 127 public MemberSignInRecordDO createSignRecord(Long userId) {
117 128 // 获取当前用户签到的最大天数
118 129 // TODO @xiaqing:db 操作,dou封装到 mapper 中;
119 130 // TODO @xiaqing:maxSignDay,是不是变量叫 lastRecord 会更容易理解哈;
120   - MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX <MemberSignInRecordDO>()
  131 + MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX<MemberSignInRecordDO>()
121 132 .eq(MemberSignInRecordDO::getUserId, userId)
122 133 .orderByDesc(MemberSignInRecordDO::getDay)
123 134 .last("limit 1"));
124 135 // 判断是否重复签到
125 136 validSignDay(maxSignDay);
126 137  
127   - // TODO @xiaqing:可以使用 // 进行注释
128   - /**1.查询出当前签到的天数**/
  138 + // 1. 查询出当前签到的天数
129 139 MemberSignInRecordDO sign = new MemberSignInRecordDO().setUserId(userId); // TODO @xiaqing:应该使用 record 变量,会更合适
130 140 sign.setDay(1); // 设置签到初始化天数
131   - sign.setPoint(0); // 设置签到分数默认为 0
  141 + sign.setPoint(0); // 设置签到积分默认为 0
  142 + sign.setExperience(0); // 设置签到经验默认为 0
132 143 // 如果不为空则修改当前签到对应的天数
133 144 // TODO @xiaqing:应该是要判断连续哈,就是昨天;
134 145 if (maxSignDay != null) {
135 146 sign.setDay(maxSignDay.getDay() + 1);
136 147 }
137   - /**2.获取签到对应的分数**/
  148 + // 2. 获取签到对应的积分数
138 149 // 获取所有的签到规则,按照天数排序,只获取启用的 TODO @xiaqing:不要使用 signInConfigMapper 直接查询,而是要通过 SigninConfigService;
139   - List <MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX <MemberSignInConfigDO>()
  150 + List<MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX<MemberSignInConfigDO>()
140 151 .eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
141 152 .orderByAsc(MemberSignInConfigDO::getDay));
142   - // 如果签到的天数大于最大启用的规则天数,直接给最大签到的分数
  153 + // 如果签到的天数大于最大启用的规则天数,直接给最大签到的积分数
143 154 // TODO @xiaqing:超过最大配置的天数,应该直接重置到第一天哈;
144 155 MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1);
145 156 if (sign.getDay() > lastConfig.getDay()) {
146 157 sign.setPoint(lastConfig.getPoint());
  158 + sign.setExperience(lastConfig.getExperience());
147 159 } else {
148 160 configDOList.forEach(el -> {
149   - // 循环匹配对应天数,设置对应分数
  161 + // 循环匹配对应天数,设置对应积分数
150 162 // TODO @xiaqing:使用 equals;另外,这种不应该去遍历比较,从可读性来说,应该 CollUtil.findOne()
151 163 if (el.getDay() == sign.getDay()) {
152 164 sign.setPoint(el.getPoint());
  165 + sign.setExperience(el.getExperience());
153 166 }
154 167  
155 168 });
156 169 }
157 170  
158   - // 3. 插入当前签到获取的分数
  171 + // 3. 插入签到记录
159 172 signInRecordMapper.insert(sign);
  173 +
  174 + // 4. 增加积分
  175 + if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) {
  176 + pointRecordService.createPointRecord(userId, sign.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(sign.getId()));
  177 + }
  178 + // 5. 增加经验
  179 + if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) {
  180 + memberLevelService.addExperience(userId, sign.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(sign.getId()));
  181 + }
  182 +
160 183 return sign;
161 184 }
162 185  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
... ... @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
18 18 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
19 19 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
20 20 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
  21 +import cn.iocoder.yudao.module.member.mq.producer.user.RegisterCouponProducer;
21 22 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
22 23 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
23 24 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
... ... @@ -58,6 +59,9 @@ public class MemberUserServiceImpl implements MemberUserService {
58 59 @Resource
59 60 private PasswordEncoder passwordEncoder;
60 61  
  62 + @Resource
  63 + private RegisterCouponProducer registerCouponProducer;
  64 +
61 65 @Override
62 66 public MemberUserDO getUserByMobile(String mobile) {
63 67 return memberUserMapper.selectByMobile(mobile);
... ... @@ -89,6 +93,9 @@ public class MemberUserServiceImpl implements MemberUserService {
89 93 user.setPassword(encodePassword(password)); // 加密密码
90 94 user.setRegisterIp(registerIp);
91 95 memberUserMapper.insert(user);
  96 +
  97 + // 发送 MQ 消息,发放新人券
  98 + registerCouponProducer.sendMailSendMessage(user.getId());
92 99 return user;
93 100 }
94 101  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.wallet;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletRespVO;
  5 +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletUserReqVO;
  6 +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
  7 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
  8 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.security.access.prepost.PreAuthorize;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.GetMapping;
  15 +import org.springframework.web.bind.annotation.RequestMapping;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import javax.annotation.Resource;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +
  22 +@Tag(name = "管理后台 - 用户钱包")
  23 +@RestController
  24 +@RequestMapping("/pay/wallet")
  25 +@Validated
  26 +@Slf4j
  27 +public class PayWalletController {
  28 +
  29 + @Resource
  30 + private PayWalletService payWalletService;
  31 +
  32 + @GetMapping("/user-wallet")
  33 + @PreAuthorize("@ss.hasPermission('pay:wallet:query')")
  34 + @Operation(summary = "获得用户钱包明细")
  35 + public CommonResult<PayWalletRespVO> getByUser(PayWalletUserReqVO reqVO) {
  36 + PayWalletDO wallet = payWalletService.getWalletByUserIdAndType(reqVO.getUserId(), reqVO.getUserType());
  37 + return success(PayWalletConvert.INSTANCE.convert02(wallet));
  38 + }
  39 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +
  8 +/**
  9 + * 用户钱包 Base VO,提供给添加、修改、详细的子 VO 使用
  10 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  11 + */
  12 +@Data
  13 +public class PayWalletBaseVO {
  14 +
  15 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020")
  16 + @NotNull(message = "用户编号不能为空")
  17 + private Long userId;
  18 +
  19 + @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  20 + @NotNull(message = "用户类型不能为空")
  21 + private Byte userType;
  22 +
  23 + @Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
  24 + @NotNull(message = "余额,单位分不能为空")
  25 + private Integer balance;
  26 +
  27 + @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
  28 + @NotNull(message = "累计支出,单位分不能为空")
  29 + private Integer totalExpense;
  30 +
  31 + @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
  32 + @NotNull(message = "累计充值,单位分不能为空")
  33 + private Integer totalRecharge;
  34 +
  35 + @Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737")
  36 + @NotNull(message = "冻结金额,单位分不能为空")
  37 + private Integer freezePrice;
  38 +
  39 +}
0 40 \ No newline at end of file
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - 用户钱包 Response VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class PayWalletRespVO extends PayWalletBaseVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528")
  17 + private Long id;
  18 +
  19 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private LocalDateTime createTime;
  21 +
  22 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletUserReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@Schema(description = "管理后台 - 用户钱包明细 Request VO")
  11 +@Data
  12 +public class PayWalletUserReqVO {
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + @NotNull(message = "用户编号不能为空")
  16 + private Long userId;
  17 +
  18 + @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + @NotNull(message = "用户类型不能为空")
  20 + @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
  21 + private Integer userType;
  22 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
1 1 package cn.iocoder.yudao.module.pay.convert.wallet;
2 2  
  3 +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletRespVO;
3 4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
4 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
5 6 import org.mapstruct.Mapper;
... ... @@ -11,4 +12,6 @@ public interface PayWalletConvert {
11 12 PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class);
12 13  
13 14 AppPayWalletRespVO convert(PayWalletDO bean);
  15 +
  16 + PayWalletRespVO convert02(PayWalletDO wallet);
14 17 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
... ... @@ -13,10 +13,10 @@ public interface PayWalletService {
13 13  
14 14 /**
15 15 * 获取钱包信息
16   - *
  16 + * <p>
17 17 * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包
18 18 *
19   - * @param userId 用户编号
  19 + * @param userId 用户编号
20 20 * @param userType 用户类型
21 21 */
22 22 PayWalletDO getOrCreateWallet(Long userId, Integer userType);
... ... @@ -31,10 +31,10 @@ public interface PayWalletService {
31 31 /**
32 32 * 钱包订单支付
33 33 *
34   - * @param userId 用户 id
35   - * @param userType 用户类型
  34 + * @param userId 用户 id
  35 + * @param userType 用户类型
36 36 * @param outTradeNo 外部订单号
37   - * @param price 金额
  37 + * @param price 金额
38 38 */
39 39 PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
40 40  
... ... @@ -43,17 +43,17 @@ public interface PayWalletService {
43 43 *
44 44 * @param outRefundNo 外部退款号
45 45 * @param refundPrice 退款金额
46   - * @param reason 退款原因
  46 + * @param reason 退款原因
47 47 */
48 48 PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason);
49 49  
50 50 /**
51 51 * 扣减钱包余额
52 52 *
53   - * @param walletId 钱包 id
54   - * @param bizId 业务关联 id
55   - * @param bizType 业务关联分类
56   - * @param price 扣减金额
  53 + * @param walletId 钱包 id
  54 + * @param bizId 业务关联 id
  55 + * @param bizType 业务关联分类
  56 + * @param price 扣减金额
57 57 * @return 钱包流水
58 58 */
59 59 PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
... ... @@ -63,9 +63,9 @@ public interface PayWalletService {
63 63 * 增加钱包余额
64 64 *
65 65 * @param walletId 钱包 id
66   - * @param bizId 业务关联 id
67   - * @param bizType 业务关联分类
68   - * @param price 增加金额
  66 + * @param bizId 业务关联 id
  67 + * @param bizType 业务关联分类
  68 + * @param price 增加金额
69 69 * @return 钱包流水
70 70 */
71 71 PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
... ... @@ -74,15 +74,25 @@ public interface PayWalletService {
74 74 /**
75 75 * 冻结钱包部分余额
76 76 *
77   - * @param id 钱包编号
  77 + * @param id 钱包编号
78 78 * @param price 冻结金额
79 79 */
80 80 void freezePrice(Long id, Integer price);
81 81  
82 82 /**
83 83 * 解冻钱包余额
84   - * @param id 钱包编号
  84 + *
  85 + * @param id 钱包编号
85 86 * @param price 解冻金额
86 87 */
87 88 void unFreezePrice(Long id, Integer price);
  89 +
  90 + /**
  91 + * 获得用户的钱包明细
  92 + *
  93 + * @param userId 用户编号
  94 + * @param userType 用户类型
  95 + * @return 用户的钱包明细
  96 + */
  97 + PayWalletDO getWalletByUserIdAndType(Long userId, Integer userType);
88 98 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
... ... @@ -195,4 +195,9 @@ public class PayWalletServiceImpl implements PayWalletService {
195 195 }
196 196 }
197 197  
  198 + @Override
  199 + public PayWalletDO getWalletByUserIdAndType(Long userId, Integer userType) {
  200 + return walletMapper.selectByUserIdAndType(userId, userType);
  201 + }
  202 +
198 203 }
... ...