Browse Source

!578 同步商城实现:会员列表、会员积分、会员签到、会员标签
Merge pull request !578 from 芋道源码/feature/mall_product

芋道源码 1 year ago
parent
commit
3124948175
100 changed files with 2165 additions and 1233 deletions
  1. 2 2
      pom.xml
  2. 9 9
      sql/mysql/ruoyi-vue-pro.sql
  3. 5 0
      yudao-framework/yudao-common/pom.xml
  4. 48 5
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  5. 2 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
  6. 11 15
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  7. 64 0
      yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtilsTest.java
  8. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
  9. 258 63
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java
  10. 6 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  11. 1 2
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java
  12. 0 21
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java
  13. 38 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java
  14. 20 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java
  15. 5 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
  16. 7 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  17. 11 5
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
  18. 11 13
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppProductCommentController.java
  19. 9 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/AppFavoriteController.java
  20. 7 2
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/comment/ProductCommentConvert.java
  21. 6 8
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
  22. 5 4
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java
  23. 4 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java
  24. 12 4
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java
  25. 7 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java
  26. 32 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApi.java
  27. 81 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/dto/BargainRecordCreateReqDTO.java
  28. 0 49
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationApi.java
  29. 63 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
  30. 4 4
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordCreateReqDTO.java
  31. 41 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordRespDTO.java
  32. 39 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordUpdateStatusReqDTO.java
  33. 24 6
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  34. 8 4
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/combination/CombinationRecordStatusEnum.java
  35. 37 8
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/decorate/DecorateComponentEnum.java
  36. 7 4
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/decorate/DecoratePageEnum.java
  37. 24 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApiImpl.java
  38. 0 42
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationApiImpl.java
  39. 54 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
  40. 79 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/BargainActivityController.java
  41. 75 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityBaseVO.java
  42. 14 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityCreateReqVO.java
  43. 21 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityPageReqVO.java
  44. 35 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityRespVO.java
  45. 20 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityUpdateReqVO.java
  46. 21 39
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationActivityController.java
  47. 11 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityBaseVO.java
  48. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityCreateReqVO.java
  49. 0 65
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityExcelVO.java
  50. 0 61
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityExportReqVO.java
  51. 0 43
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityPageReqVO.java
  52. 10 16
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityRespVO.java
  53. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityUpdateReqVO.java
  54. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductBaseVO.java
  55. 0 14
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductCreateReqVO.java
  56. 0 44
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductExcelVO.java
  57. 0 43
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductExportReqVO.java
  58. 0 14
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductUpdateReqVO.java
  59. 18 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/DecorateComponentController.http
  60. 18 14
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/DecorateComponentController.java
  61. 6 20
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/vo/DecorateComponentRespVO.java
  62. 9 22
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/vo/DecorateComponentSaveReqVO.java
  63. 18 22
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillActivityController.java
  64. 4 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillConfigController.java
  65. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityBaseVO.java
  66. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityCreateReqVO.java
  67. 6 6
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityRespVO.java
  68. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityUpdateReqVO.java
  69. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/product/SeckillProductBaseVO.java
  70. 0 13
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/product/SeckillProductCreateReqVO.java
  71. 0 14
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/product/SeckillProductUpdateReqVO.java
  72. 14 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
  73. 10 7
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/decorate/AppDecorateController.java
  74. 5 19
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/decorate/vo/AppDecorateComponentRespVO.java
  75. 33 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/bargain/BargainActivityConvert.java
  76. 33 74
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
  77. 4 23
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/decorate/DecorateComponentConvert.java
  78. 23 43
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java
  79. 101 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/bargain/BargainActivityDO.java
  80. 50 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/bargain/BargainAssistDO.java
  81. 87 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/bargain/BargainRecordDO.java
  82. 4 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationActivityDO.java
  83. 12 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationProductDO.java
  84. 5 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationRecordDO.java
  85. 6 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/decorate/DecorateComponentDO.java
  86. 31 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
  87. 15 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainRecordMapper.java
  88. 2 10
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java
  89. 3 17
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationProductMapper.java
  90. 62 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java
  91. 0 33
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/combinationactivity/CombinationRecordMapper.java
  92. 6 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
  93. 11 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/decorate/DecorateComponentMapper.java
  94. 0 13
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java
  95. 57 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
  96. 124 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
  97. 13 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainRecordService.java
  98. 14 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainRecordServiceImpl.java
  99. 10 58
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
  100. 90 168
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java

+ 2 - 2
pom.xml

@@ -16,11 +16,11 @@
         <module>yudao-module-member</module>
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
-<!--        <module>yudao-module-pay</module>-->
+        <module>yudao-module-pay</module>
 <!--        <module>yudao-module-bpm</module>-->
 <!--        <module>yudao-module-report</module>-->
 <!--        <module>yudao-module-mp</module>-->
-<!--        <module>yudao-module-mall</module>-->
+        <module>yudao-module-mall</module>
         <!-- 示例项目 -->
         <module>yudao-example</module>
     </modules>

+ 9 - 9
sql/mysql/ruoyi-vue-pro.sql

@@ -1637,18 +1637,18 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2209, '秒杀活动', '', 2, 3, 2030, 'seckill', 'ep:place', '', '', 0, b'1', b'1', b'1', '1', '2023-06-24 17:39:13', '1', '2023-06-24 18:55:15', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2262, '会员中心', '', 1, 55, 0, '/member', 'date-range', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-06-10 00:42:03', '1', '2023-06-28 22:52:34', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2275, '积分配置', '', 2, 0, 2299, 'config', '', 'member/point/config/index', 'PointConfig', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 22:50:59', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '积分设置查询', 'point:config:query', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '积分设置创建', 'point:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '积分设置查询', 'member:point:config:get', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '积分设置创建', 'member:point:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2281, '签到配置', '', 2, 2, 2300, 'sign-in-config', '', 'member/signin/config/index', 'SignInConfig', 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-07-02 15:04:15', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2282, '积分签到规则查询', 'point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2283, '积分签到规则创建', 'point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2284, '积分签到规则更新', 'point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2285, '积分签到规则删除', 'point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2282, '签到规则查询', 'member:point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2283, '签到规则创建', 'member:point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2284, '签到规则更新', 'member:point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2285, '签到规则删除', 'member:point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2286, '签到规则获取', 'member:point:sign-in-config:get', 3, 5, 2281, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-08-19 09:48:32', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2287, '积分记录', '', 2, 1, 2299, 'record', '', 'member/point/record/index', 'PointRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-06-27 22:51:07', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '用户积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '用户积分记录查询', 'member:point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2293, '签到记录', '', 2, 3, 2300, 'sign-in-record', '', 'member/signin/record/index', 'SignInRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '1', '2023-07-02 15:04:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2294, '用户签到积分查询', 'point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2297, '用户签到积分删除', 'point:sign-in-record:delete', 3, 4, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2294, '签到记录查询', 'member:point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2299, '会员积分', '', 1, 1, 2262, 'point', '', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:48:51', '1', '2023-06-27 22:48:51', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2300, '会员签到', '', 1, 2, 2262, 'signin', '', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:49:53', '1', '2023-06-27 22:49:53', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2301, '回调通知', '', 2, 4, 1117, 'notify', 'example', 'pay/notify/index', 'PayNotify', 0, b'1', b'1', b'1', '', '2023-07-20 04:41:32', '1', '2023-07-20 13:45:08', b'0');

+ 5 - 0
yudao-framework/yudao-common/pom.xml

@@ -133,6 +133,11 @@
             <artifactId>transmittable-thread-local</artifactId>
         </dependency>
 
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 48 - 5
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -5,12 +5,11 @@ import cn.hutool.core.collection.CollectionUtil;
 import com.google.common.collect.ImmutableMap;
 
 import java.util.*;
-import java.util.function.BinaryOperator;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
+import java.util.function.*;
 import java.util.stream.Collectors;
 
+import static java.util.Arrays.asList;
+
 /**
  * Collection 工具类
  *
@@ -19,13 +18,17 @@ import java.util.stream.Collectors;
 public class CollectionUtils {
 
     public static boolean containsAny(Object source, Object... targets) {
-        return Arrays.asList(targets).contains(source);
+        return asList(targets).contains(source);
     }
 
     public static boolean isAnyEmpty(Collection<?>... collections) {
         return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
     }
 
+    public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
+        return from.stream().anyMatch(predicate);
+    }
+
     public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
         if (CollUtil.isEmpty(from)) {
             return new ArrayList<>();
@@ -149,6 +152,46 @@ public class CollectionUtils {
         return builder.build();
     }
 
+    /**
+     * 对比老、新两个列表,找出新增、修改、删除的数据
+     *
+     * @param oldList 老列表
+     * @param newList 新列表
+     * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
+     *                 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
+     * @return [新增列表、修改列表、删除列表]
+     */
+    public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
+                                             BiFunction<T, T, Boolean> sameFunc) {
+        List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
+        List<T> updateList = new ArrayList<>();
+        List<T> deleteList = new ArrayList<>();
+
+        // 通过以 oldList 为主遍历,找出 updateList 和 deleteList
+        for (T oldObj : oldList) {
+            // 1. 寻找是否有匹配的
+            T foundObj = null;
+            for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
+                T newObj = iterator.next();
+                // 1.1 不匹配,则直接跳过
+                if (!sameFunc.apply(oldObj, newObj)) {
+                    continue;
+                }
+                // 1.2 匹配,则移除,并结束寻找
+                iterator.remove();
+                foundObj = newObj;
+                break;
+            }
+            // 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
+            if (foundObj != null) {
+                updateList.add(foundObj);
+            } else {
+                deleteList.add(oldObj);
+            }
+        }
+        return asList(createList, updateList, deleteList);
+    }
+
     public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
         return org.springframework.util.CollectionUtils.containsAny(source, candidates);
     }

+ 2 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java

@@ -23,6 +23,8 @@ public class DateUtils {
      */
     public static final long SECOND_MILLIS = 1000;
 
+    public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
+
     public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
 
     public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";

+ 11 - 15
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.date;
 import cn.hutool.core.date.LocalDateTimeUtil;
 
 import java.time.Duration;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 
@@ -62,23 +63,18 @@ public class LocalDateTimeUtils {
     }
 
     /**
-     * 检查时间重叠 不包含日期
+     * 判断时间段是否重叠
      *
-     * @param startTime1 需要校验的开始时间
-     * @param endTime1   需要校验的结束时间
-     * @param startTime2 校验所需的开始时间
-     * @param endTime2   校验所需的结束时间
-     * @return 是否重叠
+     * @param startTime1 开始 time1
+     * @param endTime1   结束 time1
+     * @param startTime2 开始 time2
+     * @param endTime2   结束 time2
+     * @return 重叠:true 不重叠:false
      */
-    // TODO @puhui999:LocalDateTimeUtil.isOverlap() 是不是可以满足呀?
-    public static boolean checkTimeOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
-        // 判断时间是否重叠
-        // 开始时间在已配置时段的结束时间之前 且 结束时间在已配置时段的开始时间之后 []
-        return startTime1.isBefore(endTime2) && endTime1.isAfter(startTime2)
-                // 开始时间在已配置时段的开始时间之前 且 结束时间在已配置时段的开始时间之后 (] 或 ()
-                || startTime1.isBefore(startTime2) && endTime1.isAfter(startTime2)
-                // 开始时间在已配置时段的结束时间之前 且 结束时间在已配值时段的结束时间之后 [) 或 ()
-                || startTime1.isBefore(endTime2) && endTime1.isAfter(endTime2);
+    public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
+        LocalDate nowDate = LocalDate.now();
+        return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),
+                LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
     }
 
 }

+ 64 - 0
yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtilsTest.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.framework.common.util.collection;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CollectionUtils} 的单元测试
+ */
+public class CollectionUtilsTest {
+
+    @Data
+    @AllArgsConstructor
+    private static class Dog {
+
+        private Integer id;
+        private String name;
+        private String code;
+
+    }
+
+    @Test
+    public void testDiffList() {
+        // 准备参数
+        Collection<Dog> oldList = Arrays.asList(
+                new Dog(1, "花花", "hh"),
+                new Dog(2, "旺财", "wc")
+        );
+        Collection<Dog> newList = Arrays.asList(
+                new Dog(null, "花花2", "hh"),
+                new Dog(null, "小白", "xb")
+        );
+        BiFunction<Dog, Dog, Boolean> sameFunc = (oldObj, newObj) -> {
+            boolean same = oldObj.getCode().equals(newObj.getCode());
+            // 如果相等的情况下,需要设置下 id,后续好更新
+            if (same) {
+                newObj.setId(oldObj.getId());
+            }
+            return same;
+        };
+
+        // 调用
+        List<List<Dog>> result = CollectionUtils.diffList(oldList, newList, sameFunc);
+        // 断言
+        assertEquals(result.size(), 3);
+        // 断言 create
+        assertEquals(result.get(0).size(), 1);
+        assertEquals(result.get(0).get(0), new Dog(null, "小白", "xb"));
+        // 断言 update
+        assertEquals(result.get(1).size(), 1);
+        assertEquals(result.get(1).get(0), new Dog(1, "花花2", "hh"));
+        // 断言 delete
+        assertEquals(result.get(2).size(), 1);
+        assertEquals(result.get(2).get(0), new Dog(2, "旺财", "wc"));
+    }
+
+}

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

@@ -148,7 +148,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
         // 2.1 执行请求
         AlipayTradeRefundResponse response = client.execute(request);
         if (!response.isSuccess()) {
-            return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
+            return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response);
         }
         // 2.2 创建返回结果
         // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。

+ 258 - 63
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java

@@ -1,99 +1,294 @@
 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;
+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.client.dto.refund.PayRefundRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
+import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeRefundModel;
 import com.alipay.api.request.AlipayTradePrecreateRequest;
+import com.alipay.api.request.AlipayTradeRefundRequest;
 import com.alipay.api.response.AlipayTradePrecreateResponse;
-import org.junit.jupiter.api.Disabled;
+import com.alipay.api.response.AlipayTradeRefundResponse;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotSame;
+import javax.validation.ConstraintViolationException;
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY;
+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 AlipayQrPayClient} 单元测试
+ *
+ * @author jason
+ */
 public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
 
-    private static final String SERVER_URL_SANDBOX = "https://openapi.alipaydev.com/gateway.do";
-
-    private final AlipayPayClientConfig config = new AlipayPayClientConfig()
-        .setAppId("2021000118634035")
-        .setServerUrl(SERVER_URL_SANDBOX)
-        .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
-        // TODO @tina:key 可以随机就好,简洁一点哈。
-        .setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJ" +
-                "v890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T" +
-                "01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH" +
-                "6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScw" +
-                "lSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63tr" +
-                "epo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdk" +
-                "USmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr" +
-                "8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w" +
-                "0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENi" +
-                "vAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPw" +
-                "YcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQO" +
-                "LFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsm" +
-                "yX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i9" +
-                "5Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOU" +
-                "hVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD" +
-                "/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v1" +
-                "8p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ" +
-                "8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4e" +
-                "N0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6p" +
-                "bKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erx" +
-                "TRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=")
-        .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0" +
-                "gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBN" +
-                "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" +
-                "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB");
+    private final AlipayPayClientConfig config = randomPojo(AlipayPayClientConfig.class, t -> {
+        t.setServerUrl(randomURL());
+        t.setMode(MODE_PUBLIC_KEY);
+        t.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
+        t.setAppCertContent("");
+        t.setAlipayPublicCertContent("");
+        t.setRootCertContent("");
+    });
 
     @InjectMocks
-    AlipayQrPayClient client = new AlipayQrPayClient(10L,config);
+    AlipayQrPayClient client = new AlipayQrPayClient(randomLongId(), config);
 
     @Mock
     private DefaultAlipayClient defaultAlipayClient;
 
     @Test
