Browse Source

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

# Conflicts:
#	yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
YunaiV 1 year ago
parent
commit
057952bdeb
89 changed files with 1649 additions and 568 deletions
  1. 11 0
      sql/mysql/member.sql
  2. 34 31
      sql/mysql/ruoyi-vue-pro.sql
  3. 3 0
      sql/mysql/trade_order.sql
  4. 26 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  5. 20 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApi.java
  6. 16 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  7. 1 1
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
  8. 27 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java
  9. 10 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
  10. 9 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryService.java
  11. 24 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java
  12. 10 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
  13. 23 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
  14. 28 55
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
  15. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
  16. 16 12
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponTemplateConvert.java
  17. 12 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
  18. 32 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java
  19. 29 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/consumer/coupon/RegisterCouponSendConsumer.java
  20. 16 26
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
  21. 35 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
  22. 22 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java
  23. 34 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java
  24. 0 22
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java
  25. 0 2
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/TradeStatisticsController.java
  26. 4 14
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java
  27. 2 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
  28. 2 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageUserConvert.java
  29. 8 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
  30. 12 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
  31. 23 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageUserMapper.java
  32. 4 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
  33. 14 15
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
  34. 57 15
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
  35. 12 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  36. 3 14
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java
  37. 6 7
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java
  38. 14 13
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
  39. 10 8
      yudao-module-mall/yudao-module-trade-biz/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml
  40. 98 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java
  41. 332 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java
  42. 5 0
      yudao-module-member/yudao-module-member-api/pom.xml
  43. 18 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApi.java
  44. 6 6
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/dto/MemberConfigRespDTO.java
  45. 0 9
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
  46. 1 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  47. 1 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
  48. 29 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/RegisterCouponSendMessage.java
  49. 28 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApiImpl.java
  50. 0 10
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
  51. 45 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/MemberConfigController.java
  52. 7 7
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigBaseVO.java
  53. 3 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigRespVO.java
  54. 13 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigSaveReqVO.java
  55. 0 45
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/MemberPointConfigController.java
  56. 0 13
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigSaveReqVO.java
  57. 7 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigBaseVO.java
  58. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordRespVO.java
  59. 23 4
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
  60. 1 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java
  61. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java
  62. 1 13
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java
  63. 2 7
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java
  64. 4 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/AppMemberSignInRecordRespVO.java
  65. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordRespVO.java
  66. 25 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/config/MemberConfigConvert.java
  67. 0 25
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/point/MemberPointConfigConvert.java
  68. 9 9
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java
  69. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInConfigDO.java
  70. 5 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInRecordDO.java
  71. 14 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/config/MemberConfigMapper.java
  72. 0 14
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/point/MemberPointConfigMapper.java
  73. 31 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/RegisterCouponProducer.java
  74. 29 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigService.java
  75. 44 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigServiceImpl.java
  76. 2 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java
  77. 0 29
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointConfigService.java
  78. 0 44
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointConfigServiceImpl.java
  79. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointRecordServiceImpl.java
  80. 15 4
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInConfigServiceImpl.java
  81. 45 22
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/signin/MemberSignInRecordServiceImpl.java
  82. 7 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  83. 39 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java
  84. 39 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletBaseVO.java
  85. 22 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletRespVO.java
  86. 22 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletUserReqVO.java
  87. 3 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
  88. 25 15
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
  89. 5 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java

+ 11 - 0
sql/mysql/member.sql

@@ -0,0 +1,11 @@
+-- 查询上级菜单、排序
+SELECT parent_id, sort
+INTO @parentId, @sort
+FROM system_menu
+WHERE name = '用户等级修改'
+LIMIT 1;
+-- 新增 按钮权限
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户积分修改', 'member:user:update-point', 3, @sort + 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户余额修改', 'member:user:update-balance', 3, @sort + 2, @parentId, '', '', '', 0);

+ 34 - 31
sql/mysql/ruoyi-vue-pro.sql

@@ -805,7 +805,7 @@ CREATE TABLE `member_level_record`  (
   `user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户编号',
   `level_id` bigint NOT NULL DEFAULT 0 COMMENT '等级编号',
   `level` int NOT NULL DEFAULT 0 COMMENT '会员等级',
-  `discount_percent` tinyint NOT NULL DEFAULT 100 COMMENT '享受折扣',
+  `discount_percent` int NOT NULL DEFAULT 100 COMMENT '享受折扣',
   `experience` int NOT NULL DEFAULT 0 COMMENT '升级经验',
   `user_experience` int NOT NULL DEFAULT 0 COMMENT '会员此时的经验',
   `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
@@ -827,15 +827,15 @@ BEGIN;
 COMMIT;
 
 -- ----------------------------
--- Table structure for member_point_config
+-- Table structure for member_config
 -- ----------------------------
-DROP TABLE IF EXISTS `member_point_config`;
-CREATE TABLE `member_point_config`  (
+DROP TABLE IF EXISTS `member_config`;
+CREATE TABLE `member_config`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
-  `trade_deduct_enable` bit(1) NOT NULL COMMENT '是否开启积分抵扣',
-  `trade_deduct_unit_price` int NOT NULL COMMENT '积分抵扣(单位:分)',
-  `trade_deduct_max_price` int NULL DEFAULT NULL COMMENT '积分抵扣最大值',
-  `trade_give_point` bigint NULL DEFAULT NULL COMMENT '1 元赠送多少分',
+  `point_trade_deduct_enable` bit(1) NOT NULL COMMENT '是否开启积分抵扣',
+  `point_trade_deduct_unit_price` int NOT NULL COMMENT '积分抵扣(单位:分)',
+  `point_trade_deduct_max_price` int NULL DEFAULT NULL COMMENT '积分抵扣最大值',
+  `point_trade_give_point` bigint NULL DEFAULT NULL COMMENT '1 元赠送多少分',
   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -843,13 +843,13 @@ CREATE TABLE `member_point_config`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员积分配置表';
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员配置表';
 
 -- ----------------------------
--- Records of member_point_config
+-- Records of member_config
 -- ----------------------------
 BEGIN;
-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);
+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);
 COMMIT;
 
 -- ----------------------------
