Forráskód Böngészése

Merge remote-tracking branch 'yudao/feature/mall_product' into feature/mall_product

puhui999 1 éve
szülő
commit
498edfffb1
98 módosított fájl, 1782 hozzáadás és 1003 törlés
  1. 0 7
      sql/mysql/member_level.sql
  2. 44 0
      sql/mysql/pay_wallet.sql
  3. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
  4. 6 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java
  5. 49 41
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java
  6. 170 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java
  7. 44 27
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java
  8. 1 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java
  9. 0 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java
  10. 10 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java
  11. 1 44
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java
  12. 0 30
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java
  13. 21 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/comment/ProductCommentConvert.java
  14. 0 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
  15. 16 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java
  16. 3 4
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java
  17. 23 23
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java
  18. 52 47
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java
  19. 4 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
  20. 55 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  21. 4 3
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java
  22. 22 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
  23. 0 1
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java
  24. 3 4
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  25. 30 8
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java
  26. 19 3
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
  27. 11 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java
  28. 34 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
  29. 41 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/AddressController.java
  30. 37 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressBaseVO.java
  31. 19 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressRespVO.java
  32. 17 17
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberExperienceLogController.java
  33. 5 7
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java
  34. 18 17
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelLogController.java
  35. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceLogBaseVO.java
  36. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceLogPageReqVO.java
  37. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceLogRespVO.java
  38. 2 5
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelPageReqVO.java
  39. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelLogBaseVO.java
  40. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelLogPageReqVO.java
  41. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelLogRespVO.java
  42. 18 12
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
  43. 4 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java
  44. 7 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java
  45. 31 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java
  46. 0 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateReqVO.java
  47. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java
  48. 0 27
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceLogConvert.java
  49. 30 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java
  50. 0 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java
  51. 0 27
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelLogConvert.java
  52. 37 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java
  53. 10 10
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java
  54. 3 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberExperienceLogDO.java
  55. 3 4
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelLogDO.java
  56. 0 28
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceLogMapper.java
  57. 28 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java
  58. 3 4
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java
  59. 9 9
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelLogMapper.java
  60. 0 15
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
  61. 3 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java
  62. 0 64
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogServiceImpl.java
  63. 8 18
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogService.java
  64. 54 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java
  65. 0 77
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogService.java
  66. 0 109
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogServiceImpl.java
  67. 37 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java
  68. 39 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java
  69. 10 15
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java
  70. 80 78
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java
  71. 10 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointRecordService.java
  72. 42 5
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointRecordServiceImpl.java
  73. 3 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImpl.java
  74. 47 7
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java
  75. 31 8
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  76. 8 9
      yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java
  77. 3 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
  78. 7 6
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionGategoryEnum.java
  79. 0 21
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletOperateTypeEnum.java
  80. 35 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionQueryTypeEnum.java
  81. 41 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java
  82. 43 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java
  83. 21 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletRespVO.java
  84. 16 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionPageReqVO.java
  85. 19 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionRespVO.java
  86. 14 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
  87. 15 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
  88. 0 86
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletTransactionDO.java
  89. 14 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletDO.java
  90. 70 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
  91. 0 15
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletMapper.java
  92. 0 15
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletTransactionMapper.java
  93. 18 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
  94. 33 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
  95. 18 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
  96. 24 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
  97. 23 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
  98. 41 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java

+ 0 - 7
sql/mysql/member_level.sql

@@ -1,8 +0,0 @@
-insert system_dict_type(name, type) values ('会员经验业务类型', 'member_experience_biz_type');
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '管理员调整', '0', 0);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '邀新奖励', '1', 1);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '下单奖励', '2', 2);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '退单扣除', '3', 3);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '签到奖励', '4', 4);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '抽奖奖励', '5', 5);

+ 44 - 0
sql/mysql/pay_wallet.sql

@@ -0,0 +1,44 @@
+-- ----------------------------
+-- 支付-钱包表
+-- ----------------------------
+DROP TABLE IF EXISTS `pay_wallet`;
+CREATE TABLE `pay_wallet`
+(
+    `id`             bigint   NOT NULL AUTO_INCREMENT COMMENT '编号',
+    `user_id`        bigint   NOT NULL COMMENT '用户 id',
+    `user_type`      tinyint  NOT NULL DEFAULT 0 COMMENT '用户类型',
+    `balance`        int      NOT NULL DEFAULT 0 COMMENT '余额, 单位分',
+    `total_expense`  bigint      NOT NULL DEFAULT 0 COMMENT '累计支出, 单位分',
+    `total_recharge` bigint      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 '更新者',
+    `update_time`    datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`        bit(1)   NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`      bigint   NOT NULL DEFAULT 0 COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='支付钱包表';
+
+-- ----------------------------
+-- 支付- 钱包余额明细表
+-- ----------------------------
+DROP TABLE IF EXISTS `pay_wallet_transaction`;
+CREATE TABLE `pay_wallet_transaction`
+(
+    `id`               bigint      NOT NULL AUTO_INCREMENT COMMENT '编号',
+    `wallet_id`        bigint      NOT NULL COMMENT '会员钱包 id',
+    `biz_type`         tinyint     NOT NULL COMMENT '关联类型',
+    `biz_id`           bigint      NOT NULL COMMENT '关联业务编号',
+    `no`               varchar(64) NOT NULL COMMENT '流水号',
+    `description`      varchar(255)         COMMENT '操作说明',
+    `amount`           int         NOT NULL COMMENT '交易金额, 单位分',
+    `balance`          int         NOT NULL COMMENT '余额, 单位分',
+    `transaction_time` datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP 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 '更新者',
+    `update_time`      datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `deleted`          bit(1)      NOT NULL DEFAULT b'0' COMMENT '是否删除',
+    `tenant_id`        bigint      NOT NULL DEFAULT 0 COMMENT '租户编号',
+    PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB COMMENT='支付钱包余额明细表';

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java

@@ -27,6 +27,7 @@ import com.alipay.api.request.AlipayTradeRefundRequest;
 import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
 import com.alipay.api.response.AlipayTradeQueryResponse;
 import com.alipay.api.response.AlipayTradeRefundResponse;
+import lombok.Getter;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
@@ -47,6 +48,7 @@ import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
 @Slf4j
 public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPayClientConfig> {
 
+    @Getter // 仅用于单测场景
     protected DefaultAlipayClient client;
 
     public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {

+ 6 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java

@@ -13,6 +13,8 @@ import com.alipay.api.request.AlipayTradePayRequest;
 import com.alipay.api.response.AlipayTradePayResponse;
 import lombok.extern.slf4j.Slf4j;
 
+import java.time.LocalDateTime;
+
 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
 
@@ -63,8 +65,10 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
             return buildClosedPayOrderRespDTO(reqDTO, response);
         }
         if ("10000".equals(response.getCode())) { // 免密支付
-            return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getGmtPayment()),
-                    response.getOutTradeNo(), response);
+            LocalDateTime successTime = LocalDateTimeUtil.of(response.getGmtPayment());
+            return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), successTime,
+                            response.getOutTradeNo(), response)
+                    .setDisplayMode(displayMode).setDisplayContent("");
         }
         // 大额支付,需要用户输入密码,所以返回 waiting。此时,前端一般会进行轮询
         return PayOrderRespDTO.waitingOf(displayMode, "",

+ 49 - 41
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.RandomUtil;
-import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
@@ -18,6 +17,7 @@ import com.alipay.api.DefaultSigner;
 import com.alipay.api.domain.AlipayTradeRefundModel;
 import com.alipay.api.request.AlipayTradeRefundRequest;
 import com.alipay.api.response.AlipayTradeRefundResponse;
+import lombok.Setter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -34,25 +34,26 @@ import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.when;
 
 /**
+ * 支付宝 Client 的测试基类
+ *
  * @author jason
  */
 public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
 
-    private final String privateKey = randomString();
-
-    protected AlipayPayClientConfig config = randomPojo(AlipayPayClientConfig.class, t -> {
-        t.setServerUrl(randomURL());
-        t.setPrivateKey(privateKey);
-        t.setMode(MODE_PUBLIC_KEY);
-        t.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
-        t.setAppCertContent("");
-        t.setAlipayPublicCertContent("");
-        t.setRootCertContent("");
+    protected AlipayPayClientConfig config = randomPojo(AlipayPayClientConfig.class, o -> {
+        o.setServerUrl(randomURL());
+        o.setPrivateKey(randomString());
+        o.setMode(MODE_PUBLIC_KEY);
+        o.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
+        o.setAppCertContent("");
+        o.setAlipayPublicCertContent("");
+        o.setRootCertContent("");
     });
 
     @Mock
     protected DefaultAlipayClient defaultAlipayClient;
 
+    @Setter
     private AbstractAlipayPayClient client;
 
     /**
@@ -61,24 +62,22 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
     @BeforeEach
     public abstract void setUp();
 
-    public void setClient(AbstractAlipayPayClient client) {
-        this.client = client;
-    }
-
     @Test
     @DisplayName("支付宝 Client 初始化")
-    public void test_do_init() {
+    public void testDoInit() {
+        // 调用
         client.doInit();
-        DefaultAlipayClient realClient = (DefaultAlipayClient) ReflectUtil.getFieldValue(client, "client");
+        // 断言
+        DefaultAlipayClient realClient = client.getClient();
         assertNotSame(defaultAlipayClient, realClient);
         assertInstanceOf(DefaultSigner.class, realClient.getSigner());
-        assertEquals(privateKey, ((DefaultSigner) realClient.getSigner()).getPrivateKey());
+        assertEquals(config.getPrivateKey(), ((DefaultSigner) realClient.getSigner()).getPrivateKey());
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一退款成功")
-    public void test_unified_refund_success() throws AlipayApiException {
-        // 准备返回对象
+    @DisplayName("支付宝 Client 统一退款成功")
+    public void testUnifiedRefund_success() throws AlipayApiException {
+        // mock 方法
         String notifyUrl = randomURL();
         Date refundTime = randomDate();
         String outRefundNo = randomString();
@@ -88,7 +87,6 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
             o.setSubCode("");
             o.setGmtRefundPay(refundTime);
         });
-        // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> {
             assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel());
             AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel();
@@ -104,19 +102,23 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
             o.setNotifyUrl(notifyUrl);
             o.setRefundPrice(refundAmount);
         });
+
+        // 调用
         PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO);
         // 断言
         assertEquals(PayRefundStatusRespEnum.SUCCESS.getStatus(), resp.getStatus());
+        assertEquals(outRefundNo, resp.getOutRefundNo());
         assertNull(resp.getChannelRefundNo());
         assertEquals(LocalDateTimeUtil.of(refundTime), resp.getSuccessTime());
-        assertEquals(outRefundNo, resp.getOutRefundNo());
         assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一退款,渠道返回失败")
+    @DisplayName("支付宝 Client 统一退款渠道返回失败")
     public void test_unified_refund_channel_failed() throws AlipayApiException {
-        // 准备返回对象
+        // mock 方法
         String notifyUrl = randomURL();
         String subCode = randomString();
         String subMsg = randomString();
@@ -124,7 +126,6 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
             o.setSubCode(subCode);
             o.setSubMsg(subMsg);
         });
-        // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> {
             assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel());
             return true;
@@ -137,59 +138,64 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
             o.setOutTradeNo(outTradeNo);
             o.setNotifyUrl(notifyUrl);
         });
+
+        // 调用
         PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO);
         // 断言
         assertEquals(PayRefundStatusRespEnum.FAILURE.getStatus(), resp.getStatus());
+        assertEquals(outRefundNo, resp.getOutRefundNo());
         assertNull(resp.getChannelRefundNo());
-        assertEquals(subCode, resp.getChannelErrorCode());
-        assertEquals(subMsg, resp.getChannelErrorMsg());
         assertNull(resp.getSuccessTime());
-        assertEquals(outRefundNo, resp.getOutRefundNo());
         assertSame(response, resp.getRawData());
+        assertEquals(subCode, resp.getChannelErrorCode());
+        assertEquals(subMsg, resp.getChannelErrorMsg());
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一退款,参数校验不通过")
-    public void test_unified_refund_param_validate() {
+    @DisplayName("支付宝 Client 统一退款参数校验不通过")
+    public void testUnifiedRefund_paramInvalidate() {
         // 准备请求参数
         String notifyUrl = randomURL();
         PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
             o.setOutTradeNo("");
             o.setNotifyUrl(notifyUrl);
         });
-        // 断言
+
+        // 调用,并断言
         assertThrows(ConstraintViolationException.class, () -> client.unifiedRefund(refundReqDTO));
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一退款,抛出业务异常")
-    public void test_unified_refund_throw_service_exception() throws AlipayApiException {
+    @DisplayName("支付宝 Client 统一退款抛出业务异常")
+    public void testUnifiedRefund_throwServiceException() throws AlipayApiException {
         // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
                 .thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
         // 准备请求参数
         String notifyUrl = randomURL();
         PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> o.setNotifyUrl(notifyUrl));
-        // 断言
+
+        // 调用,并断言
         assertThrows(ServiceException.class, () -> client.unifiedRefund(refundReqDTO));
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一退款,抛出系统异常")
-    public void test_unified_refund_throw_pay_exception() throws AlipayApiException {
+    @DisplayName("支付宝 Client 统一退款抛出系统异常")
+    public void testUnifiedRefund_throwPayException() throws AlipayApiException {
         // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
                 .thenThrow(new RuntimeException("系统异常"));
         // 准备请求参数
         String notifyUrl = randomURL();
         PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> o.setNotifyUrl(notifyUrl));
-        // 断言
+
+        // 调用,并断言
         assertThrows(PayException.class, () -> client.unifiedRefund(refundReqDTO));
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一下单, 参数校验不通过")
-    public void test_unified_order_param_validate() {
+    @DisplayName("支付宝 Client 统一下单参数校验不通过")
+    public void testUnifiedOrder_paramInvalidate() {
         // 准备请求参数
         String outTradeNo = randomString();
         String notifyUrl = randomURL();
@@ -197,7 +203,8 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
             o.setOutTradeNo(outTradeNo);
             o.setNotifyUrl(notifyUrl);
         });
-        // 断言
+
+        // 调用,并断言
         assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO));
     }
 
@@ -210,4 +217,5 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
             o.setBody(RandomUtil.randomString(32));
         });
     }
+
 }

+ 170 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java

@@ -0,0 +1,170 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayTradePayModel;
+import com.alipay.api.request.AlipayTradePayRequest;
+import com.alipay.api.response.AlipayTradePayResponse;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InjectMocks;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED;
+import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link AlipayBarPayClient} 单元测试
+ *
+ * @author jason
+ */
+public class AlipayBarPayClientTest extends AbstractAlipayClientTest {
+
+    @InjectMocks
+    private AlipayBarPayClient client = new AlipayBarPayClient(randomLongId(), config);
+
+    @Override
+    @BeforeEach
+    public void setUp() {
+        setClient(client);
+    }
+
+    @Test
+    @DisplayName("支付宝条码支付:非免密码支付下单成功")
+    public void testUnifiedOrder_success() throws AlipayApiException {
+        // mock 方法
+        String outTradeNo = randomString();
+        String notifyUrl = randomURL();
+        Integer price = randomInteger();
+        String authCode = randomString();
+        AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> o.setSubCode(""));
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePayRequest>) request -> {
+            assertInstanceOf(AlipayTradePayModel.class, request.getBizModel());
+            assertEquals(notifyUrl, request.getNotifyUrl());
+            AlipayTradePayModel model = (AlipayTradePayModel) request.getBizModel();
+            assertEquals(outTradeNo, model.getOutTradeNo());
+            assertEquals(String.valueOf(price / 100.0), model.getTotalAmount());
+            assertEquals(authCode, model.getAuthCode());
+            return true;
+        }))).thenReturn(response);
+        // 准备请求参数
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
+        Map<String, String> extraParam = new HashMap<>();
+        extraParam.put("auth_code", authCode);
+        reqDTO.setChannelExtras(extraParam);
+
+        // 调用方法
+        PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+        // 断言
+        assertEquals(WAITING.getStatus(), resp.getStatus());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode());
+        assertEquals("", resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
+    }
+
+    @Test
+    @DisplayName("支付宝条码支付:免密码支付下单成功")
+    public void testUnifiedOrder_code10000Success() throws AlipayApiException {
+        // mock 方法
+        String outTradeNo = randomString();
+        String channelNo = randomString();
+        String channelUserId = randomString();
+        Date payTime = randomDate();
+        AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> {
+            o.setSubCode("");
+            o.setCode("10000");
+            o.setOutTradeNo(outTradeNo);
+            o.setTradeNo(channelNo);
+            o.setBuyerUserId(channelUserId);
+            o.setGmtPayment(payTime);
+        });
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePayRequest>) request -> true)))
+                .thenReturn(response);
+        // 准备请求参数
+        String authCode = randomString();
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger());
+        Map<String, String> extraParam = new HashMap<>();
+        extraParam.put("auth_code", authCode);
+        reqDTO.setChannelExtras(extraParam);
+
+        // 下单请求
+        PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+        // 断言
+        assertEquals(PayOrderStatusRespEnum.SUCCESS.getStatus(), resp.getStatus());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertEquals(channelNo, resp.getChannelOrderNo());
+        assertEquals(channelUserId, resp.getChannelUserId());
+        assertEquals(LocalDateTimeUtil.of(payTime), resp.getSuccessTime());
+        assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode());
+        assertEquals("", resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
+    }
+
+    @Test
+    @DisplayName("支付宝条码支付:没有传条码")
+    public void testUnifiedOrder_emptyAuthCode() {
+        // 准备参数
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
+
+        // 调用,并断言
+        assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
+    }
+
+    @Test
+    @DisplayName("支付宝条码支付:渠道返回失败")
+    public void test_unified_order_channel_failed() throws AlipayApiException {
+        // mock 方法
+        String subCode = randomString();
+        String subMsg = randomString();
+        AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> {
+            o.setSubCode(subCode);
+            o.setSubMsg(subMsg);
+        });
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePayRequest>) request -> true)))
+                .thenReturn(response);
+        // 准备请求参数
+        String authCode = randomString();
+        String outTradeNo = randomString();
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger());
+        Map<String, String> extraParam = new HashMap<>();
+        extraParam.put("auth_code", authCode);
+        reqDTO.setChannelExtras(extraParam);
+
+        // 调用方法
+        PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+        // 断言
+        assertEquals(CLOSED.getStatus(), resp.getStatus());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertNull(resp.getDisplayMode());
+        assertNull(resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
+        assertEquals(subCode, resp.getChannelErrorCode());
+        assertEquals(subMsg, resp.getChannelErrorMsg());
+    }
+
+}