-    public void testDoInit(){
+    public void test_do_init() {
         client.doInit();
         assertNotSame(defaultAlipayClient, ReflectUtil.getFieldValue(client, "defaultAlipayClient"));
     }
 
     @Test
-    @Disabled // TODO 芋艿:临时禁用
-    public void create() throws AlipayApiException {
-        // TODO @tina:参数可以尽量随机一点,使用随机方法。这样的好处是,避免对固定参数的依赖,导致可能仅仅满足固定参数的结果
-        // 这里,设置可以直接随机整个对象。
-        Long shopOrderId = System.currentTimeMillis();
-        PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
-        reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis()));
-        reqDTO.setPrice(1);
-        reqDTO.setBody("内容:" + shopOrderId);
-        reqDTO.setSubject("标题:"+shopOrderId);
-        String notify="http://niubi.natapp1.cc/api/pay/order/notify";
-        reqDTO.setNotifyUrl(notify);
-
-        AlipayTradePrecreateResponse response=randomPojo(AlipayTradePrecreateResponse.class,o->o.setQrCode("success"));
-
-        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request ->{
-            assertEquals(notify,request.getNotifyUrl());
+    @DisplayName("支付包扫描支付下单成功")
+    public void test_unified_order_success() throws AlipayApiException {
+        // 准备返回对象
+        String notifyUrl = randomURL();
+        String qrCode = randomString();
+        AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> {
+            o.setQrCode(qrCode);
+            o.setSubCode("");
+        });
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
+            assertEquals(notifyUrl, request.getNotifyUrl());
+            return true;
+        }))).thenReturn(response);
+        // 准备请求参数
+        String outTradeNo = randomString();
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+
+        PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+        // 断言
+        assertEquals(WAITING.getStatus(), resp.getStatus());
+        assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertEquals(qrCode, resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
+    }
+
+    @Test
+    @DisplayName("支付包扫描支付,渠道返回失败")
+    public void test_unified_order_channel_failed() throws AlipayApiException {
+        String notifyUrl = randomURL();
+        String subCode = randomString();
+        String subMsg = randomString();
+        AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> {
+            o.setSubCode(subCode);
+            o.setSubMsg(subMsg);
+        });
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
+            assertEquals(notifyUrl, request.getNotifyUrl());
+            return true;
+        }))).thenReturn(response);
+        // 准备请求参数
+        String outTradeNo = randomString();
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+
+        PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+        // 断言
+        assertEquals(CLOSED.getStatus(), resp.getStatus());
+        assertEquals(subCode, resp.getChannelErrorCode());
+        assertEquals(subMsg, resp.getChannelErrorMsg());
+        assertSame(response, resp.getRawData());
+    }
+
+    @Test
+    @DisplayName("支付包扫描支付,抛出系统异常")
+    public void test_unified_order_throw_pay_exception() throws AlipayApiException {
+        // 准备请求参数
+        String outTradeNo = randomString();
+        String notifyUrl = randomURL();
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
+            assertEquals(notifyUrl, request.getNotifyUrl());
+            return true;
+        }))).thenThrow(new RuntimeException("系统异常"));
+        // 准备请求参数
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+        // 断言
+        assertThrows(PayException.class, () -> client.unifiedOrder(reqDTO));
+    }
+
+    @Test
+    @DisplayName("支付包扫描支付,抛出业务异常")
+    public void test_unified_order_throw_service_exception() throws AlipayApiException {
+        // 准备请求参数
+        String outTradeNo = randomString();
+        String notifyUrl = randomURL();
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
+            assertEquals(notifyUrl, request.getNotifyUrl());
+            return true;
+        }))).thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
+        // 准备请求参数
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo);
+        // 断言
+        assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
+    }
+
+    @Test
+    @DisplayName("支付包扫描支付,参数校验不通过")
+    public void test_unified_order_param_validate() {
+        // 准备请求参数
+        String outTradeNo = randomString();
+        String notifyUrl = randomURL();
+        PayOrderUnifiedReqDTO reqDTO = randomPojo(PayOrderUnifiedReqDTO.class, o -> {
+            o.setOutTradeNo(outTradeNo);
+            o.setNotifyUrl(notifyUrl);
+        });
+        // 断言
+        assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO));
+    }
+
+    private PayOrderUnifiedReqDTO buildOrderUnifiedReqDTO(String notifyUrl, String outTradeNo) {
+        return randomPojo(PayOrderUnifiedReqDTO.class, o -> {
+            o.setOutTradeNo(outTradeNo);
+            o.setNotifyUrl(notifyUrl);
+            o.setSubject(RandomUtil.randomString(32));
+            o.setBody(RandomUtil.randomString(32));
+        });
+    }
+
+    @Test
+    @DisplayName("支付包扫描退款成功")
+    public void test_unified_refund_success() throws AlipayApiException {
+        // 准备返回对象
+        String notifyUrl = randomURL();
+        Date refundTime = randomDate();
+        String outRefundNo = randomString();
+        String outTradeNo = randomString();
+        Integer refundAmount = randomInteger();
+        AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> {
+            o.setSubCode("");
+            o.setGmtRefundPay(refundTime);
+        });
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> {
+            assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel());
+            AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel();
+            assertEquals(outRefundNo, bizModel.getOutRequestNo());
+            assertEquals(outTradeNo, bizModel.getOutTradeNo());
+            assertEquals(String.valueOf(refundAmount / 100.0), bizModel.getRefundAmount());
             return true;
         }))).thenReturn(response);
+        // 准备请求参数
+        PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+            o.setOutRefundNo(outRefundNo);
+            o.setOutTradeNo(outTradeNo);
+            o.setNotifyUrl(notifyUrl);
+            o.setRefundPrice(refundAmount);
+        });
+        PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO);
+        // 断言
+        assertEquals(PayRefundStatusRespEnum.SUCCESS.getStatus(), resp.getStatus());
+        assertNull(resp.getChannelRefundNo());
+        assertEquals(LocalDateTimeUtil.of(refundTime), resp.getSuccessTime());
+        assertEquals(outRefundNo, resp.getOutRefundNo());
+        assertSame(response, resp.getRawData());
+    }
 
+    @Test
+    @DisplayName("支付包扫描退款,渠道返回失败")
+    public void test_unified_refund_channel_failed() throws AlipayApiException {
+        // 准备返回对象
+        String notifyUrl = randomURL();
+        String subCode = randomString();
+        String subMsg = randomString();
+        AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> {
+            o.setSubCode(subCode);
+            o.setSubMsg(subMsg);
+        });
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> {
+            assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel());
+            return true;
+        }))).thenReturn(response);
+        // 准备请求参数
+        String outRefundNo = randomString();
+        String outTradeNo = randomString();
+        PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+            o.setOutRefundNo(outRefundNo);
+            o.setOutTradeNo(outTradeNo);
+            o.setNotifyUrl(notifyUrl);
+        });
+        PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO);
+        // 断言
+        assertEquals(PayRefundStatusRespEnum.FAILURE.getStatus(), resp.getStatus());
+        assertNull(resp.getChannelRefundNo());
+        assertEquals(subCode, resp.getChannelErrorCode());
+        assertEquals(subMsg, resp.getChannelErrorMsg());
+        assertNull(resp.getSuccessTime());
+        assertEquals(outRefundNo, resp.getOutRefundNo());
+        assertSame(response, resp.getRawData());
+    }
 
-//        PayCommonResult<PayOrderUnifiedRespDTO> result = client.doUnifiedOrder(reqDTO);
-//        // 断言
-//        assertEquals(response.getCode(), result.getApiCode());
-//        assertEquals(response.getMsg(), result.getApiMsg());
-//        // TODO @tina:这个断言木有过?
-//        assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
-//        assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+    @Test
+    @DisplayName("支付包扫描退款,参数校验不通过")
+    public void test_unified_refund_param_validate() {
+        // 准备请求参数
+        String notifyUrl = randomURL();
+        PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> {
+            o.setOutTradeNo("");
+            o.setNotifyUrl(notifyUrl);
+        });
+        // 断言
+        assertThrows(ConstraintViolationException.class, () -> client.unifiedRefund(refundReqDTO));
+    }
 
+    @Test
+    @DisplayName("支付包扫描退款,抛出业务异常")
+    public void test_unified_refund_throw_service_exception() throws AlipayApiException {
+        // mock
+        when(defaultAlipayClient.execute(argThat((ArgumentMatcher<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("支付包扫描退款,抛出系统异常")
+    public void test_unified_refund_throw_pay_exception() 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));
     }
 }

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -44,6 +44,12 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
     }
 
+    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
+                        SFunction<T, ?> field3, Object value3) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
+                .eq(field3, value3));
+    }
+
     default Long selectCount() {
         return selectCount(new QueryWrapper<T>());
     }

+ 1 - 2
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.framework.jackson.core.databind;
 
 import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
 
@@ -20,7 +19,7 @@ public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
     public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
 
     @Override
-    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
     }
 }

+ 0 - 21
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.framework.jackson.core.databind;
-
-import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
-import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
-
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;
-
-public class LocalTimeJson {
-
-    public static final LocalTimeSerializer SERIALIZER = new LocalTimeSerializer(DateTimeFormatter
-            .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
-            .withZone(ZoneId.systemDefault()));
-
-    public static final LocalTimeDeserializer DESERIALIZABLE = new LocalTimeDeserializer(DateTimeFormatter
-            .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
-            .withZone(ZoneId.systemDefault()));
-
-}

+ 38 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.infra.controller.app.file;
+
+import cn.hutool.core.io.IoUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO;
+import cn.iocoder.yudao.module.infra.service.file.FileService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 App - 文件存储")
+@RestController
+@RequestMapping("/infra/file")
+@Validated
+@Slf4j
+public class AppFileController {
+
+    @Resource
+    private FileService fileService;
+
+    @PostMapping("/upload")
+    @Operation(summary = "上传文件")
+    public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
+        MultipartFile file = uploadReqVO.getFile();
+        String path = uploadReqVO.getPath();
+        return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
+    }
+
+}

+ 20 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.infra.controller.app.file.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "用户 App - 上传文件 Request VO")
+@Data
+public class AppFileUploadReqVO {
+
+    @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "文件附件不能为空")
+    private MultipartFile file;
+
+    @Schema(description = "文件附件", example = "yudaoyuanma.png")
+    private String path;
+
+}

+ 5 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm

@@ -120,7 +120,6 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="序号" type="index" width="70px" />
       #foreach($column in $columns)
       #if ($column.listOperationResult)
         #set ($dictType=$column.dictType)
@@ -142,7 +141,7 @@
         </template>
       </el-table-column>
         #else
-      <el-table-column label="${comment}" align="center" prop="${javaField}" width="150px"/>
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="150px" />
         #end
       #end
     #end
@@ -180,7 +179,7 @@
   <${simpleClassName}Form ref="formRef" @success="getList" />
 </template>
 
-<script setup lang="ts" name="${table.className}">
+<script setup lang="ts">
 #if ($dictMethods.size() > 0)
 import { DICT_TYPE#foreach ($dictMethod in $dictMethods), ${dictMethod}#end } from '@/utils/dict'
 #end
@@ -193,6 +192,9 @@ import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${classNameVar}'
 import ${simpleClassName}Form from './${simpleClassName}Form.vue'
+
+defineOptions({ name: '${table.className}' })
+
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 

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

@@ -21,4 +21,11 @@ public interface ProductSpuApi {
      */
     List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
 
+    /**
+     * 获得 SPU
+     *
+     * @return SPU
+     */
+    ProductSpuRespDTO getSpu(Long id);
+
 }

+ 11 - 5
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java

@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -24,15 +26,19 @@ import java.util.List;
 public class ProductSpuApiImpl implements ProductSpuApi {
 
     @Resource
-    private ProductSpuMapper productSpuMapper;
+    private ProductSpuService spuService;
 
     @Override
-    public List<ProductSpuRespDTO> getSpuList(Collection<Long> spuIds) {
-        if (CollectionUtil.isEmpty(spuIds)) {
+    public List<ProductSpuRespDTO> getSpuList(Collection<Long> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
             return Collections.emptyList();
         }
-        List<ProductSpuDO> productSpuDOList = productSpuMapper.selectBatchIds(spuIds);
-        return ProductSpuConvert.INSTANCE.convertList2(productSpuDOList);
+        return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids));
+    }
+
+    @Override
+    public ProductSpuRespDTO getSpu(Long id) {
+        return ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id));
     }
 
 }

+ 11 - 13
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppProductCommentController.java

@@ -3,16 +3,13 @@ package cn.iocoder.yudao.module.product.controller.app.comment;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO;
 import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO;
 import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
-import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
-import com.google.common.collect.Maps;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Parameters;
@@ -27,10 +24,10 @@ import org.springframework.web.bind.annotation.RestController;
 import javax.annotation.Resource;
 import javax.validation.Valid;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 
 @Tag(name = "用户 APP - 商品评价")
 @RestController