@@ -915,7 +915,8 @@ DROP TABLE IF EXISTS `member_sign_in_config`;
 CREATE TABLE `member_sign_in_config`  (
   `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
   `day` int NOT NULL COMMENT '第几天',
-  `point` int NOT NULL COMMENT '奖励积分',
+  `point` int NOT NULL DEFAULT 0 COMMENT '奖励积分',
+  `experience` int NOT NULL DEFAULT 0 COMMENT '奖励经验',
   `status` tinyint NOT NULL COMMENT '状态',
   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -930,18 +931,18 @@ CREATE TABLE `member_sign_in_config`  (
 -- Records of member_sign_in_config
 -- ----------------------------
 BEGIN;
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
-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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
+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);
 COMMIT;
 
 -- ----------------------------
@@ -952,7 +953,8 @@ CREATE TABLE `member_sign_in_record`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '签到自增id',
   `user_id` int NULL DEFAULT NULL COMMENT '签到用户',
   `day` int NULL DEFAULT NULL COMMENT '第几天签到',
-  `point` int NULL DEFAULT NULL COMMENT '签到的分数',
+  `point` int NOT NULL DEFAULT 0 COMMENT '签到的积分',
+  `experience` int NOT NULL DEFAULT 0 COMMENT '签到的经验',
   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -962,6 +964,8 @@ CREATE TABLE `member_sign_in_record`  (
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '签到记录';
 
+CREATE INDEX user_id_index ON member_sign_in_record (user_id);
+
 -- ----------------------------
 -- Records of member_sign_in_record
 -- ----------------------------
@@ -2013,20 +2017,19 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (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');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2209, '秒杀活动', '', 2, 3, 2030, 'seckill', 'ep:place', '', '', 0, b'1', b'1', b'1', '1', '2023-06-24 17:39:13', '1', '2023-06-24 18:55:15', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2262, '会员中心', '', 1, 55, 0, '/member', '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');
-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');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '积分设置查询', 'point:config:query', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '积分设置创建', 'point:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (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');
+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');
+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');
 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');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2282, '积分签到规则查询', 'point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2283, '积分签到规则创建', 'point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2284, '积分签到规则更新', 'point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2285, '积分签到规则删除', 'point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (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');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '用户积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (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');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '会员积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (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');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2294, '用户签到积分查询', 'point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2297, '用户签到积分删除', 'point:sign-in-record:delete', 3, 4, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (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');
 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');
 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');
 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');

+ 3 - 0
sql/mysql/trade_order.sql

@@ -0,0 +1,3 @@
+-- 增加字段
+ALTER TABLE trade_order
+    ADD COLUMN brokerage_user_id bigint NULL COMMENT '推广人编号' AFTER comment_status;

+ 26 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -6,6 +6,7 @@ import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.temporal.TemporalAdjusters;
 
 /**
  * 时间工具类,用于 {@link java.time.LocalDateTime}
@@ -137,4 +138,29 @@ public class LocalDateTimeUtils {
 
     }
 
+    /**
+     * 获取指定日期所在的月份的开始时间
+     * 例如:2023-09-30 00:00:00,000
+     *
+     * @param date 日期
+     * @return 月份的开始时间
+     */
+    public static LocalDateTime beginOfMonth(LocalDateTime date) {
+        return date
+                .with(TemporalAdjusters.firstDayOfMonth())
+                .with(LocalTime.MIN);
+    }
+
+    /**
+     * 获取指定日期所在的月份的最后时间
+     * 例如:2023-09-30 23:59:59,999
+     *
+     * @param date 日期
+     * @return 月份的结束时间
+     */
+    public static LocalDateTime endOfMonth(LocalDateTime date) {
+        return date
+                .with(TemporalAdjusters.lastDayOfMonth())
+                .with(LocalTime.MAX);
+    }
 }

+ 20 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApi.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.product.api.category;
+
+import java.util.Collection;
+
+/**
+ * 商品分类 API 接口
+ *
+ * @author owen
+ */
+public interface ProductCategoryApi {
+
+    /**
+     * 校验商品分类是否有效。如下情况,视为无效:
+     * 1. 商品分类编号不存在
+     * 2. 商品分类被禁用
+     *
+     * @param ids 商品分类编号数组
+     */
+    void validateCategoryList(Collection<Long> ids);
+}

+ 16 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java

@@ -21,6 +21,14 @@ public interface ProductSpuApi {
      */
     List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
 
+    /**
+     * 批量查询 SPU 数组,并且校验是否 SPU 是否有效
+     *
+     * @param ids SPU 编号列表
+     * @return SPU 数组
+     */
+    List<ProductSpuRespDTO> getSpuListAndValidate(Collection<Long> ids);
+
     /**
      * 获得 SPU
      *
@@ -28,4 +36,12 @@ public interface ProductSpuApi {
      */
     ProductSpuRespDTO getSpu(Long id);
 
+    /**
+     * 校验商品是否有效。如下情况,视为无效:
+     * 1. 商品编号不存在
+     * 2. 商品被禁用
+     *
+     * @param ids 商品编号数组
+     */
+    void validateSpuList(Collection<Long> ids);
 }

+ 1 - 1
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 {
     // ========== 商品 SPU 1-008-005-000 ==========
     ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
     ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
-    ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU 不处于上架状态");
+    ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU【[]】不处于上架状态");
     ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_003, "商品 SPU 不处于回收站状态");
 
     // ========== 商品 SKU 1-008-006-000 ==========

+ 27 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.product.api.category;
+
+import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+
+/**
+ * 商品分类 API 接口实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class ProductCategoryApiImpl implements ProductCategoryApi {
+
+    @Resource
+    private ProductCategoryService productCategoryService;
+
+    @Override
+    public void validateCategoryList(Collection<Long> ids) {
+        productCategoryService.validateCategoryList(ids);
+    }
+
+}

+ 10 - 3
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;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
-import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
-import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
-import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -36,9 +33,19 @@ public class ProductSpuApiImpl implements ProductSpuApi {
         return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids));
     }
 
+    @Override
+    public List<ProductSpuRespDTO> getSpuListAndValidate(Collection<Long> ids) {
+        return ProductSpuConvert.INSTANCE.convertList2(spuService.validateSpuList(ids));
+    }
+
     @Override
     public ProductSpuRespDTO getSpu(Long id) {
         return ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id));
     }
 
+    @Override
+    public void validateSpuList(Collection<Long> ids) {
+        spuService.validateSpuList(ids);
+    }
+
 }

+ 9 - 0
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
 import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
 
 import javax.validation.Valid;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -75,4 +76,12 @@ public interface ProductCategoryService {
      */
     List<ProductCategoryDO> getEnableCategoryList();
 
+    /**
+     * 校验商品分类是否有效。如下情况,视为无效:
+     * 1. 商品分类编号不存在
+     * 2. 商品分类被禁用
+     *
+     * @param ids 商品分类编号数组
+     */
+    void validateCategoryList(Collection<Long> ids);
 }

+ 24 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.product.service.category;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;
@@ -13,7 +15,9 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -99,6 +103,26 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
         }
     }
 
+    @Override
+    public void validateCategoryList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return;
+        }
+        // 获得商品分类信息
+        List<ProductCategoryDO> categoryList = productCategoryMapper.selectBatchIds(ids);
+        Map<Long, ProductCategoryDO> categoryMap = CollectionUtils.convertMap(categoryList, ProductCategoryDO::getId);
+        // 校验
+        ids.forEach(id -> {
+            ProductCategoryDO category = categoryMap.get(id);
+            if (category == null) {
+                throw exception(CATEGORY_NOT_EXISTS);
+            }
+            if (!CommonStatusEnum.ENABLE.getStatus().equals(category.getStatus())) {
+                throw exception(CATEGORY_DISABLED, category.getName());
+            }
+        });
+    }
+
     @Override
     public ProductCategoryDO getCategory(Long id) {
         return productCategoryMapper.selectById(id);

+ 10 - 0
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 {
      */
     Long getSpuCountByCategoryId(Long categoryId);
 
+
+    /**
+     * 校验商品是否有效。如下情况,视为无效:
+     * 1. 商品编号不存在
+     * 2. 商品被禁用
+     *
+     * @param ids 商品编号数组
+     * @return 商品 SPU 列表
+     */
+    List<ProductSpuDO> validateSpuList(Collection<Long> ids);
 }

+ 23 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.product.service.spu;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -139,6 +140,28 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         }
     }
 
+    @Override
+    public List<ProductSpuDO> validateSpuList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        // 获得商品信息
+        List<ProductSpuDO> spuList = productSpuMapper.selectBatchIds(ids);
+        Map<Long, ProductSpuDO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuDO::getId);
+        // 校验
+        ids.forEach(id -> {
+            ProductSpuDO spu = spuMap.get(id);
+            if (spu == null) {
+                throw exception(SPU_NOT_EXISTS);
+            }
+            if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
+                throw exception(SPU_NOT_ENABLE, spu.getName());
+            }
+        });
+
+        return spuList;
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void deleteSpu(Long id) {

+ 28 - 55
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.promotion.controller.app.coupon;
 
-import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -25,11 +24,9 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
 
 @Tag(name = "用户 App - 优惠劵模板")
@@ -46,7 +43,6 @@ public class AppCouponTemplateController {
     @Resource
     private ProductSpuApi productSpuApi;
 
-    // TODO 疯狂:这里应该还有个 list 接口哈;获得优惠劵模版列表,用于首页、商品页的优惠劵
     @GetMapping("/list")
     @Operation(summary = "获得优惠劵模版列表")
     @Parameters({
@@ -56,46 +52,28 @@ public class AppCouponTemplateController {
     })
     public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(
             @RequestParam(value = "spuId", required = false) Long spuId,
-            @RequestParam(value = "useType", required = false) Integer useType,
+            @RequestParam(value = "productScope", required = false) Integer productScope,
             @RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
-        List<AppCouponTemplateRespVO> list = new ArrayList<>();
-        Random random = new Random();
-        for (int i = 0; i < 10; i++) {
-            AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
-            vo.setId(i + 1L);
-            vo.setName("优惠劵" + (i + 1));
-            vo.setTakeLimitCount(random.nextInt(10) + 1);
-            vo.setUsePrice(random.nextInt(100) * 100);
-            vo.setValidityType(random.nextInt(2) + 1);
-            if (vo.getValidityType() == 1) {
-                vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
-                vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
-            } else {
-                vo.setFixedStartTerm(random.nextInt(10));
-                vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
-            }
-            vo.setDiscountType(random.nextInt(2) + 1);
-            if (vo.getDiscountType() == 1) {
-                vo.setDiscountPercent(null);
-                vo.setDiscountPrice(random.nextInt(50) * 100);
-                vo.setDiscountLimitPrice(null);
-            } else {
-                vo.setDiscountPercent(random.nextInt(90) + 10);
-                vo.setDiscountPrice(null);
-                vo.setDiscountLimitPrice(random.nextInt(200) * 100);
-            }
-            // TODO @疯狂:是否已领取,要不在 TemplateService 搞个 static 方法,让它基于 countMap 这种去计算,这样好点?
-            vo.setTakeStatus(random.nextBoolean());
-            list.add(vo);
-        }
-        return success(list);
+        // 1.1 处理查询条件:商品范围编号
+        Long productScopeValue = getProductScopeValue(productScope, spuId);
+        // 1.2 处理查询条件:领取方式 = 直接领取
+        List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
+
+        // 2. 查询
+        List<CouponTemplateDO> list = couponTemplateService.getCouponTemplateList(canTakeTypes, productScope,
+                productScopeValue, count);
+
+        // 3.1 领取数量
+        Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), list);
+        // 3.2 拼接返回
+        return success(CouponTemplateConvert.INSTANCE.convertAppList(list, canCanTakeMap));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得优惠劵模版分页")
     public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
         // 1.1 处理查询条件:商品范围编号
-        Long productScopeValue = getProductScopeValue(pageReqVO);
+        Long productScopeValue = getProductScopeValue(pageReqVO.getProductScope(), pageReqVO.getSpuId());
         // 1.2 处理查询条件:领取方式 = 直接领取
         List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
 
@@ -104,35 +82,30 @@ public class AppCouponTemplateController {
                 CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
 
         // 3.1 领取数量
-        Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
-        Long userId = getLoginUserId();
-        if (userId != null) {
-            List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId,
-                    t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置“每人限领个数”的
-            couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId);
-        }
+        Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), pageResult.getList());
         // 3.2 拼接返回
-        return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
+        return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, canCanTakeMap));
     }
 
     /**
-     * 获得分页查询的商品范围
+     * 获得商品的使用范围编号
      *
-     * @param pageReqVO 分页查询
-     * @return 商品范围
+     * @param productScope 商品范围
+     * @param spuId        商品 SPU 编号
+     * @return 商品范围编号
      */
-    private Long getProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
-        // 通用券:清除商品范围
-        if (pageReqVO.getProductScope() == null || ObjectUtils.equalsAny(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope(), null)) {
+    private Long getProductScopeValue(Integer productScope, Long spuId) {
+        // 通用券:没有商品范围
+        if (productScope == null || ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) {
             return null;
         }
-        // 品类券:查询商品的品类
-        if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
-            return Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
+        // 品类券:查询商品的品类编号
+        if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) {
+            return Optional.ofNullable(productSpuApi.getSpu(spuId))
                     .map(ProductSpuRespDTO::getCategoryId).orElse(null);
         }
         // 商品卷:直接返回
-        return pageReqVO.getSpuId();
+        return spuId;
     }
 
 }

+ 2 - 2
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 {
 
     // ========== 用户相关字段 ==========
 
-    @Schema(description = "是否领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
-    private Boolean takeStatus;
+    @Schema(description = "是否可以领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean canTake;
 
 }

+ 16 - 12
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 {
 
     PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult);
 
-    default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) {
+    List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list);
+
+    default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Boolean> userCanTakeMap) {
         PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult);
-        if (MapUtil.isEmpty(couponTakeCountMap)) {
-            return result;
-        }
-        for (AppCouponTemplateRespVO template : result.getList()) {
-            // 每人领取数量无限制
-            if (template.getTakeLimitCount() == -1) {
-                template.setTakeStatus(false);
-                continue;
-            }
+        copyTo(result.getList(), userCanTakeMap);
+        return result;
+    }
+
+    default List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list, Map<Long, Boolean> userCanTakeMap) {
+        List<AppCouponTemplateRespVO> result = convertAppList(list);
+        copyTo(result, userCanTakeMap);
+        return result;
+    }
+
+    default void copyTo(List<AppCouponTemplateRespVO> list, Map<Long, Boolean> userCanTakeMap) {
+        for (AppCouponTemplateRespVO template : list) {
             // 检查已领取数量是否超过限领数量
-            template.setTakeStatus(MapUtil.getInt(couponTakeCountMap, template.getId(), 0) >= template.getTakeLimitCount());
+            template.setCanTake(MapUtil.getBool(userCanTakeMap, template.getId(), false));
         }
-        return result;
     }
 
 }

+ 12 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
 
-import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
-import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
@@ -16,9 +15,12 @@ import org.apache.ibatis.annotations.Mapper;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
 /**
  * 优惠劵 Mapper
  *
@@ -70,14 +72,16 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
         );
     }
 
-    // TODO @疯狂:这个是不是搞个 Map 就可以呀?
-    default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
-        return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
-                .select(CouponDO::getTemplateId)
-                .selectCount(CouponDO::getId, CouponTakeCountBO::getCount)
+    default Map<Long, Integer> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
+        String templateIdAlias = "templateId";
+        String countAlias = "count";
+        List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
+                .selectAs(CouponDO::getTemplateId, templateIdAlias)
+                .selectCount(CouponDO::getId, countAlias)
                 .eq(CouponDO::getUserId, userId)
                 .in(CouponDO::getTemplateId, templateIds)
-                .groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class);
+                .groupBy(CouponDO::getTemplateId));
+        return convertMap(list, map -> MapUtil.getLong(map, templateIdAlias), map -> MapUtil.getInt(map, countAlias));
     }
 
     default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(

+ 32 - 10
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;
 import org.apache.ibatis.annotations.Param;
 
 import java.time.LocalDateTime;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -23,16 +24,8 @@ import java.util.function.Consumer;
 public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
 
     default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
-        // 构建可领取的查询条件, 好啰嗦  ( ╯-_-)╯┴—┴
-        Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
-        if (CollUtil.isNotEmpty(reqVO.getCanTakeTypes())) {
-            canTakeConsumer = w ->
-                    w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
-                            .in(CouponTemplateDO::getTakeType, reqVO.getCanTakeTypes()) // 2. 领取方式一致
-                            .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime)  // 3. 未过期
-                                    .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
-                            .apply(" take_count < total_count "); // 4. 剩余数量大于 0
-        }
+        // 构建可领取的查询条件
+        Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(reqVO.getCanTakeTypes());
         // 执行分页查询
         return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
                 .likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
@@ -48,4 +41,33 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
 
     void updateTakeCount(@Param("id") Long id, @Param("incrCount") Integer incrCount);
 
+    default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
+        return selectList(CouponTemplateDO::getTakeType, takeType);
+    }
+
+    default List<CouponTemplateDO> selectList(List<Integer> canTakeTypes, Integer productScope, Long productScopeValue, Integer count) {
+        // 构建可领取的查询条件
+        Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes);
+        return selectList(new LambdaQueryWrapperX<CouponTemplateDO>()
+                .eqIfPresent(CouponTemplateDO::getProductScope, productScope)
+                .and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)",
+                        productScopeValue))
+                .and(canTakeConsumer != null, canTakeConsumer)
+                .last(" LIMIT " + count)
+                .orderByDesc(CouponTemplateDO::getId));
+    }
+
+    static Consumer<LambdaQueryWrapper<CouponTemplateDO>> buildCanTakeQueryConsumer(List<Integer> canTakeTypes) {
+        Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
+        if (CollUtil.isNotEmpty(canTakeTypes)) {
+            canTakeConsumer = w ->
+                    w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
+                            .in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致
+                            .and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime)  // 3. 未过期
+                                    .or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
+                            .apply(" take_count < total_count "); // 4. 剩余数量大于 0
+        }
+        return canTakeConsumer;
+    }
+
 }

+ 29 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/consumer/coupon/RegisterCouponSendConsumer.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.promotion.mq.consumer.coupon;
+
+import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
+import cn.iocoder.yudao.module.member.mq.message.user.RegisterCouponSendMessage;
+import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link RegisterCouponSendMessage} 的消费者
+ *
+ * @author owen
+ */
+@Component
+@Slf4j
+public class RegisterCouponSendConsumer extends AbstractStreamMessageListener<RegisterCouponSendMessage> {
+
+    @Resource
+    private CouponService couponService;
+
+    @Override
+    public void onMessage(RegisterCouponSendMessage message) {
+        log.info("[onMessage][消息内容({})]", message);
+        couponService.takeCouponByRegister(message.getUserId());
+    }
+
+}

+ 16 - 26
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java

@@ -1,17 +1,16 @@
 package cn.iocoder.yudao.module.promotion.service.coupon;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
-import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
 
 import java.util.*;
 
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-
 /**
  * 优惠劵 Service 接口
  *
@@ -119,12 +118,9 @@ public interface CouponService {
     /**
      * 【系统】给用户发送新人券
      *
-     * @param templateId 优惠券模板编号
-     * @param userId     用户编号列表
+     * @param userId 用户编号
      */
-    default void takeCouponByRegister(Long templateId, Long userId) {
-        takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
-    }
+    void takeCouponByRegister(Long userId);
 
     /**
      * 获取会员领取指定优惠券的数量
@@ -134,11 +130,8 @@ public interface CouponService {
      * @return 领取优惠券的数量
      */
     default Integer getTakeCount(Long templateId, Long userId) {
-        return CollUtil.emptyIfNull(getTakeCountListByTemplateIds(Collections.singleton(templateId), userId))
-                .stream()
-                .findFirst()
-                .map(CouponTakeCountBO::getCount)
-                .orElse(0);
+        Map<Long, Integer> map = getTakeCountMapByTemplateIds(Collections.singleton(templateId), userId);
+        return MapUtil.getInt(map, templateId, 0);
     }
 
     /**
@@ -148,19 +141,7 @@ public interface CouponService {
      * @param userId      用户编号
      * @return 领取优惠券的数量
      */
-    default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
-        return convertMap(getTakeCountListByTemplateIds(templateIds, userId),
-                CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
-    }
-
-    /**
-     * 统计会员领取优惠券的数量
-     *
-     * @param templateIds 优惠券模板编号列表
-     * @param userId      用户编号
-     * @return 领取优惠券的数量
-     */
-    List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId);
+    Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId);
 
     /**
      * 获取用户匹配的优惠券列表
@@ -178,4 +159,13 @@ public interface CouponService {
      */
     int expireCoupon();
 
+    /**
+     * 获取用户是否可以领取优惠券
+     *
+     * @param userId    用户编号
+     * @param templates 优惠券列表
+     * @return 是否可以领取
+     */
+    Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates);
+
 }

+ 35 - 10
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;
 
 import cn.hutool.core.collection.CollStreamUtil;
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
@@ -21,7 +20,6 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
-import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -29,18 +27,14 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 import static java.util.Arrays.asList;
 
-// TODO @疯狂:注册时,赠送用户优惠劵;为了解耦,可以考虑注册时发个 MQ 消息;然后营销这里监听后消费;
 /**
  * 优惠劵 Service 实现类
  *
@@ -184,9 +178,17 @@ public class CouponServiceImpl implements CouponService {
     }
 
     @Override
-    public List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId) {
+    public void takeCouponByRegister(Long userId) {
+        List<CouponTemplateDO> templates = couponTemplateService.getCouponTemplateByTakeType(CouponTakeTypeEnum.REGISTER);
+        for (CouponTemplateDO template : templates) {
+            takeCoupon(template.getId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
+        }
+    }
+
+    @Override
+    public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
         if (CollUtil.isEmpty(templateIds)) {
-            return ListUtil.empty();
+            return Collections.emptyMap();
         }
         return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
     }
@@ -222,6 +224,29 @@ public class CouponServiceImpl implements CouponService {
         return count;
     }
 
+    @Override
+    public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
+        Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
+        // 未登录时,都显示可以领取
+        if (userId == null) {
+            return userCanTakeMap;
+        }
+
+        // 过滤领取数量无限制的
+        Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
+
+        // 检查用户领取的数量是否超过限制
+        if (CollUtil.isNotEmpty(templateIds)) {
+            Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
+            for (CouponTemplateDO template : templates) {
+                Integer takeCount = couponTakeCountMap.get(template.getId());
+                userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
+            }
+        }
+
+        return userCanTakeMap;
+    }
+
     /**
      * 过期单个优惠劵
      *

+ 22 - 0
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
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 
 import javax.validation.Valid;
+import java.util.List;
 
 /**
  * 优惠劵模板 Service 接口
@@ -69,4 +71,24 @@ public interface CouponTemplateService {
      */
     void updateCouponTemplateTakeCount(Long id, int incrCount);
 
+    /**
+     * 获得指定领取方式的优惠券模板
+     *
+     * @param takeType 领取方式
+     * @return 优惠券模板列表
+     */
+    List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType);
+
+    /**
+     * 获得优惠券模板列表
+     *
+     * @param canTakeTypes      可领取的类型列表
+     * @param productScope      商品使用范围类型
+     * @param productScopeValue 商品使用范围编号
+     * @param count             查询数量
+     * @return 优惠券模板列表
+     */
+    List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
+                                                 Long productScopeValue, Integer count);
+
 }

+ 34 - 1
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;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
+import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.util.List;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
@@ -29,9 +35,15 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
     @Resource
     private CouponTemplateMapper couponTemplateMapper;
 
-    // TODO @疯狂:新增/修改时,需要校验对应的商品、分类是否存在
+    @Resource
+    private ProductCategoryApi productCategoryApi;
+    @Resource
+    private ProductSpuApi productSpuApi;
+
     @Override
     public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
+        // 校验商品范围
+        validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
         // 插入
         CouponTemplateDO couponTemplate = CouponTemplateConvert.INSTANCE.convert(createReqVO)
                 .setStatus(CommonStatusEnum.ENABLE.getStatus());
@@ -48,6 +60,8 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
         if (updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
             throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
         }
+        // 校验商品范围
+        validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues());
 
         // 更新
         CouponTemplateDO updateObj = CouponTemplateConvert.INSTANCE.convert(updateReqVO);
@@ -78,6 +92,14 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
         return couponTemplate;
     }
 
+    private void validateProductScope(Integer productScope, List<Long> productScopeValues) {
+        if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) {
+            productSpuApi.validateSpuList(productScopeValues);
+        } else if (Objects.equals(PromotionProductScopeEnum.CATEGORY.getScope(), productScope)) {
+            productCategoryApi.validateCategoryList(productScopeValues);
+        }
+    }
+
     @Override
     public CouponTemplateDO getCouponTemplate(Long id) {
         return couponTemplateMapper.selectById(id);
@@ -93,4 +115,15 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
         couponTemplateMapper.updateTakeCount(id, incrCount);
     }
 
+    @Override
+    public List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType) {
+        return couponTemplateMapper.selectListByTakeType(takeType.getValue());
+    }
+
+    @Override
+    public List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
+                                                        Long productScopeValue, Integer count) {
+        return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count);
+    }
+
 }

+ 0 - 22
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/bo/CouponTakeCountBO.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.module.promotion.service.coupon.bo;
-
-import lombok.Data;
-
-/**
- * 优惠券领取数量 BO
- *
- * @author owen
- */
-@Data
-public class CouponTakeCountBO {
-
-    /**
-     * 优惠劵模板编号
-     */
-    private Long templateId;
-    /**
-     * 领取数量
-     */
-    private Integer count;
-
-}

+ 0 - 2
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 {
     @Resource
     private TradeStatisticsService tradeStatisticsService;
 
-    // TODO @疯狂:要不这个就是 /trend/summary 的特例,前端自己查询两次?
     @GetMapping("/summary")
     @Operation(summary = "获得交易统计")
     @PreAuthorize("@ss.hasPermission('statistics:trade:query')")
@@ -40,7 +39,6 @@ public class TradeStatisticsController {
         return success(tradeStatisticsService.getTradeSummaryComparison());
     }
 
-    // TODO @疯狂:直接 comparison?主要 trend 和 comparison 二选一,一个是数据趋势,一个是数据对比哈;
     @GetMapping("/trend/summary")
     @Operation(summary = "获得交易状况统计")
     @PreAuthorize("@ss.hasPermission('statistics:trade:query')")

+ 4 - 14
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.statistics.service.trade;
 
 import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
@@ -13,8 +14,6 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import java.time.Duration;
 import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.temporal.TemporalAdjusters;
 import java.util.List;
 
 /**
@@ -79,18 +78,9 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
      * @return 交易数据
      */
     private TradeSummaryRespBO getTradeSummaryByMonths(int months) {
-        // TODO @疯狂:可以在 LocalDateUtils 封装方法;获得月份的开始;以及结束两个方法;然后这里就可以直接调用了
-        // 月份开始时间
-        LocalDateTime beginOfMonth = LocalDateTime.now()
-                .plusMonths(months)
-                .with(TemporalAdjusters.firstDayOfMonth())
-                .with(LocalTime.MIN);
-        // 月份截止时间
-        LocalDateTime endOfToday = LocalDateTime.now()
-                .plusMonths(months)
-                .with(TemporalAdjusters.lastDayOfMonth())
-                .with(LocalTime.MAX);
-        return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(beginOfMonth, endOfToday);
+        LocalDateTime monthDate = LocalDateTime.now().plusMonths(months);
+        return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(
+                LocalDateTimeUtils.beginOfMonth(monthDate), LocalDateTimeUtils.endOfMonth(monthDate));
     }
 
 }

+ 2 - 1
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;
 import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
@@ -84,7 +85,7 @@ public class AppBrokerageUserController {
         LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
         LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
         Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(),
-                BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime);
+                BrokerageRecordBizTypeEnum.ORDER, BrokerageRecordStatusEnum.SETTLEMENT, beginTime, endTime);
         // 统计用户提现的佣金
         Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryListByUserId(Collections.singleton(brokerageUser.getId()),
                         BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream()

+ 2 - 3
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
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.mapstruct.Mapper;
 import org.mapstruct.MappingTarget;
 import org.mapstruct.factory.Mappers;
@@ -88,8 +87,8 @@ public interface BrokerageUserConvert {
         return respVO;
     }
 
-    default void copyTo(IPage<AppBrokerageUserChildSummaryRespVO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
-        for (AppBrokerageUserChildSummaryRespVO vo : pageResult.getRecords()) {
+    default void copyTo(List<AppBrokerageUserChildSummaryRespVO> list, Map<Long, MemberUserRespDTO> userMap) {
+        for (AppBrokerageUserChildSummaryRespVO vo : list) {
             Optional.ofNullable(userMap.get(vo.getId())).ifPresent(user ->
                     vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
         }

+ 8 - 1
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;
 
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
@@ -105,7 +107,12 @@ public class TradeOrderDO extends BaseDO {
      */
     private Boolean commentStatus;
 
-    // TODO @疯狂:加一个推广人编号;
+    /**
+     * 推广人编号
+     * {@link BrokerageUserDO#getId()}
+     * {@link MemberUserRespDTO#getId()}
+     */
+    private Long brokerageUserId;
 
     // ========== 价格 + 支付基本信息 ==========
 

+ 12 - 2
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<BrokerageRecordDO> {
     }
 
     @Select("SELECT SUM(price) FROM trade_brokerage_record " +
-            "WHERE user_id = #{userId} AND biz_type = #{bizType} " +
-            "AND create_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
+            "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} " +
+            "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
     Integer selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(@Param("userId") Long userId,
                                                                      @Param("bizType") Integer bizType,
+                                                                     @Param("status") Integer status,
                                                                      @Param("beginTime") LocalDateTime beginTime,
                                                                      @Param("endTime") LocalDateTime endTime);
 
@@ -98,4 +99,13 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
                                                                                  @Param("beginTime") LocalDateTime beginTime,
                                                                                  @Param("endTime") LocalDateTime endTime);
 
+    @Select("SELECT COUNT(1) FROM trade_brokerage_record " +
+            "WHERE biz_type = #{bizType} AND status = #{status} AND deleted = FALSE " +
+            "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " +
+            "GROUP BY user_id HAVING SUM(price) > #{brokeragePrice}")
+    Integer selectCountByPriceGt(@Param("brokeragePrice") Integer brokeragePrice,
+                                 @Param("bizType") Integer bizType,
+                                 @Param("status") Integer status,
+                                 @Param("beginTime") LocalDateTime beginTime,
+                                 @Param("endTime") LocalDateTime endTime);
 }

+ 23 - 1
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<BrokerageUserDO> {
                                                                                   @Param("beginTime") LocalDateTime beginTime,
                                                                                   @Param("endTime") LocalDateTime endTime);
 
+    /**
+     * 下级分销统计(分页)
+     *
+     * @param bizType      业务类型
+     * @param status       状态
+     * @param bindUserIds  绑定用户编号列表
+     * @param sortingField 排序字段
+     * @return 下级分销统计分页列表
+     */
     IPage<AppBrokerageUserChildSummaryRespVO> selectSummaryPageByUserId(Page<?> page,
-                                                                        @Param("ids") List<Long> ids, // BrokerageUser 的 ids 数组
                                                                         @Param("bizType") Integer bizType,
                                                                         @Param("status") Integer status,
                                                                         @Param("bindUserIds") List<Long> bindUserIds,
                                                                         @Param("sortingField") SortingField sortingField);
 
+    /**
+     * 下级分销统计(不分页)
+     *
+     * @param bizType      业务类型
+     * @param status       状态
+     * @param bindUserIds  绑定用户编号列表
+     * @param sortingField 排序字段
+     * @return 下级分销统计列表
+     */
+    List<AppBrokerageUserChildSummaryRespVO> selectSummaryListByUserId(@Param("bizType") Integer bizType,
+                                                                       @Param("status") Integer status,
+                                                                       @Param("bindUserIds") List<Long> bindUserIds,
+                                                                       @Param("sortingField") SortingField sortingField);
+
     default List<BrokerageUserDO> selectListByBindUserId(Long bindUserId) {
         return selectList(BrokerageUserDO::getBindUserId, bindUserId);
     }

+ 4 - 1
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
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
 
@@ -120,11 +121,13 @@ public interface BrokerageRecordService {
      *
      * @param userId    用户编号
      * @param bizType   业务类型
+     * @param status    状态
      * @param beginTime 开始时间
      * @param endTime   截止时间
      * @return 用户佣金合计
      */
-    Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime);
+    Integer getSummaryPriceByUserId(Long userId, BrokerageRecordBizTypeEnum bizType, BrokerageRecordStatusEnum status,
+                                    LocalDateTime beginTime, LocalDateTime endTime);
 
     /**
      * 获得用户佣金排行分页列表(基于佣金总数)

+ 14 - 15
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;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
-import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.BooleanUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.*;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
@@ -265,9 +262,10 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
     }
 
     @Override
-    public Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime) {
-        return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId, bizType,
-                beginTime, endTime);
+    public Integer getSummaryPriceByUserId(Long userId, BrokerageRecordBizTypeEnum bizType, BrokerageRecordStatusEnum status,
+                                           LocalDateTime beginTime, LocalDateTime endTime) {
+        return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId,
+                bizType.getType(), status.getStatus(), beginTime, endTime);
     }
 
     @Override
@@ -279,17 +277,18 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
     }
 
-    // TODO @疯狂:这个要不我们先做精准的?先查询自己的推广金额,然后查询比该金额多的有多少人?
     @Override
     public Integer getUserRankByPrice(Long userId, LocalDateTime[] times) {
-        AppBrokerageUserRankPageReqVO pageParam = new AppBrokerageUserRankPageReqVO().setTimes(times);
-        // 取前 100 名
-        pageParam.setPageSize(100);
-        PageResult<AppBrokerageUserRankByPriceRespVO> pageResult = getBrokerageUserChildSummaryPageByPrice(pageParam);
-        // 获得索引
-        int index = CollUtil.indexOf(pageResult.getList(), user -> Objects.equals(userId, user.getId()));
+        // 用户的推广金额
+        Integer price = brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId,
+                BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
+                ArrayUtil.get(times, 0), ArrayUtil.get(times, 1));
+        // 排在用户前面的人数
+        Integer greaterCount = brokerageRecordMapper.selectCountByPriceGt(price,
+                BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
+                ArrayUtil.get(times, 0), ArrayUtil.get(times, 1));
         // 获得排名
-        return index + 1;
+        return ObjUtil.defaultIfNull(greaterCount, 0) + 1;
     }
 
     @Override

+ 57 - 15
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;
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -225,25 +226,66 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
 
     @Override
     public PageResult<AppBrokerageUserChildSummaryRespVO> getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId) {
-        // 1.1 根据昵称过滤用户
-        List<Long> ids = StrUtil.isBlank(pageReqVO.getNickname())
-                ? Collections.emptyList()
-                : convertList(memberUserApi.getUserListByNickname(pageReqVO.getNickname()), MemberUserRespDTO::getId);
-        // 1.2 生成推广员编号列表
-        // TODO @疯狂:是不是可以先 1.2 查询出来,然后查询对应的昵称,进行过滤?避免昵称过滤返回的 id 过多;
+        // 生成推广员编号列表
         List<Long> bindUserIds = buildBindUserIdsByLevel(userId, pageReqVO.getLevel());
 
-        // 2. 分页查询
-        IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
-                MyBatisUtils.buildPage(pageReqVO), ids, BrokerageRecordBizTypeEnum.ORDER.getType(),
-                BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
+        // 情况一:没有昵称过滤条件时,直接使用数据库的分页查询
+        if (StrUtil.isBlank(pageReqVO.getNickname())) {
+            // 1.1 分页查询
+            IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
+                    MyBatisUtils.buildPage(pageReqVO), BrokerageRecordBizTypeEnum.ORDER.getType(),
+                    BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
+            );
+
+            // 1.2 拼接数据并返回
+            List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
+            Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
+            BrokerageUserConvert.INSTANCE.copyTo(pageResult.getRecords(), userMap);
+            return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
+        }
+
+        // 情况二:有昵称过滤条件时,需要跨模块(Member)过滤
+        // 2.1 查询所有匹配的分销用户
+        List<AppBrokerageUserChildSummaryRespVO> list = brokerageUserMapper.selectSummaryListByUserId(
+                BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
+                bindUserIds, pageReqVO.getSortingField()
         );
+        if (CollUtil.isEmpty(list)) {
+            return PageResult.empty();
+        }
 
-        // 3. 拼接数据并返回
-        List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
-        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
-        BrokerageUserConvert.INSTANCE.copyTo(pageResult, userMap);
-        return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
+        // 2.2 查出对应的用户信息
+        List<MemberUserRespDTO> users = memberUserApi.getUserList(convertList(list, AppBrokerageUserChildSummaryRespVO::getId));
+        if (CollUtil.isEmpty(users)) {
+            return PageResult.empty();
+        }
+
+        // 2.3 根据昵称过滤出用户编号
+        Map<Long, MemberUserRespDTO> userMap = users.stream()
+                .filter(user -> StrUtil.contains(user.getNickname(), pageReqVO.getNickname()))
+                .collect(Collectors.toMap(MemberUserRespDTO::getId, dto -> dto));
+        if (CollUtil.isEmpty(userMap)) {
+            return PageResult.empty();
+        }
+
+        // 2.4 根据用户编号过滤结果
+        list.removeIf(vo -> !userMap.containsKey(vo.getId()));
+        if (CollUtil.isEmpty(list)) {
+            return PageResult.empty();
+        }
+
+        // 2.5 处理分页
+        List<AppBrokerageUserChildSummaryRespVO> result = list.stream()
+                .skip((long) (pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize())
+                .limit(pageReqVO.getPageSize())
+                .collect(Collectors.toList());
+        if (CollUtil.isEmpty(result)) {
+            return PageResult.empty();
+        }
+
+        // 2.6 拼接数据并返回
+        BrokerageUserConvert.INSTANCE.copyTo(result, userMap);
+        return new PageResult<>(result, (long) list.size());
     }
 
     private boolean isUserCanBind(BrokerageUserDO user) {

+ 12 - 2
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
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -54,6 +55,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
 import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
 import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.trade.service.cart.CartService;
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
@@ -108,6 +110,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     private DeliveryExpressService deliveryExpressService;
     @Resource
     private TradeMessageService tradeMessageService;
+    @Resource
+    private BrokerageUserService brokerageUserService;
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
 
     @Resource
     private ProductSpuApi productSpuApi;
@@ -130,8 +136,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Resource
     private MemberPointApi memberPointApi;
     @Resource
-    private BrokerageRecordService brokerageRecordService;
-    @Resource
     private ProductCommentApi productCommentApi;
 
     @Resource
@@ -301,6 +305,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         // 6. 插入订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
 
+        // 7. 设置订单推广人
+        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
+        if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
+            tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setBrokerageUserId(brokerageUser.getBindUserId()));
+        }
+
         // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
     }
 

+ 3 - 14
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;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
-import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
@@ -20,7 +19,8 @@ import java.util.Map;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
 
 /**
@@ -82,18 +82,7 @@ public class TradePriceServiceImpl implements TradePriceService {
 
     private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
         // 获得商品 SPU 数组
-        List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
-
-        // 校验商品 SPU
-        spus.forEach(spu -> {
-            if (spu == null) {
-                throw exception(SPU_NOT_EXISTS);
-            }
-            if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
-                throw exception(SPU_NOT_ENABLE);
-            }
-        });
-        return spus;
+        return productSpuApi.getSpuListAndValidate(convertSet(skuList, ProductSkuRespDTO::getSpuId));
     }
 
 }

+ 6 - 7
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;
 
 import cn.hutool.core.util.BooleanUtil;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
-import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
-import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
+import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import lombok.extern.slf4j.Slf4j;
@@ -16,7 +16,6 @@ import java.util.Optional;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
 
-// TODO @疯狂:这个可以搞个单测;
 /**
  * 赠送积分的 {@link TradePriceCalculator} 实现类
  *
@@ -28,14 +27,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 public class TradePointGiveCalculator implements TradePriceCalculator {
 
     @Resource
-    private MemberPointApi memberPointApi;
+    private MemberConfigApi memberConfigApi;
 
     @Override
     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
         // 1.1 校验积分功能是否开启
-        int givePointPerYuan = Optional.ofNullable(memberPointApi.getConfig())
-                .filter(config -> BooleanUtil.isTrue(config.getTradeDeductEnable()))
-                .map(MemberPointConfigRespDTO::getTradeGivePoint)
+        int givePointPerYuan = Optional.ofNullable(memberConfigApi.getConfig())
+                .filter(config -> BooleanUtil.isTrue(config.getPointTradeDeductEnable()))
+                .map(MemberConfigRespDTO::getPointTradeGivePoint)
                 .orElse(0);
         if (givePointPerYuan <= 0) {
             return;

+ 14 - 13
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;
 
 import cn.hutool.core.util.BooleanUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
-import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
+import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
@@ -20,7 +20,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
 
-// TODO @疯狂:搞个单测,嘿嘿;
 /**
  * 使用积分的 {@link TradePriceCalculator} 实现类
  *
@@ -32,19 +31,21 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCU
 public class TradePointUsePriceCalculator implements TradePriceCalculator {
 
     @Resource
-    private MemberPointApi memberPointApi;
+    private MemberConfigApi memberConfigApi;
     @Resource
     private MemberUserApi memberUserApi;
 
     @Override
     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
+        // 默认使用积分为 0
+        result.setUsePoint(0);
         // 1.1 校验是否使用积分
         if (!BooleanUtil.isTrue(param.getPointStatus())) {
             result.setUsePoint(0);
             return;
         }
         // 1.2 校验积分抵扣是否开启
-        MemberPointConfigRespDTO config = memberPointApi.getConfig();
+        MemberConfigRespDTO config = memberConfigApi.getConfig();
         if (!isDeductPointEnable(config)) {
             return;
         }
@@ -76,20 +77,20 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
         TradePriceCalculatorHelper.recountAllPrice(result);
     }
 
-    private boolean isDeductPointEnable(MemberPointConfigRespDTO config) {
+    private boolean isDeductPointEnable(MemberConfigRespDTO config) {
         return config != null &&
-                !BooleanUtil.isTrue(config.getTradeDeductEnable()) &&  // 积分功能是否启用
-                config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分
+                BooleanUtil.isTrue(config.getPointTradeDeductEnable()) &&  // 积分功能是否启用
+                config.getPointTradeDeductUnitPrice() != null && config.getPointTradeDeductUnitPrice() > 0; // 有没有配置:1 积分抵扣多少分
     }
 
-    private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
+    private Integer calculatePointPrice(MemberConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
         // 每个订单最多可以使用的积分数量
-        if (config.getTradeDeductMaxPrice() != null && config.getTradeDeductMaxPrice() > 0) {
-            usePoint = Math.min(usePoint, config.getTradeDeductMaxPrice());
+        if (config.getPointTradeDeductMaxPrice() != null && config.getPointTradeDeductMaxPrice() > 0) {
+            usePoint = Math.min(usePoint, config.getPointTradeDeductMaxPrice());
         }
         // TODO @疯狂:这里应该是,抵扣到只剩下 0.01;
         // 积分优惠金额(分)
-        int pointPrice = usePoint * config.getTradeDeductUnitPrice();
+        int pointPrice = usePoint * config.getPointTradeDeductUnitPrice();
         if (result.getPrice().getPayPrice() <= pointPrice) {
             // 禁止 0 元购
             throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
@@ -99,7 +100,7 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
 //            pointPrice = result.getPrice().getPayPrice();
 //            // 反推需要扣除的积分
 //            usePoint = NumberUtil.toBigDecimal(pointPrice)
-//                    .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
+//                    .divide(NumberUtil.toBigDecimal(config.getPointTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
 //                    .intValue();
 //        }
         // 记录使用的积分

+ 10 - 8
yudao-module-mall/yudao-module-trade-biz/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml

@@ -2,8 +2,7 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper">
 
-    <select id="selectSummaryPageByUserId"
-            resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
+    <sql id="selectSummaryListByUserId">
         SELECT bu.id, bu.bind_user_time AS brokerageTime,
         (SELECT SUM(price) FROM trade_brokerage_record r
         WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = FALSE) AS brokeragePrice,
@@ -14,12 +13,6 @@
         FROM trade_brokerage_user AS bu
         <where>
             bu.deleted = false
-            <if test="ids != null and ids.size() > 0">
-                and bu.id in
-                <foreach collection="ids" open="(" item="id" separator="," close=")">
-                    #{id}
-                </foreach>
-            </if>
             <if test="bindUserIds != null and bindUserIds.size() > 0">
                 and bu.bind_user_id in
                 <foreach collection="bindUserIds" open="(" item="bindUserId" separator="," close=")">
@@ -41,6 +34,15 @@
                 ORDER BY bu.bind_user_time DESC
             </otherwise>
         </choose>
+    </sql>
+
+    <select id="selectSummaryPageByUserId"
+            resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
+        <include refid="selectSummaryListByUserId" />
+    </select>
+    <select id="selectSummaryListByUserId"
+            resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
+        <include refid="selectSummaryListByUserId" />
     </select>
 
 </mapper>

+ 98 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.trade.service.price.calculator;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
+import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
+import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link TradePointGiveCalculator} 的单元测试类
+ *
+ * @author owen
+ */
+public class TradePointGiveCalculatorTest extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private TradePointGiveCalculator tradePointGiveCalculator;
+
+    @Mock
+    private MemberConfigApi memberConfigApi;
+
+    @Test
+    public void testCalculate() {
+
+        // 准备参数
+        TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
+                .setUserId(233L)
+                .setItems(asList(
+                        new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 全局积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 全局积分 + SKU 积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false), // 全局积分,但是未选中
+                        new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 全局积分 + SKU 积分,但是未选中
+                ));
+        TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
+                .setType(TradeOrderTypeEnum.NORMAL.getType())
+                .setPrice(new TradePriceCalculateRespBO.Price())
+                .setPromotions(new ArrayList<>())
+                .setItems(asList(
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
+                                .setPrice(100).setSpuId(1L).setGivePoint(0),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
+                                .setPrice(50).setSpuId(2L).setGivePoint(100),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(false)
+                                .setPrice(30).setSpuId(3L).setGivePoint(0),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(40L).setCount(5).setSelected(false)
+                                .setPrice(60).setSpuId(1L).setGivePoint(100)
+                ));
+        // 保证价格被初始化上
+        TradePriceCalculatorHelper.recountPayPrice(result.getItems());
+        TradePriceCalculatorHelper.recountAllPrice(result);
+
+        // mock 方法(积分配置 信息)
+        MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
+                o -> o.setPointTradeDeductEnable(true)  // 启用积分折扣
+                        .setPointTradeGivePoint(100)); // 1 元赠送多少分
+        when(memberConfigApi.getConfig()).thenReturn(memberConfig);
+
+        // 调用
+        tradePointGiveCalculator.calculate(param, result);
+        // 断言:Price 部分
+        assertEquals(result.getGivePoint(), 2 * 100 + 3 * 50 + 100);
+        // 断言:SKU 1
+        TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
+        assertEquals(orderItem01.getSkuId(), 10L);
+        assertEquals(orderItem01.getCount(), 2);
+        assertEquals(orderItem01.getPrice(), 100);
+        assertEquals(orderItem01.getGivePoint(), 2 * 100); // 全局积分
+        // 断言:SKU 2
+        TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
+        assertEquals(orderItem02.getSkuId(), 20L);
+        assertEquals(orderItem02.getCount(), 3);
+        assertEquals(orderItem02.getPrice(), 50);
+        assertEquals(orderItem02.getGivePoint(), 3 * 50 + 100); // 全局积分 + SKU 积分
+        // 断言:SKU 3
+        TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
+        assertEquals(orderItem03.getSkuId(), 30L);
+        assertEquals(orderItem03.getCount(), 4);
+        assertEquals(orderItem03.getPrice(), 30);
+        assertEquals(orderItem03.getGivePoint(), 0); // 全局积分,但是未选中
+        // 断言:SKU 4
+        TradePriceCalculateRespBO.OrderItem orderItem04 = result.getItems().get(3);
+        assertEquals(orderItem04.getSkuId(), 40L);
+        assertEquals(orderItem04.getCount(), 5);
+        assertEquals(orderItem04.getPrice(), 60);
+        assertEquals(orderItem04.getGivePoint(), 100); // 全局积分 + SKU 积分,但是未选中
+    }
+}

+ 332 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java

@@ -0,0 +1,332 @@
+package cn.iocoder.yudao.module.trade.service.price.calculator;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
+import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
+import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link TradePointUsePriceCalculator } 的单元测试类
+ *
+ * @author owen
+ */
+public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private TradePointUsePriceCalculator tradePointUsePriceCalculator;
+
+    @Mock
+    private MemberConfigApi memberConfigApi;
+    @Mock
+    private MemberUserApi memberUserApi;
+
+    @Test
+    public void testCalculate_success() {
+        // 准备参数
+        TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
+                .setUserId(233L).setPointStatus(true) // 是否使用积分
+                .setItems(asList(
+                        new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
+                ));
+        TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
+                .setType(TradeOrderTypeEnum.NORMAL.getType())
+                .setPrice(new TradePriceCalculateRespBO.Price())
+                .setPromotions(new ArrayList<>())
+                .setItems(asList(
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
+                                .setPrice(100).setSpuId(1L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
+                                .setPrice(50).setSpuId(2L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
+                                .setPrice(30).setSpuId(3L)
+                ));
+        // 保证价格被初始化上
+        TradePriceCalculatorHelper.recountPayPrice(result.getItems());
+        TradePriceCalculatorHelper.recountAllPrice(result);
+
+        // mock 方法(积分配置 信息)
+        MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
+                o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
+                        .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分)
+                        .setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值
+        when(memberConfigApi.getConfig()).thenReturn(memberConfig);
+        // mock 方法(会员 信息)
+        MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
+        when(memberUserApi.getUser(user.getId())).thenReturn(user);
+
+        // 调用
+        tradePointUsePriceCalculator.calculate(param, result);
+        // 断言:使用了多少积分
+        assertEquals(result.getUsePoint(), 100);
+        // 断言:Price 部分
+        TradePriceCalculateRespBO.Price price = result.getPrice();
+        assertEquals(price.getTotalPrice(), 350);
+        assertEquals(price.getPayPrice(), 250);
+        assertEquals(price.getPointPrice(), 100);
+        // 断言:SKU 1
+        TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
+        assertEquals(orderItem01.getSkuId(), 10L);
+        assertEquals(orderItem01.getCount(), 2);
+        assertEquals(orderItem01.getPrice(), 100);
+        assertEquals(orderItem01.getPointPrice(), 57);
+        assertEquals(orderItem01.getPayPrice(), 143);
+        // 断言:SKU 2
+        TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
+        assertEquals(orderItem02.getSkuId(), 20L);
+        assertEquals(orderItem02.getCount(), 3);
+        assertEquals(orderItem02.getPrice(), 50);
+        assertEquals(orderItem02.getPointPrice(), 43);
+        assertEquals(orderItem02.getPayPrice(), 107);
+        // 断言:SKU 3
+        TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
+        assertEquals(orderItem03.getSkuId(), 30L);
+        assertEquals(orderItem03.getCount(), 5);
+        assertEquals(orderItem03.getPrice(), 30);
+        assertEquals(orderItem03.getPointPrice(), 0);
+        assertEquals(orderItem03.getPayPrice(), 150);
+        // 断言:Promotion 部分
+        assertEquals(result.getPromotions().size(), 1);
+        TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
+        assertEquals(promotion01.getId(), user.getId());
+        assertEquals(promotion01.getName(), "积分抵扣");
+        assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType());
+        assertEquals(promotion01.getTotalPrice(), 350);
+        assertEquals(promotion01.getDiscountPrice(), 100);
+        assertTrue(promotion01.getMatch());
+        assertEquals(promotion01.getDescription(), "积分抵扣:省 1.00 元");
+        assertEquals(promotion01.getItems().size(), 2);
+        TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
+        assertEquals(promotionItem011.getSkuId(), 10L);
+        assertEquals(promotionItem011.getTotalPrice(), 200);
+        assertEquals(promotionItem011.getDiscountPrice(), 57);
+        TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
+        assertEquals(promotionItem012.getSkuId(), 20L);
+        assertEquals(promotionItem012.getTotalPrice(), 150);
+        assertEquals(promotionItem012.getDiscountPrice(), 43);
+    }
+
+    /**
+     * 当用户积分充足时,抵扣的金额为:配置表的“积分抵扣最大值”
+     */
+    @Test
+    public void testCalculate_TradeDeductMaxPrice() {
+        // 准备参数
+        TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
+                .setUserId(233L).setPointStatus(true) // 是否使用积分
+                .setItems(asList(
+                        new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
+                ));
+        TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
+                .setType(TradeOrderTypeEnum.NORMAL.getType())
+                .setPrice(new TradePriceCalculateRespBO.Price())
+                .setPromotions(new ArrayList<>())
+                .setItems(asList(
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
+                                .setPrice(100).setSpuId(1L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
+                                .setPrice(50).setSpuId(2L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
+                                .setPrice(30).setSpuId(3L)
+                ));
+        // 保证价格被初始化上
+        TradePriceCalculatorHelper.recountPayPrice(result.getItems());
+        TradePriceCalculatorHelper.recountAllPrice(result);
+
+        // mock 方法(积分配置 信息)
+        MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
+                o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
+                        .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分)
+                        .setPointTradeDeductMaxPrice(50)); // 积分抵扣最大值
+        when(memberConfigApi.getConfig()).thenReturn(memberConfig);
+        // mock 方法(会员 信息)
+        MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
+        when(memberUserApi.getUser(user.getId())).thenReturn(user);
+
+        // 调用
+        tradePointUsePriceCalculator.calculate(param, result);
+        // 断言:使用了多少积分
+        assertEquals(result.getUsePoint(), 50);
+        // 断言:Price 部分
+        TradePriceCalculateRespBO.Price price = result.getPrice();
+        assertEquals(price.getTotalPrice(), 350);
+        assertEquals(price.getPayPrice(), 300);
+        assertEquals(price.getPointPrice(), 50);
+        // 断言:SKU 1
+        TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
+        assertEquals(orderItem01.getSkuId(), 10L);
+        assertEquals(orderItem01.getCount(), 2);
+        assertEquals(orderItem01.getPrice(), 100);
+        assertEquals(orderItem01.getPointPrice(), 28);
+        assertEquals(orderItem01.getPayPrice(), 172);
+        // 断言:SKU 2
+        TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
+        assertEquals(orderItem02.getSkuId(), 20L);
+        assertEquals(orderItem02.getCount(), 3);
+        assertEquals(orderItem02.getPrice(), 50);
+        assertEquals(orderItem02.getPointPrice(), 22);
+        assertEquals(orderItem02.getPayPrice(), 128);
+        // 断言:SKU 3
+        TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
+        assertEquals(orderItem03.getSkuId(), 30L);
+        assertEquals(orderItem03.getCount(), 5);
+        assertEquals(orderItem03.getPrice(), 30);
+        assertEquals(orderItem03.getPointPrice(), 0);
+        assertEquals(orderItem03.getPayPrice(), 150);
+        // 断言:Promotion 部分
+        assertEquals(result.getPromotions().size(), 1);
+        TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
+        assertEquals(promotion01.getId(), user.getId());
+        assertEquals(promotion01.getName(), "积分抵扣");
+        assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType());
+        assertEquals(promotion01.getTotalPrice(), 350);
+        assertEquals(promotion01.getDiscountPrice(), 50);
+        assertTrue(promotion01.getMatch());
+        assertEquals(promotion01.getDescription(), "积分抵扣:省 0.50 元");
+        assertEquals(promotion01.getItems().size(), 2);
+        TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
+        assertEquals(promotionItem011.getSkuId(), 10L);
+        assertEquals(promotionItem011.getTotalPrice(), 200);
+        assertEquals(promotionItem011.getDiscountPrice(), 28);
+        TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
+        assertEquals(promotionItem012.getSkuId(), 20L);
+        assertEquals(promotionItem012.getTotalPrice(), 150);
+        assertEquals(promotionItem012.getDiscountPrice(), 22);
+    }
+
+    /**
+     * 订单不使用积分,不会产生优惠
+     */
+    @Test
+    public void testCalculate_PointStatusFalse() {
+        // 准备参数
+        TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
+                .setUserId(233L).setPointStatus(false) // 是否使用积分
+                .setItems(asList(
+                        new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
+                ));
+        TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
+                .setType(TradeOrderTypeEnum.NORMAL.getType())
+                .setPrice(new TradePriceCalculateRespBO.Price())
+                .setPromotions(new ArrayList<>())
+                .setItems(asList(
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
+                                .setPrice(100).setSpuId(1L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
+                                .setPrice(50).setSpuId(2L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
+                                .setPrice(30).setSpuId(3L)
+                ));
+        // 保证价格被初始化上
+        TradePriceCalculatorHelper.recountPayPrice(result.getItems());
+        TradePriceCalculatorHelper.recountAllPrice(result);
+
+        // 调用
+        tradePointUsePriceCalculator.calculate(param, result);
+        // 断言:没有使用积分
+        assertNotUsePoint(result);
+    }
+
+    /**
+     * 会员积分不足,不会产生优惠
+     */
+    @Test
+    public void testCalculate_UserPointNotEnough() {
+        // 准备参数
+        TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
+                .setUserId(233L).setPointStatus(true) // 是否使用积分
+                .setItems(asList(
+                        new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
+                        new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分
+                ));
+        TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
+                .setType(TradeOrderTypeEnum.NORMAL.getType())
+                .setPrice(new TradePriceCalculateRespBO.Price())
+                .setPromotions(new ArrayList<>())
+                .setItems(asList(
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
+                                .setPrice(100).setSpuId(1L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
+                                .setPrice(50).setSpuId(2L),
+                        new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
+                                .setPrice(30).setSpuId(3L)
+                ));
+        // 保证价格被初始化上
+        TradePriceCalculatorHelper.recountPayPrice(result.getItems());
+        TradePriceCalculatorHelper.recountAllPrice(result);
+
+        // mock 方法(积分配置 信息)
+        MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
+                o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
+                        .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分)
+                        .setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值
+        when(memberConfigApi.getConfig()).thenReturn(memberConfig);
+        // mock 方法(会员 信息)
+        MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(0));
+        when(memberUserApi.getUser(user.getId())).thenReturn(user);
+
+        // 调用
+        tradePointUsePriceCalculator.calculate(param, result);
+
+        // 断言:没有使用积分
+        assertNotUsePoint(result);
+    }
+
+    /**
+     * 断言:没有使用积分
+     */
+    private static void assertNotUsePoint(TradePriceCalculateRespBO result) {
+        // 断言:使用了多少积分
+        assertEquals(result.getUsePoint(), 0);
+        // 断言:Price 部分
+        TradePriceCalculateRespBO.Price price = result.getPrice();
+        assertEquals(price.getTotalPrice(), 350);
+        assertEquals(price.getPayPrice(), 350);
+        assertEquals(price.getPointPrice(), 0);
+        // 断言:SKU 1
+        TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
+        assertEquals(orderItem01.getSkuId(), 10L);
+        assertEquals(orderItem01.getCount(), 2);
+        assertEquals(orderItem01.getPrice(), 100);
+        assertEquals(orderItem01.getPointPrice(), 0);
+        assertEquals(orderItem01.getPayPrice(), 200);
+        // 断言:SKU 2
+        TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
+        assertEquals(orderItem02.getSkuId(), 20L);
+        assertEquals(orderItem02.getCount(), 3);
+        assertEquals(orderItem02.getPrice(), 50);
+        assertEquals(orderItem02.getPointPrice(), 0);
+        assertEquals(orderItem02.getPayPrice(), 150);
+        // 断言:SKU 3
+        TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
+        assertEquals(orderItem03.getSkuId(), 30L);
+        assertEquals(orderItem03.getCount(), 5);
+        assertEquals(orderItem03.getPrice(), 30);
+        assertEquals(orderItem03.getPointPrice(), 0);
+        assertEquals(orderItem03.getPayPrice(), 150);
+        // 断言:Promotion 部分
+        assertEquals(result.getPromotions().size(), 0);
+    }
+}

+ 5 - 0
yudao-module-member/yudao-module-member-api/pom.xml

@@ -22,6 +22,11 @@
             <artifactId>yudao-common</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mq</artifactId>
+            <scope>compile</scope>
+        </dependency>
         <!-- 参数校验 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 18 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApi.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.member.api.config;
+
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+
+/**
+ * 用户配置 API 接口
+ *
+ * @author owen
+ */
+public interface MemberConfigApi {
+
+    /**
+     * 获得积分配置
+     *
+     * @return 积分配置
+     */
+    MemberConfigRespDTO getConfig();
+}

+ 6 - 6
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/dto/MemberPointConfigRespDTO.java → yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/dto/MemberConfigRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.api.point.dto;
+package cn.iocoder.yudao.module.member.api.config.dto;
 
 import lombok.Data;
 
@@ -8,25 +8,25 @@ import lombok.Data;
  * @author 芋道源码
  */
 @Data
-public class MemberPointConfigRespDTO {
+public class MemberConfigRespDTO {
 
     /**
      * 积分抵扣开关
      */
-    private Boolean tradeDeductEnable;
+    private Boolean pointTradeDeductEnable;
     /**
      * 积分抵扣,单位:分
      * <p>
      * 1 积分抵扣多少分
      */
-    private Integer tradeDeductUnitPrice;
+    private Integer pointTradeDeductUnitPrice;
     /**
      * 积分抵扣最大值
      */
-    private Integer tradeDeductMaxPrice;
+    private Integer pointTradeDeductMaxPrice;
     /**
      * 1 元赠送多少分
      */
-    private Integer tradeGivePoint;
+    private Integer pointTradeGivePoint;
 
 }

+ 0 - 9
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.member.api.point;
 
-import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 
 import javax.validation.constraints.Min;
@@ -12,14 +11,6 @@ import javax.validation.constraints.Min;
  */
 public interface MemberPointApi {
 
-    // TODO @疯狂:这个我们要不要搞成通用的会员配置?MemberConfig?
-    /**
-     * 获得积分配置
-     *
-     * @return 积分配置
-     */
-    MemberPointConfigRespDTO getConfig();
-
     /**
      * 增加用户积分
      *

+ 1 - 0
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 {
     //========== 签到配置 1-004-009-000 ==========
     ErrorCode SIGN_IN_CONFIG_NOT_EXISTS = new ErrorCode(1_004_009_000, "签到天数规则不存在");
     ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1_004_009_001, "签到天数规则已存在");
+    ErrorCode SIGN_IN_CONFIG_AWARD_EMPTY = new ErrorCode(1_004_009_002, "签到奖励积分和经验不能同时为空");
 
     //========== 签到配置 1-004-010-000 ==========
     ErrorCode SIGN_IN_RECORD_TODAY_EXISTS = new ErrorCode(1_004_010_000,"今日已签到,请勿重复签到");

+ 1 - 0
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;
 public enum MemberPointBizTypeEnum implements IntArrayValuable {
 
     SIGN(1, "签到", "签到获得 {} 积分", true),
+    ADMIN(2, "管理员修改", "管理员修改 {} 积分", true),
     ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分
     ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时,退回积分
     ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时,扣减积分

+ 29 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/RegisterCouponSendMessage.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.mq.message.user;
+
+import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 新人券发放消息
+ *
+ * @author owen
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterCouponSendMessage extends AbstractStreamMessage {
+
+    /**
+     * 用户编号
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Override
+    public String getStreamKey() {
+        return "member.register-coupon.send";
+    }
+
+}

+ 28 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApiImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.api.config;
+
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
+import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 用户配置 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberConfigApiImpl implements MemberConfigApi {
+
+    @Resource
+    private MemberConfigService memberConfigService;
+
+    @Override
+    public MemberConfigRespDTO getConfig() {
+        return MemberConfigConvert.INSTANCE.convert01(memberConfigService.getConfig());
+    }
+
+}

+ 0 - 10
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java

@@ -1,10 +1,7 @@
 package cn.iocoder.yudao.module.member.api.point;
 
 import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
-import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
 import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
-import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
 import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -25,13 +22,6 @@ public class MemberPointApiImpl implements MemberPointApi {
 
     @Resource
     private MemberPointRecordService memberPointRecordService;
-    @Resource
-    private MemberPointConfigService memberPointConfigService;
-
-    @Override
-    public MemberPointConfigRespDTO getConfig() {
-        return MemberPointConfigConvert.INSTANCE.convert01(memberPointConfigService.getPointConfig());
-    }
 
     @Override
     public void addPoint(Long userId, Integer point, Integer bizType, String bizId) {

+ 45 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/MemberConfigController.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.member.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 会员设置")
+@RestController
+@RequestMapping("/member/config")
+@Validated
+public class MemberConfigController {
+
+    @Resource
+    private MemberConfigService memberConfigService;
+
+    @PutMapping("/save")
+    @Operation(summary = "保存会员配置")
+    @PreAuthorize("@ss.hasPermission('member:config:save')")
+    public CommonResult<Boolean> saveConfig(@Valid @RequestBody MemberConfigSaveReqVO saveReqVO) {
+        memberConfigService.saveConfig(saveReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得会员配置")
+    @PreAuthorize("@ss.hasPermission('member:config:query')")
+    public CommonResult<MemberConfigRespVO> getConfig() {
+        MemberConfigDO config = memberConfigService.getConfig();
+        return success(MemberConfigConvert.INSTANCE.convert(config));
+    }
+
+}

+ 7 - 7
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigBaseVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
+package cn.iocoder.yudao.module.member.controller.admin.config.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -6,26 +6,26 @@ import lombok.Data;
 import javax.validation.constraints.NotNull;
 
 /**
- * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 会员配置 Base VO,提供给添加、修改、详细的子 VO 使用
  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  */
 @Data
-public class MemberPointConfigBaseVO {
+public class MemberConfigBaseVO {
 
     @Schema(description = "积分抵扣开关", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
     @NotNull(message = "积分抵扣开发不能为空")
-    private Boolean tradeDeductEnable;
+    private Boolean pointTradeDeductEnable;
 
     @Schema(description = "积分抵扣,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "13506")
     @NotNull(message = "积分抵扣不能为空")
-    private Integer tradeDeductUnitPrice;
+    private Integer pointTradeDeductUnitPrice;
 
     @Schema(description = "积分抵扣最大值", requiredMode = Schema.RequiredMode.REQUIRED, example = "32428")
     @NotNull(message = "积分抵扣最大值不能为空")
-    private Integer tradeDeductMaxPrice;
+    private Integer pointTradeDeductMaxPrice;
 
     @Schema(description = "1 元赠送多少分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     @NotNull(message = "1 元赠送积分不能为空")
-    private Integer tradeGivePoint;
+    private Integer pointTradeGivePoint;
 
 }

+ 3 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigRespVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigRespVO.java

@@ -1,15 +1,15 @@
-package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
+package cn.iocoder.yudao.module.member.controller.admin.config.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
-@Schema(description = "管理后台 - 会员积分配置 Response VO")
+@Schema(description = "管理后台 - 会员配置 Response VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberPointConfigRespVO extends MemberPointConfigBaseVO {
+public class MemberConfigRespVO extends MemberConfigBaseVO {
 
     @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;

+ 13 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigSaveReqVO.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.member.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 会员配置保存 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberConfigSaveReqVO extends MemberConfigBaseVO {
+}

+ 0 - 45
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/MemberPointConfigController.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.member.controller.admin.point;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigRespVO;
-import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
-import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
-import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import javax.validation.Valid;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - 会员积分设置")
-@RestController
-@RequestMapping("/member/point/config")
-@Validated
-public class MemberPointConfigController {
-
-    @Resource
-    private MemberPointConfigService memberPointConfigService;
-
-    @PutMapping("/save")
-    @Operation(summary = "保存会员积分配置")
-    @PreAuthorize("@ss.hasPermission('point:config:save')")
-    public CommonResult<Boolean> savePointConfig(@Valid @RequestBody MemberPointConfigSaveReqVO saveReqVO) {
-        memberPointConfigService.savePointConfig(saveReqVO);
-        return success(true);
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得会员积分配置")
-    @PreAuthorize("@ss.hasPermission('point:config:query')")
-    public CommonResult<MemberPointConfigRespVO> getPointConfig() {
-        MemberPointConfigDO config = memberPointConfigService.getPointConfig();
-        return success(MemberPointConfigConvert.INSTANCE.convert(config));
-    }
-
-}

+ 0 - 13
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigSaveReqVO.java

@@ -1,13 +0,0 @@
-package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 会员积分配置保存 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class MemberPointConfigSaveReqVO extends MemberPointConfigBaseVO {
-}

+ 7 - 0
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;
 import lombok.Data;
 
 import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
 
 /**
  * 签到规则 Base VO,提供给添加、修改、详细的子 VO 使用
@@ -20,8 +21,14 @@ public class MemberSignInConfigBaseVO {
 
     @Schema(description = "奖励积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @NotNull(message = "奖励积分不能为空")
+    @PositiveOrZero(message = "奖励积分不能小于 0")
     private Integer point;
 
+    @Schema(description = "奖励经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @NotNull(message = "奖励经验不能为空")
+    @PositiveOrZero(message = "奖励经验不能小于 0")
+    private Integer experience;
+
     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "状态不能为空")
     @InEnum(CommonStatusEnum.class)

+ 1 - 1
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 {
     @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer day;
 
-    @Schema(description = "签到的分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "签到的分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer point;
 
     @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)

+ 23 - 4
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;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserRespVO;
-import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateLevelReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.*;
 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
 import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
 import cn.iocoder.yudao.module.member.service.tag.MemberTagService;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -33,6 +32,7 @@ import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - 会员用户")
 @RestController
@@ -48,6 +48,8 @@ public class MemberUserController {
     private MemberLevelService memberLevelService;
     @Resource
     private MemberGroupService memberGroupService;
+    @Resource
+    private MemberPointRecordService memberPointRecordService;
 
     @PutMapping("/update")
     @Operation(summary = "更新会员用户")
@@ -65,6 +67,23 @@ public class MemberUserController {
         return success(true);
     }
 
+    @PutMapping("/update-point")
+    @Operation(summary = "更新会员用户积分")
+    @PreAuthorize("@ss.hasPermission('member:user:update-point')")
+    public CommonResult<Boolean> updateUserPoint(@Valid @RequestBody MemberUserUpdatePointReqVO updateReqVO) {
+        memberPointRecordService.createPointRecord(updateReqVO.getId(), updateReqVO.getPoint(),
+                MemberPointBizTypeEnum.ADMIN, String.valueOf(getLoginUserId()));
+        return success(true);
+    }
+
+    @PutMapping("/update-balance")
+    @Operation(summary = "更新会员用户余额")
+    @PreAuthorize("@ss.hasPermission('member:user:update-balance')")
+    public CommonResult<Boolean> updateUserBalance(@Valid @RequestBody Long id) {
+        // todo @jason:增加一个【修改余额】
+        return success(true);
+    }
+
     @GetMapping("/get")
     @Operation(summary = "获得会员用户")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")

+ 1 - 3
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;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
 import javax.validation.constraints.NotBlank;
@@ -10,9 +9,8 @@ import javax.validation.constraints.NotNull;
 
 @Schema(description = "管理后台 - 会员用户 修改等级 Request VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberUserUpdateLevelReqVO extends MemberUserBaseVO {
+public class MemberUserUpdateLevelReqVO {
 
     @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
     @NotNull(message = "用户编号不能为空")

+ 22 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 会员用户 修改积分 Request VO")
+@Data
+@ToString(callSuper = true)
+public class MemberUserUpdatePointReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+    @Schema(description = "变动积分,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "变动积分不能为空")
+    private Integer point;
+
+}

+ 1 - 13
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInController.java

@@ -1,13 +1,10 @@
 package cn.iocoder.yudao.module.member.controller.app.signin;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
 import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -29,17 +26,8 @@ public class AppMemberSignInController {
     // TODO @xiaqing:合并到 AppMemberSignInRecordController 的 getSignInRecordSummary 里哈。
     @Operation(summary = "个人签到信息")
     @GetMapping("/get-summary")
-    public CommonResult getUserSummary(){
+    public CommonResult getUserSummary() {
         return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
     }
 
-    // TODO @xiaqing:泛型:
-    // TODO @xiaqing:合并到 AppMemberSignInRecordController 的 createSignInRecord 里哈。
-    @Operation(summary = "会员签到")
-    @PostMapping("/create")
-    public CommonResult create(){
-        MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
-        return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
-    }
-
 }

+ 2 - 7
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;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -50,16 +49,12 @@ public class AppMemberSignInRecordController {
         return success(respVO);
     }
 
-    // TODO 芋艿:临时 mock => UserSignController.info
     @PostMapping("/create")
     @Operation(summary = "签到")
     @PreAuthenticated
     public CommonResult<AppMemberSignInRecordRespVO> createSignInRecord() {
-        AppMemberSignInRecordRespVO respVO = new AppMemberSignInRecordRespVO()
-                .setPoint(10)
-                .setDay(10)
-                .setCreateTime(LocalDateTime.now());
-        return success(respVO);
+        MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
+        return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
     }
 
     @GetMapping("/page")

+ 4 - 1
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 {
     @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer day;
 
-    @Schema(description = "签到的分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @Schema(description = "签到的分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer point;
 
+    @Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer experience;
+
     @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 3 - 0
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 {
     @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer point;
 
+    @Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer experience;
+
     @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 25 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/config/MemberConfigConvert.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.member.convert.config;
+
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 会员配置 Convert
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberConfigConvert {
+
+    MemberConfigConvert INSTANCE = Mappers.getMapper(MemberConfigConvert.class);
+
+    MemberConfigRespVO convert(MemberConfigDO bean);
+
+    MemberConfigDO convert(MemberConfigSaveReqVO bean);
+
+    MemberConfigRespDTO convert01(MemberConfigDO config);
+}

+ 0 - 25
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/point/MemberPointConfigConvert.java

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.module.member.convert.point;
-
-import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
-import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigRespVO;
-import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-/**
- * 会员积分配置 Convert
- *
- * @author QingX
- */
-@Mapper
-public interface MemberPointConfigConvert {
-
-    MemberPointConfigConvert INSTANCE = Mappers.getMapper(MemberPointConfigConvert.class);
-
-    MemberPointConfigRespVO convert(MemberPointConfigDO bean);
-
-    MemberPointConfigDO convert(MemberPointConfigSaveReqVO bean);
-
-    MemberPointConfigRespDTO convert01(MemberPointConfigDO pointConfig);
-}

+ 9 - 9
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.dal.dataobject.point;
+package cn.iocoder.yudao.module.member.dal.dataobject.config;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -7,19 +7,19 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
 /**
- * 会员积分配置 DO
+ * 会员配置 DO
  *
  * @author QingX
  */
-@TableName(value = "member_point_config", autoResultMap = true)
-@KeySequence("member_point_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName(value = "member_config", autoResultMap = true)
+@KeySequence("member_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class MemberPointConfigDO extends BaseDO {
+public class MemberConfigDO extends BaseDO {
 
     /**
      * 自增主键
@@ -29,20 +29,20 @@ public class MemberPointConfigDO extends BaseDO {
     /**
      * 积分抵扣开关
      */
-    private Boolean tradeDeductEnable;
+    private Boolean pointTradeDeductEnable;
     /**
      * 积分抵扣,单位:分
      *
      * 1 积分抵扣多少分
      */
-    private Integer tradeDeductUnitPrice;
+    private Integer pointTradeDeductUnitPrice;
     /**
      * 积分抵扣最大值
      */
-    private Integer tradeDeductMaxPrice;
+    private Integer pointTradeDeductMaxPrice;
     /**
      * 1 元赠送多少分
      */
-    private Integer tradeGivePoint;
+    private Integer pointTradeGivePoint;
 
 }

+ 4 - 0
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 {
      * 奖励积分
      */
     private Integer point;
+    /**
+     * 奖励经验
+     */
+    private Integer experience;
 
     /**
      * 状态

+ 5 - 3
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 {
      */
     private Integer day;
     /**
-     * 签到的分
+     * 签到的
      */
     private Integer point;
-
-    // TODO 疯狂:签到的经验
+    /**
+     * 签到的经验
+     */
+    private Integer experience;
 
 }

+ 14 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/config/MemberConfigMapper.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.member.dal.mysql.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 积分设置 Mapper
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberConfigMapper extends BaseMapperX<MemberConfigDO> {
+}

+ 0 - 14
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/point/MemberPointConfigMapper.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.member.dal.mysql.point;
-
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
-import org.apache.ibatis.annotations.Mapper;
-
-/**
- * 积分设置 Mapper
- *
- * @author QingX
- */
-@Mapper
-public interface MemberPointConfigMapper extends BaseMapperX<MemberPointConfigDO> {
-}

+ 31 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/RegisterCouponProducer.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.mq.producer.user;
+
+import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
+import cn.iocoder.yudao.module.member.mq.message.user.RegisterCouponSendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 新人券发放 Producer
+ *
+ * @author owen
+ */
+@Slf4j
+@Component
+public class RegisterCouponProducer {
+
+    @Resource
+    private RedisMQTemplate redisMQTemplate;
+
+    /**
+     * 发送 {@link RegisterCouponSendMessage} 消息
+     *
+     * @param userId 用户编号
+     */
+    public void sendMailSendMessage(Long userId) {
+        redisMQTemplate.send(new RegisterCouponSendMessage().setUserId(userId));
+    }
+
+}

+ 29 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigService.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.service.config;
+
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+
+import javax.validation.Valid;
+
+/**
+ * 会员配置 Service 接口
+ *
+ * @author QingX
+ */
+public interface MemberConfigService {
+
+    /**
+     * 保存会员配置
+     *
+     * @param saveReqVO 更新信息
+     */
+    void saveConfig(@Valid MemberConfigSaveReqVO saveReqVO);
+
+    /**
+     * 获得会员配置
+     *
+     * @return 积分配置
+     */
+    MemberConfigDO getConfig();
+
+}

+ 44 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigServiceImpl.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.member.service.config;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import cn.iocoder.yudao.module.member.dal.mysql.config.MemberConfigMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 会员配置 Service 实现类
+ *
+ * @author QingX
+ */
+@Service
+@Validated
+public class MemberConfigServiceImpl implements MemberConfigService {
+
+    @Resource
+    private MemberConfigMapper memberConfigMapper;
+
+    @Override
+    public void saveConfig(MemberConfigSaveReqVO saveReqVO) {
+        // 存在,则进行更新
+        MemberConfigDO dbConfig = getConfig();
+        if (dbConfig != null) {
+            memberConfigMapper.updateById(MemberConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
+            return;
+        }
+        // 不存在,则进行插入
+        memberConfigMapper.insert(MemberConfigConvert.INSTANCE.convert(saveReqVO));
+    }
+
+    @Override
+    public MemberConfigDO getConfig() {
+        List<MemberConfigDO> list = memberConfigMapper.selectList();
+        return CollectionUtils.getFirst(list);
+    }
+
+}

+ 2 - 1
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 {
 
         // 1. 创建经验记录
         MemberUserDO user = memberUserService.getUser(userId);
-        int userExperience = NumberUtil.max(user.getExperience() + experience, 0); // 防止扣出负数
+        Integer userExperience = ObjUtil.defaultIfNull(user.getExperience(), 0);
+        userExperience = NumberUtil.max(userExperience + experience, 0); // 防止扣出负数
         MemberLevelRecordDO levelRecord = new MemberLevelRecordDO()
                 .setUserId(user.getId())
                 .setExperience(experience)

+ 0 - 29
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointConfigService.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.member.service.point;
-
-import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
-
-import javax.validation.Valid;
-
-/**
- * 会员积分配置 Service 接口
- *
- * @author QingX
- */
-public interface MemberPointConfigService {
-
-    /**
-     * 保存会员积分配置
-     *
-     * @param saveReqVO 更新信息
-     */
-    void savePointConfig(@Valid MemberPointConfigSaveReqVO saveReqVO);
-
-    /**
-     * 获得会员积分配置
-     *
-     * @return 积分配置
-     */
-    MemberPointConfigDO getPointConfig();
-
-}

+ 0 - 44
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointConfigServiceImpl.java

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.member.service.point;
-
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
-import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
-import cn.iocoder.yudao.module.member.dal.mysql.point.MemberPointConfigMapper;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 会员积分配置 Service 实现类
- *
- * @author QingX
- */
-@Service
-@Validated
-public class MemberPointConfigServiceImpl implements MemberPointConfigService {
-
-    @Resource
-    private MemberPointConfigMapper memberPointConfigMapper;
-
-    @Override
-    public void savePointConfig(MemberPointConfigSaveReqVO saveReqVO) {
-        // 存在,则进行更新
-        MemberPointConfigDO dbConfig = getPointConfig();
-        if (dbConfig != null) {
-            memberPointConfigMapper.updateById(MemberPointConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
-            return;
-        }
-        // 不存在,则进行插入
-        memberPointConfigMapper.insert(MemberPointConfigConvert.INSTANCE.convert(saveReqVO));
-    }
-
-    @Override
-    public MemberPointConfigDO getPointConfig() {
-        List<MemberPointConfigDO> list = memberPointConfigMapper.selectList();
-        return CollectionUtils.getFirst(list);
-    }
-
-}

+ 3 - 0
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 {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId) {
+        if (point == 0) {
+            return;
+        }
         // 1. 校验用户积分余额
         MemberUserDO user = memberUserService.getUser(userId);
         Integer userPoint = ObjectUtil.defaultIfNull(user.getPoint(), 0);

+ 15 - 4
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;
 import javax.annotation.Resource;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_CONFIG_EXISTS;
-import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_CONFIG_NOT_EXISTS;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
 
 /**
  * 签到规则 Service 实现类
@@ -30,6 +30,8 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
 
     @Override
     public Long createSignInConfig(MemberSignInConfigCreateReqVO createReqVO) {
+        // 校验奖励积分、奖励经验
+        validatePointAndExperience(createReqVO.getPoint(), createReqVO.getExperience());
         // 判断是否重复插入签到天数
         validateSignInConfigDayDuplicate(createReqVO.getDay(), null);
 
@@ -42,6 +44,8 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
 
     @Override
     public void updateSignInConfig(MemberSignInConfigUpdateReqVO updateReqVO) {
+        // 校验奖励积分、奖励经验
+        validatePointAndExperience(updateReqVO.getPoint(), updateReqVO.getExperience());
         // 校验存在
         validateSignInConfigExists(updateReqVO.getId());
         // 判断是否重复插入签到天数
@@ -70,7 +74,7 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
      * 校验 day 是否重复
      *
      * @param day 天
-     * @param id 编号,只有更新的时候会传递
+     * @param id  编号,只有更新的时候会传递
      */
     private void validateSignInConfigDayDuplicate(Integer day, Long id) {
         MemberSignInConfigDO config = memberSignInConfigMapper.selectByDay(day);
@@ -84,13 +88,20 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
         }
     }
 
+    private void validatePointAndExperience(Integer point, Integer experience) {
+        // 奖励积分、经验 至少要配置一个,否则没有意义
+        if (Objects.equals(point, 0) && Objects.equals(experience, 0)) {
+            throw exception(SIGN_IN_CONFIG_AWARD_EMPTY);
+        }
+    }
+
     @Override
     public MemberSignInConfigDO getSignInConfig(Long id) {
         return memberSignInConfigMapper.selectById(id);
     }
 
     @Override
-    public List <MemberSignInConfigDO> getSignInConfigList() {
+    public List<MemberSignInConfigDO> getSignInConfigList() {
         List<MemberSignInConfigDO> list = memberSignInConfigMapper.selectList();
         list.sort(Comparator.comparing(MemberSignInConfigDO::getDay));
         return list;

+ 45 - 22
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;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
@@ -13,8 +14,13 @@ import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO
 import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInConfigMapper;
 import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper;
 import cn.iocoder.yudao.module.member.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.validation.annotation.Validated;
 
@@ -40,20 +46,24 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
     private MemberSignInRecordMapper signInRecordMapper;
     @Resource
     private MemberSignInConfigMapper signInConfigMapper;
+    @Resource
+    private MemberPointRecordService pointRecordService;
+    @Resource
+    private MemberLevelService memberLevelService;
 
     @Resource
     private MemberUserApi memberUserApi;
 
     @Override
     public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) {
-        AppMemberSignInSummaryRespVO vo  = new AppMemberSignInSummaryRespVO();
+        AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
         vo.setTotalDay(0);
         vo.setContinuousDay(0);
         vo.setTodaySignIn(false);
         //获取用户签到的记录,按照天数倒序获取
-        List <MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
+        List<MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
         // TODO @xiaqing:if 空的时候,直接 return;这样括号少,逻辑更简洁;
-        if(!CollectionUtils.isEmpty(signInRecordDOList)){
+        if (!CollectionUtils.isEmpty(signInRecordDOList)) {
             //设置总签到天数
             vo.setTotalDay(signInRecordDOList.size()); // TODO @xiaqing:是不是不用读取 signInRecordDOList 所有的,而是 count下,然后另外再读取一条最后一条;
             //判断当天是否有签到复用校验方法
@@ -61,11 +71,11 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
             try {
                 validSignDay(signInRecordDOList.get(0));
                 vo.setTodaySignIn(false);
-            }catch (Exception e){
+            } catch (Exception e) {
                 vo.setTodaySignIn(true);
             }
             //如果当天签到了则说明连续签到天数有意义,否则直接用默认值0
-            if(vo.getTodaySignIn()){
+            if (vo.getTodaySignIn()) {
                 //下方计算连续签到从2天开始,此处直接设置一天连续签到
                 vo.setContinuousDay(1);
                 //判断连续签到天数
@@ -73,10 +83,10 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
                 for (int i = 1; i < signInRecordDOList.size(); i++) {
                     //前一天减1等于当前天数则说明连续,继续循环
                     LocalDate cur = signInRecordDOList.get(i).getCreateTime().toLocalDate();
-                    LocalDate pre = signInRecordDOList.get(i-1).getCreateTime().toLocalDate();
-                    if(1==daysBetween(cur,pre)){
-                        vo.setContinuousDay(i+1);
-                    }else{
+                    LocalDate pre = signInRecordDOList.get(i - 1).getCreateTime().toLocalDate();
+                    if (1 == daysBetween(cur, pre)) {
+                        vo.setContinuousDay(i + 1);
+                    } else {
                         break;
                     }
                 }
@@ -87,16 +97,16 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
         return vo;
     }
 
-    private long daysBetween(LocalDate date1,LocalDate date2){
+    private long daysBetween(LocalDate date1, LocalDate date2) {
         return ChronoUnit.DAYS.between(date1, date2);
     }
 
     @Override
-    public PageResult <MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
+    public PageResult<MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
         // 根据用户昵称查询出用户ids
-        Set <Long> userIds = null;
+        Set<Long> userIds = null;
         if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
-            List <MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
+            List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
             // 如果查询用户结果为空直接返回无需继续查询
             if (CollectionUtils.isEmpty(users)) {
                 return PageResult.empty();
@@ -113,50 +123,63 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public MemberSignInRecordDO createSignRecord(Long userId) {
         // 获取当前用户签到的最大天数
         // TODO @xiaqing:db 操作,dou封装到 mapper 中;
         // TODO @xiaqing:maxSignDay,是不是变量叫 lastRecord 会更容易理解哈;
-        MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX <MemberSignInRecordDO>()
+        MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX<MemberSignInRecordDO>()
                 .eq(MemberSignInRecordDO::getUserId, userId)
                 .orderByDesc(MemberSignInRecordDO::getDay)
                 .last("limit 1"));
         // 判断是否重复签到
         validSignDay(maxSignDay);
 
-        // TODO @xiaqing:可以使用 // 进行注释
-        /**1.查询出当前签到的天数**/
+        // 1. 查询出当前签到的天数
         MemberSignInRecordDO sign = new MemberSignInRecordDO().setUserId(userId); // TODO @xiaqing:应该使用 record 变量,会更合适
         sign.setDay(1); // 设置签到初始化天数
-        sign.setPoint(0);  // 设置签到分数默认为 0
+        sign.setPoint(0);  // 设置签到积分默认为 0
+        sign.setExperience(0);  // 设置签到经验默认为 0
         // 如果不为空则修改当前签到对应的天数
         // TODO @xiaqing:应该是要判断连续哈,就是昨天;
         if (maxSignDay != null) {
             sign.setDay(maxSignDay.getDay() + 1);
         }
-        /**2.获取签到对应的分数**/
+        // 2. 获取签到对应的积分数
         // 获取所有的签到规则,按照天数排序,只获取启用的 TODO @xiaqing:不要使用 signInConfigMapper 直接查询,而是要通过 SigninConfigService;
-        List <MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX <MemberSignInConfigDO>()
+        List<MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX<MemberSignInConfigDO>()
                 .eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
                 .orderByAsc(MemberSignInConfigDO::getDay));
-        // 如果签到的天数大于最大启用的规则天数,直接给最大签到的分数
+        // 如果签到的天数大于最大启用的规则天数,直接给最大签到的分数
         // TODO @xiaqing:超过最大配置的天数,应该直接重置到第一天哈;
         MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1);
         if (sign.getDay() > lastConfig.getDay()) {
             sign.setPoint(lastConfig.getPoint());
+            sign.setExperience(lastConfig.getExperience());
         } else {
             configDOList.forEach(el -> {
-                // 循环匹配对应天数,设置对应分数
+                // 循环匹配对应天数,设置对应分数
                 // TODO @xiaqing:使用 equals;另外,这种不应该去遍历比较,从可读性来说,应该  CollUtil.findOne()
                 if (el.getDay() == sign.getDay()) {
                     sign.setPoint(el.getPoint());
+                    sign.setExperience(el.getExperience());
                 }
 
             });
         }
 
-        // 3. 插入当前签到获取的分数
+        // 3. 插入签到记录
         signInRecordMapper.insert(sign);
+
+        // 4. 增加积分
+        if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) {
+            pointRecordService.createPointRecord(userId, sign.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(sign.getId()));
+        }
+        // 5. 增加经验
+        if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) {
+            memberLevelService.addExperience(userId, sign.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(sign.getId()));
+        }
+
         return sign;
     }
 

+ 7 - 0
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;
 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
+import cn.iocoder.yudao.module.member.mq.producer.user.RegisterCouponProducer;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
@@ -58,6 +59,9 @@ public class MemberUserServiceImpl implements MemberUserService {
     @Resource
     private PasswordEncoder passwordEncoder;
 
+    @Resource
+    private RegisterCouponProducer registerCouponProducer;
+
     @Override
     public MemberUserDO getUserByMobile(String mobile) {
         return memberUserMapper.selectByMobile(mobile);
@@ -89,6 +93,9 @@ public class MemberUserServiceImpl implements MemberUserService {
         user.setPassword(encodePassword(password)); // 加密密码
         user.setRegisterIp(registerIp);
         memberUserMapper.insert(user);
+
+        // 发送 MQ 消息,发放新人券
+        registerCouponProducer.sendMailSendMessage(user.getId());
         return user;
     }
 

+ 39 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.pay.controller.admin.wallet;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletRespVO;
+import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletUserReqVO;
+import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 用户钱包")
+@RestController
+@RequestMapping("/pay/wallet")
+@Validated
+@Slf4j
+public class PayWalletController {
+
+    @Resource
+    private PayWalletService payWalletService;
+
+    @GetMapping("/user-wallet")
+    @PreAuthorize("@ss.hasPermission('pay:wallet:query')")
+    @Operation(summary = "获得用户钱包明细")
+    public CommonResult<PayWalletRespVO> getByUser(PayWalletUserReqVO reqVO) {
+        PayWalletDO wallet = payWalletService.getWalletByUserIdAndType(reqVO.getUserId(), reqVO.getUserType());
+        return success(PayWalletConvert.INSTANCE.convert02(wallet));
+    }
+}

+ 39 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletBaseVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 用户钱包 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class PayWalletBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "用户类型不能为空")
+    private Byte userType;
+
+    @Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "余额,单位分不能为空")
+    private Integer balance;
+
+    @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "累计支出,单位分不能为空")
+    private Integer totalExpense;
+
+    @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "累计充值,单位分不能为空")
+    private Integer totalRecharge;
+
+    @Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737")
+    @NotNull(message = "冻结金额,单位分不能为空")
+    private Integer freezePrice;
+
+}

+ 22 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 用户钱包 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayWalletRespVO extends PayWalletBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 22 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/PayWalletUserReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 用户钱包明细 Request VO")
+@Data
+public class PayWalletUserReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "用户类型不能为空")
+    @InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
+    private Integer userType;
+}

+ 3 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.convert.wallet;
 
+import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletRespVO;
 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import org.mapstruct.Mapper;
@@ -11,4 +12,6 @@ public interface PayWalletConvert {
     PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class);
 
     AppPayWalletRespVO convert(PayWalletDO bean);
+
+    PayWalletRespVO convert02(PayWalletDO wallet);
 }

+ 25 - 15
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 {
 
     /**
      * 获取钱包信息
-     *
+     * <p>
      * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包
      *
-     * @param userId 用户编号
+     * @param userId   用户编号
      * @param userType 用户类型
      */
     PayWalletDO getOrCreateWallet(Long userId, Integer userType);
@@ -31,10 +31,10 @@ public interface PayWalletService {
     /**
      * 钱包订单支付
      *
-     * @param userId  用户 id
-     * @param userType 用户类型
+     * @param userId     用户 id
+     * @param userType   用户类型
      * @param outTradeNo 外部订单号
-     * @param price 金额
+     * @param price      金额
      */
     PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
 
@@ -43,17 +43,17 @@ public interface PayWalletService {
      *
      * @param outRefundNo 外部退款号
      * @param refundPrice 退款金额
-     * @param reason  退款原因
+     * @param reason      退款原因
      */
     PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason);
 
     /**
      * 扣减钱包余额
      *
-     * @param walletId  钱包 id
-     * @param bizId 业务关联 id
-     * @param bizType 业务关联分类
-     * @param price 扣减金额
+     * @param walletId 钱包 id
+     * @param bizId    业务关联 id
+     * @param bizType  业务关联分类
+     * @param price    扣减金额
      * @return 钱包流水
      */
     PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
@@ -63,9 +63,9 @@ public interface PayWalletService {
      * 增加钱包余额
      *
      * @param walletId 钱包 id
-     * @param bizId 业务关联 id
-     * @param bizType 业务关联分类
-     * @param price 增加金额
+     * @param bizId    业务关联 id
+     * @param bizType  业务关联分类
+     * @param price    增加金额
      * @return 钱包流水
      */
     PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
@@ -74,15 +74,25 @@ public interface PayWalletService {
     /**
      * 冻结钱包部分余额
      *
-     * @param id  钱包编号
+     * @param id    钱包编号
      * @param price 冻结金额
      */
     void freezePrice(Long id, Integer price);
 
     /**
      * 解冻钱包余额
-     * @param id 钱包编号
+     *
+     * @param id    钱包编号
      * @param price 解冻金额
      */
     void unFreezePrice(Long id, Integer price);
+
+    /**
+     * 获得用户的钱包明细
+     *
+     * @param userId   用户编号
+     * @param userType 用户类型
+     * @return 用户的钱包明细
+     */
+    PayWalletDO getWalletByUserIdAndType(Long userId, Integer userType);
 }

+ 5 - 0
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 {
         }
     }
 
+    @Override
+    public PayWalletDO getWalletByUserIdAndType(Long userId, Integer userType) {
+        return walletMapper.selectByUserIdAndType(userId, userType);
+    }
+
 }