+ 44 - 27
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java

@@ -16,13 +16,15 @@ import org.mockito.InjectMocks;
 import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED;
 import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
 /**
+ * {@link  AlipayPcPayClient} 单元测试
+ *
  * @author jason
  */
 public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
@@ -37,78 +39,93 @@ public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
     }
 
     @Test
-    @DisplayName("支付宝 PC 网站支付 URL Display Mode 下单成功")
-    public void test_unified_order_url_display_mode_success() throws AlipayApiException {
-        // 准备返回对象
+    @DisplayName("支付宝 PC 网站支付URL Display Mode 下单成功")
+    public void testUnifiedOrder_urlSuccess() throws AlipayApiException {
+        // mock 方法
         String notifyUrl = randomURL();
-        Integer price = randomInteger();
-        AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> {
-            o.setSubCode("");
-        });
-        // mock
+        AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode(""));
         when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradePagePayRequest>) request -> true),
                 eq(Method.GET.name()))).thenReturn(response);
         // 准备请求参数
         String outTradeNo = randomString();
+        Integer price = randomInteger();
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
-        // 设置  displayMode 为 null.
         reqDTO.setDisplayMode(null);
 
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
         // 断言
         assertEquals(WAITING.getStatus(), resp.getStatus());
-        assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
         assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
+        assertEquals(response.getBody(), resp.getDisplayContent());
         assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
     }
 
     @Test
-    @DisplayName("支付宝 PC 网站支付 FORM Display Mode 下单成功")
-    public void test_unified_order_form_display_mode_success() throws AlipayApiException {
-        // 准备返回对象
-        String notifyUrl = randomURL();
-        Integer price = randomInteger();
-        AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> {
-            o.setSubCode("");
-        });
+    @DisplayName("支付宝 PC 网站支付:Form Display Mode 下单成功")
+    public void testUnifiedOrder_formSuccess() throws AlipayApiException {
         // mock
+        String notifyUrl = randomURL();
+        AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode(""));
         when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradePagePayRequest>) request -> true),
                 eq(Method.POST.name()))).thenReturn(response);
         // 准备请求参数
         String outTradeNo = randomString();
+        Integer price = randomInteger();
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
         reqDTO.setDisplayMode(PayOrderDisplayModeEnum.FORM.getMode());
 
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
         // 断言
         assertEquals(WAITING.getStatus(), resp.getStatus());
-        assertEquals(PayOrderDisplayModeEnum.FORM.getMode(), resp.getDisplayMode());
         assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertEquals(PayOrderDisplayModeEnum.FORM.getMode(), resp.getDisplayMode());
+        assertEquals(response.getBody(), resp.getDisplayContent());
         assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
     }
 
     @Test
-    @DisplayName("支付宝 PC 网站支付,渠道返回失败")
-    public void test_unified_order_channel_failed() throws AlipayApiException {
-        // 准备响应对象
+    @DisplayName("支付宝 PC 网站支付渠道返回失败")
+    public void testUnifiedOrder_channelFailed() throws AlipayApiException {
+        // mock
         String subCode = randomString();
         String subMsg = randomString();
         AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> {
             o.setSubCode(subCode);
             o.setSubMsg(subMsg);
         });
-        // mock
         when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradePagePayRequest>) request -> true),
                 eq(Method.GET.name()))).thenReturn(response);
         // 准备请求参数
-        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
+        String outTradeNo = randomString();
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger());
         reqDTO.setDisplayMode(PayOrderDisplayModeEnum.URL.getMode());
 
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
         // 断言
         assertEquals(CLOSED.getStatus(), resp.getStatus());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertNull(resp.getDisplayMode());
+        assertNull(resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
         assertEquals(subCode, resp.getChannelErrorCode());
         assertEquals(subMsg, resp.getChannelErrorMsg());
-        assertSame(response, resp.getRawData());
     }
+
 }

+ 1 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java

@@ -51,7 +51,7 @@ public class ProductCommentController {
         return success(true);
     }
 
-    @PutMapping("/create")
+    @PostMapping("/create")
     @Operation(summary = "添加自评")
     @PreAuthorize("@ss.hasPermission('product:comment:update')")
     public CommonResult<Boolean> createComment(@Valid @RequestBody ProductCommentCreateReqVO createReqVO) {

+ 0 - 2
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java

@@ -11,11 +11,9 @@ import java.util.List;
 public class ProductCommentBaseVO {
 
     @Schema(description = "评价人", requiredMode = Schema.RequiredMode.REQUIRED, example = "16868")
-    @NotNull(message = "评价人不能为空")
     private Long userId;
 
     @Schema(description = "评价订单项", requiredMode = Schema.RequiredMode.REQUIRED, example = "19292")
-    @NotNull(message = "评价订单项不能为空")
     private Long orderItemId;
 
     @Schema(description = "评价人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小姑凉")

+ 10 - 2
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.product.controller.admin.comment.vo;
 
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -7,6 +8,7 @@ import lombok.ToString;
 
 import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "管理后台 - 商品评价 Response VO")
 @Data
@@ -23,10 +25,10 @@ public class ProductCommentRespVO extends ProductCommentBaseVO {
     @Schema(description = "交易订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24428")
     private Long orderId;
 
-    @Schema(description = "是否可见:[true:显示 false:隐藏]", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED)
     private Boolean visible;
 
-    @Schema(description = "商家是否回复:[1:回复 0:未回复]", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "商家是否回复", requiredMode = Schema.RequiredMode.REQUIRED)
     private Boolean replyStatus;
 
     @Schema(description = "回复管理员编号", example = "9527")
@@ -52,4 +54,10 @@ public class ProductCommentRespVO extends ProductCommentBaseVO {
     @NotNull(message = "商品 SPU 名称不能为空")
     private String spuName;
 
+    @Schema(description = "商品 SKU 图片地址", example = "https://www.iocoder.cn/yudao.jpg")
+    private String skuPicUrl;
+
+    @Schema(description = "商品 SKU 规格值数组")
+    private List<ProductSkuBaseVO.Property> skuProperties;
+
 }

+ 1 - 44
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java

@@ -1,57 +1,14 @@
 package cn.iocoder.yudao.module.product.controller.admin.sku;
 
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
-import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
-import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
-import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
-import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
-import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
-import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 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 java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-
-@Tag(name = "管理后台 - 商品 sku")
+@Tag(name = "管理后台 - 商品 SKU")
 @RestController
 @RequestMapping("/product/sku")
 @Validated
 public class ProductSkuController {
 
-    @Resource
-    private ProductSkuService productSkuService;
-    @Resource
-    private ProductSpuService productSpuService;
-
-    @GetMapping("/get-option-list")
-    @Operation(summary = "获得商品 SKU 选项的列表")
-//    @PreAuthorize("@ss.hasPermission('product:sku:query')")
-    public CommonResult<List<ProductSkuOptionRespVO>> getSkuOptionList() {
-        // 获得 SKU 列表
-        List<ProductSkuDO> skus = productSkuService.getSkuList();
-        if (CollUtil.isEmpty(skus)) {
-            return success(Collections.emptyList());
-        }
-
-        // 获得对应的 SPU 映射
-        Map<Long, ProductSpuDO> spuMap = productSpuService.getSpuMap(convertSet(skus, ProductSkuDO::getSpuId));
-        // 转换为返回结果
-        List<ProductSkuOptionRespVO> skuVOs = ProductSkuConvert.INSTANCE.convertList05(skus);
-        skuVOs.forEach(sku -> MapUtils.findAndThen(spuMap, sku.getSpuId(),
-                spu -> sku.setSpuId(spu.getId()).setSpuName(spu.getName())));
-        return success(skuVOs);
-    }
-
 }

+ 0 - 30
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "管理后台 - 商品 SKU 选项 Response VO") // 用于前端 SELECT 选项
-@Data
-public class ProductSkuOptionRespVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    private Long id;
-
-    @Schema(description = "商品 SKU 名字", example = "红色")
-    private String name;
-
-    @Schema(description = "销售价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
-    private String price;
-
-    @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
-    private Integer stock;
-
-    // ========== 商品 SPU 信息 ==========
-
-    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Long spuId;
-
-    @Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "iPhone 11")
-    private String spuName;
-
-}

+ 21 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/comment/ProductCommentConvert.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.product.convert.comment;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
 import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentCreateReqVO;
@@ -24,6 +23,8 @@ import java.math.RoundingMode;
 import java.util.List;
 import java.util.Map;
 
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+
 /**
  * 商品评价 Convert
  *
@@ -60,7 +61,7 @@ public interface ProductCommentConvert {
                 item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS);
             }
             // 设置 SKU 规格值
-            MapUtils.findAndThen(skuMap, item.getSkuId(),
+            findAndThen(skuMap, item.getSkuId(),
                     sku -> item.setSkuProperties(convertList01(sku.getProperties())));
         });
         return page;
@@ -87,7 +88,7 @@ public interface ProductCommentConvert {
 
     @Mapping(target = "scores",
             expression = "java(convertScores(createReqDTO.getDescriptionScores(), createReqDTO.getBenefitScores()))")
-    default ProductCommentDO convert(ProductCommentCreateReqDTO createReqDTO, ProductSpuDO spuDO, MemberUserRespDTO user) {
+    default ProductCommentDO convert(ProductCommentCreateReqDTO createReqDTO, ProductSpuDO spuDO, ProductSkuDO skuDO, MemberUserRespDTO user) {
         ProductCommentDO commentDO = convert(createReqDTO);
         if (user != null) {
             commentDO.setUserId(user.getId());
@@ -98,9 +99,15 @@ public interface ProductCommentConvert {
             commentDO.setSpuId(spuDO.getId());
             commentDO.setSpuName(spuDO.getName());
         }
+        if (skuDO != null) {
+            commentDO.setSkuPicUrl(skuDO.getPicUrl());
+            commentDO.setSkuProperties(skuDO.getProperties());
+        }
         return commentDO;
     }
 
+    @Mapping(target = "visible", constant = "true")
+    @Mapping(target = "replyStatus", constant = "false")
     @Mapping(target = "userId", constant = "0L")
     @Mapping(target = "orderId", constant = "0L")
     @Mapping(target = "orderItemId", constant = "0L")
@@ -111,4 +118,15 @@ public interface ProductCommentConvert {
 
     List<AppProductCommentRespVO> convertList02(List<ProductCommentDO> list);
 
+    default ProductCommentDO convert(ProductCommentCreateReqVO createReq, ProductSpuDO spu, ProductSkuDO sku) {
+        ProductCommentDO commentDO = convert(createReq);
+        if (spu != null) {
+            commentDO.setSpuId(spu.getId()).setSpuName(spu.getName());
+        }
+        if (sku != null) {
+            commentDO.setSkuPicUrl(sku.getPicUrl()).setSkuProperties(sku.getProperties());
+        }
+        return commentDO;
+    }
+
 }

+ 0 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java

@@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
-import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import org.mapstruct.Mapper;
@@ -44,8 +43,6 @@ public interface ProductSkuConvert {
 
     List<ProductSkuRespDTO> convertList04(List<ProductSkuDO> list);
 
-    List<ProductSkuOptionRespVO> convertList05(List<ProductSkuDO> skus);
-
     /**
      * 获得 SPU 的库存变化 Map
      *

+ 16 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java

@@ -73,11 +73,14 @@ public class ProductCommentDO extends BaseDO {
 
     /**
      * 商品 SPU 编号
+     *
      * 关联 {@link ProductSpuDO#getId()}
      */
     private Long spuId;
     /**
      * 商品 SPU 名称
+     *
+     * 关联 {@link ProductSpuDO#getName()}
      */
     private String spuName;
     /**
@@ -86,6 +89,19 @@ public class ProductCommentDO extends BaseDO {
      * 关联 {@link ProductSkuDO#getId()}
      */
     private Long skuId;