@@ -60,16 +57,17 @@ public class AppProductCommentController {
     @GetMapping("/page")
     @Operation(summary = "获得商品评价分页")
     public CommonResult<PageResult<AppProductCommentRespVO>> getCommentPage(@Valid AppCommentPageReqVO pageVO) {
-        // TODO @puhui999:写到 convert 里,可以更简洁哈。
-        PageResult<ProductCommentDO> commentDOPage = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
-        Set<Long> skuIds = CollectionUtils.convertSet(commentDOPage.getList(), ProductCommentDO::getSkuId);
-        List<ProductSkuDO> skuList = productSkuService.getSkuList(skuIds);
-        Map<Long, ProductSkuDO> skuDOMap = Maps.newLinkedHashMapWithExpectedSize(skuIds.size());
-        if (CollUtil.isNotEmpty(skuList)) {
-            skuDOMap.putAll(CollectionUtils.convertMap(skuList, ProductSkuDO::getId, c -> c));
+        // 查询评论分页
+        PageResult<ProductCommentDO> commentPageResult = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
+        if (CollUtil.isEmpty(commentPageResult.getList())) {
+            return success(PageResult.empty(commentPageResult.getTotal()));
         }
-        PageResult<AppProductCommentRespVO> page = ProductCommentConvert.INSTANCE.convertPage02(commentDOPage, skuDOMap);
-        return success(page);
+
+        // 拼接返回
+        Set<Long> skuIds = convertSet(commentPageResult.getList(), ProductCommentDO::getSkuId);
+        PageResult<AppProductCommentRespVO> commentVOPageResult = ProductCommentConvert.INSTANCE.convertPage02(
+                commentPageResult, productSkuService.getSkuList(skuIds));
+        return success(commentVOPageResult);
     }
 
     // TODO 芋艿:需要搞下

+ 9 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/AppFavoriteController.java

@@ -20,7 +20,6 @@ import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import javax.validation.Valid;
 import java.util.List;
-import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -84,8 +83,15 @@ public class AppFavoriteController {
     @Operation(summary = "检查是否收藏过商品")
     @PreAuthenticated
     public CommonResult<Boolean> isFavoriteExists(AppFavoriteReqVO reqVO) {
-        ProductFavoriteDO favoriteDO = productFavoriteService.getFavorite(getLoginUserId(), reqVO.getSpuId());
-        return success(Objects.nonNull(favoriteDO));
+        ProductFavoriteDO favorite = productFavoriteService.getFavorite(getLoginUserId(), reqVO.getSpuId());
+        return success(favorite != null);
+    }
+
+    @GetMapping(value = "/get-count")
+    @Operation(summary = "获得商品收藏数量")
+    @PreAuthenticated
+    public CommonResult<Long> getFavoriteCount() {
+        return success(productFavoriteService.getFavoriteCount(getLoginUserId()));
     }
 
 }

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

@@ -2,6 +2,7 @@ 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;
@@ -37,11 +38,12 @@ public interface ProductCommentConvert {
 
     @Mapping(target = "scores", expression = "java(calculateOverallScore(goodCount, mediocreCount, negativeCount))")
     AppCommentStatisticsRespVO convert(Long goodCount, Long mediocreCount, Long negativeCount);
+
     @Named("calculateOverallScore")
     default double calculateOverallScore(long goodCount, long mediocreCount, long negativeCount) {
         return (goodCount * 5 + mediocreCount * 3 + negativeCount) / (double) (goodCount + mediocreCount + negativeCount);
     }
-    
+
     List<ProductCommentRespVO> convertList(List<ProductCommentDO> list);
 
     PageResult<ProductCommentRespVO> convertPage(PageResult<ProductCommentDO> page);
@@ -49,18 +51,21 @@ public interface ProductCommentConvert {
     PageResult<AppProductCommentRespVO> convertPage01(PageResult<ProductCommentDO> pageResult);
 
     default PageResult<AppProductCommentRespVO> convertPage02(PageResult<ProductCommentDO> pageResult,
-                                                              Map<Long, ProductSkuDO> skuMap) {
+                                                              List<ProductSkuDO> skuList) {
+        Map<Long, ProductSkuDO> skuMap = CollectionUtils.convertMap(skuList, ProductSkuDO::getId);
         PageResult<AppProductCommentRespVO> page = convertPage01(pageResult);
         page.getList().forEach(item -> {
             // 判断用户是否选择匿名
             if (ObjectUtil.equal(item.getAnonymous(), true)) {
                 item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS);
             }
+            // 设置 SKU 规格值
             MapUtils.findAndThen(skuMap, item.getSkuId(),
                     sku -> item.setSkuProperties(convertList01(sku.getProperties())));
         });
         return page;
     }
+
     List<AppProductPropertyValueDetailRespVO> convertList01(List<ProductSkuDO.Property> properties);
 
     /**

+ 6 - 8
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.product.convert.spu;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
@@ -63,6 +64,8 @@ public interface ProductSpuConvert {
 
     ProductSpuDetailRespVO convert03(ProductSpuDO spu);
 
+    ProductSpuRespDTO convert02(ProductSpuDO bean);
+
     // ========== 用户 App 相关 ==========
 
     PageResult<AppProductSpuPageRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page);
@@ -81,6 +84,7 @@ public interface ProductSpuConvert {
         }
         return voList;
     }
+
     @Named("convertListForGetSpuList0")
     List<AppProductSpuPageRespVO> convertListForGetSpuList0(List<ProductSpuDO> list);
 
@@ -110,15 +114,9 @@ public interface ProductSpuConvert {
     }
 
     default List<ProductSpuDetailRespVO> convertForSpuDetailRespListVO(List<ProductSpuDO> spus, List<ProductSkuDO> skus) {
-        List<ProductSpuDetailRespVO> vos = new ArrayList<>(spus.size());
         Map<Long, List<ProductSkuDO>> skuMultiMap = convertMultiMap(skus, ProductSkuDO::getSpuId);
-        // TODO @puhui999:可以直接使用 CollUtils.convertList
-        spus.forEach(spu -> {
-            ProductSpuDetailRespVO detailRespVO = convert03(spu);
-            detailRespVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skuMultiMap.get(spu.getId())));
-            vos.add(detailRespVO);
-        });
-        return vos;
+        return CollectionUtils.convertList(spus, spu -> convert03(spu)
+                .setSkus(ProductSkuConvert.INSTANCE.convertList(skuMultiMap.get(spu.getId()))));
     }
 
 }

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

@@ -26,18 +26,19 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
     }
 
     static void appendTabQuery(LambdaQueryWrapperX<ProductCommentDO> queryWrapper, Integer type) {
-        // TODO @puhui999:是不是不用 apply 拉?直接用 mybatis 的方法就好啦
+        LambdaQueryWrapperX<ProductCommentDO> queryWrapperX = new LambdaQueryWrapperX<>();
         // 构建好评查询语句:好评计算 总评 >= 4
         if (ObjectUtil.equal(type, AppCommentPageReqVO.GOOD_COMMENT)) {
-            queryWrapper.apply("scores >= 4");
+            queryWrapperX.ge(ProductCommentDO::getScores, 4);
         }
         // 构建中评查询语句:中评计算 总评 >= 3 且 总评 < 4
         if (ObjectUtil.equal(type, AppCommentPageReqVO.MEDIOCRE_COMMENT)) {
-            queryWrapper.apply("scores >=3 and scores < 4");
+            queryWrapperX.ge(ProductCommentDO::getScores, 3);
+            queryWrapperX.lt(ProductCommentDO::getScores, 4);
         }
         // 构建差评查询语句:差评计算 总评 < 3
         if (ObjectUtil.equal(type, AppCommentPageReqVO.NEGATIVE_COMMENT)) {
-            queryWrapper.apply("scores < 3");
+            queryWrapperX.lt(ProductCommentDO::getScores, 3);
         }
     }
 

+ 4 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java

@@ -21,4 +21,8 @@ public interface ProductFavoriteMapper extends BaseMapperX<ProductFavoriteDO> {
                 .orderByDesc(ProductFavoriteDO::getId));
     }
 
+    default Long selectCountByUserId(Long userId) {
+        return selectCount(ProductFavoriteDO::getUserId, userId);
+    }
+
 }

+ 12 - 4
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java

@@ -16,7 +16,7 @@ public interface ProductFavoriteService {
     /**
      * 创建商品收藏
      *
-     * @param userId 用户 id
+     * @param userId 用户编号
      * @param spuId SPU 编号
      */
     Long createFavorite(Long userId, Long spuId);
@@ -24,7 +24,7 @@ public interface ProductFavoriteService {
     /**
      * 取消商品收藏
      *
-     * @param userId 用户 id
+     * @param userId 用户编号
      * @param spuId SPU 编号
      */
     void deleteFavorite(Long userId, Long spuId);
@@ -32,7 +32,7 @@ public interface ProductFavoriteService {
     /**
      * 分页查询用户收藏列表
      *
-     * @param userId 用户 id
+     * @param userId 用户编号
      * @param reqVO 请求 vo
      */
     PageResult<ProductFavoriteDO> getFavoritePage(Long userId, @Valid AppFavoritePageReqVO reqVO);
@@ -40,9 +40,17 @@ public interface ProductFavoriteService {
     /**
      * 获取收藏过商品
      *
-     * @param userId 用户id
+     * @param userId 用户编号
      * @param spuId SPU 编号
      */
     ProductFavoriteDO getFavorite(Long userId, Long spuId);
 
+    /**
+     * 获取用户收藏数量
+     *
+     * @param userId 用户编号
+     * @return 数量
+     */
+    Long getFavoriteCount(Long userId);
+
 }

+ 7 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java

@@ -10,7 +10,6 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
-import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.FAVORITE_EXISTS;
@@ -31,7 +30,7 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService {
     @Override
     public Long createFavorite(Long userId, Long spuId) {
         ProductFavoriteDO favorite = productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId);
-        if (Objects.nonNull(favorite)) {
+        if (favorite != null) {
             throw exception(FAVORITE_EXISTS);
         }
 
@@ -43,7 +42,7 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService {
     @Override
     public void deleteFavorite(Long userId, Long spuId) {
         ProductFavoriteDO favorite = productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId);
-        if (Objects.isNull(favorite)) {
+        if (favorite == null) {
             throw exception(FAVORITE_NOT_EXISTS);
         }
 
@@ -60,4 +59,9 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService {
         return productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId);
     }
 
+    @Override
+    public Long getFavoriteCount(Long userId) {
+        return productFavoriteMapper.selectCountByUserId(userId);
+    }
+
 }

+ 32 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApi.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.promotion.api.bargain;
+
+import cn.iocoder.yudao.module.promotion.api.bargain.dto.BargainRecordCreateReqDTO;
+
+import javax.validation.Valid;
+
+// TODO @芋艿:后面也再撸撸这几个接口
+
+/**
+ * 砍价记录 API 接口
+ *
+ * @author HUIHUI
+ */
+public interface BargainRecordApi {
+
+    /**
+     * 创建砍价记录
+     *
+     * @param reqDTO 请求 DTO
+     */
+    void createBargainRecord(@Valid BargainRecordCreateReqDTO reqDTO);
+
+    /**
+     * 查询砍价是否成功
+     *
+     * @param userId  用户编号
+     * @param orderId 订单编号
+     * @return 砍价是否成功
+     */
+    boolean isBargainRecordSuccess(Long userId, Long orderId);
+
+}

+ 81 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/dto/BargainRecordCreateReqDTO.java

@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.promotion.api.bargain.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+// TODO @芋艿:这块要在看看
+
+/**
+ * 砍价记录的创建 Request DTO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class BargainRecordCreateReqDTO {
+
+    /**
+     * 砍价活动编号
+     */
+    @NotNull(message = "砍价活动编号不能为空")
+    private Long activityId;
+    /**
+     * spu 编号
+     */
+    @NotNull(message = "spu 编号不能为空")
+    private Long spuId;
+    /**
+     * sku 编号
+     */
+    @NotNull(message = "sku 编号不能为空")
+    private Long skuId;
+    /**
+     * 用户编号
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+    /**
+     * 订单编号
+     */
+    @NotNull(message = "订单编号不能为空")
+    private Long orderId;
+    // TODO @puhui999:spuName、picUrl、 之类字段不用传递;
+    /**
+     * 商品名字
+     */
+    @NotEmpty(message = "商品名字不能为空")
+    private String spuName;
+    /**
+     * 商品图片
+     */
+    @NotEmpty(message = "商品图片不能为空")
+    private String picUrl;
+    /**
+     * 砍价商品单价
+     */
+    @NotNull(message = "砍价底价不能为空")
+    private Integer bargainPrice;
+    /**
+     * 商品原价,单位分
+     */
+    @NotNull(message = "商品原价不能为空")
+    private Integer price;
+    // TODO @puhui999:nickname、avatar 不用传递,去查询;
+    /**
+     * 用户昵称
+     */
+    @NotEmpty(message = "用户昵称不能为空")
+    private String nickname;
+    /**
+     * 用户头像
+     */
+    @NotEmpty(message = "用户头像不能为空")
+    private String avatar;
+    /**
+     * 开团状态:进行中 砍价成功 砍价失败
+     */
+    @NotNull(message = "开团状态不能为空")
+    private Integer status;
+
+}

+ 0 - 49
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationApi.java

@@ -1,49 +0,0 @@
-package cn.iocoder.yudao.module.promotion.api.combination;
-
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
-
-import javax.validation.Valid;
-
-// TODO @puhui999:CombinationRecordApi 分成活动、记录哈
-// TODO @芋艿:后面也再撸撸这几个接口
-/**
- * 拼团活动 API 接口
- *
- * @author HUIHUI
- */
-public interface CombinationApi {
-
-    /**
-     * 创建开团记录
-     *
-     * @param reqDTO 请求 DTO
-     */
-    void createRecord(@Valid CombinationRecordReqDTO reqDTO);
-
-    /**
-     * 获取开团记录状态
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     */
-    boolean validateRecordStatusIsSuccess(Long userId, Long orderId);
-
-    /**
-     * 更新开团记录状态
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     * @param status  状态值
-     */
-    void updateRecordStatus(Long userId, Long orderId, Integer status);
-
-    /**
-     * 更新开团记录状态和开始时间
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     * @param status  状态值
-     */
-    void updateRecordStatusAndStartTime(Long userId, Long orderId, Integer status);
-
-}

+ 63 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.promotion.api.combination;
+
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
+
+import javax.validation.Valid;
+import java.util.List;
+
+// TODO @芋艿:后面也再撸撸这几个接口
+
+/**
+ * 拼团记录 API 接口
+ *
+ * @author HUIHUI
+ */
+public interface CombinationRecordApi {
+
+    /**
+     * 创建开团记录
+     *
+     * @param reqDTO 请求 DTO
+     */
+    void createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
+
+    /**
+     * 查询拼团记录是否成功
+     *
+     * @param userId  用户编号
+     * @param orderId 订单编号
+     * @return 拼团是否成功
+     */
+    boolean isCombinationRecordSuccess(Long userId, Long orderId);
+
+    /**
+     * 获取拼团记录
+     *
+     * @param userId     用户编号
+     * @param activityId 活动编号
+     * @return 拼团记录列表
+     */
+    List<CombinationRecordRespDTO> getRecordListByUserIdAndActivityId(Long userId, Long activityId);
+
+    /**
+     * 验证组合限制数
+     * 校验是否满足限购要求
+     *
+     * @param count      本次购买数量
+     * @param sumCount   已购买数量合计
+     * @param activityId 活动编号
+     */
+    void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount);
+
+    // TODO @puhui999:是不是搞成具体的方法,拼团成功,拼团失败,这种方法;
+
+    /**
+     * 更新开团记录状态
+     *
+     * @param reqDTO 请求 DTO
+     */
+    void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO);
+
+}

+ 4 - 4
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordReqDTO.java → yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordCreateReqDTO.java

@@ -5,14 +5,14 @@ import lombok.Data;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
-// TODO @puhui999:CombinationRecordCreateReqDTO,这样更容易知道是创建噢
+// TODO @芋艿:这块要在看看
 /**
- * 拼团记录 Request DTO
+ * 拼团记录的创建 Request DTO
  *
  * @author HUIHUI
  */
 @Data
-public class CombinationRecordReqDTO {
+public class CombinationRecordCreateReqDTO {
 
     /**
      * 拼团活动编号
@@ -70,7 +70,7 @@ public class CombinationRecordReqDTO {
     @NotEmpty(message = "用户头像不能为空")
     private String avatar;
     /**
-     * 开团状态:正在开团 拼团成功 拼团失败 TODO 等待支付
+     * 开团状态:正在开团 拼团成功 拼团失败
      */
     @NotNull(message = "开团状态不能为空")
     private Integer status;

+ 41 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordRespDTO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.promotion.api.combination.dto;
+
+import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
+import lombok.Data;
+
+/**
+ * 拼团记录 Response DTO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class CombinationRecordRespDTO {
+
+    /**
+     * 拼团活动编号
+     */
+    private Long activityId;
+    /**
+     * SPU 编号
+     */
+    private Long spuId;
+    /**
+     * SKU 编号
+     */
+    private Long skuId;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 订单编号
+     */
+    private Long orderId;
+    /**
+     * 开团状态
+     *
+     * 枚举 {@link CombinationRecordStatusEnum}
+     */
+    private Integer status;
+
+}

+ 39 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordUpdateStatusReqDTO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.promotion.api.combination.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+/**
+ * 拼团记录的更新状态 Request DTO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class CombinationRecordUpdateStatusReqDTO {
+
+    /**
+     * 用户编号
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    /**
+     * 订单编号
+     */
+    @NotNull(message = "订单编号不能为空")
+    private Long orderId;
+
+    /**
+     * 开团状态:正在开团 拼团成功 拼团失败
+     */
+    @NotNull(message = "开团状态不能为空")
+    private Integer status;
+
+    /**
+     * 团开始时间
+     */
+    private LocalDateTime startTime;
+
+}

+ 24 - 6
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -50,14 +50,11 @@ public interface ErrorCodeConstants {
     ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1013008003, "秒杀活动已关闭,不能修改");
     ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013008004, "秒杀活动未关闭或未结束,不能删除");
     ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013008005, "秒杀活动已关闭,不能重复关闭");
-    ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013008006, "秒杀活动已结束,不能关闭");
 
     // ========== 秒杀时段 1013009000 ==========
-    ErrorCode SECKILL_TIME_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
-    ErrorCode SECKILL_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
-    ErrorCode SECKILL_TIME_EQUAL = new ErrorCode(1013009002, "秒杀时段开始时间和结束时间不能相等");
-    ErrorCode SECKILL_START_TIME_BEFORE_END_TIME = new ErrorCode(1013009003, "秒杀时段开始时间不能在结束时间之后");
-    ErrorCode SECKILL_TIME_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
+    ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1013009000, "秒杀时段不存在");
+    ErrorCode SECKILL_CONFIG_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
+    ErrorCode SECKILL_CONFIG_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
 
     // ========== 拼团活动 1013010000 ==========
     ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");
@@ -66,4 +63,25 @@ public interface ErrorCodeConstants {
     ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013010003, "拼团活动未关闭或未结束,不能删除");
     ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013010004, "拼团不存在");
 
+    // ========== 拼团记录 1013011000 ==========
+    ErrorCode COMBINATION_RECORD_EXISTS = new ErrorCode(1013011000, "拼团失败,已参与过该拼团");
+    ErrorCode COMBINATION_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013011001, "拼团失败,父拼团不存在");
+    ErrorCode COMBINATION_RECORD_USER_FULL = new ErrorCode(1013011002, "拼团失败,拼团人数已满");
+    ErrorCode COMBINATION_RECORD_FAILED_HAVE_JOINED = new ErrorCode(1013011003, "拼团失败,已参与其它拼团");
+    ErrorCode COMBINATION_RECORD_FAILED_TIME_END = new ErrorCode(1013011004, "拼团失败,活动已经结束");
+    ErrorCode COMBINATION_RECORD_FAILED_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1013011005, "拼团失败,单次限购超出");
+    ErrorCode COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED = new ErrorCode(1013011006, "拼团失败,单次限购超出");
+
+    // ========== 砍价活动 1013012000 ==========
+    ErrorCode BARGAIN_ACTIVITY_NOT_EXISTS = new ErrorCode(1013012000, "砍价活动不存在");
+    ErrorCode BARGAIN_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013012001, "存在商品参加了其它砍价活动");
+    ErrorCode BARGAIN_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013012002, "砍价活动已关闭不能修改");
+    ErrorCode BARGAIN_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013012003, "砍价活动未关闭或未结束,不能删除");
+
+    // ========== 砍价记录 1013013000 ==========
+    ErrorCode BARGAIN_RECORD_NOT_EXISTS = new ErrorCode(1013013000, "砍价记录不存在");
+    ErrorCode BARGAIN_RECORD_EXISTS = new ErrorCode(1013013001, "砍价失败,已参与过该砍价");
+    ErrorCode BARGAIN_RECORD_HEAD_NOT_EXISTS = new ErrorCode(1013013002, "砍价失败,父砍价不存在");
+    ErrorCode BARGAIN_RECORD_USER_FULL = new ErrorCode(1013013003, "砍价失败,砍价人数已满");
+
 }

+ 8 - 4
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/combination/CombinationRecordStatusEnum.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.promotion.enums.combination;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -15,10 +16,9 @@ import java.util.Arrays;
 @Getter
 public enum CombinationRecordStatusEnum implements IntArrayValuable {
 
-    WAITING(0, "未付款"),
-    IN_PROGRESS(1, "进行中"),
-    SUCCESS(2, "拼团成功"),
-    FAILED(3, "拼团失败");
+    IN_PROGRESS(0, "进行中"),
+    SUCCESS(1, "拼团成功"),
+    FAILED(2, "拼团失败");
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CombinationRecordStatusEnum::getStatus).toArray();
 
@@ -36,4 +36,8 @@ public enum CombinationRecordStatusEnum implements IntArrayValuable {
         return ARRAYS;
     }
 
+    public static boolean isSuccess(Integer status) {
+        return ObjectUtil.equal(status, SUCCESS.getStatus());
+    }
+
 }

+ 37 - 8
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/decorate/DecorateComponentEnum.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.promotion.enums.decorate;
 
+import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 /**
@@ -8,11 +9,44 @@ import lombok.Getter;
  * @author jason
  */
 @Getter
+@AllArgsConstructor
+@SuppressWarnings("JavadocLinkAsPlainText")
 public enum DecorateComponentEnum {
 
-    NAV_MENU("nav-menu", "导航菜单"),
-    ROLLING_BANNER("rolling-banner", "滚动横幅广告"),
-    PRODUCT_CATEGORY("product-category", "商品分类");
+    /**
+     * 格式:[{
+     *  "name": "标题"
+     *  "picUrl": "https://www.iocoder.cn/xxx.png",
+     *  "url": "/pages/users/index"
+     * }]
+     *
+     * 最多 10 个
+     */
+    MENU("menu", "菜单"),
+    /**
+     * 格式:[{
+     *  "name": "标题"
+     *  "url": "/pages/users/index"
+     * }]
+     */
+    ROLLING_NEWS("scrolling-news", "滚动新闻"),
+    /**
+     * 格式:[{
+     *  "picUrl": "https://www.iocoder.cn/xxx.png",
+     *  "url": "/pages/users/index"
+     * }]
+     */
+    SLIDE_SHOW("slide-show", "轮播图"),
+    /**
+     * 格式:[{
+     *  "name": "标题"
+     *  "type": "类型", // best、hot、new、benefit、good
+     *  "tag": "标签" // 例如说:多买多省
+     * }]
+     *
+     * 最多 4 个
+     */
+    PRODUCT_RECOMMEND("product-recommend", "商品推荐");
 
     /**
      * 页面组件代码
@@ -24,9 +58,4 @@ public enum DecorateComponentEnum {
      */
     private final String desc;
 
-    DecorateComponentEnum(String code, String desc) {
-        this.code = code;
-        this.desc = desc;
-    }
-
 }

+ 7 - 4
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/decorate/DecoratePageEnum.java

@@ -15,14 +15,17 @@ import java.util.Arrays;
 @Getter
 public enum DecoratePageEnum implements IntArrayValuable {
 
-    INDEX(1, "首页");
+    INDEX(1, "首页"),
+    MY(2, "个人中心"),
+    ;
 
-    private static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DecoratePageEnum::getId).toArray();
+    private static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DecoratePageEnum::getPage).toArray();
 
     /**
-     * 页面 id
+     * 页面编号
      */
-    private final Integer id;
+    private final Integer page;
+
     /**
      * 页面名称
      */

+ 24 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApiImpl.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.promotion.api.bargain;
+
+import cn.iocoder.yudao.module.promotion.api.bargain.dto.BargainRecordCreateReqDTO;
+import org.springframework.stereotype.Service;
+
+/**
+ * 砍价活动 API 实现类 TODO @puhui999
+ *
+ * @author HUIHUI
+ */
+@Service
+public class BargainRecordApiImpl implements BargainRecordApi {
+
+    @Override
+    public void createBargainRecord(BargainRecordCreateReqDTO reqDTO) {
+
+    }
+
+    @Override
+    public boolean isBargainRecordSuccess(Long userId, Long orderId) {
+        return false;
+    }
+
+}

+ 0 - 42
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationApiImpl.java

@@ -1,42 +0,0 @@
-package cn.iocoder.yudao.module.promotion.api.combination;
-
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
-import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.time.LocalDateTime;
-
-/**
- * 拼团活动 API 实现类
- *
- * @author HUIHUI
- */
-@Service
-public class CombinationApiImpl implements CombinationApi {
-
-    @Resource
-    private CombinationActivityService activityService;
-
-    @Override
-    public void createRecord(CombinationRecordReqDTO reqDTO) {
-        activityService.createRecord(reqDTO);
-    }
-
-    @Override
-    public boolean validateRecordStatusIsSuccess(Long userId, Long orderId) {
-        return activityService.validateRecordStatusIsSuccess(userId, orderId);
-    }
-
-    @Override
-    public void updateRecordStatus(Long userId, Long orderId, Integer status) {
-        activityService.updateRecordStatusByUserIdAndOrderId(userId, orderId, status);
-    }
-
-    @Override
-    public void updateRecordStatusAndStartTime(Long userId, Long orderId, Integer status) {
-        activityService.updateRecordStatusAndStartTimeByUserIdAndOrderId(userId, orderId, status, LocalDateTime.now());
-    }
-
-
-}

+ 54 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.promotion.api.combination;
+
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordUpdateStatusReqDTO;
+import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
+import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
+import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 拼团活动 API 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+public class CombinationRecordApiImpl implements CombinationRecordApi {
+
+    @Resource
+    private CombinationRecordService recordService;
+
+    @Override
+    public void createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
+        recordService.createCombinationRecord(reqDTO);
+    }
+
+    @Override
+    public boolean isCombinationRecordSuccess(Long userId, Long orderId) {
+        return CombinationRecordStatusEnum.isSuccess(recordService.getCombinationRecord(userId, orderId).getStatus());
+    }
+
+    @Override
+    public List<CombinationRecordRespDTO> getRecordListByUserIdAndActivityId(Long userId, Long activityId) {
+        return CombinationActivityConvert.INSTANCE.convert(recordService.getRecordListByUserIdAndActivityId(userId, activityId));
+    }
+
+    @Override
+    public void validateCombinationLimitCount(Long activityId, Integer count, Integer sumCount) {
+        recordService.validateCombinationLimitCount(activityId, count, sumCount);
+    }
+
+    @Override
+    public void updateCombinationRecordStatus(CombinationRecordUpdateStatusReqDTO reqDTO) {
+        if (null == reqDTO.getStartTime()) {
+            recordService.updateCombinationRecordStatusByUserIdAndOrderId(reqDTO);
+        } else {
+            recordService.updateCombinationRecordStatusAndStartTimeByUserIdAndOrderId(reqDTO);
+        }
+    }
+
+}

+ 79 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/BargainActivityController.java