+    /**
+     * 商品 SKU 图片地址
+     *
+     * 关联 {@link ProductSkuDO#getPicUrl()}
+     */
+    private String skuPicUrl;
+    /**
+     * 属性数组,JSON 格式
+     *
+     * 关联 {@link ProductSkuDO#getProperties()}
+     */
+    @TableField(typeHandler = ProductSkuDO.PropertyTypeHandler.class)
+    private List<ProductSkuDO.Property> skuProperties;
 
     /**
      * 是否可见

+ 3 - 4
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.product.dal.mysql.comment;
 
-
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -20,6 +19,7 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
                 .eqIfPresent(ProductCommentDO::getOrderId, reqVO.getOrderId())
                 .eqIfPresent(ProductCommentDO::getSpuId, reqVO.getSpuId())
                 .eqIfPresent(ProductCommentDO::getScores, reqVO.getScores())
+                .eqIfPresent(ProductCommentDO::getReplyStatus, reqVO.getReplyStatus())
                 .betweenIfPresent(ProductCommentDO::getCreateTime, reqVO.getCreateTime())
                 .likeIfPresent(ProductCommentDO::getSpuName, reqVO.getSpuName())
                 .orderByDesc(ProductCommentDO::getId));
@@ -53,11 +53,10 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
         return selectPage(reqVO, queryWrapper);
     }
 
-    default ProductCommentDO selectByUserIdAndOrderItemIdAndSpuId(Long userId, Long orderItemId, Long skuId) {
+    default ProductCommentDO selectByUserIdAndOrderItemId(Long userId, Long orderItemId) {
         return selectOne(new LambdaQueryWrapperX<ProductCommentDO>()
                 .eq(ProductCommentDO::getUserId, userId)
-                .eq(ProductCommentDO::getOrderItemId, orderItemId)
-                .eq(ProductCommentDO::getSpuId, skuId));
+                .eq(ProductCommentDO::getOrderItemId, orderItemId));
     }
 
     default Long selectCountBySpuId(Long spuId, Boolean visible, Integer type) {

+ 23 - 23
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java

@@ -25,12 +25,21 @@ import java.util.List;
 public interface ProductCommentService {
 
     /**
-     * 获得商品评价分页
+     * 创建商品评论
+     * 后台管理员创建评论使用
      *
-     * @param pageReqVO 分页查询
-     * @return 商品评价分页
+     * @param createReqVO 商品评价创建 Request VO 对象
      */
-    PageResult<ProductCommentDO> getCommentPage(ProductCommentPageReqVO pageReqVO);
+    void createComment(ProductCommentCreateReqVO createReqVO);
+
+    /**
+     * 创建评论
+     * 创建商品评论 APP 端创建商品评论使用
+     *
+     * @param createReqDTO 创建请求 dto
+     * @return 返回评论 id
+     */
+    Long createComment(ProductCommentCreateReqDTO createReqDTO);
 
     /**
      * 修改评论是否可见
@@ -43,35 +52,26 @@ public interface ProductCommentService {
      * 商家回复
      *
      * @param replyVO     商家回复
-     * @param loginUserId 管理后台商家登陆人 ID
+     * @param userId 管理后台商家登陆人 ID
      */
-    void replyComment(ProductCommentReplyReqVO replyVO, Long loginUserId);
+    void replyComment(ProductCommentReplyReqVO replyVO, Long userId);
 
     /**
-     * 获得商品评价分页
+     * 【管理员】获得商品评价分页
      *
-     * @param pageVO  分页查询
-     * @param visible 是否可见
+     * @param pageReqVO 分页查询
      * @return 商品评价分页
      */
-    PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
+    PageResult<ProductCommentDO> getCommentPage(ProductCommentPageReqVO pageReqVO);
 
     /**
-     * 创建商品评论
-     * 后台管理员创建评论使用
+     * 【会员】获得商品评价分页
      *
-     * @param createReqVO 商品评价创建 Request VO 对象
-     */
-    void createComment(ProductCommentCreateReqVO createReqVO);
-
-    /**
-     * 创建评论
-     * 创建商品评论 APP 端创建商品评论使用
-     *
-     * @param createReqDTO 创建请求 dto
-     * @return 返回评论 id
+     * @param pageVO  分页查询
+     * @param visible 是否可见
+     * @return 商品评价分页
      */
-    Long createComment(ProductCommentCreateReqDTO createReqDTO);
+    PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
 
     /**
      * 获得商品的评价统计

+ 52 - 47
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java

@@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
@@ -53,69 +52,55 @@ public class ProductCommentServiceImpl implements ProductCommentService {
     private MemberUserApi memberUserApi;
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateCommentVisible(ProductCommentUpdateVisibleReqVO updateReqVO) {
-        // 校验评论是否存在
-        ProductCommentDO productCommentDO = validateCommentExists(updateReqVO.getId());
-        productCommentDO.setVisible(updateReqVO.getVisible());
-
-        // 更新可见状态
-        productCommentMapper.updateById(productCommentDO);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void replyComment(ProductCommentReplyReqVO replyVO, Long loginUserId) {
-        // 校验评论是否存在
-        ProductCommentDO productCommentDO = validateCommentExists(replyVO.getId());
-        productCommentDO.setReplyTime(LocalDateTime.now());
-        productCommentDO.setReplyUserId(loginUserId);
-        productCommentDO.setReplyStatus(Boolean.TRUE);
-        productCommentDO.setReplyContent(replyVO.getReplyContent());
-
-        // 回复评论
-        productCommentMapper.updateById(productCommentDO);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
     public void createComment(ProductCommentCreateReqVO createReqVO) {
-        // 校验评论
-        validateComment(createReqVO.getSkuId(), createReqVO.getUserId(), createReqVO.getOrderItemId());
+        // 校验 SKU
+        ProductSkuDO skuDO = validateSku(createReqVO.getSkuId());
+        // 校验 SPU
+        ProductSpuDO spuDO = validateSpu(skuDO.getSpuId());
 
-        ProductCommentDO commentDO = ProductCommentConvert.INSTANCE.convert(createReqVO);
-        productCommentMapper.insert(commentDO);
+        // 创建评论
+        ProductCommentDO comment = ProductCommentConvert.INSTANCE.convert(createReqVO, spuDO, skuDO);
+        productCommentMapper.insert(comment);
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public Long createComment(ProductCommentCreateReqDTO createReqDTO) {
-        // 通过 sku ID 拿到 spu 相关信息
-        ProductSkuDO sku = productSkuService.getSku(createReqDTO.getSkuId());
-        if (sku == null) {
-            throw exception(SKU_NOT_EXISTS);
-        }
-        // 校验 spu 如果存在返回详情
-        ProductSpuDO spuDO = validateSpu(sku.getSpuId());
+        // 校验 SKU
+        ProductSkuDO skuDO = validateSku(createReqDTO.getSkuId());
+        // 校验 SPU
+        ProductSpuDO spuDO = validateSpu(skuDO.getSpuId());
         // 校验评论
-        validateComment(spuDO.getId(), createReqDTO.getUserId(), createReqDTO.getOrderId());
+        validateCommentExists(createReqDTO.getUserId(), createReqDTO.getOrderId());
         // 获取用户详细信息
         MemberUserRespDTO user = memberUserApi.getUser(createReqDTO.getUserId());
 
         // 创建评论
-        ProductCommentDO commentDO = ProductCommentConvert.INSTANCE.convert(createReqDTO, spuDO, user);
-        productCommentMapper.insert(commentDO);
-        return commentDO.getId();
+        ProductCommentDO comment = ProductCommentConvert.INSTANCE.convert(createReqDTO, spuDO, skuDO, user);
+        productCommentMapper.insert(comment);
+        return comment.getId();
     }
 
-    private void validateComment(Long skuId, Long userId, Long orderItemId) {
-        // 判断当前订单的当前商品用户是否评价过
-        ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemIdAndSpuId(userId, orderItemId, skuId);
-        if (null != exist) {
+    /**
+     * 判断当前订单的当前商品用户是否评价过
+     *
+     * @param userId      用户编号
+     * @param orderItemId 订单项编号
+     */
+    private void validateCommentExists(Long userId, Long orderItemId) {
+        ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemId(userId, orderItemId);
+        if (exist != null) {
             throw exception(COMMENT_ORDER_EXISTS);
         }
     }
 
+    private ProductSkuDO validateSku(Long skuId) {
+        ProductSkuDO sku = productSkuService.getSku(skuId);
+        if (sku == null) {
+            throw exception(SKU_NOT_EXISTS);
+        }
+        return sku;
+    }
+
     private ProductSpuDO validateSpu(Long spuId) {
         ProductSpuDO spu = productSpuService.getSpu(spuId);
         if (null == spu) {
@@ -124,6 +109,26 @@ public class ProductCommentServiceImpl implements ProductCommentService {
         return spu;
     }
 
+    @Override
+    public void updateCommentVisible(ProductCommentUpdateVisibleReqVO updateReqVO) {
+        // 校验评论是否存在
+        validateCommentExists(updateReqVO.getId());
+
+        // 更新可见状态
+        productCommentMapper.updateById(new ProductCommentDO().setId(updateReqVO.getId())
+                .setVisible(true));
+    }
+
+    @Override
+    public void replyComment(ProductCommentReplyReqVO replyVO, Long userId) {
+        // 校验评论是否存在
+        validateCommentExists(replyVO.getId());
+        // 回复评论
+        productCommentMapper.updateById(new ProductCommentDO().setId(replyVO.getId())
+                .setReplyTime(LocalDateTime.now()).setReplyUserId(userId)
+                .setReplyStatus(Boolean.TRUE).setReplyContent(replyVO.getReplyContent()));
+    }
+
     private ProductCommentDO validateCommentExists(Long id) {
         ProductCommentDO productComment = productCommentMapper.selectById(id);
         if (productComment == null) {

+ 4 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.product.service.sku;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
@@ -74,6 +75,9 @@ public class ProductSkuServiceImpl implements ProductSkuService {
 
     @Override
     public List<ProductSkuDO> getSkuList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return ListUtil.empty();
+        }
         return productSkuMapper.selectBatchIds(ids);
     }
 

+ 55 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
@@ -12,8 +13,12 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
+import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
@@ -57,6 +62,7 @@ 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;
 import org.jetbrains.annotations.NotNull;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -101,9 +107,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     private AddressApi addressApi;
     @Resource
     private CouponApi couponApi;
+
     @Resource
     private MemberUserApi memberUserApi;
     @Resource
+    private MemberLevelApi memberLevelApi;
+    @Resource
+    private MemberPointApi memberPointApi;
+
+    @Resource
     private ProductCommentApi productCommentApi;
     @Resource
     private TradeMessageService tradeMessageService;
@@ -336,6 +348,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         // TODO 芋艿:发送站内信
 
         // TODO 芋艿:OrderLog
+
+        // 增加用户积分
+        getSelf().addUserPointAsync(order.getUserId(), order.getPayPrice(), order.getId());
+        // 增加用户经验
+        getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
     }
 
     /**
@@ -603,6 +620,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         }
 
         // TODO 芋艿:未来如果有分佣,需要更新相关分佣订单为已失效
+
+        // 扣减用户积分
+        getSelf().reduceUserPointAsync(order.getUserId(), orderRefundPrice, afterSaleId);
+        // 扣减用户经验
+        getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
     }
 
     @Override
@@ -650,4 +672,37 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus()));
     }
 
+    @Async
+    protected void addUserExperienceAsync(Long userId, Integer payPrice, Long orderId) {
+        int bizType = MemberExperienceBizTypeEnum.ORDER.getType();
+        memberLevelApi.addExperience(userId, payPrice, bizType, String.valueOf(orderId));
+    }
+
+    @Async
+    protected void reduceUserExperienceAsync(Long userId, Integer refundPrice, Long afterSaleId) {
+        int bizType = MemberExperienceBizTypeEnum.REFUND.getType();
+        memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
+    }
+
+    @Async
+    protected void addUserPointAsync(Long userId, Integer payPrice, Long orderId) {
+        int bizType = MemberPointBizTypeEnum.ORDER_BUY.getType();
+        memberPointApi.addPoint(userId, payPrice, bizType, String.valueOf(orderId));
+    }
+
+    @Async
+    protected void reduceUserPointAsync(Long userId, Integer refundPrice, Long afterSaleId) {
+        int bizType = MemberPointBizTypeEnum.ORDER_CANCEL.getType();
+        memberPointApi.addPoint(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
+    }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private TradeOrderUpdateServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
 }

+ 4 - 3
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.member.api.level;
 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 
 /**
- * 会员等级 API接口
+ * 会员等级 API 接口
  *
  * @author owen
  */
@@ -14,8 +14,9 @@ public interface MemberLevelApi {
      *
      * @param userId     会员ID
      * @param experience 经验
-     * @param bizType    业务类型
+     * @param bizType    业务类型 {@link MemberExperienceBizTypeEnum}
      * @param bizId      业务编号
      */
-    void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId);
+    void addExperience(Long userId, Integer experience, Integer bizType, String bizId);
+
 }

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

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.api.point;
+
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+
+/**
+ * 用户积分的 API 接口
+ *
+ * @author owen
+ */
+public interface MemberPointApi {
+
+    /**
+     * 增加用户积分
+     *
+     * @param userId  用户编号
+     * @param point   积分
+     * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
+     * @param bizId   业务编号
+     */
+    void addPoint(Long userId, Integer point, Integer bizType, String bizId);
+
+}

+ 0 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java

@@ -56,5 +56,4 @@ public interface MemberUserApi {
      * @return 用户信息
      */
     MemberUserRespDTO getUserByMobile(String mobile);
-
 }

+ 3 - 4
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 
 /**
  * Member 错误码枚举类
- *
+ * <p>
  * member 系统,使用 1-004-000-000 段
  */
 public interface ErrorCodeConstants {
@@ -32,6 +32,7 @@ public interface ErrorCodeConstants {
     //========== 积分配置 1004007000 ==========
 
     //========== 积分记录 1004008000 ==========
+    ErrorCode POINT_RECORD_BIZ_NOT_SUPPORT = new ErrorCode(1004008000, "用户积分记录业务类型不支持");
 
     //========== 签到配置 1004009000 ==========
     ErrorCode SIGN_IN_CONFIG_NOT_EXISTS = new ErrorCode(1004009000, "签到天数规则不存在");
@@ -48,9 +49,7 @@ public interface ErrorCodeConstants {
     ErrorCode LEVEL_EXPERIENCE_MAX = new ErrorCode(1004011004, "升级经验必须小于下一个等级[{}]设置的升级经验[{}]");
     ErrorCode LEVEL_HAS_USER = new ErrorCode(1004011005, "用户等级下存在用户,无法删除");
 
-    ErrorCode LEVEL_LOG_NOT_EXISTS = new ErrorCode(1004011100, "用户等级记录不存在");
-    ErrorCode EXPERIENCE_LOG_NOT_EXISTS = new ErrorCode(1004011200, "用户经验记录不存在");
-    ErrorCode LEVEL_REASON_NOT_EXISTS = new ErrorCode(1004011300, "用户等级调整原因不能为空");
+    ErrorCode EXPERIENCE_BIZ_NOT_SUPPORT = new ErrorCode(1004011201, "用户经验业务类型不支持");
 
     //========== 用户分组 1004012000 ==========
     ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1004012000, "用户分组不存在");

+ 30 - 8
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.member.enums;
 
+import cn.hutool.core.util.EnumUtil;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+import java.util.Objects;
+
 /**
  * 会员经验 - 业务类型
  *
@@ -11,18 +14,37 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum MemberExperienceBizTypeEnum {
+
     /**
      * 管理员调整、邀请新用户、下单、退单、签到、抽奖
      */
-    ADMIN(0, "管理员调整","管理员调整获得{}经验"),
-    INVITE_REGISTER(1, "邀新奖励","邀请好友获得{}经验"),
-    ORDER(2, "下单奖励", "下单获得{}经验"),
-    REFUND(3, "退单扣除","退单获得{}经验"),
-    SIGN_IN(4, "签到奖励","签到获得{}经验"),
-    LOTTERY(5, "抽奖奖励","抽奖获得{}经验"),
+    ADMIN(0, "管理员调整", "管理员调整获得 {} 经验", true),
+    INVITE_REGISTER(1, "邀新奖励", "邀请好友获得 {} 经验", true),
+    ORDER(2, "下单奖励", "下单获得 {} 经验", true),
+    REFUND(3, "退单扣除", "退单获得 {} 经验", false),
+    SIGN_IN(4, "签到奖励", "签到获得 {} 经验", true),
+    LOTTERY(5, "抽奖奖励", "抽奖获得 {} 经验", true),
     ;
 
-    private final int value;
+    /**
+     * 业务类型
+     */
+    private final int type;
+    /**
+     * 标题
+     */
     private final String title;
-    private final String desc;
+    /**
+     * 描述
+     */
+    private final String description;
+    /**
+     * 是否为扣减积分
+     */
+    private final boolean add;
+
+    public static MemberExperienceBizTypeEnum getByType(Integer type) {
+        return EnumUtil.getBy(MemberExperienceBizTypeEnum.class,
+                e -> Objects.equals(type, e.getType()));
+    }
 }

+ 19 - 3
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java

@@ -1,9 +1,12 @@
 package cn.iocoder.yudao.module.member.enums.point;
 
+import cn.hutool.core.util.EnumUtil;
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+import java.util.Objects;
+
 /**
  * 会员积分的业务类型枚举
  *
@@ -13,9 +16,9 @@ import lombok.Getter;
 @Getter
 public enum MemberPointBizTypeEnum implements IntArrayValuable {
 
-    SIGN(1, "签到"),
-    ORDER_BUY(10, "订单消费"),
-    ORDER_CANCEL(11, "订单取消"); // 退回积分
+    SIGN(1, "签到", "签到获得 {} 积分", true),
+    ORDER_BUY(10, "订单消费", "下单获得 {} 积分", true),
+    ORDER_CANCEL(11, "订单取消", "退单获得 {} 积分", false); // 退回积分
 
     /**
      * 类型
@@ -25,10 +28,23 @@ public enum MemberPointBizTypeEnum implements IntArrayValuable {
      * 名字
      */
     private final String name;
+    /**
+     * 描述
+     */
+    private final String description;
+    /**
+     * 是否为扣减积分
+     */
+    private final boolean add;
 
     @Override
     public int[] array() {
         return new int[0];
     }
 
+    public static MemberPointBizTypeEnum getByType(Integer type) {
+        return EnumUtil.getBy(MemberPointBizTypeEnum.class,
+                e -> Objects.equals(type, e.getType()));
+    }
+
 }

+ 11 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java

@@ -7,6 +7,9 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.EXPERIENCE_BIZ_NOT_SUPPORT;
+
 /**
  * 会员等级 API 实现类
  *
@@ -19,7 +22,13 @@ public class MemberLevelApiImpl implements MemberLevelApi {
     @Resource
     private MemberLevelService memberLevelService;
 
-    public void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId) {
-        memberLevelService.plusExperience(userId, experience, bizType, bizId);
+    @Override
+    public void addExperience(Long userId, Integer experience, Integer bizType, String bizId) {
+        MemberExperienceBizTypeEnum bizTypeEnum = MemberExperienceBizTypeEnum.getByType(bizType);
+        if (bizTypeEnum == null) {
+            throw exception(EXPERIENCE_BIZ_NOT_SUPPORT);
+        }
+        memberLevelService.addExperience(userId, experience, bizTypeEnum, bizId);
     }
+
 }

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

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.member.api.point;
+
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.POINT_RECORD_BIZ_NOT_SUPPORT;
+
+/**
+ * 用户积分的 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberPointApiImpl implements MemberPointApi {
+
+    @Resource
+    private MemberPointRecordService memberPointRecordService;
+
+    @Override
+    public void addPoint(Long userId, Integer point, Integer bizType, String bizId) {
+        MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType);
+        if (bizTypeEnum == null) {
+            throw exception(POINT_RECORD_BIZ_NOT_SUPPORT);
+        }
+        memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
+    }
+
+}

+ 41 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/AddressController.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.controller.admin.address;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.admin.address.vo.AddressRespVO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO;
+import cn.iocoder.yudao.module.member.service.address.AddressService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 用户收件地址")
+@RestController
+@RequestMapping("/member/address")
+@Validated
+public class AddressController {
+
+    @Resource
+    private AddressService addressService;
+
+    @GetMapping("/list")
+    @Operation(summary = "获得用户收件地址列表")
+    @Parameter(name = "userId", description = "用户编号", required = true)
+    @PreAuthorize("@ss.hasPermission('member:user:query')")
+    public CommonResult<List<AddressRespVO>> getAddressList(@RequestParam("userId") Long userId) {
+        List<MemberAddressDO> list = addressService.getAddressList(userId);
+        return success(AddressConvert.INSTANCE.convertList2(list));
+    }
+
+}

+ 37 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressBaseVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.controller.admin.address.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import java.util.*;
+import javax.validation.constraints.*;
+
+/**
+ * 用户收件地址 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class AddressBaseVO {
+
+    @Schema(description = "收件人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @NotNull(message = "收件人名称不能为空")
+    private String name;
+
+    @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "手机号不能为空")
+    private String mobile;
+
+    @Schema(description = "地区编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "15716")
+    @NotNull(message = "地区编码不能为空")
+    private Long areaId;
+
+    @Schema(description = "收件详细地址", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "收件详细地址不能为空")
+    private String detailAddress;
+
+    @Schema(description = "是否默认", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "是否默认不能为空")
+    private Boolean defaultStatus;
+
+}

+ 19 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.controller.admin.address.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 用户收件地址 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AddressRespVO extends AddressBaseVO {
+
+    @Schema(description = "收件地址编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7380")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 17 - 17
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberExperienceLogController.java

@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.member.controller.admin.level;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogPageReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogRespVO;
-import cn.iocoder.yudao.module.member.convert.level.MemberExperienceLogConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
-import cn.iocoder.yudao.module.member.service.level.MemberExperienceLogService;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.service.level.MemberExperienceRecordService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,31 +22,31 @@ import javax.validation.Valid;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-// TODO @疯狂:要不 Log 改成 Record,和 PointRecord 保持一致
 @Tag(name = "管理后台 - 会员经验记录")
 @RestController
-@RequestMapping("/member/experience-log")
+@RequestMapping("/member/experience-record")
 @Validated
-public class MemberExperienceLogController {
+public class MemberExperienceRecordController {
 
     @Resource
-    private MemberExperienceLogService experienceLogService;
+    private MemberExperienceRecordService experienceLogService;
 
     @GetMapping("/get")
     @Operation(summary = "获得会员经验记录")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('member:experience-log:query')")
-    public CommonResult<MemberExperienceLogRespVO> getExperienceLog(@RequestParam("id") Long id) {
-        MemberExperienceLogDO experienceLog = experienceLogService.getExperienceLog(id);
-        return success(MemberExperienceLogConvert.INSTANCE.convert(experienceLog));
+    @PreAuthorize("@ss.hasPermission('member:experience-record:query')")
+    public CommonResult<MemberExperienceRecordRespVO> getExperienceRecord(@RequestParam("id") Long id) {
+        MemberExperienceRecordDO experienceLog = experienceLogService.getExperienceRecord(id);
+        return success(MemberExperienceRecordConvert.INSTANCE.convert(experienceLog));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得会员经验记录分页")
-    @PreAuthorize("@ss.hasPermission('member:experience-log:query')")
-    public CommonResult<PageResult<MemberExperienceLogRespVO>> getExperienceLogPage(@Valid MemberExperienceLogPageReqVO pageVO) {
-        PageResult<MemberExperienceLogDO> pageResult = experienceLogService.getExperienceLogPage(pageVO);
-        return success(MemberExperienceLogConvert.INSTANCE.convertPage(pageResult));
+    @PreAuthorize("@ss.hasPermission('member:experience-record:query')")
+    public CommonResult<PageResult<MemberExperienceRecordRespVO>> getExperienceRecordPage(
+            @Valid MemberExperienceRecordPageReqVO pageVO) {
+        PageResult<MemberExperienceRecordDO> pageResult = experienceLogService.getExperienceRecordPage(pageVO);
+        return success(MemberExperienceRecordConvert.INSTANCE.convertPage(pageResult));
     }
 
 }

+ 5 - 7
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.member.controller.admin.level;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.*;
 import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
@@ -70,13 +69,12 @@ public class MemberLevelController {
         return success(MemberLevelConvert.INSTANCE.convertSimpleList(list));
     }
 
-    // TODO @疯狂:是不是不做分页,直接 list 就好啦;返回的时候,按照经验排序下哈;
-    @GetMapping("/page")
-    @Operation(summary = "获得会员等级分页")
+    @GetMapping("/list")
+    @Operation(summary = "获得会员等级列表")
     @PreAuthorize("@ss.hasPermission('member:level:query')")
-    public CommonResult<PageResult<MemberLevelRespVO>> getLevelPage(@Valid MemberLevelPageReqVO pageVO) {
-        PageResult<MemberLevelDO> pageResult = levelService.getLevelPage(pageVO);
-        return success(MemberLevelConvert.INSTANCE.convertPage(pageResult));
+    public CommonResult<List<MemberLevelRespVO>> getLevelList(@Valid MemberLevelListReqVO pageVO) {
+        List<MemberLevelDO> result = levelService.getLevelList(pageVO);
+        return success(MemberLevelConvert.INSTANCE.convertList(result));
     }
 
 }

+ 18 - 17
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelLogController.java

@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.member.controller.admin.level;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogPageReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogRespVO;
-import cn.iocoder.yudao.module.member.convert.level.MemberLevelLogConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
-import cn.iocoder.yudao.module.member.service.level.MemberLevelLogService;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelRecordService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,30 +22,31 @@ import javax.validation.Valid;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-// TODO @疯狂:是不是不用这个 controller;因为日志只是为了记录,db 可以查询、和审计即可,目前暂时不需要开放出来;
 @Tag(name = "管理后台 - 会员等级记录")
 @RestController
-@RequestMapping("/member/level-log")
+@RequestMapping("/member/level-record")
 @Validated
-public class MemberLevelLogController {
+public class MemberLevelRecordController {
 
     @Resource
-    private MemberLevelLogService levelLogService;
+    private MemberLevelRecordService levelLogService;
 
     @GetMapping("/get")
     @Operation(summary = "获得会员等级记录")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('member:level-log:query')")
-    public CommonResult<MemberLevelLogRespVO> getLevelLog(@RequestParam("id") Long id) {
-        MemberLevelLogDO levelLog = levelLogService.getLevelLog(id);
-        return success(MemberLevelLogConvert.INSTANCE.convert(levelLog));
+    @PreAuthorize("@ss.hasPermission('member:level-record:query')")
+    public CommonResult<MemberLevelRecordRespVO> getLevelRecord(@RequestParam("id") Long id) {
+        MemberLevelRecordDO levelLog = levelLogService.getLevelRecord(id);
+        return success(MemberLevelRecordConvert.INSTANCE.convert(levelLog));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得会员等级记录分页")
-    @PreAuthorize("@ss.hasPermission('member:level-log:query')")
-    public CommonResult<PageResult<MemberLevelLogRespVO>> getLevelLogPage(@Valid MemberLevelLogPageReqVO pageVO) {
-        PageResult<MemberLevelLogDO> pageResult = levelLogService.getLevelLogPage(pageVO);
-        return success(MemberLevelLogConvert.INSTANCE.convertPage(pageResult));
+    @PreAuthorize("@ss.hasPermission('member:level-record:query')")
+    public CommonResult<PageResult<MemberLevelRecordRespVO>> getLevelRecordPage(
+            @Valid MemberLevelRecordPageReqVO pageVO) {
+        PageResult<MemberLevelRecordDO> pageResult = levelLogService.getLevelRecordPage(pageVO);
+        return success(MemberLevelRecordConvert.INSTANCE.convertPage(pageResult));
     }
+
 }

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceLogBaseVO.java

@@ -10,7 +10,7 @@ import javax.validation.constraints.NotNull;
  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  */
 @Data
-public class MemberExperienceLogBaseVO {
+public class MemberExperienceRecordBaseVO {
 
     @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3638")
     @NotNull(message = "用户编号不能为空")

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceLogPageReqVO.java

@@ -15,7 +15,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberExperienceLogPageReqVO extends PageParam {
+public class MemberExperienceRecordPageReqVO extends PageParam {
 
     @Schema(description = "用户编号", example = "3638")
     private Long userId;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceLogRespVO.java

@@ -11,7 +11,7 @@ import java.time.LocalDateTime;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberExperienceLogRespVO extends MemberExperienceLogBaseVO {
+public class MemberExperienceRecordRespVO extends MemberExperienceRecordBaseVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19610")
     private Long id;

+ 2 - 5
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelPageReqVO.java

@@ -1,16 +1,13 @@
 package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
 
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
-@Schema(description = "管理后台 - 会员等级分页 Request VO")
+@Schema(description = "管理后台 - 会员等级列表筛选 Request VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberLevelPageReqVO extends PageParam {
+public class MemberLevelListReqVO {
 
     @Schema(description = "等级名称", example = "芋艿")
     private String name;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelLogBaseVO.java

@@ -10,7 +10,7 @@ import javax.validation.constraints.NotNull;
  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  */
 @Data
-public class MemberLevelLogBaseVO {
+public class MemberLevelRecordBaseVO {
 
     @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25923")
     @NotNull(message = "用户编号不能为空")

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelLogPageReqVO.java

@@ -15,7 +15,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberLevelLogPageReqVO extends PageParam {
+public class MemberLevelRecordPageReqVO extends PageParam {
 
     @Schema(description = "用户编号", example = "25923")
     private Long userId;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelLogRespVO.java

@@ -11,7 +11,7 @@ import java.time.LocalDateTime;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberLevelLogRespVO extends MemberLevelLogBaseVO {
+public class MemberLevelRecordRespVO extends MemberLevelRecordBaseVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8741")
     private Long id;

+ 18 - 12
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java

@@ -5,6 +5,7 @@ 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.convert.user.MemberUserConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
@@ -24,10 +25,14 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 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;
 
 @Tag(name = "管理后台 - 会员用户")
 @RestController
@@ -52,6 +57,14 @@ public class MemberUserController {
         return success(true);
     }
 
+    @PutMapping("/update-level")
+    @Operation(summary = "更新会员用户等级")
+    @PreAuthorize("@ss.hasPermission('member:user:update-level')")
+    public CommonResult<Boolean> updateUserLevel(@Valid @RequestBody MemberUserUpdateLevelReqVO updateReqVO) {
+        memberLevelService.updateUserLevel(updateReqVO);
+        return success(true);
+    }
+
     @GetMapping("/get")
     @Operation(summary = "获得会员用户")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
@@ -70,26 +83,19 @@ public class MemberUserController {
             return success(PageResult.empty());
         }
 
-        Set<Long> groupIds = new HashSet<>(pageResult.getList().size());
-
         // 处理用户标签返显
         Set<Long> tagIds = pageResult.getList().stream()
-                .peek(m -> {
-                    if (m.getGroupId() != null) {
-                        groupIds.add(m.getGroupId());
-                    }
-                })
                 .map(MemberUserDO::getTagIds)
                 .filter(Objects::nonNull)
                 .flatMap(Collection::stream)
                 .collect(Collectors.toSet());
         List<MemberTagDO> tags = memberTagService.getTagList(tagIds);
-
         // 处理用户级别返显
-        List<MemberLevelDO> levels = memberLevelService.getEnableLevelList();
+        List<MemberLevelDO> levels = memberLevelService.getLevelList(
+                convertSet(pageResult.getList(), MemberUserDO::getLevelId));
         // 处理用户分组返显
-        List<MemberGroupDO> groups = memberGroupService.getGroupList(groupIds);
-
+        List<MemberGroupDO> groups = memberGroupService.getGroupList(
+                convertSet(pageResult.getList(), MemberUserDO::getGroupId));
         return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags, levels, groups));
     }
 

+ 4 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java

@@ -40,9 +40,12 @@ public class MemberUserBaseVO {
     @Schema(description = "用户性别", example = "1")
     private Byte sex;
 
-    @Schema(description = "所在地", example = "4371")
+    @Schema(description = "所在地编号", example = "4371")
     private Long areaId;
 
+    @Schema(description = "所在地全程", example = "上海上海市普陀区")
+    private String areaName;
+
     @Schema(description = "出生日期", example = "2023-03-12")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
     private LocalDateTime birthday;

+ 7 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java

@@ -31,9 +31,12 @@ public class MemberUserRespVO extends MemberUserBaseVO {
 
     // ========== 其它信息 ==========
 
-    @Schema(description = "积分", example = "100")
+    @Schema(description = "积分", requiredMode  = Schema.RequiredMode.REQUIRED, example = "100")
     private Integer point;
 
+    @Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
+    private Integer totalPoint;
+
     @Schema(description = "会员标签", example = "[红色, 快乐]")
     private List<String> tagNames;
 
@@ -43,4 +46,7 @@ public class MemberUserRespVO extends MemberUserBaseVO {
     @Schema(description = "用户分组", example = "购物达人")
     private String groupName;
 
+    @Schema(description = "用户经验值", requiredMode  = Schema.RequiredMode.REQUIRED, example = "200")
+    private Integer experience;
+
 }

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

@@ -0,0 +1,31 @@
+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;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 会员用户 修改等级 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberUserUpdateLevelReqVO extends MemberUserBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+    /**
+     * 取消用户等级时,值为空
+     */
+    @Schema(description = "用户等级编号", example = "1")
+    private Long levelId;
+
+    @Schema(description = "修改原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "推广需要")
+    @NotBlank(message = "修改原因不能为空")
+    private String reason;
+
+}

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

@@ -17,7 +17,4 @@ public class MemberUserUpdateReqVO extends MemberUserBaseVO {
     @NotNull(message = "编号不能为空")
     private Long id;
 
-    @Schema(description = "会员级别修改原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "推广需要")
-    private String levelReason;
-
 }

+ 3 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.member.convert.address;
 
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.address.vo.AddressRespVO;
 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
@@ -39,4 +40,6 @@ public interface AddressConvert {
         return AreaUtils.format(areaId);
     }
 
+    List<AddressRespVO> convertList2(List<MemberAddressDO> list);
+
 }

+ 0 - 27
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceLogConvert.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.member.convert.level;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogRespVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-/**
- * 会员经验记录 Convert
- *
- * @author owen
- */
-@Mapper
-public interface MemberExperienceLogConvert {
-
-    MemberExperienceLogConvert INSTANCE = Mappers.getMapper(MemberExperienceLogConvert.class);
-
-    MemberExperienceLogRespVO convert(MemberExperienceLogDO bean);
-
-    List<MemberExperienceLogRespVO> convertList(List<MemberExperienceLogDO> list);
-
-    PageResult<MemberExperienceLogRespVO> convertPage(PageResult<MemberExperienceLogDO> page);
-
-}

+ 30 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.member.convert.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 会员经验记录 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberExperienceRecordConvert {
+
+    MemberExperienceRecordConvert INSTANCE = Mappers.getMapper(MemberExperienceRecordConvert.class);
+
+    MemberExperienceRecordRespVO convert(MemberExperienceRecordDO bean);
+
+    List<MemberExperienceRecordRespVO> convertList(List<MemberExperienceRecordDO> list);
+
+    PageResult<MemberExperienceRecordRespVO> convertPage(PageResult<MemberExperienceRecordDO> page);
+
+    MemberExperienceRecordDO convert(Long userId, Integer experience, Integer totalExperience,
+                                     String bizId, Integer bizType,
+                                     String title, String description);
+}

+ 0 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.member.convert.level;
 
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelRespVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelSimpleRespVO;
@@ -29,7 +28,5 @@ public interface MemberLevelConvert {
 
     List<MemberLevelRespVO> convertList(List<MemberLevelDO> list);
 
-    PageResult<MemberLevelRespVO> convertPage(PageResult<MemberLevelDO> page);
-
     List<MemberLevelSimpleRespVO> convertSimpleList(List<MemberLevelDO> list);
 }

+ 0 - 27
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelLogConvert.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.member.convert.level;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogRespVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-/**
- * 会员等级记录 Convert
- *
- * @author owen
- */
-@Mapper
-public interface MemberLevelLogConvert {
-
-    MemberLevelLogConvert INSTANCE = Mappers.getMapper(MemberLevelLogConvert.class);
-
-    MemberLevelLogRespVO convert(MemberLevelLogDO bean);
-
-    List<MemberLevelLogRespVO> convertList(List<MemberLevelLogDO> list);
-
-    PageResult<MemberLevelLogRespVO> convertPage(PageResult<MemberLevelLogDO> page);
-
-}

+ 37 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.convert.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 会员等级记录 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberLevelRecordConvert {
+
+    MemberLevelRecordConvert INSTANCE = Mappers.getMapper(MemberLevelRecordConvert.class);
+
+    MemberLevelRecordRespVO convert(MemberLevelRecordDO bean);
+
+    List<MemberLevelRecordRespVO> convertList(List<MemberLevelRecordDO> list);
+
+    PageResult<MemberLevelRecordRespVO> convertPage(PageResult<MemberLevelRecordDO> page);
+
+    default MemberLevelRecordDO copyTo(MemberLevelDO from, MemberLevelRecordDO to) {
+        if (from != null) {
+            to.setLevelId(from.getId());
+            to.setLevel(from.getLevel());
+            to.setDiscountPercent(from.getDiscountPercent());
+            to.setExperience(from.getExperience());
+        }
+        return to;
+    }
+}

+ 10 - 10
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java

@@ -1,17 +1,17 @@
 package cn.iocoder.yudao.module.member.convert.user;
 
-import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserRespVO;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserInfoRespVO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
 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 org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 
 import java.util.List;
@@ -20,7 +20,7 @@ import java.util.Map;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
-@Mapper
+@Mapper(uses = {AddressConvert.class})
 public interface MemberUserConvert {
 
     MemberUserConvert INSTANCE = Mappers.getMapper(MemberUserConvert.class);
@@ -35,6 +35,7 @@ public interface MemberUserConvert {
 
     PageResult<MemberUserRespVO> convertPage(PageResult<MemberUserDO> page);
 
+    @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName")
     MemberUserRespVO convert03(MemberUserDO bean);
 
     default PageResult<MemberUserRespVO> convertPage(PageResult<MemberUserDO> pageResult,
@@ -42,18 +43,17 @@ public interface MemberUserConvert {
                                                      List<MemberLevelDO> levels,
                                                      List<MemberGroupDO> groups) {
         PageResult<MemberUserRespVO> result = convertPage(pageResult);
-
         // 处理关联数据
         Map<Long, String> tagMap = convertMap(tags, MemberTagDO::getId, MemberTagDO::getName);
         Map<Long, String> levelMap = convertMap(levels, MemberLevelDO::getId, MemberLevelDO::getName);
         Map<Long, String> groupMap = convertMap(groups, MemberGroupDO::getId, MemberGroupDO::getName);
-
         // 填充关联数据
-        for (MemberUserRespVO vo : result.getList()) {
-            vo.setTagNames(convertList(vo.getTagIds(), tagMap::get));
-            vo.setLevelName(MapUtil.getStr(levelMap, vo.getLevelId(), StrUtil.EMPTY));
-            vo.setGroupName(MapUtil.getStr(groupMap, vo.getGroupId(), StrUtil.EMPTY));
-        }
+        result.getList().forEach(user -> {
+            user.setTagNames(convertList(user.getTagIds(), tagMap::get));
+            user.setLevelName(levelMap.get(user.getLevelId()));
+            user.setGroupName(groupMap.get(user.getGroupId()));
+        });
         return result;
     }
+
 }

+ 3 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberExperienceLogDO.java

@@ -13,15 +13,15 @@ import lombok.*;
  *
  * @author owen
  */
-@TableName("member_experience_log")
-@KeySequence("member_experience_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("member_experience_record")
+@KeySequence("member_experience_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class MemberExperienceLogDO extends BaseDO {
+public class MemberExperienceRecordDO extends BaseDO {
 
     /**
      * 编号

+ 3 - 4
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelLogDO.java

@@ -14,15 +14,15 @@ import lombok.*;
  *
  * @author owen
  */
-@TableName("member_level_log")
-@KeySequence("member_level_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("member_level_record")
+@KeySequence("member_level_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class MemberLevelLogDO extends BaseDO {
+public class MemberLevelRecordDO extends BaseDO {
 
     /**
      * 编号
@@ -59,7 +59,6 @@ public class MemberLevelLogDO extends BaseDO {
      * 会员此时的经验
      */
     private Integer userExperience;
-    // TODO @疯狂:是不是 remark 和 description 可以合并成 description 就够了
     /**
      * 备注
      */

+ 0 - 28
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceLogMapper.java

@@ -1,28 +0,0 @@
-package cn.iocoder.yudao.module.member.dal.mysql.level;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
-import org.apache.ibatis.annotations.Mapper;
-
-/**
- * 会员经验记录 Mapper
- *
- * @author owen
- */
-@Mapper
-public interface MemberExperienceLogMapper extends BaseMapperX<MemberExperienceLogDO> {
-
-    default PageResult<MemberExperienceLogDO> selectPage(MemberExperienceLogPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<MemberExperienceLogDO>()
-                .eqIfPresent(MemberExperienceLogDO::getUserId, reqVO.getUserId())
-                .eqIfPresent(MemberExperienceLogDO::getBizId, reqVO.getBizId())
-                .eqIfPresent(MemberExperienceLogDO::getBizType, reqVO.getBizType())
-                .eqIfPresent(MemberExperienceLogDO::getTitle, reqVO.getTitle())
-                .betweenIfPresent(MemberExperienceLogDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(MemberExperienceLogDO::getId));
-    }
-
-}

+ 28 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.dal.mysql.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 会员经验记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberExperienceRecordMapper extends BaseMapperX<MemberExperienceRecordDO> {
+
+    default PageResult<MemberExperienceRecordDO> selectPage(MemberExperienceRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MemberExperienceRecordDO>()
+                .eqIfPresent(MemberExperienceRecordDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(MemberExperienceRecordDO::getBizId, reqVO.getBizId())
+                .eqIfPresent(MemberExperienceRecordDO::getBizType, reqVO.getBizType())
+                .eqIfPresent(MemberExperienceRecordDO::getTitle, reqVO.getTitle())
+                .betweenIfPresent(MemberExperienceRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(MemberExperienceRecordDO::getId));
+    }
+
+}

+ 3 - 4
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java

@@ -1,9 +1,8 @@
 package cn.iocoder.yudao.module.member.dal.mysql.level;
 
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -17,8 +16,8 @@ import java.util.List;
 @Mapper
 public interface MemberLevelMapper extends BaseMapperX<MemberLevelDO> {
 
-    default PageResult<MemberLevelDO> selectPage(MemberLevelPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<MemberLevelDO>()
+    default List<MemberLevelDO> selectList(MemberLevelListReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<MemberLevelDO>()
                 .likeIfPresent(MemberLevelDO::getName, reqVO.getName())
                 .eqIfPresent(MemberLevelDO::getStatus, reqVO.getStatus())
                 .orderByAsc(MemberLevelDO::getLevel));

+ 9 - 9
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelLogMapper.java

@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.member.dal.mysql.level;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -13,14 +13,14 @@ import org.apache.ibatis.annotations.Mapper;
  * @author owen
  */
 @Mapper
-public interface MemberLevelLogMapper extends BaseMapperX<MemberLevelLogDO> {
+public interface MemberLevelRecordMapper extends BaseMapperX<MemberLevelRecordDO> {
 
-    default PageResult<MemberLevelLogDO> selectPage(MemberLevelLogPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<MemberLevelLogDO>()
-                .eqIfPresent(MemberLevelLogDO::getUserId, reqVO.getUserId())
-                .eqIfPresent(MemberLevelLogDO::getLevelId, reqVO.getLevelId())
-                .betweenIfPresent(MemberLevelLogDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(MemberLevelLogDO::getId));
+    default PageResult<MemberLevelRecordDO> selectPage(MemberLevelRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MemberLevelRecordDO>()
+                .eqIfPresent(MemberLevelRecordDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(MemberLevelRecordDO::getLevelId, reqVO.getLevelId())
+                .betweenIfPresent(MemberLevelRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(MemberLevelRecordDO::getId));
     }
 
 }

+ 0 - 15
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java

@@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -50,20 +49,6 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
                 .orderByDesc(MemberUserDO::getId));
     }
 
-    // TODO @疯狂:命名可以改成 updateUserLevelToNull;db 侧的操作,尽量无业务含义,更多是 select、update、insert 操作
-    /**
-     * 取消会员的等级
-     *
-     * @param userId 会员编号
-     * @return 受影响的行数
-     */
-    default int cancelUserLevel(Long userId) {
-        return update(null, new LambdaUpdateWrapper<MemberUserDO>()
-                .eq(MemberUserDO::getId, userId)
-                .set(MemberUserDO::getExperience, 0)
-                .set(MemberUserDO::getLevelId, null));
-    }
-
     default Long selectCountByGroupId(Long groupId) {
         return selectCount(MemberUserDO::getGroupId, groupId);
     }

+ 3 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java

@@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupUpdat
 import cn.iocoder.yudao.module.member.convert.group.MemberGroupConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
 import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
-import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -33,7 +33,7 @@ public class MemberGroupServiceImpl implements MemberGroupService {
     @Resource
     private MemberGroupMapper groupMapper;
     @Resource
-    private MemberUserMapper memberUserMapper;
+    private MemberUserService memberUserService;
 
     @Override
     public Long createGroup(MemberGroupCreateReqVO createReqVO) {
@@ -70,7 +70,7 @@ public class MemberGroupServiceImpl implements MemberGroupService {
     }
 
     void validateGroupHasUser(Long id) {
-        Long count = memberUserMapper.selectCountByGroupId(id);
+        Long count = memberUserService.getUserCountByGroupId(id);
         if (count > 0) {
             throw exception(GROUP_HAS_USER);
         }

+ 0 - 64
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogServiceImpl.java

@@ -1,64 +0,0 @@
-package cn.iocoder.yudao.module.member.service.level;
-
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
-import cn.iocoder.yudao.module.member.dal.mysql.level.MemberExperienceLogMapper;
-import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * 会员经验记录 Service 实现类
- *
- * @author owen
- */
-@Service
-@Validated
-public class MemberExperienceLogServiceImpl implements MemberExperienceLogService {
-
-    @Resource
-    private MemberExperienceLogMapper experienceLogMapper;
-
-
-    @Override
-    public MemberExperienceLogDO getExperienceLog(Long id) {
-        return experienceLogMapper.selectById(id);
-    }
-
-    @Override
-    public List<MemberExperienceLogDO> getExperienceLogList(Collection<Long> ids) {
-        return experienceLogMapper.selectBatchIds(ids);
-    }
-
-    @Override
-    public PageResult<MemberExperienceLogDO> getExperienceLogPage(MemberExperienceLogPageReqVO pageReqVO) {
-        return experienceLogMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public void createAdjustLog(Long userId, int experience, int totalExperience) {
-        // 管理员调整时, 没有业务编号, 记录对应的枚举值
-        String bizId = MemberExperienceBizTypeEnum.ADMIN.getValue() + "";
-        this.createBizLog(userId, experience, totalExperience, MemberExperienceBizTypeEnum.ADMIN, bizId);
-    }
-
-    @Override
-    public void createBizLog(Long userId, int experience, int totalExperience, MemberExperienceBizTypeEnum bizType, String bizId) {
-        MemberExperienceLogDO experienceLogDO = new MemberExperienceLogDO();
-        experienceLogDO.setUserId(userId);
-        experienceLogDO.setExperience(experience);
-        experienceLogDO.setTotalExperience(totalExperience);
-        experienceLogDO.setBizId(bizId);
-        experienceLogDO.setBizType(bizType.getValue());
-        experienceLogDO.setTitle(bizType.getTitle());
-        experienceLogDO.setDescription(StrUtil.format(bizType.getDesc(), experience));
-        experienceLogMapper.insert(experienceLogDO);
-    }
-
-}

+ 8 - 18
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogService.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.member.service.level;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 
 import java.util.Collection;
@@ -13,7 +13,7 @@ import java.util.List;
  *
  * @author owen
  */
-public interface MemberExperienceLogService {
+public interface MemberExperienceRecordService {
 
     /**
      * 获得会员经验记录
@@ -21,7 +21,7 @@ public interface MemberExperienceLogService {
      * @param id 编号
      * @return 会员经验记录
      */
-    MemberExperienceLogDO getExperienceLog(Long id);
+    MemberExperienceRecordDO getExperienceRecord(Long id);
 
     /**
      * 获得会员经验记录列表
@@ -29,7 +29,7 @@ public interface MemberExperienceLogService {
      * @param ids 编号
      * @return 会员经验记录列表
      */
-    List<MemberExperienceLogDO> getExperienceLogList(Collection<Long> ids);
+    List<MemberExperienceRecordDO> getExperienceRecordList(Collection<Long> ids);
 
     /**
      * 获得会员经验记录分页
@@ -37,18 +37,7 @@ public interface MemberExperienceLogService {
      * @param pageReqVO 分页查询
      * @return 会员经验记录分页
      */
-    PageResult<MemberExperienceLogDO> getExperienceLogPage(MemberExperienceLogPageReqVO pageReqVO);
-
-    // TODO @疯狂:类似 MemberLevelLogService 的方法,这里也需要提供一个通用的方法,用于创建经验变动记录
-
-    /**
-     * 创建 手动调整 经验变动记录
-     *
-     * @param userId          会员编号
-     * @param experience      变动经验值
-     * @param totalExperience 会员当前的经验
-     */
-    void createAdjustLog(Long userId, int experience, int totalExperience);
+    PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO);
 
     /**
      * 根据业务类型, 创建 经验变动记录
@@ -59,5 +48,6 @@ public interface MemberExperienceLogService {
      * @param bizType         业务类型
      * @param bizId           业务ID
      */
-    void createBizLog(Long userId, int experience, int totalExperience, MemberExperienceBizTypeEnum bizType, String bizId);
+    void createExperienceRecord(Long userId, Integer experience, Integer totalExperience,
+                                MemberExperienceBizTypeEnum bizType, String bizId);
 }

+ 54 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.dal.mysql.level.MemberExperienceRecordMapper;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 会员经验记录 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberExperienceRecordServiceImpl implements MemberExperienceRecordService {
+
+    @Resource
+    private MemberExperienceRecordMapper experienceLogMapper;
+
+    @Override
+    public MemberExperienceRecordDO getExperienceRecord(Long id) {
+        return experienceLogMapper.selectById(id);
+    }
+
+    @Override
+    public List<MemberExperienceRecordDO> getExperienceRecordList(Collection<Long> ids) {
+        return experienceLogMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO) {
+        return experienceLogMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void createExperienceRecord(Long userId, Integer experience, Integer totalExperience,
+                                       MemberExperienceBizTypeEnum bizType, String bizId) {
+        String description = StrUtil.format(bizType.getDescription(), experience);
+        MemberExperienceRecordDO record = MemberExperienceRecordConvert.INSTANCE.convert(
+                userId, experience, totalExperience,
+                bizId, bizType.getType(), bizType.getTitle(), description);
+        experienceLogMapper.insert(record);
+    }
+
+}

+ 0 - 77
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogService.java

@@ -1,77 +0,0 @@
-package cn.iocoder.yudao.module.member.service.level;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * 会员等级记录 Service 接口
- *
- * @author owen
- */
-public interface MemberLevelLogService {
-
-    /**
-     * 删除会员等级记录
-     *
-     * @param id 编号
-     */
-    void deleteLevelLog(Long id);
-
-    /**
-     * 获得会员等级记录
-     *
-     * @param id 编号
-     * @return 会员等级记录
-     */
-    MemberLevelLogDO getLevelLog(Long id);
-
-    /**
-     * 获得会员等级记录列表
-     *
-     * @param ids 编号
-     * @return 会员等级记录列表
-     */
-    List<MemberLevelLogDO> getLevelLogList(Collection<Long> ids);
-
-    /**
-     * 获得会员等级记录分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 会员等级记录分页
-     */
-    PageResult<MemberLevelLogDO> getLevelLogPage(MemberLevelLogPageReqVO pageReqVO);
-
-    // TODO @疯狂:把 createCancelLog、createAdjustLog、createAutoUpgradeLog 几个日志合并成一个通用的日志方法;整体的内容,交给 MemberLevelService 去做;以及对应的 level 变化的通知;
-
-    /**
-     * 创建记录: 取消等级
-     *
-     * @param userId 会员编号
-     * @param reason 调整原因
-     */
-    void createCancelLog(Long userId, String reason);
-
-    /**
-     * 创建记录: 手动调整
-     *
-     * @param user       会员
-     * @param level      等级
-     * @param experience 变动经验值
-     * @param reason     调整原因
-     */
-    void createAdjustLog(MemberUserDO user, MemberLevelDO level, int experience, String reason);
-
-    /**
-     * 创建记录: 自动升级
-     *
-     * @param user  会员
-     * @param level 等级
-     */
-    void createAutoUpgradeLog(MemberUserDO user, MemberLevelDO level);
-}

+ 0 - 109
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogServiceImpl.java

@@ -1,109 +0,0 @@
-package cn.iocoder.yudao.module.member.service.level;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelLogMapper;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.LEVEL_LOG_NOT_EXISTS;
-
-/**
- * 会员等级记录 Service 实现类
- *
- * @author owen
- */
-@Service
-@Validated
-public class MemberLevelLogServiceImpl implements MemberLevelLogService {
-
-    @Resource
-    private MemberLevelLogMapper levelLogMapper;
-
-    @Override
-    public void deleteLevelLog(Long id) {
-        // 校验存在
-        validateLevelLogExists(id);
-        // 删除
-        levelLogMapper.deleteById(id);
-    }
-
-    private void validateLevelLogExists(Long id) {
-        if (levelLogMapper.selectById(id) == null) {
-            throw exception(LEVEL_LOG_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    public MemberLevelLogDO getLevelLog(Long id) {
-        return levelLogMapper.selectById(id);
-    }
-
-    @Override
-    public List<MemberLevelLogDO> getLevelLogList(Collection<Long> ids) {
-        return levelLogMapper.selectBatchIds(ids);
-    }
-
-    @Override
-    public PageResult<MemberLevelLogDO> getLevelLogPage(MemberLevelLogPageReqVO pageReqVO) {
-        return levelLogMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public void createCancelLog(Long userId, String reason) {
-        MemberLevelLogDO levelLogDO = new MemberLevelLogDO();
-        levelLogDO.setUserId(userId);
-        levelLogDO.setRemark(reason);
-        levelLogDO.setDescription("管理员取消");
-        levelLogMapper.insert(levelLogDO);
-
-        // 给会员发送等级变动消息
-        notifyMember(userId, levelLogDO);
-    }
-
-    @Override
-    public void createAdjustLog(MemberUserDO user, MemberLevelDO level, int experience, String reason) {
-        MemberLevelLogDO levelLogDO = new MemberLevelLogDO();
-        levelLogDO.setUserId(user.getId());
-        levelLogDO.setLevelId(level.getId());
-        levelLogDO.setLevel(level.getLevel());
-        levelLogDO.setDiscountPercent(level.getDiscountPercent());
-        levelLogDO.setUserExperience(level.getExperience());
-        levelLogDO.setExperience(experience);
-        levelLogDO.setRemark(reason);
-        levelLogDO.setDescription("管理员调整为:" + level.getName());
-        levelLogMapper.insert(levelLogDO);
-
-        // 给会员发送等级变动消息
-        notifyMember(user.getId(), levelLogDO);
-    }
-
-    @Override
-    public void createAutoUpgradeLog(MemberUserDO user, MemberLevelDO level) {
-        MemberLevelLogDO levelLogDO = new MemberLevelLogDO();
-        levelLogDO.setUserId(user.getId());
-        levelLogDO.setLevelId(level.getId());
-        levelLogDO.setLevel(level.getLevel());
-        levelLogDO.setDiscountPercent(level.getDiscountPercent());
-        levelLogDO.setExperience(level.getExperience());
-        levelLogDO.setUserExperience(user.getExperience());
-        levelLogDO.setDescription("成为:" + level.getName());
-        levelLogMapper.insert(levelLogDO);
-
-        // 给会员发送等级变动消息
-        notifyMember(user.getId(), levelLogDO);
-    }
-
-    private void notifyMember(Long userId, MemberLevelLogDO level) {
-        //todo: 给会员发消息
-    }
-
-}

+ 37 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+
+/**
+ * 会员等级记录 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberLevelRecordService {
+
+    /**
+     * 获得会员等级记录
+     *
+     * @param id 编号
+     * @return 会员等级记录
+     */
+    MemberLevelRecordDO getLevelRecord(Long id);
+
+    /**
+     * 获得会员等级记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 会员等级记录分页
+     */
+    PageResult<MemberLevelRecordDO> getLevelRecordPage(MemberLevelRecordPageReqVO pageReqVO);
+
+    /**
+     * 创建会员等级记录
+     *
+     * @param levelRecord 会员等级记录
+     */
+    void createLevelRecord(MemberLevelRecordDO levelRecord);
+
+}

+ 39 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelRecordMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 会员等级记录 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberLevelRecordServiceImpl implements MemberLevelRecordService {
+
+    @Resource
+    private MemberLevelRecordMapper levelLogMapper;
+
+    @Override
+    public MemberLevelRecordDO getLevelRecord(Long id) {
+        return levelLogMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MemberLevelRecordDO> getLevelRecordPage(MemberLevelRecordPageReqVO pageReqVO) {
+        return levelLogMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void createLevelRecord(MemberLevelRecordDO levelRecord) {
+        levelLogMapper.insert(levelRecord);
+    }
+
+}

+ 10 - 15
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java

@@ -1,15 +1,13 @@
 package cn.iocoder.yudao.module.member.service.level;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateLevelReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 
-import javax.annotation.Nullable;
 import javax.validation.Valid;
 import java.util.Collection;
 import java.util.List;
@@ -60,13 +58,12 @@ public interface MemberLevelService {
     List<MemberLevelDO> getLevelList(Collection<Long> ids);
 
     /**
-     * 获得会员等级分页
+     * 获得会员等级列表
      *
-     * @param pageReqVO 分页查询
-     * @return 会员等级分页
+     * @param listReqVO 查询参数
+     * @return 会员等级列表
      */
-    PageResult<MemberLevelDO> getLevelPage(MemberLevelPageReqVO pageReqVO);
-
+    List<MemberLevelDO> getLevelList(MemberLevelListReqVO listReqVO);
 
     /**
      * 获得指定状态的会员等级列表
@@ -76,7 +73,6 @@ public interface MemberLevelService {
      */
     List<MemberLevelDO> getLevelListByStatus(Integer status);
 
-
     /**
      * 获得开启状态的会员等级列表
      *
@@ -89,11 +85,9 @@ public interface MemberLevelService {
     /**
      * 修改会员的等级
      *
-     * @param user        会员
-     * @param levelId     要修改的等级编号,编号为空时,代表取消会员的等级
-     * @param levelReason 修改原因
+     * @param updateReqVO 修改参数
      */
-    void updateUserLevel(MemberUserDO user, @Nullable Long levelId, String levelReason);
+    void updateUserLevel(MemberUserUpdateLevelReqVO updateReqVO);
 
     /**
      * 增加会员经验
@@ -103,5 +97,6 @@ public interface MemberLevelService {
      * @param bizType    业务类型
      * @param bizId      业务编号
      */
-    void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId);
+    void addExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId);
+
 }

+ 80 - 78
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java

@@ -4,17 +4,18 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateLevelReqVO;
 import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelRecordConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
-import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -23,6 +24,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
@@ -42,11 +44,11 @@ public class MemberLevelServiceImpl implements MemberLevelService {
     @Resource
     private MemberLevelMapper levelMapper;
     @Resource
-    private MemberLevelLogService memberLevelLogService;
+    private MemberLevelRecordService memberLevelRecordService;
     @Resource
-    private MemberExperienceLogService memberExperienceLogService;
+    private MemberExperienceRecordService memberExperienceRecordService;
     @Resource
-    private MemberUserMapper memberUserMapper;
+    private MemberUserService memberUserService;
 
     @Override
     public Long createLevel(MemberLevelCreateReqVO createReqVO) {
@@ -97,7 +99,6 @@ public class MemberLevelServiceImpl implements MemberLevelService {
             if (ObjUtil.notEqual(levelDO.getName(), name)) {
                 continue;
             }
-
             if (id == null || !id.equals(levelDO.getId())) {
                 throw exception(LEVEL_NAME_EXISTS, levelDO.getName());
             }
@@ -151,9 +152,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
     @VisibleForTesting
     void validateLevelHasUser(Long id) {
-        Long count = memberUserMapper.selectCountByLevelId(id);
+        Long count = memberUserService.getUserCountByLevelId(id);
         if (count > 0) {
-            throw exception(GROUP_HAS_USER);
+            throw exception(LEVEL_HAS_USER);
         }
     }
 
@@ -164,12 +165,15 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
     @Override
     public List<MemberLevelDO> getLevelList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
         return levelMapper.selectBatchIds(ids);
     }
 
     @Override
-    public PageResult<MemberLevelDO> getLevelPage(MemberLevelPageReqVO pageReqVO) {
-        return levelMapper.selectPage(pageReqVO);
+    public List<MemberLevelDO> getLevelList(MemberLevelListReqVO listReqVO) {
+        return levelMapper.selectList(listReqVO);
     }
 
     @Override
@@ -179,83 +183,81 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateUserLevel(MemberUserDO user, Long levelId, String reason) {
-        // TODO @疯狂:可以直接 ObjUtil.equal(user.getLevelId(), levelId),解决这 2 个场景
-        // 未调整的情况1
-        if (user.getLevelId() == null && levelId == null) {
-            return;
+    public void updateUserLevel(MemberUserUpdateLevelReqVO updateReqVO) {
+        MemberUserDO user = memberUserService.getUser(updateReqVO.getId());
+        if (user == null) {
+            throw exception(USER_NOT_EXISTS);
         }
-        // 未调整的情况2
-        if (ObjUtil.equal(user.getLevelId(), levelId)) {
+        // 等级未发生变化
+        if (ObjUtil.equal(user.getLevelId(), updateReqVO.getLevelId())) {
             return;
         }
 
-        // 需要后台用户填写为什么调整会员的等级
-        // TODO @疯狂:这个 reason 是不是可以放到 validator 里做哈;
-        if (StrUtil.isBlank(reason)) {
-            throw exception(LEVEL_REASON_NOT_EXISTS);
-        }
-
-        int experience;
-        int totalExperience = 0;
-        // 记录等级变动
-        if (levelId == null) {
-            experience = -user.getExperience();
-
-            // TODO @疯狂:这里的逻辑,应该和下面的 207 到 210 行的逻辑一致,都是先记录日志,再更新会员表;所以,是不是都可以类似 214 的写法哈。
-            // 取消了会员的等级
-            memberLevelLogService.createCancelLog(user.getId(), reason);
-            memberUserMapper.cancelUserLevel(user.getId());
+        // 1. 记录等级变动
+        MemberLevelRecordDO levelRecord = new MemberLevelRecordDO()
+                .setUserId(user.getId()).setRemark(updateReqVO.getReason());
+        MemberLevelDO memberLevel = null;
+        if (updateReqVO.getLevelId() == null) {
+            // 取消用户等级时,需要扣减经验
+            levelRecord.setExperience(-user.getExperience());
+            levelRecord.setUserExperience(0);
+            levelRecord.setDescription("管理员取消了等级");
         } else {
-            MemberLevelDO level = validateLevelExists(levelId);
+            // 复制等级配置
+            memberLevel = validateLevelExists(updateReqVO.getLevelId());
+            MemberLevelRecordConvert.INSTANCE.copyTo(memberLevel, levelRecord);
             // 变动经验值 = 等级的升级经验 - 会员当前的经验;正数为增加经验,负数为扣减经验
-            experience = level.getExperience() - user.getExperience();
-            // 会员当前的经验 = 等级的升级经验
-            totalExperience = level.getExperience();
+            levelRecord.setExperience(memberLevel.getExperience() - user.getExperience());
+            levelRecord.setUserExperience(memberLevel.getExperience()); // 会员当前的经验 = 等级的升级经验
+            levelRecord.setDescription("管理员调整为:" + memberLevel.getName());
+        }
+        memberLevelRecordService.createLevelRecord(levelRecord);
 
-            memberLevelLogService.createAdjustLog(user, level, experience, reason);
+        // 2. 记录会员经验变动
+        memberExperienceRecordService.createExperienceRecord(user.getId(),
+                levelRecord.getExperience(), levelRecord.getUserExperience(),
+                MemberExperienceBizTypeEnum.ADMIN, String.valueOf(MemberExperienceBizTypeEnum.ADMIN.getType()));
 
-            // 更新会员表上的等级编号、经验值
-            updateUserLevelIdAndExperience(user.getId(), levelId, totalExperience);
-        }
+        // 3. 更新会员表上的等级编号、经验值
+        memberUserService.updateUserLevel(user.getId(), updateReqVO.getLevelId(),
+                levelRecord.getUserExperience());
 
-        // 记录会员经验变动
-        memberExperienceLogService.createAdjustLog(user.getId(), experience, totalExperience);
+        // 4. 给会员发送等级变动消息
+        notifyMemberLevelChange(user.getId(), memberLevel);
     }
 
-    // TODO @疯狂:方法名,建议改成 increase 或者 add 经验,和项目更统一一些
-    // TODO @疯狂:bizType 改成具体数值,主要是枚举在 api 不好传递,rpc 情况下
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId) {
+    public void addExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId) {
         if (experience == 0) {
             return;
         }
-
-        MemberUserDO user = memberUserMapper.selectById(userId);
-        // TODO @疯狂:默认给 Experience 搞个 0 哈。这里就不做兜底逻辑啦
-        if (user.getExperience() == null) {
-            user.setExperience(0);
+        if (!bizType.isAdd() && experience > 0) {
+            experience = -experience;
         }
 
-        // 防止扣出负数
-        // TODO @疯狂:如果经验出现负数,是不是抛出异常会更合理;按道理不应该出现的;
-        int userExperience = NumberUtil.max(user.getExperience() + experience, 0);
-        // 创建经验记录
-        memberExperienceLogService.createBizLog(userId, experience, userExperience, bizType, bizId);
-
-        // 计算会员等级
-        Long levelId = calcLevel(user, userExperience);
-        // 更新会员表上的等级编号、经验值
-        updateUserLevelIdAndExperience(user.getId(), levelId, userExperience);
-    }
+        // 1. 创建经验记录
+        MemberUserDO user = memberUserService.getUser(userId);
+        int userExperience = NumberUtil.max(user.getExperience() + experience, 0); // 防止扣出负数
+        MemberLevelRecordDO levelRecord = new MemberLevelRecordDO()
+                .setUserId(user.getId())
+                .setExperience(experience)
+                .setUserExperience(userExperience);
+        memberExperienceRecordService.createExperienceRecord(userId, experience, userExperience,
+                bizType, bizId);
+
+        // 2.1 保存等级变更记录
+        MemberLevelDO newLevel = calculateNewLevel(user, userExperience);
+        if (newLevel != null) {
+            MemberLevelRecordConvert.INSTANCE.copyTo(newLevel, levelRecord);
+            memberLevelRecordService.createLevelRecord(levelRecord);
+
+            // 2.2 给会员发送等级变动消息
+            notifyMemberLevelChange(userId, newLevel);
+        }
 
-    // TODO @疯狂:让 memberUserService 那开个方法;每个模块,不直接操作对方的 mapper;
-    private void updateUserLevelIdAndExperience(Long userId, Long levelId, Integer experience) {
-        memberUserMapper.updateById(new MemberUserDO()
-                .setId(userId)
-                .setLevelId(levelId).setExperience(experience)
-        );
+        // 3. 更新会员表上的等级编号、经验值
+        memberUserService.updateUserLevel(user.getId(), levelRecord.getLevelId(), userExperience);
     }
 
     /**
@@ -263,11 +265,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
      *
      * @param user           会员
      * @param userExperience 会员当前的经验值
-     * @return 会员等级编号,null表示无变化
+     * @return 会员新的等级,null表示无变化
      */
-    // calc
-    // TODO @疯狂:calc 改成完整的拼写哈。是不是改成 calculateNewLevel
-    private Long calcLevel(MemberUserDO user, int userExperience) {
+    private MemberLevelDO calculateNewLevel(MemberUserDO user, int userExperience) {
         List<MemberLevelDO> list = getEnableLevelList();
         if (CollUtil.isEmpty(list)) {
             log.warn("计算会员等级失败:会员等级配置不存在");
@@ -288,9 +288,11 @@ public class MemberLevelServiceImpl implements MemberLevelService {
             return null;
         }
 
-        // TODO @疯狂:这个方法,应该只做 level 的计算,不做登记的变更。
-        // 保存等级变更记录
-        memberLevelLogService.createAutoUpgradeLog(user, matchLevel);
-        return matchLevel.getId();
+        return matchLevel;
     }
+
+    private void notifyMemberLevelChange(Long userId, MemberLevelDO level) {
+        //todo: 给会员发消息
+    }
+
 }

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

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod.MemberPointRecordPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 
 /**
  * 用户积分记录 Service 接口
@@ -29,4 +30,13 @@ public interface MemberPointRecordService {
      */
     PageResult<MemberPointRecordDO> getPointRecordPage(Long userId, PageParam pageVO);
 
+    /**
+     * 创建用户积分记录
+     *
+     * @param userId  用户ID
+     * @param point   变动积分
+     * @param bizType 业务类型
+     * @param bizId   业务编号
+     */
+    void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId);
 }

+ 42 - 5
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/point/MemberPointRecordServiceImpl.java

@@ -1,12 +1,17 @@
 package cn.iocoder.yudao.module.member.service.point;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
-import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod.MemberPointRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.point.MemberPointRecordMapper;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
@@ -24,27 +29,30 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
  *
  * @author QingX
  */
+@Slf4j
 @Service
 @Validated
 public class MemberPointRecordServiceImpl implements MemberPointRecordService {
 
     @Resource
     private MemberPointRecordMapper recordMapper;
+    @Resource
+    private MemberPointConfigService memberPointConfigService;
 
     @Resource
-    private MemberUserApi memberUserApi;
+    private MemberUserService memberUserService;
 
     @Override
     public PageResult<MemberPointRecordDO> getPointRecordPage(MemberPointRecordPageReqVO pageReqVO) {
         // 根据用户昵称查询出用户 ids
         Set<Long> userIds = null;
         if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
-            List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
+            List<MemberUserDO> users = memberUserService.getUserListByNickname(pageReqVO.getNickname());
             // 如果查询用户结果为空直接返回无需继续查询
             if (CollectionUtils.isEmpty(users)) {
                 return PageResult.empty();
             }
-            userIds = convertSet(users, MemberUserRespDTO::getId);
+            userIds = convertSet(users, MemberUserDO::getId);
         }
         // 执行查询
         return recordMapper.selectPage(pageReqVO, userIds);
@@ -55,4 +63,33 @@ public class MemberPointRecordServiceImpl implements MemberPointRecordService {
         return recordMapper.selectPage(userId, pageVO);
     }
 
+    @Override
+    public void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId) {
+        MemberPointConfigDO pointConfig = memberPointConfigService.getPointConfig();
+        if (pointConfig == null || pointConfig.getTradeGivePoint() == null) {
+            log.error("[createPointRecord][增加积分失败:tradeGivePoint 未配置,userId({}) point({}) bizType({}) bizId({})]",
+                    userId, point, bizType.getType(), bizId);
+            return;
+        }
+
+        // 1. 根据配置的比例,换算实际的积分
+        point = point * pointConfig.getTradeGivePoint();
+        if (!bizType.isAdd() && point > 0) {
+            point = -point;
+        }
+
+        // 2. 增加积分记录
+        MemberUserDO user = memberUserService.getUser(userId);
+        Integer userPoint = ObjectUtil.defaultIfNull(user.getPoint(), 0);
+        Integer totalPoint = userPoint + point; // 用户变动后的积分
+        MemberPointRecordDO record = new MemberPointRecordDO()
+                .setUserId(userId).setBizId(bizId).setBizType(bizType.getType())
+                .setTitle(bizType.getName()).setDescription(StrUtil.format(bizType.getDescription(), point))
+                .setPoint(point).setTotalPoint(totalPoint);
+        recordMapper.insert(record);
+
+        // 3. 更新用户积分
+        memberUserService.updateUserPoint(userId, totalPoint);
+    }
+
 }

+ 3 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImpl.java

@@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagUpdateReq
 import cn.iocoder.yudao.module.member.convert.tag.MemberTagConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
 import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper;
-import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -33,7 +33,7 @@ public class MemberTagServiceImpl implements MemberTagService {
     @Resource
     private MemberTagMapper tagMapper;
     @Resource
-    private MemberUserMapper memberUserMapper;
+    private MemberUserService memberUserService;
 
     @Override
     public Long createTag(MemberTagCreateReqVO createReqVO) {
@@ -92,7 +92,7 @@ public class MemberTagServiceImpl implements MemberTagService {
     }
 
     void validateTagHasUser(Long id) {
-        Long count = memberUserMapper.selectCountByTagId(id);
+        Long count = memberUserService.getUserCountByTagId(id);
         if (count > 0) {
             throw exception(TAG_HAS_USER);
         }

+ 47 - 7
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java

@@ -5,9 +5,9 @@ import cn.iocoder.yudao.framework.common.validation.Mobile;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserResetPasswordReqVO;
+import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdatePasswordReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateReqVO;
-import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 
 import javax.validation.Valid;
@@ -41,7 +41,7 @@ public interface MemberUserService {
      * 基于手机号创建用户。
      * 如果用户已经存在,则直接进行返回
      *
-     * @param mobile 手机号
+     * @param mobile     手机号
      * @param registerIp 注册 IP
      * @return 用户对象
      */
@@ -50,7 +50,7 @@ public interface MemberUserService {
     /**
      * 更新用户的最后登陆信息
      *
-     * @param id 用户编号
+     * @param id      用户编号
      * @param loginIp 登陆 IP
      */
     void updateUserLogin(Long id, String loginIp);
@@ -75,7 +75,7 @@ public interface MemberUserService {
      * 【会员】修改基本信息
      *
      * @param userId 用户编号
-     * @param reqVO 基本信息
+     * @param reqVO  基本信息
      */
     void updateUser(Long userId, AppMemberUserUpdateReqVO reqVO);
 
@@ -83,7 +83,7 @@ public interface MemberUserService {
      * 【会员】修改手机
      *
      * @param userId 用户编号
-     * @param reqVO 请求信息
+     * @param reqVO  请求信息
      */
     void updateUserMobile(Long userId, AppMemberUserUpdateMobileReqVO reqVO);
 
@@ -91,7 +91,7 @@ public interface MemberUserService {
      * 【会员】修改密码
      *
      * @param userId 用户编号
-     * @param reqVO 请求信息
+     * @param reqVO  请求信息
      */
     void updateUserPassword(Long userId, AppMemberUserUpdatePasswordReqVO reqVO);
 
@@ -105,7 +105,7 @@ public interface MemberUserService {
     /**
      * 判断密码是否匹配
      *
-     * @param rawPassword 未加密的密码
+     * @param rawPassword     未加密的密码
      * @param encodedPassword 加密后的密码
      * @return 是否匹配
      */
@@ -126,4 +126,44 @@ public interface MemberUserService {
      */
     PageResult<MemberUserDO> getUserPage(MemberUserPageReqVO pageReqVO);
 
+    /**
+     * 更新用户的等级和经验
+     *
+     * @param id         用户编号
+     * @param levelId    用户等级
+     * @param experience 用户经验
+     */
+    void updateUserLevel(Long id, Long levelId, Integer experience);
+
+    /**
+     * 获得指定用户分组下的用户数量
+     *
+     * @param groupId 用户分组编号
+     * @return 用户数量
+     */
+    Long getUserCountByGroupId(Long groupId);
+
+    /**
+     * 获得指定用户等级下的用户数量
+     *
+     * @param levelId 用户等级编号
+     * @return 用户数量
+     */
+    Long getUserCountByLevelId(Long levelId);
+
+    /**
+     * 获得指定会员标签下的用户数量
+     *
+     * @param tagId 用户标签编号
+     * @return 用户数量
+     */
+    Long getUserCountByTagId(Long tagId);
+
+    /**
+     * 更新用户的积分
+     *
+     * @param userId 用户编号
+     * @param point  积分数量
+     */
+    void updateUserPoint(Long userId, Integer point);
 }

+ 31 - 8
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.member.service.user;
 
 import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -15,7 +16,6 @@ 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.service.level.MemberLevelService;
 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;
@@ -56,9 +56,6 @@ public class MemberUserServiceImpl implements MemberUserService {
     @Resource
     private PasswordEncoder passwordEncoder;
 
-    @Resource
-    private MemberLevelService memberLevelService;
-
     @Override
     public MemberUserDO getUserByMobile(String mobile) {
         return memberUserMapper.selectByMobile(mobile);
@@ -195,10 +192,6 @@ public class MemberUserServiceImpl implements MemberUserService {
         // 更新
         MemberUserDO updateObj = MemberUserConvert.INSTANCE.convert(updateReqVO);
         memberUserMapper.updateById(updateObj);
-
-        // 会员级别修改
-        // TODO @疯狂:修改用户等级,要不要单独一个前端操作 + 接口;因为它是个相对严肃独立的动作
-        memberLevelService.updateUserLevel(user, updateReqVO.getLevelId(), updateReqVO.getLevelReason());
     }
 
     @VisibleForTesting
@@ -236,4 +229,34 @@ public class MemberUserServiceImpl implements MemberUserService {
         return memberUserMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public void updateUserLevel(Long id, Long levelId, Integer experience) {
+        // 0 代表无等级:防止UpdateById时,会被过滤掉的问题
+        levelId = ObjectUtil.defaultIfNull(levelId, 0L);
+        memberUserMapper.updateById(new MemberUserDO()
+                .setId(id)
+                .setLevelId(levelId).setExperience(experience)
+        );
+    }
+
+    @Override
+    public Long getUserCountByGroupId(Long groupId) {
+        return memberUserMapper.selectCountByGroupId(groupId);
+    }
+
+    @Override
+    public Long getUserCountByLevelId(Long levelId) {
+        return memberUserMapper.selectCountByLevelId(levelId);
+    }
+
+    @Override
+    public Long getUserCountByTagId(Long tagId) {
+        return memberUserMapper.selectCountByTagId(tagId);
+    }
+
+    @Override
+    public void updateUserPoint(Long userId, Integer point) {
+        memberUserMapper.updateById(new MemberUserDO().setId(userId).setPoint(point));
+    }
+
 }

+ 8 - 9
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java

@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
@@ -41,9 +41,9 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
     private MemberLevelMapper levelMapper;
 
     @MockBean
-    private MemberLevelLogService memberLevelLogService;
+    private MemberLevelRecordService memberLevelRecordService;
     @MockBean
-    private MemberExperienceLogService memberExperienceLogService;
+    private MemberExperienceRecordService memberExperienceRecordService;
 
     @Test
     public void testCreateLevel_success() {
@@ -121,7 +121,7 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testGetLevelPage() {
+    public void testGetLevelList() {
         // mock 数据
         MemberLevelDO dbLevel = randomPojo(MemberLevelDO.class, o -> { // 等会查询到
             o.setName("黄金会员");
@@ -133,16 +133,15 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
         // 测试 status 不匹配
         levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(0)));
         // 准备参数
-        MemberLevelPageReqVO reqVO = new MemberLevelPageReqVO();
+        MemberLevelListReqVO reqVO = new MemberLevelListReqVO();
         reqVO.setName("黄金会员");
         reqVO.setStatus(1);
 
         // 调用
-        PageResult<MemberLevelDO> pageResult = levelService.getLevelPage(reqVO);
+        List<MemberLevelDO> list = levelService.getLevelList(reqVO);
         // 断言
-        assertEquals(1, pageResult.getTotal());
-        assertEquals(1, pageResult.getList().size());
-        assertPojoEquals(dbLevel, pageResult.getList().get(0));
+        assertEquals(1, list.size());
+        assertPojoEquals(dbLevel, list.get(0));
     }
 
     @Test

+ 3 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java

@@ -41,6 +41,9 @@ public interface ErrorCodeConstants {
     ErrorCode REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
     ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
 
+    // ========== 钱包模块(退款) 1007007000 ==========
+    ErrorCode WALLET_NOT_FOUND = new ErrorCode(1007007000, "用户钱包不存在");
+
     // ========== 示例订单 1007900000 ==========
     ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
     ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态");

+ 7 - 6
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionGategoryEnum.java

@@ -4,20 +4,21 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 /**
- * 钱包交易大类枚举
+ * 钱包交易业务分类
  *
  * @author jason
  */
 @AllArgsConstructor
 @Getter
-public enum WalletTransactionGategoryEnum {
-    TOP_UP(1, "充值"),
-    SPENDING(2, "支出");
+public enum WalletBizTypeEnum {
+    RECHARGE(1, "充值"),
+    RECHARGE_REFUND(2, "充值退款");
 
+    // TODO 后续增加
     /**
-     * 分类
+     * 业务分类
      */
-    private final Integer category;
+    private final Integer bizType;
 
     /**
      * 说明

+ 0 - 21
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletOperateTypeEnum.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.pay.enums.member;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 钱包操作类型枚举
- *
- * @author jason
- */
-@AllArgsConstructor
-@Getter
-public enum WalletOperateTypeEnum {
-    TOP_UP_INC(1, "充值增加"),
-    ORDER_DEC(2, "订单消费扣除");
-    // TODO 其它类型
-
-    private final Integer type;
-
-    private final String desc;
-}

+ 35 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionQueryTypeEnum.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.pay.enums.member;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 钱包明细查询类型
+ *
+ * @author jason
+ */
+@AllArgsConstructor
+@Getter
+public enum WalletTransactionQueryTypeEnum implements IntArrayValuable  {
+    RECHARGE(1, "充值"),
+    EXPENSE(2, "消费");
+
+    private final Integer type;
+
+    private final String desc;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(WalletTransactionQueryTypeEnum::getType).toArray();
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+    public static WalletTransactionQueryTypeEnum valueOf(Integer type) {
+        return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
+    }
+}

+ 41 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pay.controller.app.wallet;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletRespVO;
+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.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;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * @author jason
+ */
+@Tag(name = "用户 APP - 钱包")
+@RestController
+@RequestMapping("/pay/wallet")
+@Validated
+@Slf4j
+public class AppPayWalletController {
+
+    @Resource
+    private PayWalletService payWalletService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获取钱包")
+    public CommonResult<AppPayWalletRespVO> getPayWallet() {
+        PayWalletDO payWallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
+        return success(PayWalletConvert.INSTANCE.convert(payWallet));
+    }
+}

+ 43 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pay.controller.app.wallet;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionRespVO;
+import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 APP - 钱包余额明细")
+@RestController
+@RequestMapping("/pay/wallet-transaction")
+@Validated
+@Slf4j
+public class AppPayWalletTransactionController {
+
+    @Resource
+    private PayWalletTransactionService payWalletTransactionService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得钱包余额明细分页")
+    public CommonResult<PageResult<AppPayWalletTransactionRespVO>> pageWalletTransaction(
+            @Valid AppPayWalletTransactionPageReqVO pageVO) {
+        PageResult<PayWalletTransactionDO> result = payWalletTransactionService.getWalletTransactionPage(getLoginUserId(),
+                UserTypeEnum.MEMBER.getValue(), pageVO);
+        return success(PayWalletTransactionConvert.INSTANCE.convertPage(result));
+    }
+}

+ 21 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author jason
+ */
+@Schema(description = "用户 APP - 获取用户钱包 Response VO")
+@Data
+public class AppPayWalletRespVO {
+
+    @Schema(description = "钱包余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Integer balance;
+
+    @Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Long totalExpense;
+
+    @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Long totalRecharge;
+}

+ 16 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionPageReqVO.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 APP - 钱包余额明细分页 Request VO")
+@Data
+public class AppPayWalletTransactionPageReqVO extends PageParam {
+
+    @Schema(description = "余额明细查询分类",  example = "1")
+    @InEnum(value = WalletTransactionQueryTypeEnum.class, message = "查询类型必须是 {value}")
+    private Integer type;
+}

+ 19 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 APP - 钱包余额明细分页 Response VO")
+@Data
+public class AppPayWalletTransactionRespVO {
+    @Schema(description = "交易金额, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Integer amount;
+
+    @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer bizType;
+
+    @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private LocalDateTime transactionTime;
+}

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

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.pay.convert.wallet;
+
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface PayWalletConvert {
+
+    PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class);
+
+    AppPayWalletRespVO convert(PayWalletDO bean);
+}

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

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.pay.convert.wallet;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface PayWalletTransactionConvert {
+
+    PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class);
+
+    PageResult<AppPayWalletTransactionRespVO> convertPage(PageResult<PayWalletTransactionDO> page);
+}

+ 0 - 86
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletTransactionDO.java

@@ -1,86 +0,0 @@
-package cn.iocoder.yudao.module.pay.dal.dataobject.member;
-
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.pay.enums.member.WalletOperateTypeEnum;
-import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionGategoryEnum;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 支付-会员钱包明细 DO
- *
- * @author jason
- */
-@TableName(value ="pay_member_wallet_transaction")
-@KeySequence("pay_member_wallet_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
-@Data
-public class MemberWalletTransactionDO extends BaseDO {
-
-    /**
-     * 编号
-     */
-    @TableId
-    private Long id;
-
-    /**
-     * 会员钱包 id
-     *
-     * 关联 {@link MemberWalletDO#getId()}
-     */
-    private Long walletId;
-
-    /**
-     * 用户 id
-     *
-     * 关联 MemberUserDO 的 id 编号
-     */
-    private Long userId;
-
-    /**
-     * 交易单号  @芋艿 这里是关联交易单号, 还是订单号 , 退款单号!  ??
-     */
-    private String tradeNo;
-
-    /**
-     * 交易分类
-     *
-     * 枚举 {@link WalletTransactionGategoryEnum#getCategory()}
-     */
-    private Integer category;
-
-    /**
-     * 操作分类
-     *
-     * 枚举 {@link WalletOperateTypeEnum#getType()}
-     */
-    private Integer operateType;
-
-    /**
-     * 操作详情
-     */
-    private String operateDesc;
-
-    /**
-     * 交易金额, 单位分
-     */
-    private Integer price;
-
-    /**
-     * 余额, 单位分
-     */
-    private Integer balance;
-
-    /**
-     * 备注
-     */
-    private String mark;
-
-    /**
-     * 交易时间
-     */
-    private LocalDateTime transactionTime;
-}

+ 14 - 8
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletDO.java

@@ -1,21 +1,21 @@
-package cn.iocoder.yudao.module.pay.dal.dataobject.member;
+package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
 
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
-// TODO @jason:修改 MemberWalletDO 为 PayWalletDO
 /**
  * 支付 - 会员钱包 DO
  *
  * @author jason
  */
-@TableName(value ="pay_member_wallet")
-@KeySequence("pay_member_wallet_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName(value ="pay_wallet")
+@KeySequence("pay_wallet_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
-public class MemberWalletDO extends BaseDO {
+public class PayWalletDO extends BaseDO {
 
     /**
      * 编号
@@ -23,7 +23,6 @@ public class MemberWalletDO extends BaseDO {
     @TableId
     private Long id;
 
-    // TODO @jaosn:增加 userType 字段;
     /**
      * 用户 id
      *
@@ -33,6 +32,13 @@ public class MemberWalletDO extends BaseDO {
     private Long userId;
 
     /**
+     * 用户类型, 预留 多商户转帐可能需要用到
+     *
+     * 关联 {@link UserTypeEnum}
+     */
+    private Integer userType;
+
+    /**
      * 余额, 单位分
      */
     private Integer balance;
@@ -40,10 +46,10 @@ public class MemberWalletDO extends BaseDO {
     /**
      * 累计支出, 单位分
      */
-    private Integer totalSpending;
+    private Long totalExpense;
 
     /**
      * 累计充值, 单位分
      */
-    private Integer totalTopUp;
+    private Long totalRecharge;
 }

+ 70 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.pay.enums.member.WalletBizTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 支付-会员钱包明细 DO
+ *
+ * @author jason
+ */
+@TableName(value ="pay_wallet_transaction")
+@KeySequence("pay_wallet_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class PayWalletTransactionDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 会员钱包 id
+     * 关联 {@link PayWalletDO#getId()}
+     */
+    private Long walletId;
+
+    /**
+     * 钱包交易业务分类
+     * 关联枚举 {@link WalletBizTypeEnum#getBizType()}
+     */
+    private Integer bizType;
+
+    /**
+     * 关联业务编号
+     */
+    private Long bizId;
+
+    /**
+     * 流水号
+     */
+    private String no;
+
+    /**
+     * 附加说明
+     */
+    private String description;
+
+    /**
+     * 交易金额, 单位分
+     * 正值表示余额增加,负值表示余额减少
+     */
+    private Integer amount;
+
+    /**
+     * 交易后余额,单位分
+     */
+    private Integer balance;
+
+    /**
+     * 交易时间
+     */
+    private LocalDateTime transactionTime;
+}

+ 0 - 15
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletMapper.java

@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.pay.dal.mysql.member;
-
-
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.pay.dal.dataobject.member.MemberWalletDO;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface MemberWalletMapper extends BaseMapperX<MemberWalletDO> {
-
-}
-
-
-
-

+ 0 - 15
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletTransactionMapper.java

@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.pay.dal.mysql.member;
-
-
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.pay.dal.dataobject.member.MemberWalletTransactionDO;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface MemberWalletTransactionMapper extends BaseMapperX<MemberWalletTransactionDO> {
-
-}
-
-
-
-

+ 18 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pay.dal.mysql.wallet;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
+
+    default PayWalletDO selectByUserIdAndType(Long userId, Integer userType) {
+        return selectOne(PayWalletDO::getUserId, userId, PayWalletDO::getUserType, userType);
+    }
+}
+
+
+
+

+ 33 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.pay.dal.mysql.wallet;
+
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayWalletTransactionMapper extends BaseMapperX<PayWalletTransactionDO> {
+
+    default PageResult<PayWalletTransactionDO> selectPageByWalletIdAndQueryType(Long walletId,
+                                                                                WalletTransactionQueryTypeEnum queryType,
+                                                                                PageParam pageParam) {
+        LambdaQueryWrapperX<PayWalletTransactionDO> query = new LambdaQueryWrapperX<PayWalletTransactionDO>()
+                .eq(PayWalletTransactionDO::getWalletId, walletId);
+        if (WalletTransactionQueryTypeEnum.RECHARGE == queryType ) {
+            query.ge(PayWalletTransactionDO::getAmount, 0);
+        }
+        if (WalletTransactionQueryTypeEnum.EXPENSE == queryType ) {
+            query.lt(PayWalletTransactionDO::getAmount, 0);
+        }
+        query.orderByDesc(PayWalletTransactionDO::getTransactionTime);
+        return selectPage(pageParam, query);
+    }
+}
+
+
+
+

+ 18 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pay.service.wallet;
+
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+
+/**
+ * 钱包 Service 接口
+ *
+ * @author jason
+ */
+public interface PayWalletService {
+
+    /**
+     * 获取钱包信息
+     * @param userId 用户 id
+     * @param userType 用户类型
+     */
+    PayWalletDO getPayWallet(Long userId, Integer userType);
+}

+ 24 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.pay.service.wallet;
+
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 钱包 Service 实现类
+ *
+ * @author jason
+ */
+@Service
+public class PayWalletServiceImpl implements  PayWalletService {
+
+    @Resource
+    private PayWalletMapper payWalletMapper;
+
+    @Override
+    public PayWalletDO getPayWallet(Long userId, Integer userType) {
+        return payWalletMapper.selectByUserIdAndType(userId, userType);
+    }
+}

+ 23 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.pay.service.wallet;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+
+/**
+ * 钱包余额明细 Service 接口
+ *
+ * @author jason
+ */
+public interface PayWalletTransactionService {
+
+    /**
+     * 查询钱包余额明细, 分页
+     *
+     * @param userId   用户 id
+     * @param userType 用户类型
+     * @param pageVO   分页查询参数
+     */
+    PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
+                                                                AppPayWalletTransactionPageReqVO pageVO);
+}

+ 41 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pay.service.wallet;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper;
+import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;
+
+/**
+ * 钱包余额明细 Service 实现类
+ *
+ * @author jason
+ */
+@Service
+@Slf4j
+public class PayWalletTransactionServiceImpl implements  PayWalletTransactionService{
+    @Resource
+    private PayWalletService payWalletService;
+    @Resource
+    private PayWalletTransactionMapper payWalletTransactionMapper;
+
+    @Override
+    public PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
+                                                                       AppPayWalletTransactionPageReqVO pageVO) {
+        PayWalletDO payWallet = payWalletService.getPayWallet(userId, userType);
+        if (payWallet == null) {
+            log.error("[pageWalletTransaction] 用户 {} 钱包不存在", userId);
+            throw exception(WALLET_NOT_FOUND);
+        }
+        return payWalletTransactionMapper.selectPageByWalletIdAndQueryType(payWallet.getId(),
+                WalletTransactionQueryTypeEnum.valueOf(pageVO.getType()), pageVO);
+    }
+}