@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.bargain;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.convert.bargain.BargainActivityConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
+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.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 砍价活动")
+@RestController
+@RequestMapping("/promotion/bargain-activity")
+@Validated
+public class BargainActivityController {
+
+    @Resource
+    private BargainActivityService activityService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建砍价活动")
+    @PreAuthorize("@ss.hasPermission('promotion:bargain-activity:create')")
+    public CommonResult<Long> createBargainActivity(@Valid @RequestBody BargainActivityCreateReqVO createReqVO) {
+        return success(activityService.createBargainActivity(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新砍价活动")
+    @PreAuthorize("@ss.hasPermission('promotion:bargain-activity:update')")
+    public CommonResult<Boolean> updateBargainActivity(@Valid @RequestBody BargainActivityUpdateReqVO updateReqVO) {
+        activityService.updateBargainActivity(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除砍价活动")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('promotion:bargain-activity:delete')")
+    public CommonResult<Boolean> deleteBargainActivity(@RequestParam("id") Long id) {
+        activityService.deleteBargainActivity(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得砍价活动")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('promotion:bargain-activity:query')")
+    public CommonResult<BargainActivityRespVO> getBargainActivity(@RequestParam("id") Long id) {
+        return success(BargainActivityConvert.INSTANCE.convert(activityService.getBargainActivity(id)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得砍价活动分页")
+    @PreAuthorize("@ss.hasPermission('promotion:bargain-activity:query')")
+    public CommonResult<PageResult<BargainActivityRespVO>> getBargainActivityPage(
+            @Valid BargainActivityPageReqVO pageVO) {
+        // 查询砍价活动
+        PageResult<BargainActivityDO> pageResult = activityService.getBargainActivityPage(pageVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        return success(BargainActivityConvert.INSTANCE.convertPage(activityService.getBargainActivityPage(pageVO)));
+    }
+
+}

+ 75 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityBaseVO.java

@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 砍价活动 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ *
+ * @author HUIHUI
+ */
+@Data
+public class BargainActivityBaseVO {
+
+    @Schema(description = "砍价活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "越拼越省钱")
+    @NotNull(message = "砍价名称不能为空")
+    private String name;
+
+    @Schema(description = "商品 SPU 编号", example = "1")
+    @NotNull(message = "砍价商品不能为空")
+    private Long spuId;
+
+    @Schema(description = "商品 skuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
+    @NotNull(message = "商品 skuId 不能为空")
+    private Long skuId;
+
+    @Schema(description = "砍价起始价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
+    @NotNull(message = "砍价起始价格不能为空")
+    private Integer bargainFirstPrice;
+
+    @Schema(description = "砍价底价", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
+    @NotNull(message = "砍价底价不能为空")
+    private Integer bargainPrice;
+
+    @Schema(description = "活动库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "23")
+    @NotNull(message = "活动库存不能为空")
+    private Integer stock;
+
+    @Schema(description = "总限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "16218")
+    @NotNull(message = "总限购数量不能为空")
+    private Integer totalLimitCount;
+
+    @Schema(description = "活动开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 23:59:59]")
+    @NotNull(message = "活动开始时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime startTime;
+
+    @Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 23:59:59]")
+    @NotNull(message = "活动结束时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime endTime;
+
+    @Schema(description = "砍价人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
+    @NotNull(message = "砍价人数不能为空")
+    private Integer userSize;
+
+    @Schema(description = "最大帮砍次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
+    @NotNull(message = "最大帮砍次数不能为空")
+    private Integer bargainCount;
+
+    @Schema(description = "用户每次砍价的最小金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
+    @NotNull(message = "用户每次砍价的最小金额不能为空")
+    private Integer randomMinPrice;
+
+    @Schema(description = "用户每次砍价的最大金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
+    @NotNull(message = "用户每次砍价的最大金额不能为空")
+    private Integer randomMaxPrice;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 砍价活动创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BargainActivityCreateReqVO extends BargainActivityBaseVO {
+
+}

+ 21 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityPageReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo;
+
+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")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BargainActivityPageReqVO extends PageParam {
+
+    @Schema(description = "砍价名称", example = "赵六")
+    private String name;
+
+    @Schema(description = "活动状态", example = "0")
+    private Integer status;
+
+}

+ 35 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityRespVO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+@Schema(description = "管理后台 - 砍价活动 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BargainActivityRespVO extends BargainActivityBaseVO {
+
+    @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
+    private String spuName;
+
+    @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
+    private String picUrl;
+
+    @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-01 23:59:59")
+    private LocalDateTime createTime;
+
+    @Schema(description = "砍价成功数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "999")
+    private Integer successCount;
+
+    @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "活动状态不能为空")
+    private Integer status;
+
+}

+ 20 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/bargain/vo/BargainActivityUpdateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 砍价活动更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BargainActivityUpdateReqVO extends BargainActivityBaseVO {
+
+    @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
+    @NotNull(message = "活动编号不能为空")
+    private Long id;
+
+}

+ 21 - 39
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationActivityController.java

@@ -1,16 +1,17 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.combination;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.*;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -20,16 +21,12 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
-import java.io.IOException;
-import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 import static cn.hutool.core.collection.CollectionUtil.newArrayList;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 
 @Tag(name = "管理后台 - 拼团活动")
 @RestController
@@ -39,8 +36,9 @@ public class CombinationActivityController {
 
     @Resource
     private CombinationActivityService combinationActivityService;
+
     @Resource
-    private ProductSpuApi spuApi;
+    private ProductSpuApi productSpuApi;
 
     @PostMapping("/create")
     @Operation(summary = "创建拼团活动")
@@ -72,43 +70,27 @@ public class CombinationActivityController {
     @PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
     public CommonResult<CombinationActivityRespVO> getCombinationActivity(@RequestParam("id") Long id) {
         CombinationActivityDO activity = combinationActivityService.getCombinationActivity(id);
-        List<CombinationProductDO> products = combinationActivityService.getProductsByActivityIds(newArrayList(id));
+        List<CombinationProductDO> products = combinationActivityService.getCombinationProductsByActivityIds(newArrayList(id));
         return success(CombinationActivityConvert.INSTANCE.convert(activity, products));
     }
 
-    @GetMapping("/list")
-    @Operation(summary = "获得拼团活动列表")
-    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
-    @PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
-    public CommonResult<List<CombinationActivityRespVO>> getCombinationActivityList(@RequestParam("ids") Collection<Long> ids) {
-        List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(ids);
-        return success(CombinationActivityConvert.INSTANCE.convertList(list));
-    }
-
     @GetMapping("/page")
     @Operation(summary = "获得拼团活动分页")
     @PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
     public CommonResult<PageResult<CombinationActivityRespVO>> getCombinationActivityPage(
             @Valid CombinationActivityPageReqVO pageVO) {
+        // 查询拼团活动
         PageResult<CombinationActivityDO> pageResult = combinationActivityService.getCombinationActivityPage(pageVO);
-        // TODO @puhui999:可以不一定 aIds,直接批量查询结果出来;下面也是类似;
-        Set<Long> aIds = CollectionUtils.convertSet(pageResult.getList(), CombinationActivityDO::getId);
-        List<CombinationProductDO> products = combinationActivityService.getProductsByActivityIds(aIds);
-        Set<Long> spuIds = CollectionUtils.convertSet(pageResult.getList(), CombinationActivityDO::getSpuId);
-        List<ProductSpuRespDTO> spus = spuApi.getSpuList(spuIds);
-        return success(CombinationActivityConvert.INSTANCE.convertPage(pageResult, products, spus));
-    }
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
 
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出拼团活动 Excel")
-    @PreAuthorize("@ss.hasPermission('promotion:combination-activity:export')")
-    @OperateLog(type = EXPORT)
-    public void exportCombinationActivityExcel(@Valid CombinationActivityExportReqVO exportReqVO,
-                                               HttpServletResponse response) throws IOException {
-        List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(exportReqVO);
-        // 导出 Excel
-        List<CombinationActivityExcelVO> datas = CombinationActivityConvert.INSTANCE.convertList02(list);
-        ExcelUtils.write(response, "拼团活动.xls", "数据", CombinationActivityExcelVO.class, datas);
+        // 拼接数据
+        List<CombinationProductDO> products = combinationActivityService.getCombinationProductsByActivityIds(
+                convertSet(pageResult.getList(), CombinationActivityDO::getId));
+        List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(
+                convertSet(pageResult.getList(), CombinationActivityDO::getSpuId));
+        return success(CombinationActivityConvert.INSTANCE.convertPage(pageResult, products, spus));
     }
 
 }

+ 11 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityBaseVO.java

@@ -12,6 +12,8 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 /**
  * 拼团活动 Base VO,提供给添加、修改、详细的子 VO 使用
  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ *
+ * @author HUIHUI
  */
 @Data
 public class CombinationActivityBaseVO {
@@ -20,7 +22,7 @@ public class CombinationActivityBaseVO {
     @NotNull(message = "拼团名称不能为空")
     private String name;
 
-    @Schema(description = "商品 SPU 编号,关联 ProductSpuDO 的 id", example = "[1,2,3]")
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "拼团商品不能为空")
     private Long spuId;
 
@@ -32,17 +34,21 @@ public class CombinationActivityBaseVO {
     @NotNull(message = "单次限购数量不能为空")
     private Integer singleLimitCount;
 
-    // TODO @puhui999:是不是弄成 2 个字段会好点哈。开始、结束
-    @Schema(description = "活动时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")
+    @Schema(description = "活动时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 23:59:59]")
     @NotNull(message = "活动时间不能为空")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] activityTime;
+    private LocalDateTime startTime;
+
+    @Schema(description = "活动时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 23:59:59]")
+    @NotNull(message = "活动时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime endTime;
 
     @Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
     @NotNull(message = "开团人数不能为空")
     private Integer userSize;
 
-    @Schema(description = "限制时长(小时)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "限制时长(小时)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
     @NotNull(message = "限制时长不能为空")
     private Integer limitDuration;
 

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityCreateReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
 
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductBaseVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -17,6 +17,6 @@ public class CombinationActivityCreateReqVO extends CombinationActivityBaseVO {
 
     @Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
     @Valid
-    private List<CombinationProductCreateReqVO> products;
+    private List<CombinationProductBaseVO> products;
 
 }

+ 0 - 65
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityExcelVO.java

@@ -1,65 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
-
-import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
-import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
-import com.alibaba.excel.annotation.ExcelProperty;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-// TODO @puhui999:如无必要,导出都可以删除哈
-/**
- * 拼团活动 Excel VO
- *
- * @author HUIHUI
- */
-@Data
-public class CombinationActivityExcelVO {
-
-    @ExcelProperty("活动编号")
-    private Long id;
-
-    @ExcelProperty("拼团名称")
-    private String name;
-
-    @ExcelProperty("商品 SPU 编号关联 ProductSpuDO 的 id")
-    private Long spuId;
-
-    @ExcelProperty("总限购数量")
-    private Integer totalLimitCount;
-
-    @ExcelProperty("单次限购数量")
-    private Integer singleLimitCount;
-
-    @ExcelProperty("开始时间")
-    private LocalDateTime startTime;
-
-    @ExcelProperty("结束时间")
-    private LocalDateTime endTime;
-
-    @ExcelProperty("开团人数")
-    private Integer userSize;
-
-    @ExcelProperty("开团组数")
-    private Integer totalNum;
-
-    @ExcelProperty("成团组数")
-    private Integer successNum;
-
-    @ExcelProperty("参与人数")
-    private Integer orderUserCount;
-
-    @ExcelProperty("虚拟成团")
-    private Integer virtualGroup;
-
-    @ExcelProperty(value = "活动状态:0开启 1关闭", converter = DictConvert.class)
-    @DictFormat("common_status") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中
-    private Integer status;
-
-    @ExcelProperty("限制时长(小时)")
-    private Integer limitDuration;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-}

+ 0 - 61
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityExportReqVO.java

@@ -1,61 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-// TODO @puhui999:如无必要,导出都可以删除哈
-@Schema(description = "管理后台 - 拼团活动 Excel 导出 Request VO,参数和 CombinationActivityPageReqVO 是一致的")
-@Data
-public class CombinationActivityExportReqVO {
-
-    @Schema(description = "拼团名称", example = "赵六")
-    private String name;
-
-    @Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "14016")
-    private Long spuId;
-
-    @Schema(description = "总限购数量", example = "16218")
-    private Integer totalLimitCount;
-
-    @Schema(description = "单次限购数量", example = "28265")
-    private Integer singleLimitCount;
-
-    @Schema(description = "开始时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] startTime;
-
-    @Schema(description = "结束时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] endTime;
-
-    @Schema(description = "开团人数")
-    private Integer userSize;
-
-    @Schema(description = "开团组数")
-    private Integer totalNum;
-
-    @Schema(description = "成团组数")
-    private Integer successNum;
-
-    @Schema(description = "参与人数", example = "25222")
-    private Integer orderUserCount;
-
-    @Schema(description = "虚拟成团")
-    private Integer virtualGroup;
-
-    @Schema(description = "活动状态:0开启 1关闭", example = "0")
-    private Integer status;
-
-    @Schema(description = "限制时长(小时)")
-    private Integer limitDuration;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
-}

+ 0 - 43
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityPageReqVO.java

@@ -5,11 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
 @Schema(description = "管理后台 - 拼团活动分页 Request VO")
 @Data
@@ -20,46 +15,8 @@ public class CombinationActivityPageReqVO extends PageParam {
     @Schema(description = "拼团名称", example = "赵六")
     private String name;
 
-    @Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "14016")
-    private Long spuId;
-
-    @Schema(description = "总限购数量", example = "16218")
-    private Integer totalLimitCount;
-
-    @Schema(description = "单次限购数量", example = "28265")
-    private Integer singleLimitCount;
-
-    @Schema(description = "开始时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] startTime;
-
-    @Schema(description = "结束时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] endTime;
-
-    @Schema(description = "开团人数")
-    private Integer userSize;
-
-    @Schema(description = "开团组数")
-    private Integer totalNum;
-
-    @Schema(description = "成团组数")
-    private Integer successNum;
-
-    @Schema(description = "参与人数", example = "25222")
-    private Integer orderUserCount;
-
-    @Schema(description = "虚拟成团")
-    private Integer virtualGroup;
-
     @Schema(description = "活动状态:0开启 1关闭", example = "0")
     private Integer status;
 
-    @Schema(description = "限制时长(小时)")
-    private Integer limitDuration;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
 
 }

+ 10 - 16
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityRespVO.java

@@ -17,40 +17,34 @@ import java.util.List;
 @ToString(callSuper = true)
 public class CombinationActivityRespVO extends CombinationActivityBaseVO {
 
-    @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
+    @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
+    private Long id;
+
+    @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618 大促")
     private String spuName;
 
     @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
     private String picUrl;
 
-    @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
-    private Long id;
-
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
-    @Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "开团人数不能为空")
+    @Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
     private Integer userSize;
 
-    @Schema(description = "开团组数", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "开团组数不能为空")
-    private Integer totalNum;
+    @Schema(description = "开团组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "33")
+    private Integer totalCount;
 
-    @Schema(description = "成团组数", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "成团组数不能为空")
-    private Integer successNum;
+    @Schema(description = "成团组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Integer successCount;
 
-    @Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "虚拟成团不能为空")
+    @Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer virtualGroup;
 
     @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
-    @NotNull(message = "活动状态不能为空")
     private Integer status;
 
     @Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
-    @Valid
     private List<CombinationProductRespVO> products;
 
 }

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityUpdateReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
 
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductBaseVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -22,6 +22,6 @@ public class CombinationActivityUpdateReqVO extends CombinationActivityBaseVO {
 
     @Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
     @Valid
-    private List<CombinationProductUpdateReqVO> products;
+    private List<CombinationProductBaseVO> products;
 
 }

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductBaseVO.java

@@ -21,7 +21,7 @@ public class CombinationProductBaseVO {
     private Long skuId;
 
     @Schema(description = "拼团价格,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "27682")
-    @NotNull(message = "拼团价格,单位分不能为空")
-    private Integer activePrice;
+    @NotNull(message = "拼团价格不能为空")
+    private Integer combinationPrice;
 
 }

+ 0 - 14
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductCreateReqVO.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 拼团商品创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CombinationProductCreateReqVO extends CombinationProductBaseVO {
-
-}

+ 0 - 44
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductExcelVO.java

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
-
-import com.alibaba.excel.annotation.ExcelProperty;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-// TODO @puhui999:可以考虑删除 excel 导出哈
-/**
- * 拼团商品 Excel VO
- *
- * @author HUIHUI
- */
-@Data
-public class CombinationProductExcelVO {
-
-    @ExcelProperty("编号")
-    private Long id;
-
-    @ExcelProperty("拼团活动编号")
-    private Long activityId;
-
-    @ExcelProperty("商品 SPU 编号")
-    private Long spuId;
-
-    @ExcelProperty("商品 SKU 编号")
-    private Long skuId;
-
-    @ExcelProperty("拼团商品状态")
-    private Integer activityStatus;
-
-    @ExcelProperty("活动开始时间点")
-    private LocalDateTime activityStartTime;
-
-    @ExcelProperty("活动结束时间点")
-    private LocalDateTime activityEndTime;
-
-    @ExcelProperty("拼团价格,单位分")
-    private Integer activePrice;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-}

+ 0 - 43
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductExportReqVO.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-// TODO @puhui999:可以考虑删除 excel 导出哈
-@Schema(description = "管理后台 - 拼团商品 Excel 导出 Request VO,参数和 CombinationProductPageReqVO 是一致的")
-@Data
-public class CombinationProductExportReqVO {
-
-    @Schema(description = "拼团活动编号", example = "6829")
-    private Long activityId;
-
-    @Schema(description = "商品 SPU 编号", example = "18731")
-    private Long spuId;
-
-    @Schema(description = "商品 SKU 编号", example = "31675")
-    private Long skuId;
-
-    @Schema(description = "拼团商品状态", example = "2")
-    private Integer activityStatus;
-
-    @Schema(description = "活动开始时间点")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] activityStartTime;
-
-    @Schema(description = "活动结束时间点")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] activityEndTime;
-
-    @Schema(description = "拼团价格,单位分", example = "27682")
-    private Integer activePrice;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
-}

+ 0 - 14
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/product/CombinationProductUpdateReqVO.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 拼团商品更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CombinationProductUpdateReqVO extends CombinationProductBaseVO {
-
-}

+ 18 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/DecorateComponentController.http

@@ -0,0 +1,18 @@
+### /promotion/decorate/save 保存页面装修组件
+POST {{baseUrl}}/promotion/decorate/save
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+{
+  "page": 1,
+  "code": "slide-show",
+  "status": 0,
+  "value": "null"
+}
+
+### /promotion/decorate/list 获取指定页面的组件列表
+GET {{baseUrl}}/promotion/decorate/list?page=1
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}

+ 18 - 14
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/DecorateComponentController.java

@@ -2,45 +2,49 @@ package cn.iocoder.yudao.module.promotion.controller.admin.decorate;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentSaveReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentSaveReqVO;
+import cn.iocoder.yudao.module.promotion.convert.decorate.DecorateComponentConvert;
 import cn.iocoder.yudao.module.promotion.enums.decorate.DecoratePageEnum;
 import cn.iocoder.yudao.module.promotion.service.decorate.DecorateComponentService;
 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.*;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
+import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.module.promotion.convert.decorate.DecorateComponentConvert.INSTANCE;
 
 @Tag(name = "管理后台 - 店铺页面装修")
 @RestController
 @RequestMapping("/promotion/decorate")
 @Validated
 public class DecorateComponentController {
+
     @Resource
     private DecorateComponentService decorateComponentService;
 
-    @PostMapping("/page-save")
-    @Operation(summary = "保存页面装修")
-    // TODO 加权限
-    public CommonResult<Boolean> savePageComponents(@Valid @RequestBody DecorateComponentSaveReqVO reqVO) {
-        decorateComponentService.savePageComponents(reqVO);
+    @PostMapping("/save")
+    @Operation(summary = "保存页面装修组件")
+    @PreAuthorize("@ss.hasPermission('promotion:decorate:save')")
+    public CommonResult<Boolean> saveDecorateComponent(@Valid @RequestBody DecorateComponentSaveReqVO reqVO) {
+        decorateComponentService.saveDecorateComponent(reqVO);
         return success(true);
     }
 
-    @GetMapping("/get-page-components")
-    @Operation(summary = "获取装修页面组件")
-    @Parameter(name = "pageId", description = "页面 id", required = true)
-    // TODO 加权限
-    public CommonResult<DecorateComponentRespVO> getPageComponents(
-            @RequestParam("pageId") @InEnum(DecoratePageEnum.class) Integer pageId) {
-        return success(INSTANCE.convert2(pageId, decorateComponentService.getPageComponents(pageId)));
+    @GetMapping("/list")
+    @Operation(summary = "获取指定页面的组件列表")
+    @Parameter(name = "page", description = "页面 id", required = true)
+    @PreAuthorize("@ss.hasPermission('promotion:decorate:query')")
+    public CommonResult<List<DecorateComponentRespVO>> getDecorateComponentListByPage(
+            @RequestParam("page") @InEnum(DecoratePageEnum.class) Integer page) {
+        return success(DecorateComponentConvert.INSTANCE.convertList02(
+                decorateComponentService.getDecorateComponentListByPage(page, null)));
     }
 
 }

+ 6 - 20
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/vo/DecorateComponentRespVO.java

@@ -3,31 +3,17 @@ package cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.util.List;
-
 @Schema(description = "管理后台 - 页面装修 Resp VO")
 @Data
 public class DecorateComponentRespVO {
 
-    @Schema(description = "页面 id ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer pageId;
-
-    @Schema(description = "页面组件", requiredMode = Schema.RequiredMode.REQUIRED, example = "TODO")
-    private List<ComponentRespVO> components;
-
-    @Schema(description = "管理后台 - 页面组件 Resp VO")
-    @Data
-    public static class ComponentRespVO {
-
-        @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-        private Long id;
-
-        @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "nav-menu")
-        private String code;
+    @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "nav-menu")
+    private String code;
 
-        @Schema(description = "组件的内容配置项", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "TODO")
-        private String value;
+    @Schema(description = "组件的内容配置项", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "TODO")
+    private String value;
 
-    }
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
 
 }

+ 9 - 22
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/decorate/vo/DecorateComponentSaveReqVO.java

@@ -5,10 +5,8 @@ import cn.iocoder.yudao.module.promotion.enums.decorate.DecoratePageEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
-import java.util.List;
 
 @Schema(description = "管理后台 - 页面装修的保存 Request VO ")
 @Data
@@ -17,28 +15,17 @@ public class DecorateComponentSaveReqVO {
     @Schema(description = "页面 id ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "页面 id 不能为空")
     @InEnum(DecoratePageEnum.class)
-    private Integer pageId;
+    private Integer page;
 
-    @Schema(description = "页面组件列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "TODO")
-    @NotEmpty(message = "页面组件列表不能为空")
-    @Valid
-    private List<ComponentReqVO> components;
+    @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "nav-menu")
+    @NotEmpty(message = "组件编码不能为空")
+    private String code;
 
-    @Schema(description = "管理后台 - 页面装修组件 Request VO")
-    @Data
-    public static class ComponentReqVO {
+    @Schema(description = "组件对应值, json 字符串, 含内容配置,具体数据", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "组件值为空")
+    private String value;
 
-        @Schema(description = "组件编码",  example = "1")
-        private Long id;
-
-        @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "nav-menu")
-        @NotEmpty(message = "组件编码不能为空")
-        private String code;
-
-        @Schema(description = "组件对应值, json 字符串, 含内容配置,具体数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "TODO")
-        @NotEmpty(message = "组件值为空")
-        private String value;
-
-    }
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
 
 }

+ 18 - 22
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillActivityController.java

@@ -1,15 +1,15 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.*;
 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
-import cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity.SeckillActivityService;
+import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -19,11 +19,10 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
-import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 
 @Tag(name = "管理后台 - 秒杀活动")
 @RestController
@@ -34,7 +33,7 @@ public class SeckillActivityController {
     @Resource
     private SeckillActivityService seckillActivityService;
     @Resource
-    private ProductSpuApi spuApi;
+    private ProductSpuApi productSpuApi;
 
     @PostMapping("/create")
     @Operation(summary = "创建秒杀活动")
@@ -74,30 +73,27 @@ public class SeckillActivityController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
     public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
-        SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id);
-        List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
-        return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity, seckillProducts));
-    }
-
-    @GetMapping("/list")
-    @Operation(summary = "获得秒杀活动列表")
-    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
-    @PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
-    public CommonResult<List<SeckillActivityRespVO>> getSeckillActivityList(@RequestParam("ids") Collection<Long> ids) {
-        List<SeckillActivityDO> list = seckillActivityService.getSeckillActivityList(ids);
-        return success(SeckillActivityConvert.INSTANCE.convertList(list));
+        SeckillActivityDO activity = seckillActivityService.getSeckillActivity(id);
+        List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityId(id);
+        return success(SeckillActivityConvert.INSTANCE.convert(activity, products));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得秒杀活动分页")
     @PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
     public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) {
+        // 查询活动列表
         PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO);
-        Set<Long> aIds = CollectionUtils.convertSet(pageResult.getList(), SeckillActivityDO::getId);
-        List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(aIds);
-        Set<Long> spuIds = CollectionUtils.convertSet(pageResult.getList(), SeckillActivityDO::getSpuId);
-        List<ProductSpuRespDTO> spuList = spuApi.getSpuList(spuIds);
-        return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, seckillProducts, spuList));
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+
+        // 拼接数据
+        List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityId(
+                convertSet(pageResult.getList(), SeckillActivityDO::getId));
+        List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(
+                convertSet(pageResult.getList(), SeckillActivityDO::getSpuId));
+        return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, products, spuList));
     }
 
 }

+ 4 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillConfigController.java

@@ -1,11 +1,12 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.*;
 import cn.iocoder.yudao.module.promotion.convert.seckill.seckillconfig.SeckillConfigConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillconfig.SeckillConfigDO;
-import cn.iocoder.yudao.module.promotion.service.seckill.seckillconfig.SeckillConfigService;
+import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -80,7 +81,7 @@ public class SeckillConfigController {
     @GetMapping("/list-all-simple")
     @Operation(summary = "获得所有开启状态的秒杀时段精简列表", description = "主要用于前端的下拉选项")
     public CommonResult<List<SeckillConfigSimpleRespVO>> getListAllSimple() {
-        List<SeckillConfigDO> list = seckillConfigService.getListAllSimple();
+        List<SeckillConfigDO> list = seckillConfigService.getSeckillConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
         return success(SeckillConfigConvert.INSTANCE.convertList1(list));
     }
 
@@ -91,4 +92,5 @@ public class SeckillConfigController {
         PageResult<SeckillConfigDO> pageResult = seckillConfigService.getSeckillConfigPage(pageVO);
         return success(SeckillConfigConvert.INSTANCE.convertPage(pageResult));
     }
+
 }

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityBaseVO.java

@@ -20,7 +20,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 public class SeckillActivityBaseVO {
 
-    @Schema(description = "秒杀活动商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
+    @Schema(description = "秒杀活动商品 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
     @NotNull(message = "秒杀活动商品不能为空")
     private Long spuId;
 

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityCreateReqVO.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
 
 
-import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -16,6 +16,6 @@ import java.util.List;
 public class SeckillActivityCreateReqVO extends SeckillActivityBaseVO {
 
     @Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
-    private List<SeckillProductCreateReqVO> products;
+    private List<SeckillProductBaseVO> products;
 
 }

+ 6 - 6
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityRespVO.java

@@ -21,28 +21,28 @@ public class SeckillActivityRespVO extends SeckillActivityBaseVO {
     @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
     private String picUrl;
 
-    @Schema(description = "秒杀活动id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @Schema(description = "秒杀活动 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
     @Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
     private List<SeckillProductRespVO> products;
 
-    @Schema(description = "活动状态 开启:0 禁用:1", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
     private Integer status;
 
     @Schema(description = "订单实付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22354")
     private Integer totalPrice;
 
-    @Schema(description = "秒杀库存", example = "10")
+    @Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer stock;
 
-    @Schema(description = "秒杀总库存", example = "20")
+    @Schema(description = "秒杀总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
     private Integer totalStock;
 
-    @Schema(description = "新增订单数", example = "20")
+    @Schema(description = "新增订单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
     private Integer orderCount;
 
-    @Schema(description = "付款人数", example = "20")
+    @Schema(description = "付款人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
     private Integer userCount;
 
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityUpdateReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
 
-import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -18,6 +18,6 @@ public class SeckillActivityUpdateReqVO extends SeckillActivityBaseVO {
     private Long id;
 
     @Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
-    private List<SeckillProductUpdateReqVO> products;
+    private List<SeckillProductBaseVO> products;
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/product/SeckillProductBaseVO.java

@@ -22,7 +22,7 @@ public class SeckillProductBaseVO {
     @NotNull(message = "秒杀金额,单位:分不能为空")
     private Integer seckillPrice;
 
-    @Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "秒杀库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     @NotNull(message = "秒杀库存不能为空")
     private Integer stock;
 

+ 0 - 13
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/product/SeckillProductCreateReqVO.java

@@ -1,13 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-@Schema(description = "管理后台 - 秒杀参与商品创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class SeckillProductCreateReqVO extends SeckillProductBaseVO {
-
-}

+ 0 - 14
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/product/SeckillProductUpdateReqVO.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 秒杀参与商品更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class SeckillProductUpdateReqVO extends SeckillProductBaseVO {
-
-}

+ 14 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java

@@ -2,22 +2,26 @@ package cn.iocoder.yudao.module.promotion.controller.app.coupon;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
+import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
 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.*;
 
+import javax.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
 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
@@ -25,6 +29,9 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 @Validated
 public class AppCouponController {
 
+    @Resource
+    private CouponService couponService;
+
     // TODO 芋艿:待实现
     @PostMapping("/take")
     @Operation(summary = "领取优惠劵")
@@ -93,4 +100,11 @@ public class AppCouponController {
         return success(new PageResult<>(list, 20L));
     }
 
+    @GetMapping(value = "/get-unused-count")
+    @Operation(summary = "获得未使用的优惠劵数量")
+    @PreAuthenticated
+    public CommonResult<Long> getUnusedCouponCount() {
+        return success(couponService.getUnusedCouponCount(getLoginUserId()));
+    }
+
 }

+ 10 - 7
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/decorate/AppDecorateController.java

@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.promotion.controller.app.decorate;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.promotion.controller.app.decorate.vo.AppDecorateComponentRespVO;
+import cn.iocoder.yudao.module.promotion.convert.decorate.DecorateComponentConvert;
 import cn.iocoder.yudao.module.promotion.enums.decorate.DecoratePageEnum;
 import cn.iocoder.yudao.module.promotion.service.decorate.DecorateComponentService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -15,9 +17,9 @@ 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;
-import static cn.iocoder.yudao.module.promotion.convert.decorate.DecorateComponentConvert.INSTANCE;
 
 @Tag(name = "用户 APP - 店铺装修")
 @RestController
@@ -28,12 +30,13 @@ public class AppDecorateController {
     @Resource
     private DecorateComponentService decorateComponentService;
 
-    @GetMapping("/get-page-components")
-    @Operation(summary = "获取装修页面组件")
-    @Parameter(name = "pageId", description = "页面 id", required = true)
-    public CommonResult<AppDecorateComponentRespVO> getPageComponents(
-            @RequestParam("pageId") @InEnum(DecoratePageEnum.class) Integer pageId) {
-        return success(INSTANCE.appConvert(pageId, decorateComponentService.getPageComponents(pageId)));
+    @GetMapping("/list")
+    @Operation(summary = "获取指定页面的组件列表")
+    @Parameter(name = "page", description = "页面编号", required = true)
+    public CommonResult<List<AppDecorateComponentRespVO>> getDecorateComponentListByPage(
+            @RequestParam("page") @InEnum(DecoratePageEnum.class) Integer page) {
+        return success(DecorateComponentConvert.INSTANCE.convertList(
+                decorateComponentService.getDecorateComponentListByPage(page, CommonStatusEnum.ENABLE.getStatus())));
     }
 
 }

+ 5 - 19
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/decorate/vo/AppDecorateComponentRespVO.java

@@ -3,28 +3,14 @@ package cn.iocoder.yudao.module.promotion.controller.app.decorate.vo;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.util.List;
-
-@Schema(description = "用户 App - 页面装修 Resp VO")
+@Schema(description = "用户 App - 页面组件 Resp VO")
 @Data
 public class AppDecorateComponentRespVO {
 
-    @Schema(description = "页面 id ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer pageId;
-
-    @Schema(description = "页面组件", requiredMode = Schema.RequiredMode.REQUIRED, example = "TODO")
-    private List<AppComponentRespVO> components;
-
-    @Schema(description = "用户 App - 页面组件 Resp VO")
-    @Data
-    public static class AppComponentRespVO {
-
-        @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "nav-menu")
-        private String code;
-
-        @Schema(description = "组件的内容配置项", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "TODO")
-        private String value;
+    @Schema(description = "组件编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "nav-menu")
+    private String code;
 
-    }
+    @Schema(description = "组件的内容配置项", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "TODO")
+    private String value;
 
 }

+ 33 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/bargain/BargainActivityConvert.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.promotion.convert.bargain;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityBaseVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 拼团活动 Convert
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface BargainActivityConvert {
+
+    BargainActivityConvert INSTANCE = Mappers.getMapper(BargainActivityConvert.class);
+
+    BargainActivityDO convert(BargainActivityBaseVO bean);
+
+    BargainActivityDO convert(BargainActivityUpdateReqVO bean);
+
+    BargainActivityRespVO convert(BargainActivityDO bean);
+
+    List<BargainActivityRespVO> convertList(List<BargainActivityDO> list);
+
+    PageResult<BargainActivityRespVO> convertPage(PageResult<BargainActivityDO> page);
+
+}

+ 33 - 74
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java

@@ -1,27 +1,24 @@
 package cn.iocoder.yudao.module.promotion.convert.combination;
 
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.product.api.spu.dto.ProductSpuRespDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
+import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExcelVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityRespVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductBaseVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationRecordDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
-import org.mapstruct.Named;
 import org.mapstruct.factory.Mappers;
 
-import java.time.LocalDateTime;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -37,38 +34,16 @@ public interface CombinationActivityConvert {
 
     CombinationActivityConvert INSTANCE = Mappers.getMapper(CombinationActivityConvert.class);
 
-    @Mappings({
-            @Mapping(target = "startTime", expression = "java(bean.getActivityTime()[0])"),
-            @Mapping(target = "endTime", expression = "java(bean.getActivityTime()[1])")
-    })
     CombinationActivityDO convert(CombinationActivityCreateReqVO bean);
 
-    @Mappings({
-            @Mapping(target = "startTime", expression = "java(bean.getActivityTime()[0])"),
-            @Mapping(target = "endTime", expression = "java(bean.getActivityTime()[1])")
-    })
     CombinationActivityDO convert(CombinationActivityUpdateReqVO bean);
 
-    @Named("mergeTime")
-    default LocalDateTime[] mergeTime(LocalDateTime startTime, LocalDateTime endTime) {
-        // TODO 有点怪第一次这样写 hh
-        LocalDateTime[] localDateTime = new LocalDateTime[2];
-        localDateTime[0] = startTime;
-        localDateTime[1] = endTime;
-        return localDateTime;
-    }
-
-    @Mappings({
-            @Mapping(target = "activityTime", expression = "java(mergeTime(bean.getStartTime(),bean.getEndTime()))")
-    })
     CombinationActivityRespVO convert(CombinationActivityDO bean);
 
     CombinationProductRespVO convert(CombinationProductDO bean);
 
-    default CombinationActivityRespVO convert(CombinationActivityDO bean, List<CombinationProductDO> productDOs) {
-        CombinationActivityRespVO respVO = convert(bean);
-        respVO.setProducts(convertList2(productDOs));
-        return respVO;
+    default CombinationActivityRespVO convert(CombinationActivityDO activity, List<CombinationProductDO> products) {
+        return convert(activity).setProducts(convertList2(products));
     }
 
     List<CombinationActivityRespVO> convertList(List<CombinationActivityDO> list);
@@ -78,13 +53,13 @@ public interface CombinationActivityConvert {
     default PageResult<CombinationActivityRespVO> convertPage(PageResult<CombinationActivityDO> page,
                                                               List<CombinationProductDO> productList,
                                                               List<ProductSpuRespDTO> spuList) {
-        // TODO @puhui999:c -> c 可以去掉哈
-        Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId, c -> c);
+        Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
         PageResult<CombinationActivityRespVO> pageResult = convertPage(page);
         pageResult.getList().forEach(item -> {
-            // TODO @puhui999:最好 MapUtils.findAndThen,万一没找到呢,啊哈哈。
-            item.setSpuName(spuMap.get(item.getSpuId()).getName());
-            item.setPicUrl(spuMap.get(item.getSpuId()).getPicUrl());
+            MapUtils.findAndThen(spuMap, item.getSpuId(), spu -> {
+                item.setSpuName(spu.getName());
+                item.setPicUrl(spu.getPicUrl());
+            });
             item.setProducts(convertList2(productList));
         });
         return pageResult;
@@ -92,47 +67,31 @@ public interface CombinationActivityConvert {
 
     List<CombinationProductRespVO> convertList2(List<CombinationProductDO> productDOs);
 
-    List<CombinationActivityExcelVO> convertList02(List<CombinationActivityDO> list);
-
     @Mappings({
             @Mapping(target = "id", ignore = true),
-            @Mapping(target = "activityId", source = "activityDO.id"),
-            @Mapping(target = "spuId", source = "activityDO.spuId"),
-            @Mapping(target = "skuId", source = "vo.skuId"),
-            @Mapping(target = "activePrice", source = "vo.activePrice"),
-            @Mapping(target = "activityStartTime", source = "activityDO.startTime"),
-            @Mapping(target = "activityEndTime", source = "activityDO.endTime")
+            @Mapping(target = "activityId", source = "activity.id"),
+            @Mapping(target = "spuId", source = "activity.spuId"),
+            @Mapping(target = "skuId", source = "product.skuId"),
+            @Mapping(target = "combinationPrice", source = "product.combinationPrice"),
+            @Mapping(target = "activityStartTime", source = "activity.startTime"),
+            @Mapping(target = "activityEndTime", source = "activity.endTime")
     })
-    CombinationProductDO convert(CombinationActivityDO activityDO, CombinationProductBaseVO vo);
-
-    default List<CombinationProductDO> convertList(CombinationActivityDO activityDO, List<? extends CombinationProductBaseVO> products) {
-        List<CombinationProductDO> list = new ArrayList<>();
-        products.forEach(sku -> {
-            CombinationProductDO productDO = convert(activityDO, sku);
-            // TODO 状态设置
-            productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
-            list.add(productDO);
-        });
-        return list;
+    CombinationProductDO convert(CombinationActivityDO activity, CombinationProductBaseVO product);
+
+    default List<CombinationProductDO> convertList(List<? extends CombinationProductBaseVO> products, CombinationActivityDO activity) {
+        return CollectionUtils.convertList(products, item -> convert(activity, item).setActivityStatus(activity.getStatus()));
     }
 
-    // TODO @puhui999:这个方法的参数,调整成 productDOs、vos、activityDO;因为 productDOs 是主角;
-    // 然后,这个方法,感觉不是为了 convert,而是为了补全;
-    default List<CombinationProductDO> convertList1(CombinationActivityDO activityDO,
-                                                    List<CombinationProductUpdateReqVO> vos,
-                                                    List<CombinationProductDO> productDOs) {
-        Map<Long, Long> longMap = convertMap(productDOs, CombinationProductDO::getSkuId, CombinationProductDO::getId);
-        List<CombinationProductDO> list = new ArrayList<>();
-        vos.forEach(sku -> {
-            CombinationProductDO productDO = convert(activityDO, sku);
-            productDO.setId(longMap.get(sku.getSkuId()));
-            // TODO @puhui999:是是不是用 activityDO 的状态;
-            productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
-            list.add(productDO);
-        });
-        return list;
+    default List<CombinationProductDO> convertList(List<CombinationProductBaseVO> updateProductVOs,
+                                                   List<CombinationProductDO> products, CombinationActivityDO activity) {
+        Map<Long, Long> productMap = convertMap(products, CombinationProductDO::getSkuId, CombinationProductDO::getId);
+        return CollectionUtils.convertList(updateProductVOs, updateProductVO -> convert(activity, updateProductVO)
+                .setId(productMap.get(updateProductVO.getSkuId()))
+                .setActivityStatus(activity.getStatus()));
     }
 
-    CombinationRecordDO convert(CombinationRecordReqDTO reqDTO);
+    CombinationRecordDO convert(CombinationRecordCreateReqDTO reqDTO);
+
+    List<CombinationRecordRespDTO> convert(List<CombinationRecordDO> bean);
 
 }

+ 4 - 23
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/decorate/DecorateComponentConvert.java

@@ -1,8 +1,7 @@
 package cn.iocoder.yudao.module.promotion.convert.decorate;
 
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentSaveReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentSaveReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.decorate.vo.AppDecorateComponentRespVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.decorate.DecorateComponentDO;
 import org.mapstruct.Mapper;
@@ -10,33 +9,15 @@ import org.mapstruct.factory.Mappers;
 
 import java.util.List;
 
-import static cn.iocoder.yudao.module.promotion.controller.admin.decorate.vo.DecorateComponentRespVO.*;
-import static cn.iocoder.yudao.module.promotion.controller.app.decorate.vo.AppDecorateComponentRespVO.*;
-
 @Mapper
 public interface DecorateComponentConvert {
 
     DecorateComponentConvert INSTANCE = Mappers.getMapper(DecorateComponentConvert.class);
 
-    default List<DecorateComponentDO> convertList(Integer pageId, List<DecorateComponentSaveReqVO.ComponentReqVO> components) {
-        return CollectionUtils.convertList(components, c -> convert(pageId, c));
-    }
-
-    default DecorateComponentRespVO convert2(Integer pageId, List<DecorateComponentDO> list) {
-        List<ComponentRespVO> components = CollectionUtils.convertList(list, this::convert3);
-        return new DecorateComponentRespVO().setPageId(pageId).setComponents(components);
-    }
-
-    DecorateComponentDO convert(Integer pageId, DecorateComponentSaveReqVO.ComponentReqVO reqVO);
-
-    ComponentRespVO convert3(DecorateComponentDO componentDO);
+    List<DecorateComponentRespVO> convertList02(List<DecorateComponentDO> list);
 
-    // ========== App convert ==========
-    default AppDecorateComponentRespVO appConvert(Integer pageId, List<DecorateComponentDO> list) {
-        List<AppComponentRespVO> components = CollectionUtils.convertList(list, this::appConvert2);
-        return new AppDecorateComponentRespVO().setPageId(pageId).setComponents(components);
-    }
+    DecorateComponentDO convert(DecorateComponentSaveReqVO bean);
 
-    AppComponentRespVO appConvert2(DecorateComponentDO bean);
+    List<AppDecorateComponentRespVO> convertList(List<DecorateComponentDO> list);
 
 }

+ 23 - 43
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
 
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO;
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.Se
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductBaseVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductRespVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.SeckillProductUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
 import org.mapstruct.Mapper;
@@ -18,7 +17,6 @@ import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -42,61 +40,43 @@ public interface SeckillActivityConvert {
 
     PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page);
 
-    default PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page, List<SeckillProductDO> seckillProducts, List<ProductSpuRespDTO> spuList) {
-        Map<Long, ProductSpuRespDTO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuRespDTO::getId, c -> c);
+    default PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page,
+                                                          List<SeckillProductDO> seckillProducts,
+                                                          List<ProductSpuRespDTO> spuList) {
         PageResult<SeckillActivityRespVO> pageResult = convertPage(page);
+        // 拼接商品
+        Map<Long, ProductSpuRespDTO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuRespDTO::getId);
         pageResult.getList().forEach(item -> {
-            item.setSpuName(spuMap.get(item.getSpuId()).getName());
-            item.setPicUrl(spuMap.get(item.getSpuId()).getPicUrl());
             item.setProducts(convertList2(seckillProducts));
+            MapUtils.findAndThen(spuMap, item.getSpuId(),
+                    spu -> item.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()));
         });
         return pageResult;
     }
 
-    SeckillActivityDetailRespVO convert1(SeckillActivityDO seckillActivity);
+    SeckillActivityDetailRespVO convert1(SeckillActivityDO activity);
 
-    default SeckillActivityDetailRespVO convert(SeckillActivityDO seckillActivity, List<SeckillProductDO> seckillProducts) {
-        SeckillActivityDetailRespVO respVO = convert1(seckillActivity);
-        respVO.setProducts(convertList2(seckillProducts));
-        return respVO;
+    default SeckillActivityDetailRespVO convert(SeckillActivityDO activity, List<SeckillProductDO> products) {
+        return convert1(activity).setProducts(convertList2(products));
     }
 
     @Mappings({
             @Mapping(target = "id", ignore = true),
-            @Mapping(target = "activityId", source = "activityDO.id"),
-            @Mapping(target = "configIds", source = "activityDO.configIds"),
-            @Mapping(target = "spuId", source = "activityDO.spuId"),
-            @Mapping(target = "skuId", source = "vo.skuId"),
-            @Mapping(target = "seckillPrice", source = "vo.seckillPrice"),
-            @Mapping(target = "stock", source = "vo.stock"),
-            @Mapping(target = "activityStartTime", source = "activityDO.startTime"),
-            @Mapping(target = "activityEndTime", source = "activityDO.endTime")
+            @Mapping(target = "activityId", source = "activity.id"),
+            @Mapping(target = "configIds", source = "activity.configIds"),
+            @Mapping(target = "spuId", source = "activity.spuId"),
+            @Mapping(target = "skuId", source = "product.skuId"),
+            @Mapping(target = "seckillPrice", source = "product.seckillPrice"),
+            @Mapping(target = "stock", source = "product.stock"),
+            @Mapping(target = "activityStartTime", source = "activity.startTime"),
+            @Mapping(target = "activityEndTime", source = "activity.endTime")
     })
-    SeckillProductDO convert(SeckillActivityDO activityDO, SeckillProductBaseVO vo);
-
-    default List<SeckillProductDO> convertList(SeckillActivityDO activityDO, List<? extends SeckillProductBaseVO> products) {
-        List<SeckillProductDO> list = new ArrayList<>();
-        products.forEach(sku -> {
-            SeckillProductDO productDO = convert(activityDO, sku);
-            productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
-            list.add(productDO);
-        });
-        return list;
-    }
+    SeckillProductDO convert(SeckillActivityDO activity, SeckillProductBaseVO product);
 
-    // TODO @puhui999:同拼团那个 convert 想通的情况哈。
-    default List<SeckillProductDO> convertList1(SeckillActivityDO activityDO, List<SeckillProductUpdateReqVO> vos, List<SeckillProductDO> productDOs) {
-        Map<Long, Long> longMap = CollectionUtils.convertMap(productDOs, SeckillProductDO::getSkuId, SeckillProductDO::getId);
-        List<SeckillProductDO> list = new ArrayList<>();
-        vos.forEach(sku -> {
-            SeckillProductDO productDO = convert(activityDO, sku);
-            productDO.setId(longMap.get(sku.getSkuId()));
-            productDO.setActivityStatus(CommonStatusEnum.ENABLE.getStatus());
-            list.add(productDO);
-        });
-        return list;
+    default List<SeckillProductDO> convertList(List<? extends SeckillProductBaseVO> products, SeckillActivityDO activity) {
+        return CollectionUtils.convertList(products, item -> convert(activity, item).setActivityStatus(activity.getStatus()));
     }
 
-    List<SeckillProductRespVO> convertList2(List<SeckillProductDO> productDOs);
+    List<SeckillProductRespVO> convertList2(List<SeckillProductDO> list);
 
 }

+ 101 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/bargain/BargainActivityDO.java

@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.module.promotion.dal.dataobject.bargain;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+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.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 砍价活动 DO
+ *
+ * @author HUIHUI
+ */
+@TableName("promotion_bargain_activity")
+@KeySequence("promotion_bargain_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BargainActivityDO extends BaseDO {
+
+    /**
+     * 砍价活动编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 砍价活动名称
+     */
+    private String name;
+
+    /**
+     * 活动开始时间
+     */
+    private LocalDateTime startTime;
+    /**
+     * 活动结束时间
+     */
+    private LocalDateTime endTime;
+
+    /**
+     * 活动状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 商品 SPU 编号
+     */
+    private Long spuId;
+    /**
+     * 商品 SKU 编号
+     */
+    private Long skuId;
+    /**
+     * 砍价起始价格,单位分
+     */
+    private Integer bargainFirstPrice;
+    /**
+     * 砍价底价,单位:分
+     */
+    private Integer bargainPrice;
+    /**
+     * 砍价活动库存
+     */
+    private Integer stock;
+
+    /**
+     * 达到该人数,才能砍到低价
+     */
+    private Integer userSize;
+    /**
+     * 最大帮砍次数
+     */
+    private Integer bargainCount;
+
+    /**
+     * 总限购数量
+     */
+    private Integer totalLimitCount;
+    /**
+     * 用户每次砍价的最小金额,单位:分
+     */
+    private Integer randomMinPrice;
+    /**
+     * 用户每次砍价的最大金额,单位:分
+     */
+    private Integer randomMaxPrice;
+    /**
+     * 砍价成功数量
+     */
+    private Integer successCount;
+
+}

+ 50 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/bargain/BargainAssistDO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.promotion.dal.dataobject.bargain;
+
+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.*;
+
+/**
+ * 砍价助力 DO TODO 芋艿:表结构
+ *
+ * @author HUIHUI
+ */
+@TableName("promotion_bargain_assist")
+@KeySequence("promotion_bargain_assist_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BargainAssistDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 砍价活动编号
+     */
+    private Long activityId;
+
+    /**
+     * 砍价记录编号
+     */
+    private Long recordId;
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
+
+    /**
+     * 减少价格。单位分
+     */
+    private Integer reducePrice;
+
+}

+ 87 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/bargain/BargainRecordDO.java

@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.promotion.dal.dataobject.bargain;
+
+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.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 砍价记录 DO TODO
+ *
+ * @author HUIHUI
+ */
+@TableName("promotion_bargain_record")
+@KeySequence("promotion_bargain_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BargainRecordDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 砍价活动编号
+     */
+    private Long activityId;
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
+
+    /**
+     * 商品 SPU 编号
+     */
+    private Long spuId;
+
+    /**
+     * 商品 SKU 编号
+     */
+    private Long skuId;
+
+    /**
+     * 砍价底价,单位分
+     */
+    private Integer bargainPrice;
+
+    /**
+     * 商品原价,单位分
+     */
+    private Integer price;
+
+    /**
+     * 应付金额,单位分
+     */
+    private Integer payPrice;
+
+    /**
+     * 状态1 - 砍价中;2- 砍价成功;3 - 砍价失败
+     */
+    private Integer status;
+
+    /**
+     * 订单编号
+     */
+    private Long orderId;
+
+    /**
+     * 结束时间
+     */
+    private LocalDateTime endTime;
+
+    /**
+     * 过期时间
+     */
+    private Data expireTime;
+
+}

+ 4 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/combinationactivity/CombinationActivityDO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationActivityDO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity;
+package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
@@ -9,7 +9,6 @@ import lombok.*;
 
 import java.time.LocalDateTime;
 
-// TODO @puhui999:是不是应该在 combination 哈?
 /**
  * 拼团活动 DO
  *
@@ -63,11 +62,11 @@ public class CombinationActivityDO extends BaseDO {
     /**
      * 开团组数
      */
-    private Integer totalNum;
+    private Integer totalCount;
     /**
      * 成团组数
      */
-    private Integer successNum;
+    private Integer successCount;
     /**
      * 参与人数
      */
@@ -77,7 +76,7 @@ public class CombinationActivityDO extends BaseDO {
      */
     private Integer virtualGroup;
     /**
-     * 活动状态:0开启 1关闭
+     * 活动状态
      *
      * 枚举 {@link CommonStatusEnum}
      */

+ 12 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/combinationactivity/CombinationProductDO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationProductDO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity;
+package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -40,21 +40,28 @@ public class CombinationProductDO extends BaseDO {
      * 商品 SKU 编号
      */
     private Long skuId;
+    /**
+     * 拼团价格,单位分
+     */
+    private Integer combinationPrice;
+
     /**
      * 拼团商品状态
+     *
+     * 关联 {@link CombinationActivityDO#getStatus()}
      */
     private Integer activityStatus;
     /**
      * 活动开始时间点
+     *
+     * 冗余 {@link CombinationActivityDO#getStartTime()}
      */
     private LocalDateTime activityStartTime;
     /**
      * 活动结束时间点
+     *
+     * 冗余 {@link CombinationActivityDO#getEndTime()}
      */
     private LocalDateTime activityEndTime;
-    /**
-     * 拼团价格,单位分
-     */
-    private Integer activePrice;
 
 }

+ 5 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/combinationactivity/CombinationRecordDO.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/combination/CombinationRecordDO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity;
+package cn.iocoder.yudao.module.promotion.dal.dataobject.combination;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
@@ -12,6 +12,9 @@ import java.time.LocalDateTime;
 /**
  * 拼团记录 DO
  *
+ * 1. 用户参与拼团时,会创建一条记录
+ * 2. 团长的拼团记录,和参团人的拼团记录,通过 {@link #headId} 关联
+ *
  * @author HUIHUI
  */
 @TableName("promotion_combination_record")
@@ -49,7 +52,7 @@ public class CombinationRecordDO extends BaseDO {
     /**
      * 团长编号
      *
-     * 关联 {@link CombinationRecordDO#getUserId()}
+     * 关联 {@link CombinationRecordDO#getId()}
      */
     private Long headId;
     /**

+ 6 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/decorate/DecorateComponentDO.java

@@ -26,13 +26,14 @@ public class DecorateComponentDO extends BaseDO {
 
     /**
      * 所属页面 id
-     * 枚举 {@link DecoratePageEnum#getId()}
+     *
+     * 枚举 {@link DecoratePageEnum#getPage()}
      */
-    private Integer pageId;
+    private Integer page;
 
     /**
-     *  组件编码
-     *  枚举 {@link DecorateComponentEnum#getCode()}
+     * 组件编码
+     * 枚举 {@link DecorateComponentEnum#getCode()}
      */
     private String code;
 
@@ -47,4 +48,5 @@ public class DecorateComponentDO extends BaseDO {
      * 枚举 {@link CommonStatusEnum}
      */
     private Integer status;
+
 }

+ 31 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.bargain;
+
+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.promotion.controller.admin.bargain.vo.BargainActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 砍价活动 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
+
+    default PageResult<BargainActivityDO> selectPage(BargainActivityPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BargainActivityDO>()
+                .likeIfPresent(BargainActivityDO::getName, reqVO.getName())
+                .eqIfPresent(BargainActivityDO::getStatus, reqVO.getStatus())
+                .orderByDesc(BargainActivityDO::getId));
+    }
+
+    default List<BargainActivityDO> selectListByStatus(Integer status) {
+        return selectList(BargainActivityDO::getStatus, status);
+    }
+
+}

+ 15 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainRecordMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.bargain;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 砍价记录 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface BargainRecordMapper extends BaseMapperX<BargainRecordDO> {
+
+}

+ 2 - 10
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/combinationactivity/CombinationActivityMapper.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java

@@ -1,16 +1,14 @@
-package cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity;
+package cn.iocoder.yudao.module.promotion.dal.mysql.combination;
 
 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.promotion.controller.admin.combination.vo.activity.CombinationActivityExportReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
 
-// TODO @puhui999:是不是应该在 combination 哈?
 /**
  * 拼团活动 Mapper
  *
@@ -21,12 +19,6 @@ public interface CombinationActivityMapper extends BaseMapperX<CombinationActivi
 
     default PageResult<CombinationActivityDO> selectPage(CombinationActivityPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<CombinationActivityDO>()
-                .likeIfPresent(CombinationActivityDO::getName, reqVO.getName())
-                .orderByDesc(CombinationActivityDO::getId));
-    }
-
-    default List<CombinationActivityDO> selectList(CombinationActivityExportReqVO reqVO) {
-        return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
                 .likeIfPresent(CombinationActivityDO::getName, reqVO.getName())
                 .eqIfPresent(CombinationActivityDO::getStatus, reqVO.getStatus())
                 .orderByDesc(CombinationActivityDO::getId));

+ 3 - 17
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/combinationactivity/CombinationProductMapper.java → yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationProductMapper.java

@@ -1,11 +1,10 @@
-package cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity;
+package cn.iocoder.yudao.module.promotion.dal.mysql.combination;
 
 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.promotion.controller.admin.combination.vo.product.CombinationProductExportReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductPageReqVO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.Collection;
@@ -27,20 +26,7 @@ public interface CombinationProductMapper extends BaseMapperX<CombinationProduct
                 .eqIfPresent(CombinationProductDO::getActivityStatus, reqVO.getActivityStatus())
                 .betweenIfPresent(CombinationProductDO::getActivityStartTime, reqVO.getActivityStartTime())
                 .betweenIfPresent(CombinationProductDO::getActivityEndTime, reqVO.getActivityEndTime())
-                .eqIfPresent(CombinationProductDO::getActivePrice, reqVO.getActivePrice())
-                .betweenIfPresent(CombinationProductDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(CombinationProductDO::getId));
-    }
-
-    default List<CombinationProductDO> selectList(CombinationProductExportReqVO reqVO) {
-        return selectList(new LambdaQueryWrapperX<CombinationProductDO>()
-                .eqIfPresent(CombinationProductDO::getActivityId, reqVO.getActivityId())
-                .eqIfPresent(CombinationProductDO::getSpuId, reqVO.getSpuId())
-                .eqIfPresent(CombinationProductDO::getSkuId, reqVO.getSkuId())
-                .eqIfPresent(CombinationProductDO::getActivityStatus, reqVO.getActivityStatus())
-                .betweenIfPresent(CombinationProductDO::getActivityStartTime, reqVO.getActivityStartTime())
-                .betweenIfPresent(CombinationProductDO::getActivityEndTime, reqVO.getActivityEndTime())
-                .eqIfPresent(CombinationProductDO::getActivePrice, reqVO.getActivePrice())
+                .eqIfPresent(CombinationProductDO::getCombinationPrice, reqVO.getActivePrice())
                 .betweenIfPresent(CombinationProductDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(CombinationProductDO::getId));
     }

+ 62 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.combination;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 拼团记录 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO> {
+
+    default CombinationRecordDO selectByUserIdAndOrderId(Long userId, Long orderId) {
+        return selectOne(CombinationRecordDO::getUserId, userId,
+                CombinationRecordDO::getOrderId, orderId);
+    }
+
+    default List<CombinationRecordDO> selectListByUserIdAndStatus(Long userId, Integer status) {
+        return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
+                .eq(CombinationRecordDO::getUserId, userId)
+                .eq(CombinationRecordDO::getStatus, status));
+    }
+    /**
+     * 查询拼团记录
+     *
+     * @param headId 团长编号
+     * @return 拼团记录
+     */
+    default CombinationRecordDO selectOneByHeadId(Long headId, Integer status) {
+        return selectOne(new LambdaQueryWrapperX<CombinationRecordDO>()
+                .eq(CombinationRecordDO::getId, headId)
+                .eq(CombinationRecordDO::getStatus, status));
+    }
+
+    default List<CombinationRecordDO> selectListByHeadIdAndStatus(Long headId, Integer status) {
+        return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
+                .eq(CombinationRecordDO::getHeadId, headId)
+                .eq(CombinationRecordDO::getStatus, status));
+    }
+
+    default List<CombinationRecordDO> selectListByStatus(Integer status) {
+        return selectList(CombinationRecordDO::getStatus, status);
+    }
+
+    /**
+     * 查询拼团记录
+     *
+     * @param userId     用户 id
+     * @param activityId 活动 id
+     * @return 拼团记录
+     */
+    default List<CombinationRecordDO> selectListByUserIdAndActivityId(Long userId, Long activityId) {
+        return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
+                .eq(CombinationRecordDO::getUserId, userId)
+                .eq(CombinationRecordDO::getActivityId, activityId));
+    }
+}

+ 0 - 33
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/combinationactivity/CombinationRecordMapper.java

@@ -1,33 +0,0 @@
-package cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity;
-
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationRecordDO;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-/**
- * 拼团记录 Mapper
- *
- * @author HUIHUI
- */
-@Mapper
-public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO> {
-
-    default CombinationRecordDO selectRecord(Long userId, Long orderId) {
-        return selectOne(CombinationRecordDO::getUserId, userId,
-                CombinationRecordDO::getOrderId, orderId);
-    }
-
-    default List<CombinationRecordDO> selectListByHeadIdAndStatus(Long headId, Integer status) {
-        return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
-                .eq(CombinationRecordDO::getHeadId, headId)
-                .eq(CombinationRecordDO::getStatus, status));
-    }
-
-    default List<CombinationRecordDO> selectListByStatus(Integer status) {
-        return selectList(CombinationRecordDO::getStatus, status);
-    }
-
-}

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

@@ -49,4 +49,10 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
                 .eq(CouponDO::getId, id).eq(CouponDO::getStatus, status));
     }
 
+    default Long selectCountByUserIdAndStatus(Long userId, Integer status) {
+        return selectCount(new LambdaQueryWrapperX<CouponDO>()
+                .eq(CouponDO::getUserId, userId)
+                .eq(CouponDO::getStatus, status));
+    }
+
 }

+ 11 - 3
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/decorate/DecorateComponentMapper.java

@@ -1,7 +1,8 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.decorate;
 
-import cn.iocoder.yudao.module.promotion.dal.dataobject.decorate.DecorateComponentDO;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.decorate.DecorateComponentDO;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -9,8 +10,15 @@ import java.util.List;
 @Mapper
 public interface DecorateComponentMapper extends BaseMapperX<DecorateComponentDO> {
 
-    default List<DecorateComponentDO> selectByPage(Integer pageId){
-        return selectList(DecorateComponentDO::getPageId, pageId);
+    default List<DecorateComponentDO> selectListByPageAndStatus(Integer page, Integer status) {
+        return selectList(new LambdaQueryWrapperX<DecorateComponentDO>()
+                        .eq(DecorateComponentDO::getPage, page)
+                        .eqIfPresent(DecorateComponentDO::getStatus, status));
+    }
+
+    default DecorateComponentDO selectByPageAndCode(Integer page, String code) {
+        return selectOne(DecorateComponentDO::getPage, page,
+                DecorateComponentDO::getCode, code);
     }
 
 }

+ 0 - 13
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
-import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.Collection;
@@ -25,15 +23,4 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
         return selectList(SeckillProductDO::getActivityId, ids);
     }
 
-    default List<SeckillProductDO> selectListBySkuIds(Collection<Long> skuIds) {
-        return selectList(SeckillProductDO::getSkuId, skuIds);
-    }
-
-    default void updateTimeIdsByActivityId(Long id, List<Long> timeIds) {
-        new LambdaUpdateChainWrapper<>(this)
-                .set(SeckillProductDO::getConfigIds, CollUtil.join(timeIds, ","))
-                .eq(SeckillProductDO::getActivityId, id)
-                .update();
-    }
-
 }

+ 57 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.promotion.service.bargain;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+
+import javax.validation.Valid;
+
+/**
+ * 砍价活动 Service 接口
+ *
+ * @author HUIHUI
+ */
+public interface BargainActivityService {
+
+    /**
+     * 创建砍价活动
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createBargainActivity(@Valid BargainActivityCreateReqVO createReqVO);
+
+    /**
+     * 更新砍价活动
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateBargainActivity(@Valid BargainActivityUpdateReqVO updateReqVO);
+
+    /**
+     * 删除砍价活动
+     *
+     * @param id 编号
+     */
+    void deleteBargainActivity(Long id);
+
+    /**
+     * 获得砍价活动
+     *
+     * @param id 编号
+     * @return 砍价活动
+     */
+    BargainActivityDO getBargainActivity(Long id);
+
+    /**
+     * 获得砍价活动分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 砍价活动分页
+     */
+    PageResult<BargainActivityDO> getBargainActivityPage(BargainActivityPageReqVO pageReqVO);
+
+
+}

+ 124 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java

@@ -0,0 +1,124 @@
+package cn.iocoder.yudao.module.promotion.service.bargain;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.convert.bargain.BargainActivityConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.bargain.BargainActivityMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
+
+/**
+ * 砍价活动 Service 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class BargainActivityServiceImpl implements BargainActivityService {
+
+    @Resource
+    private BargainActivityMapper bargainActivityMapper;
+
+    @Resource
+    private ProductSkuApi productSkuApi;
+
+    @Override
+    public Long createBargainActivity(BargainActivityCreateReqVO createReqVO) {
+        // 校验商品 SPU 是否存在是否参加的别的活动
+        validateBargainConflict(createReqVO.getSpuId(), null);
+        // 校验商品 sku 是否存在
+        validateSku(createReqVO.getSkuId());
+
+        // 插入砍价活动
+        BargainActivityDO activityDO = BargainActivityConvert.INSTANCE.convert(createReqVO)
+                .setStatus(CommonStatusEnum.ENABLE.getStatus()).setSuccessCount(0);
+        bargainActivityMapper.insert(activityDO);
+        return activityDO.getId();
+    }
+
+    @Override
+    public void updateBargainActivity(BargainActivityUpdateReqVO updateReqVO) {
+        // 校验存在
+        BargainActivityDO activityDO = validateBargainActivityExists(updateReqVO.getId());
+        // 校验状态
+        if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
+            throw exception(BARGAIN_ACTIVITY_STATUS_DISABLE);
+        }
+        // 校验商品冲突
+        validateBargainConflict(updateReqVO.getSpuId(), updateReqVO.getId());
+        // 校验商品 sku 是否存在
+        validateSku(updateReqVO.getSkuId());
+
+        // 更新
+        BargainActivityDO updateObj = BargainActivityConvert.INSTANCE.convert(updateReqVO);
+        bargainActivityMapper.updateById(updateObj);
+    }
+
+    private void validateBargainConflict(Long spuId, Long activityId) {
+        // 查询所有开启的砍价活动
+        List<BargainActivityDO> activityList = bargainActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        if (activityId != null) { // 更新时排除自己
+            activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
+        }
+        // 校验商品 spu 是否参加了其它活动
+        if (anyMatch(activityList, activity -> ObjectUtil.equal(activity.getSpuId(), spuId))) {
+            throw exception(BARGAIN_ACTIVITY_SPU_CONFLICTS);
+        }
+    }
+
+    private void validateSku(Long skuId) {
+        ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
+        if (sku == null) {
+            throw exception(SKU_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteBargainActivity(Long id) {
+        // 校验存在
+        BargainActivityDO activityDO = validateBargainActivityExists(id);
+        // 校验状态
+        if (ObjectUtil.equal(activityDO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            throw exception(BARGAIN_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
+        }
+
+        // 删除
+        bargainActivityMapper.deleteById(id);
+    }
+
+    private BargainActivityDO validateBargainActivityExists(Long id) {
+        BargainActivityDO activityDO = bargainActivityMapper.selectById(id);
+        if (activityDO == null) {
+            throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
+        }
+        return activityDO;
+    }
+
+    @Override
+    public BargainActivityDO getBargainActivity(Long id) {
+        return bargainActivityMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<BargainActivityDO> getBargainActivityPage(BargainActivityPageReqVO pageReqVO) {
+        return bargainActivityMapper.selectPage(pageReqVO);
+    }
+
+}

+ 13 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainRecordService.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.promotion.service.bargain;
+
+
+/**
+ * 砍价记录 service 接口
+ *
+ * @author HUIHUI
+ */
+public interface BargainRecordService {
+
+//    TODO
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainRecordServiceImpl.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.promotion.service.bargain;
+
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * 砍价记录 Service 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class BargainRecordServiceImpl implements BargainRecordService {
+}

+ 10 - 58
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java

@@ -1,16 +1,13 @@
 package cn.iocoder.yudao.module.promotion.service.combination;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExportReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
 
 import javax.validation.Valid;
-import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -44,20 +41,20 @@ public interface CombinationActivityService {
     void deleteCombinationActivity(Long id);
 
     /**
-     * 获得拼团活动
+     * 校验拼团活动是否存在
      *
      * @param id 编号
      * @return 拼团活动
      */
-    CombinationActivityDO getCombinationActivity(Long id);
+    CombinationActivityDO validateCombinationActivityExists(Long id);
 
     /**
-     * 获得拼团活动列表
+     * 获得拼团活动
      *
-     * @param ids 编号
-     * @return 拼团活动列表
+     * @param id 编号
+     * @return 拼团活动
      */
-    List<CombinationActivityDO> getCombinationActivityList(Collection<Long> ids);
+    CombinationActivityDO getCombinationActivity(Long id);
 
     /**
      * 获得拼团活动分页
@@ -67,57 +64,12 @@ public interface CombinationActivityService {
      */
     PageResult<CombinationActivityDO> getCombinationActivityPage(CombinationActivityPageReqVO pageReqVO);
 
-    /**
-     * 获得拼团活动列表, 用于 Excel 导出
-     *
-     * @param exportReqVO 查询条件
-     * @return 拼团活动列表
-     */
-    List<CombinationActivityDO> getCombinationActivityList(CombinationActivityExportReqVO exportReqVO);
-
     /**
      * 获得拼团活动商品列表
      *
-     * @param ids 拼团活动 ids
+     * @param activityIds 拼团活动 ids
      * @return 拼团活动的商品列表
      */
-    List<CombinationProductDO> getProductsByActivityIds(Collection<Long> ids);
-
-    // TODO @puhui999:拆一个 CombinationRecordService 里,方法名可以简洁成 updateRecordStatusByOrderId;service 方法可以稍微简单一点,如果是 update 方法
-    /**
-     * 更新拼团状态
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     * @param status  状态
-     */
-    void updateRecordStatusByUserIdAndOrderId(Long userId, Long orderId, Integer status);
-
-    /**
-     * 更新拼团状态和开始时间
-     *
-     * @param userId    用户编号
-     * @param orderId   订单编号
-     * @param status    状态
-     * @param startTime 开始时间
-     */
-    void updateRecordStatusAndStartTimeByUserIdAndOrderId(Long userId, Long orderId, Integer status, LocalDateTime startTime);
-
-    // TODO @puhui999:拆一个 CombinationRecordService 里
-    /**
-     * 创建拼团记录
-     *
-     * @param reqDTO 创建信息
-     */
-    void createRecord(CombinationRecordReqDTO reqDTO);
-
-    /**
-     * 获得拼团状态
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     * @return 拼团状态
-     */
-    boolean validateRecordStatusIsSuccess(Long userId, Long orderId);
+    List<CombinationProductDO> getCombinationProductsByActivityIds(Collection<Long> activityIds);
 
 }

+ 90 - 168
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.promotion.service.combination;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -10,35 +9,31 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
-import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityExportReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductCreateReqVO;
-import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductBaseVO;
 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationRecordDO;
-import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationActivityMapper;
-import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationProductMapper;
-import cn.iocoder.yudao.module.promotion.dal.mysql.combination.combinationactivity.CombinationRecordMapper;
-import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationActivityMapper;
+import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationProductMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.module.promotion.util.PromotionUtils.validateProductSkuExistence;
+import static java.util.Collections.singletonList;
 
 /**
  * 拼团活动 Service 实现类
@@ -52,61 +47,77 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
     @Resource
     private CombinationActivityMapper combinationActivityMapper;
     @Resource
-    private CombinationRecordMapper recordMapper;
-    @Resource
     private CombinationProductMapper combinationProductMapper;
+
     @Resource
     private ProductSpuApi productSpuApi;
     @Resource
     private ProductSkuApi productSkuApi;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createCombinationActivity(CombinationActivityCreateReqVO createReqVO) {
         // 校验商品 SPU 是否存在是否参加的别的活动
-        validateProductCombinationConflict(createReqVO.getSpuId(), null);
-        // 获取所选 spu下的所有 sku
-        List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollectionUtil.newArrayList(createReqVO.getSpuId()));
-        // 校验商品 sku 是否存在
-        validateProductSkuExistence(skus, createReqVO.getProducts(), CombinationProductCreateReqVO::getSkuId);
+        validateProductConflict(createReqVO.getSpuId(), null);
+        // 校验商品是否存在
+        validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
 
-        // TODO 艿艿 有个小问题:现在有活动时间和限制时长,活动时间的结束时间早于设置的限制时间怎么算状态比如:
-        //  活动时间 2023-08-05 15:00:00 - 2023-08-05 15:20:00 限制时长 2小时,那么活动时间结束就结束还是加时到满两小时
         // 插入拼团活动
-        CombinationActivityDO activityDO = CombinationActivityConvert.INSTANCE.convert(createReqVO);
-        // TODO 营销相关属性初始化
-        activityDO.setTotalNum(0);
-        activityDO.setSuccessNum(0);
-        activityDO.setOrderUserCount(0);
-        activityDO.setVirtualGroup(0);
-        activityDO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        combinationActivityMapper.insert(activityDO);
+        CombinationActivityDO activity = CombinationActivityConvert.INSTANCE.convert(createReqVO)
+                .setStatus(CommonStatusEnum.ENABLE.getStatus())
+                .setTotalCount(0).setSuccessCount(0).setOrderUserCount(0).setVirtualGroup(0);
+        combinationActivityMapper.insert(activity);
         // 插入商品
-        List<CombinationProductDO> productDOs = CombinationActivityConvert.INSTANCE.convertList(activityDO, createReqVO.getProducts());
-        combinationProductMapper.insertBatch(productDOs);
+        List<CombinationProductDO> products = CombinationActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), activity);
+        combinationProductMapper.insertBatch(products);
         // 返回
-        return activityDO.getId();
+        return activity.getId();
     }
 
-    private void validateProductCombinationConflict(Long spuId, Long activityId) {
-        // 校验商品 spu 是否存在
-        List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(CollUtil.newArrayList(spuId));
-        if (CollUtil.isEmpty(spuList)) {
-            throw exception(SPU_NOT_EXISTS);
-        }
+    /**
+     * 校验拼团商品参与的活动是否存在冲突
+     *
+     * @param spuId 商品 SPU 编号
+     * @param activityId 拼团活动编号
+     */
+    private void validateProductConflict(Long spuId, Long activityId) {
         // 查询所有开启的拼团活动
-        List<CombinationActivityDO> activityDOs = combinationActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
-        // 更新时排除自己
-        if (activityId != null) {
-            activityDOs.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
+        List<CombinationActivityDO> activityList = combinationActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        if (activityId != null) { // 时排除自己
+            activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
         }
-        // 过滤出所有 spuIds 有交集的活动
-        List<CombinationActivityDO> doList = CollectionUtils.convertList(activityDOs, c -> c, s -> ObjectUtil.equal(s.getId(), spuId));
-        if (CollUtil.isNotEmpty(doList)) {
+        // 查找是否有其它活动,选择了该产品
+        List<CombinationActivityDO> matchActivityList = filterList(activityList, activity -> ObjectUtil.equal(activity.getId(), spuId));
+        if (CollUtil.isNotEmpty(matchActivityList)) {
             throw exception(COMBINATION_ACTIVITY_SPU_CONFLICTS);
         }
     }
 
+    /**
+     * 校验拼团商品是否都存在
+     *
+     * @param spuId 商品 SPU 编号
+     * @param products 秒杀商品
+     */
+    private void validateProductExists(Long spuId, List<CombinationProductBaseVO> products) {
+        // 1. 校验商品 spu 是否存在
+        ProductSpuRespDTO spu = productSpuApi.getSpu(spuId);
+        if (spu == null) {
+            throw exception(SPU_NOT_EXISTS);
+        }
+
+        // 2. 校验商品 sku 都存在
+        Map<Long, ProductSkuRespDTO> skuMap = convertMap(productSkuApi.getSkuListBySpuId(singletonList(spuId)),
+                ProductSkuRespDTO::getId);
+        products.forEach(product -> {
+            if (!skuMap.containsKey(product.getSkuId())) {
+                throw exception(SKU_NOT_EXISTS);
+            }
+        });
+    }
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateCombinationActivity(CombinationActivityUpdateReqVO updateReqVO) {
         // 校验存在
         CombinationActivityDO activityDO = validateCombinationActivityExists(updateReqVO.getId());
@@ -115,13 +126,11 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
             throw exception(COMBINATION_ACTIVITY_STATUS_DISABLE);
         }
         // 校验商品冲突
-        validateProductCombinationConflict(updateReqVO.getSpuId(), updateReqVO.getId());
-        // 获取所选 spu下的所有 sku
-        List<ProductSkuRespDTO> skus = productSkuApi.getSkuListBySpuId(CollectionUtil.newArrayList(updateReqVO.getSpuId()));
-        // 校验商品 sku 是否存在
-        validateProductSkuExistence(skus, updateReqVO.getProducts(), CombinationProductUpdateReqVO::getSkuId);
+        validateProductConflict(updateReqVO.getSpuId(), updateReqVO.getId());
+        // 校验商品是否存在
+        validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
 
-        // 更新
+        // 更新活动
         CombinationActivityDO updateObj = CombinationActivityConvert.INSTANCE.convert(updateReqVO);
         combinationActivityMapper.updateById(updateObj);
         // 更新商品
@@ -129,40 +138,37 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
     }
 
     /**
-     * 更新秒杀商品
-     *  TODO 更新商品要不要封装成通用方法
+     * 更新拼团商品
      *
-     * @param updateObj DO
-     * @param products  商品配置
+     * @param activity 拼团活动
+     * @param products  该活动的最新商品配置
      */
-    private void updateCombinationProduct(CombinationActivityDO updateObj, List<CombinationProductUpdateReqVO> products) {
-        List<CombinationProductDO> combinationProductDOs = combinationProductMapper.selectListByActivityIds(CollUtil.newArrayList(updateObj.getId()));
-        // 数据库中的活动商品
-        Set<Long> convertSet = CollectionUtils.convertSet(combinationProductDOs, CombinationProductDO::getSkuId);
-        // 前端传过来的活动商品
-        Set<Long> convertSet1 = CollectionUtils.convertSet(products, CombinationProductUpdateReqVO::getSkuId);
-        // 删除后台存在的前端不存在的商品
-        List<Long> d = CollectionUtils.filterList(convertSet, item -> !convertSet1.contains(item));
-        if (CollUtil.isNotEmpty(d)) {
-            combinationProductMapper.deleteBatchIds(d);
+    private void updateCombinationProduct(CombinationActivityDO activity, List<CombinationProductBaseVO> products) {
+        // 第一步,对比新老数据,获得添加、修改、删除的列表
+        List<CombinationProductDO> newList = CombinationActivityConvert.INSTANCE.convertList(products, activity);
+        List<CombinationProductDO> oldList = combinationProductMapper.selectListByActivityIds(CollUtil.newArrayList(activity.getId()));
+        List<List<CombinationProductDO>> diffList = CollectionUtils.diffList(oldList, newList, (oldVal, newVal) -> {
+            boolean same = ObjectUtil.equal(oldVal.getSkuId(), newVal.getSkuId());
+            if (same) {
+                newVal.setId(oldVal.getId());
+            }
+            return same;
+        });
+
+        // 第二步,批量添加、修改、删除
+        if (CollUtil.isNotEmpty(diffList.get(0))) {
+            combinationProductMapper.insertBatch(diffList.get(0));
         }
-        // 前端存在的后端不存在的商品
-        List<Long> c = CollectionUtils.filterList(convertSet1, item -> !convertSet.contains(item));
-        if (CollUtil.isNotEmpty(c)) {
-            List<CombinationProductUpdateReqVO> vos = CollectionUtils.filterList(products, item -> c.contains(item.getSkuId()));
-            List<CombinationProductDO> productDOs = CombinationActivityConvert.INSTANCE.convertList(updateObj, vos);
-            combinationProductMapper.insertBatch(productDOs);
+        if (CollUtil.isNotEmpty(diffList.get(1))) {
+            combinationProductMapper.updateBatch(diffList.get(1));
         }
-        // 更新已存在的商品
-        List<Long> u = CollectionUtils.filterList(convertSet1, convertSet::contains);
-        if (CollUtil.isNotEmpty(u)) {
-            List<CombinationProductUpdateReqVO> vos = CollectionUtils.filterList(products, item -> u.contains(item.getSkuId()));
-            List<CombinationProductDO> productDOs = CombinationActivityConvert.INSTANCE.convertList1(updateObj, vos, combinationProductDOs);
-            combinationProductMapper.updateBatch(productDOs);
+        if (CollUtil.isNotEmpty(diffList.get(2))) {
+            combinationProductMapper.deleteBatchIds(CollectionUtils.convertList(diffList.get(2), CombinationProductDO::getId));
         }
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteCombinationActivity(Long id) {
         // 校验存在
         CombinationActivityDO activityDO = validateCombinationActivityExists(id);
@@ -175,7 +181,8 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
         combinationActivityMapper.deleteById(id);
     }
 
-    private CombinationActivityDO validateCombinationActivityExists(Long id) {
+    @Override
+    public CombinationActivityDO validateCombinationActivityExists(Long id) {
         CombinationActivityDO activityDO = combinationActivityMapper.selectById(id);
         if (activityDO == null) {
             throw exception(COMBINATION_ACTIVITY_NOT_EXISTS);
@@ -188,99 +195,14 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
         return validateCombinationActivityExists(id);
     }
 
-    @Override
-    public List<CombinationActivityDO> getCombinationActivityList(Collection<Long> ids) {
-        return combinationActivityMapper.selectBatchIds(ids);
-    }
-
     @Override
     public PageResult<CombinationActivityDO> getCombinationActivityPage(CombinationActivityPageReqVO pageReqVO) {
         return combinationActivityMapper.selectPage(pageReqVO);
     }
 
     @Override
-    public List<CombinationActivityDO> getCombinationActivityList(CombinationActivityExportReqVO exportReqVO) {
-        return combinationActivityMapper.selectList(exportReqVO);
-    }
-
-    @Override
-    public List<CombinationProductDO> getProductsByActivityIds(Collection<Long> ids) {
-        return combinationProductMapper.selectListByActivityIds(ids);
-    }
-
-    @Override
-    public void updateRecordStatusByUserIdAndOrderId(Long userId, Long orderId, Integer status) {
-        // 校验拼团是否存在
-        CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
-
-        // 更新状态
-        recordDO.setStatus(status);
-        recordMapper.updateById(recordDO);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateRecordStatusAndStartTimeByUserIdAndOrderId(Long userId, Long orderId, Integer status, LocalDateTime startTime) {
-        CombinationRecordDO recordDO = validateCombinationRecord(userId, orderId);
-        // 更新状态
-        recordDO.setStatus(status);
-        // 更新开始时间
-        recordDO.setStartTime(startTime);
-        recordMapper.updateById(recordDO);
-
-        // 更新拼团参入人数
-        List<CombinationRecordDO> recordDOs = recordMapper.selectListByHeadIdAndStatus(recordDO.getHeadId(), status);
-        if (CollUtil.isNotEmpty(recordDOs)) {
-            recordDOs.forEach(item -> {
-                item.setUserCount(recordDOs.size());
-                // 校验拼团是否满足要求
-                if (recordDOs.size() >= recordDO.getUserSize()) {
-                    item.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
-                }
-            });
-        }
-        recordMapper.updateBatch(recordDOs);
-    }
-
-    private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
-        // 校验拼团是否存在
-        CombinationRecordDO recordDO = recordMapper.selectRecord(userId, orderId);
-        if (recordDO == null) {
-            throw exception(COMBINATION_RECORD_NOT_EXISTS);
-        }
-        return recordDO;
-    }
-
-    @Override
-    public void createRecord(CombinationRecordReqDTO reqDTO) {
-        // 校验拼团活动
-        CombinationActivityDO activity = validateCombinationActivityExists(reqDTO.getActivityId());
-        // TODO @puhui999:需要校验下,它当前是不是已经参加了该拼团;
-        // TODO @puhui999: 父拼团是否存在,是否已经满了
-
-        CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO);
-        record.setVirtualGroup(false);
-        // TODO @puhui999:过期时间,应该是 Date 哈;
-        record.setExpireTime(activity.getLimitDuration());
-        record.setUserSize(activity.getUserSize());
-        recordMapper.insert(record);
-    }
-
-    @Override
-    public boolean validateRecordStatusIsSuccess(Long userId, Long orderId) {
-        CombinationRecordDO record = validateCombinationRecord(userId, orderId);
-        // TODO @puhui999:可以搞个 getRecrod 方法,然后业务通过 CombinationRecordStatusEnum.isSuccess 方法校验
-        return ObjectUtil.equal(record.getStatus(), CombinationRecordStatusEnum.SUCCESS.getStatus());
-    }
-
-    // TODO @puhui999:status 传入进来搞哈;
-    /**
-     * APP 端获取开团记录
-     *
-     * @return 开团记录
-     */
-    public List<CombinationRecordDO> getRecordList() {
-        return recordMapper.selectListByStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
+    public List<CombinationProductDO> getCombinationProductsByActivityIds(Collection<Long> activityIds) {
+        return combinationProductMapper.selectListByActivityIds(activityIds);
     }
 
 }

Some files were not shown because too many files changed in this diff