Browse Source

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

jason 1 year atrás
parent
commit
2e207019b3
100 changed files with 2347 additions and 857 deletions
  1. 10 3
      sql/mysql/statistics.sql
  2. 17 2
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  3. 38 1
      yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java
  4. 3 4
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
  5. 16 7
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
  6. 3 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
  7. 7 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
  8. 20 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java
  9. 21 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPage2VO.java
  10. 5 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.http
  11. 4 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java
  12. 2 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java
  13. 32 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
  14. 18 16
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
  15. 17 16
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java
  16. 37 7
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java
  17. 39 21
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
  18. 20 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java
  19. 30 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/combination/CombinationRecordExpireJob.java
  20. 21 9
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
  21. 10 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
  22. 19 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java
  23. 158 42
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java
  24. 14 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
  25. 23 4
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
  26. 5 0
      yudao-module-mall/yudao-module-statistics-biz/pom.xml
  27. 31 16
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java
  28. 0 9
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/package-info.java
  29. 19 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseComparisonRespVO.java
  30. 19 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseReqVO.java
  31. 26 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseRespVO.java
  32. 0 7
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSexStatisticsRespVO.java
  33. 1 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSummaryRespVO.java
  34. 1 8
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberTerminalStatisticsRespVO.java
  35. 39 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/member/MemberStatisticsConvert.java
  36. 6 6
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/trade/TradeStatisticsConvert.java
  37. 23 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java
  38. 27 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java
  39. 37 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java
  40. 22 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java
  41. 23 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java
  42. 37 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java
  43. 5 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java
  44. 30 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java
  45. 31 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsServiceImpl.java
  46. 48 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java
  47. 113 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsServiceImpl.java
  48. 33 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java
  49. 46 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsServiceImpl.java
  50. 23 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java
  51. 28 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsServiceImpl.java
  52. 3 3
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/TradeBrokerageApi.java
  53. 31 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsServiceImpl.java
  54. 59 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java
  55. 53 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsServiceImpl.java
  56. 8 1
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java
  57. 30 33
      yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java
  58. 2 2
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/aftersale/dto/AfterSaleSummaryRespDTO.java
  59. 4 4
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/dto/TradeOrderSummaryRespDTO.java
  60. 3 2
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/WalletSummaryRespDTO.java
  61. 22 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/infra/ApiAccessLogStatisticsMapper.xml
  62. 37 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/member/MemberStatisticsMapper.xml
  63. 48 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/pay/PayWalletStatisticsMapper.xml
  64. 14 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/AfterSaleStatisticsMapper.xml
  65. 14 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/BrokerageStatisticsMapper.xml
  66. 64 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml
  67. 13 0
      yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeStatisticsMapper.xml
  68. 0 23
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/aftersale/TradeAfterSaleApi.java
  69. 1 21
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java
  70. 2 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  71. 0 28
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/aftersale/TradeAfterSaleApiImpl.java
  72. 0 27
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/TradeBrokerageApiImpl.java
  73. 2 14
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java
  74. 0 13
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java
  75. 0 11
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
  76. 13 17
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
  77. 0 12
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
  78. 0 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
  79. 0 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
  80. 0 7
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
  81. 11 12
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
  82. 5 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
  83. 34 200
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  84. 0 55
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
  85. 78 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java
  86. 118 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java
  87. 0 61
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
  88. 91 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java
  89. 42 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java
  90. 120 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java
  91. 32 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
  92. 46 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java
  93. 0 42
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
  94. 64 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java
  95. 10 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java
  96. 3 2
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java
  97. 8 5
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
  98. 5 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java
  99. 0 23
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java
  100. 0 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java

+ 10 - 3
sql/mysql/statistics.sql

@@ -28,16 +28,22 @@ CREATE INDEX trade_statistics_time_index
     ON trade_statistics (time);
 
 -- 菜单
-INSERT INTO `ruoyi-vue-pro`.system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
 VALUES ('统计管理', '', 1, 4, 0, '/statistics', 'ep:data-line', '', '');
 SELECT @parentId := LAST_INSERT_ID();
-INSERT INTO `ruoyi-vue-pro`.system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
-VALUES ('交易统计', '', 2, 4, @parentId, 'trade', 'fa-solid:credit-card', 'statistics/trade/index', 'TradeStatistics');
+-- 交易统计
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
+VALUES ('交易统计', '', 2, 1, @parentId, 'trade', 'fa-solid:credit-card', 'statistics/trade/index', 'TradeStatistics');
 SELECT @parentId := LAST_INSERT_ID();
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
 VALUES ('交易统计查询', 'statistics:trade:query', 3, 1, @parentId, '', '', '', 0);
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
 VALUES ('交易统计导出', 'statistics:trade:export', 3, 2, @parentId, '', '', '', 0);
+-- 会员统计
+INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name)
+VALUES ('会员统计', '', 2, 2, @parentId, 'member', 'ep:avatar', 'statistics/member/index', 'MemberStatistics');
+SELECT @parentId := LAST_INSERT_ID();
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('会员统计查询', 'statistics:member:query', 3, 1, @parentId, '', '', '', 0);
 
 

+ 17 - 2
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ArrayUtil;
 import com.google.common.collect.ImmutableMap;
 
 import java.util.*;
@@ -50,6 +51,13 @@ public class CollectionUtils {
         return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
     }
 
+    public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
+        if (ArrayUtil.isEmpty(from)) {
+            return new ArrayList<>();
+        }
+        return convertList(Arrays.asList(from), func);
+    }
+
     public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return new ArrayList<>();
@@ -64,6 +72,13 @@ public class CollectionUtils {
         return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
     }
 
+    public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
+        return map.values()
+                .stream()
+                .flatMap(List::stream)
+                .collect(Collectors.toList());
+    }
+
     public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
         if (CollUtil.isEmpty(from)) {
             return new HashSet<>();
@@ -162,8 +177,8 @@ public class CollectionUtils {
     /**
      * 对比老、新两个列表,找出新增、修改、删除的数据
      *
-     * @param oldList 老列表
-     * @param newList 新列表
+     * @param oldList  老列表
+     * @param newList  新列表
      * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
      *                 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
      * @return [新增列表、修改列表、删除列表]

+ 38 - 1
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java

@@ -7,12 +7,16 @@ import cn.hutool.core.text.csv.CsvUtil;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.ip.core.Area;
 import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
+import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 /**
  * 区域工具类
@@ -108,7 +112,7 @@ public class AreaUtils {
             // “递归”父节点
             area = area.getParent();
             if (area == null
-                || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
+                    || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
                 break;
             }
             sb.insert(0, separator);
@@ -116,4 +120,37 @@ public class AreaUtils {
         return sb.toString();
     }
 
+    /**
+     * 获取指定类型的区域列表
+     *
+     * @param type 区域类型
+     * @param func 转换函数
+     * @param <T>  结果类型
+     * @return 区域列表
+     */
+    public static <T> List<T> getByType(AreaTypeEnum type, Function<Area, T> func) {
+        return convertList(areas.values(), func, area -> type.getType().equals(area.getType()));
+    }
+
+    // TODO @疯狂:注释写下;
+    public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {
+        // TODO @疯狂:这种不要用 while true;因为万一脏数据,可能会死循环;可以转换成 for (int i = 0; i < Byte.MAX; i++) 一般是优先层级;
+        do {
+            Area area = AreaUtils.getArea(id);
+            if (area == null) {
+                return null;
+            }
+
+            if (type.getType().equals(area.getType())) {
+                return area.getId();
+            }
+
+            if (area.getParent() == null || area.getParent().getId() == null) {
+                return null;
+            }
+
+            id = area.getParent().getId();
+        } while (true);
+    }
+
 }

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

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.promotion.api.combination;
 
+import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
 
 import javax.validation.Valid;
 
-// TODO @芋艿:后面也再撸撸这几个接口
-
 /**
  * 拼团记录 API 接口
  *
@@ -29,9 +28,9 @@ public interface CombinationRecordApi {
      * 创建开团记录
      *
      * @param reqDTO 请求 DTO
-     * @return 开团记录编号
+     * @return key 开团记录编号、value 团长编号
      */
-    Long createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
+    KeyValue<Long, Long> createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
 
     /**
      * 查询拼团记录是否成功

+ 16 - 7
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java

@@ -10,13 +10,22 @@ import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinResp
 public interface SeckillActivityApi {
 
     /**
-     * 更新秒杀库存
+     * 更新秒杀库存(减少)
      *
-     * @param id 活动编号
-     * @param skuId      sku 编号
-     * @param count      数量
+     * @param id    活动编号
+     * @param skuId sku 编号
+     * @param count 数量(正数)
+     */
+    void updateSeckillStockDecr(Long id, Long skuId, Integer count);
+
+    /**
+     * 更新秒杀库存(增加)
+     *
+     * @param id    活动编号
+     * @param skuId sku 编号
+     * @param count 数量(正数)
      */
-    void updateSeckillStock(Long id, Long skuId, Integer count);
+    void updateSeckillStockIncr(Long id, Long skuId, Integer count);
 
     /**
      * 【下单前】校验是否参与秒杀活动
@@ -24,8 +33,8 @@ public interface SeckillActivityApi {
      * 如果校验失败,则抛出业务异常
      *
      * @param activityId 活动编号
-     * @param skuId SKU 编号
-     * @param count 数量
+     * @param skuId      SKU 编号
+     * @param count      数量
      * @return 秒杀信息
      */
     SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);

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

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.promotion.api.combination;
 
+import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@@ -28,8 +29,9 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
         recordService.validateCombinationRecord(userId, activityId, headId, skuId, count);
     }
 
+    // TODO @puhui999:搞个创建的 RespDTO 哈;
     @Override
-    public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
+    public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
         return recordService.createCombinationRecord(reqDTO);
     }
 

+ 7 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java

@@ -18,8 +18,13 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
     private SeckillActivityService activityService;
 
     @Override
-    public void updateSeckillStock(Long id, Long skuId, Integer count) {
-        activityService.updateSeckillStock(id, skuId, count);
+    public void updateSeckillStockDecr(Long id, Long skuId, Integer count) {
+        activityService.updateSeckillStockDecr(id, skuId, count);
+    }
+
+    @Override
+    public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
+        activityService.updateSeckillStockIncr(id, skuId, count);
     }
 
     @Override

+ 20 - 3
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.combination;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordSummaryVO;
 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
@@ -39,6 +40,9 @@ public class CombinationRecordController {
     @Lazy
     private CombinationRecordService combinationRecordService;
 
+    // TODO @puhui999:getBargainRecordPage 和 getBargainRecordPage 是不是可以合并;然后 CombinationRecordReqPageVO 加一个 headId;
+    // 然后如果 headId 非空,并且第一页,单独多查询一条 head ;放到第 0 个位置;相当于说,第一页特殊一点;
+
     @GetMapping("/page")
     @Operation(summary = "获得拼团记录分页")
     @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
@@ -46,6 +50,18 @@ public class CombinationRecordController {
         PageResult<CombinationRecordDO> recordPage = combinationRecordService.getCombinationRecordPage(pageVO);
         List<CombinationActivityDO> activities = combinationActivityService.getCombinationActivityListByIds(
                 convertSet(recordPage.getList(), CombinationRecordDO::getActivityId));
+        // TODO @puhui999:商品没读取
+        return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
+    }
+
+    @GetMapping("/page-by-headId")
+    @Operation(summary = "获得拼团记录分页")
+    @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
+    public CommonResult<PageResult<CombinationRecordPageItemRespVO>> getBargainRecordPage(@Valid CombinationRecordReqPage2VO pageVO) {
+        // 包含团长和团员的分页记录
+        PageResult<CombinationRecordDO> recordPage = combinationRecordService.getCombinationRecordPage2(pageVO);
+        List<CombinationActivityDO> activities = combinationActivityService.getCombinationActivityListByIds(
+                convertSet(recordPage.getList(), CombinationRecordDO::getActivityId));
         return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
     }
 
@@ -54,10 +70,11 @@ public class CombinationRecordController {
     @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
     public CommonResult<CombinationRecordSummaryVO> getCombinationRecordSummary() {
         CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO();
-        summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null)); // 获取所有拼团记录
+        summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null, null)); // 获取拼团用户参与数量
         summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录
-                CombinationRecordStatusEnum.SUCCESS.getStatus(), null));
-        summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(null, Boolean.TRUE));// 获取虚拟成团记录
+                CombinationRecordStatusEnum.SUCCESS.getStatus(), null, CombinationRecordDO.HEAD_ID_GROUP));
+        summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(// 获取虚拟成团记录
+                null, Boolean.TRUE, CombinationRecordDO.HEAD_ID_GROUP));
         return success(summaryVO);
     }
 

+ 21 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPage2VO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
+
+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;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 拼团记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CombinationRecordReqPage2VO extends PageParam {
+
+    @Schema(description = "团长编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "团长编号不能为空")
+    private Long headId;
+
+}

+ 5 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.http

@@ -0,0 +1,5 @@
+### /promotion/activity/list-by-spu-ids 获得多个商品,近期参与的每个活动
+GET {{appApi}}/promotion/activity/list-by-spu-ids?spuIds=222&spuIds=633
+Authorization: Bearer {{appToken}}
+Content-Type: application/json
+tenant-id: {{appTenentId}}

+ 4 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java

@@ -46,18 +46,18 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
 public class AppBargainRecordController {
 
     @Resource
+    private BargainHelpService bargainHelpService;
+    @Resource
     private BargainRecordService bargainRecordService;
     @Resource
     private BargainActivityService bargainActivityService;
-    @Resource
-    private BargainHelpService bargainHelpService;
 
     @Resource
+    private TradeOrderApi tradeOrderApi;
+    @Resource
     private MemberUserApi memberUserApi;
     @Resource
     private ProductSpuApi productSpuApi;
-    @Resource
-    private TradeOrderApi tradeOrderApi;
 
     @GetMapping("/get-summary")
     @Operation(summary = "获得砍价记录的概要信息", description = "用于小程序首页")

+ 2 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java

@@ -46,8 +46,8 @@ public class AppCombinationRecordController {
     @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
     public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() {
         AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO();
-        // 1. 获得拼团记录数量
-        Long count = combinationRecordService.getCombinationRecordCount(null, null);
+        // 1. 获得拼团参与用户数量
+        Long count = combinationRecordService.getCombinationRecordCount(null, null, null);
         if (count == 0) {
             summary.setAvatars(Collections.emptyList());
             summary.setUserCount(count);

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

@@ -28,7 +28,7 @@ import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
-import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -114,9 +114,6 @@ public interface CombinationActivityConvert {
                                         ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
         return convert(reqDTO).setVirtualGroup(false)
                 .setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中
-                .setStartTime(LocalDateTime.now()) // TODO @puhui999:想了下,这个 startTime 应该是团长的;
-                // TODO @puhui999:有团长的情况下,expireTime 应该是团长的;
-                .setExpireTime(activity.getStartTime().plusHours(activity.getLimitDuration()))
                 .setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录
                 // 用户信息
                 .setNickname(user.getNickname()).setAvatar(user.getAvatar())
@@ -200,4 +197,35 @@ public interface CombinationActivityConvert {
         return respVO;
     }
 
+    /**
+     * 转换生成虚拟成团虚拟记录
+     *
+     * @param virtualGroupHeadRecords 虚拟成团团长记录列表
+     * @return 虚拟记录列表
+     */
+    default List<CombinationRecordDO> convertVirtualGroupList(List<CombinationRecordDO> virtualGroupHeadRecords) {
+        List<CombinationRecordDO> createRecords = new ArrayList<>();
+        virtualGroupHeadRecords.forEach(headRecord -> {
+            // 计算需要创建的虚拟成团记录数量
+            int count = headRecord.getUserSize() - headRecord.getUserCount();
+            for (int i = 0; i < count; i++) {
+                // 基础信息和团长保持一致
+                CombinationRecordDO newRecord = new CombinationRecordDO().setActivityId(headRecord.getActivityId())
+                        .setCombinationPrice(headRecord.getCombinationPrice()).setSpuId(headRecord.getSpuId()).setSpuName(headRecord.getSpuName())
+                        .setPicUrl(headRecord.getPicUrl()).setSkuId(headRecord.getSkuId()).setHeadId(headRecord.getId())
+                        .setStatus(headRecord.getStatus()) // 状态保持和创建时一致,创建完成后会接着处理
+                        .setVirtualGroup(headRecord.getVirtualGroup()).setExpireTime(headRecord.getExpireTime())
+                        .setStartTime(headRecord.getStartTime()).setUserSize(headRecord.getUserSize()).setUserCount(headRecord.getUserCount());
+                // 虚拟信息
+                newRecord.setCount(0);
+                newRecord.setUserId(0L);
+                newRecord.setNickname("");
+                newRecord.setAvatar("");
+                newRecord.setOrderId(0L);
+                createRecords.add(newRecord);
+            }
+        });
+        return createRecords;
+    }
+
 }

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

@@ -6,13 +6,14 @@ 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.activity.BargainActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
 
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 砍价活动 Mapper
@@ -85,25 +86,26 @@ public interface BargainActivityMapper extends BaseMapperX<BargainActivityDO> {
                 .last("LIMIT " + count));
     }
 
-    // TODO @puhui999:一个商品,在统一时间,不会参与多个活动;so 是不是不用 inner join 哈?
-    // PS:如果可以参与多个,其实可以这样写 select * from promotion_bargain_activity group by spu_id ORDER BY create_time DESC;通过 group 来过滤
+    // TODO @puhui999:是不是返回 BargainActivityDO 更干净哈?
     /**
-     * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
+     * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
      *
      * @param spuIds spu 编号
      * @param status 状态
-     * @return 砍价活动列表
+     * @return 包含 spuId 和 activityId 的 map 对象列表
      */
-    @Select("SELECT p1.* " +
-            "FROM promotion_bargain_activity p1 " +
-            "INNER JOIN ( " +
-            "  SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
-            "  FROM promotion_bargain_activity " +
-            "  WHERE spu_id IN #{spuIds} " +
-            "  GROUP BY spu_id " +
-            ") p2 " +
-            "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
-            "ORDER BY p1.create_time DESC;")
-    List<BargainActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status);
+    default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
+        return selectMaps(new QueryWrapper<BargainActivityDO>()
+                .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
+                .in("spu_id", spuIds)
+                .eq("status", status)
+                .groupBy("spu_id"));
+    }
+
+    default List<BargainActivityDO> selectListByIds(Collection<Long> ids) {
+        return selectList(new LambdaQueryWrapperX<BargainActivityDO>()
+                .in(BargainActivityDO::getId, ids)
+                .orderByDesc(BargainActivityDO::getCreateTime));
+    }
 
 }

+ 17 - 16
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java

@@ -6,12 +6,13 @@ 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.CombinationActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.annotations.Select;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 拼团活动 Mapper
@@ -43,24 +44,24 @@ public interface CombinationActivityMapper extends BaseMapperX<CombinationActivi
                 .last("LIMIT " + count));
     }
 
-    // TODO @puhui999:类似 BargainActivityMapper
     /**
-     * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
-     *
+     * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
      * @param spuIds spu 编号
      * @param status 状态
-     * @return 拼团活动列表
+     * @return 包含 spuId 和 activityId 的 map 对象列表
      */
-    @Select("SELECT p1.* " +
-            "FROM promotion_combination_activity p1 " +
-            "INNER JOIN ( " +
-            "  SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
-            "  FROM promotion_combination_activity " +
-            "  WHERE spu_id IN #{spuIds} " +
-            "  GROUP BY spu_id " +
-            ") p2 " +
-            "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
-            "ORDER BY p1.create_time DESC;")
-    List<CombinationActivityDO> selectListBySpuIds(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status);
+    default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
+        return selectMaps(new QueryWrapper<CombinationActivityDO>()
+                .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
+                .in("spu_id", spuIds)
+                .eq("status", status)
+                .groupBy("spu_id"));
+    }
+
+    default List<CombinationActivityDO> selectListByIds(Collection<Long> ids) {
+        return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
+                .in(CombinationActivityDO::getId, ids)
+                .orderByDesc(CombinationActivityDO::getCreateTime));
+    }
 
 }

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

@@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 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.recrod.CombinationRecordReqPage2VO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -100,13 +102,41 @@ public interface CombinationRecordMapper extends BaseMapperX<CombinationRecordDO
                 .betweenIfPresent(CombinationRecordDO::getCreateTime, pageVO.getCreateTime()));
     }
 
-    // TODO @puhui999:这个最好把 headId 也作为一个参数;因为有个要求 userCount,它要 DISTINCT 下;整体可以参考 selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId
-    default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup) {
-        return selectCount(new LambdaQueryWrapperX<CombinationRecordDO>()
-                .eq(status != null || virtualGroup != null,
-                        CombinationRecordDO::getHeadId, CombinationRecordDO.HEAD_ID_GROUP) // 统计团信息则指定团长
-                .eqIfPresent(CombinationRecordDO::getStatus, status)
-                .eqIfPresent(CombinationRecordDO::getVirtualGroup, virtualGroup));
+    default PageResult<CombinationRecordDO> selectPage(CombinationRecordReqPage2VO pageVO) {
+        return selectPage(pageVO, new LambdaQueryWrapperX<CombinationRecordDO>()
+                .eq(CombinationRecordDO::getId, pageVO.getHeadId())
+                .or()
+                .eq(CombinationRecordDO::getHeadId, pageVO.getHeadId()));
+    }
+
+    /**
+     * 查询指定条件的记录数
+     * 如果参数都为 null 时则查询用户拼团记录(DISTINCT 去重),也就是说查询会员表中的用户有多少人参与过拼团活动每个人只统计一次
+     *
+     * @param status       状态,可为 null
+     * @param virtualGroup 是否虚拟成团,可为 null
+     * @param headId       团长编号,可为 null
+     * @return 记录数
+     */
+    default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup, Long headId) {
+        return selectCount(new QueryWrapper<CombinationRecordDO>()
+                // TODO @puhui999:这种偏逻辑性的,不要给 mapper 哈;可以考虑拆成 2 个 mapper,上层也是 2 个 service;
+                .select(status == null && virtualGroup == null && headId == null, "DISTINCT (user_id)")
+                .eq(status != null, "status", status)
+                .eq(virtualGroup != null, "virtual_group", virtualGroup)
+                .eq(headId != null, "head_id", headId)
+                .groupBy("user_id"));
+    }
+
+    default List<CombinationRecordDO> selectListByHeadIdAndStatusAndExpireTimeLt(Long headId, Integer status, LocalDateTime dateTime) {
+        return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
+                .eq(CombinationRecordDO::getHeadId, headId)
+                .eq(CombinationRecordDO::getStatus, status)
+                .lt(CombinationRecordDO::getExpireTime, dateTime));
+    }
+
+    default List<CombinationRecordDO> selectListByHeadIds(Collection<Long> headIds) {
+        return selectList(new LambdaQueryWrapperX<CombinationRecordDO>().in(CombinationRecordDO::getHeadId, headIds));
     }
 
 }

+ 39 - 21
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
 
+import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
@@ -7,12 +8,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 秒杀活动 Mapper
@@ -37,18 +40,32 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
     }
 
     /**
-     * 更新活动库存
+     * 更新活动库存(减少)
      *
      * @param id    活动编号
-     * @param count 扣减的库存数量
+     * @param count 扣减的库存数量(正数)
      * @return 影响的行数
      */
-    default int updateStock(Long id, int count) {
+    default int updateStockDecr(Long id, int count) {
+        Assert.isTrue(count > 0);
         return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
                 .eq(SeckillActivityDO::getId, id)
-                .gt(SeckillActivityDO::getTotalStock, 0)
-                .setSql("stock = stock + " + count)
-                .setSql("total_stock = total_stock - " + count));
+                .gt(SeckillActivityDO::getStock, count)
+                .setSql("stock = stock - " + count));
+    }
+
+    /**
+     * 更新活动库存(增加)
+     *
+     * @param id    活动编号
+     * @param count 增加的库存数量(正数)
+     * @return 影响的行数
+     */
+    default int updateStockIncr(Long id, int count) {
+        Assert.isTrue(count > 0);
+        return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
+                .eq(SeckillActivityDO::getId, id)
+                .setSql("stock = stock + " + count));
     }
 
     default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
@@ -58,24 +75,25 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
                 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
     }
 
-    // TODO @puhui999:类似 BargainActivityMapper
     /**
-     * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
+     * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
      *
      * @param spuIds spu 编号
      * @param status 状态
-     * @return 秒杀活动列表
+     * @return 包含 spuId 和 activityId 的 map 对象列表
      */
-    @Select("SELECT p1.* " +
-            "FROM promotion_seckill_activity p1 " +
-            "INNER JOIN ( " +
-            "  SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +
-            "  FROM promotion_seckill_activity " +
-            "  WHERE spu_id IN #{spuIds} " +
-            "  GROUP BY spu_id " +
-            ") p2 " +
-            "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +
-            "ORDER BY p1.create_time DESC;")
-    List<SeckillActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status);
+    default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
+        return selectMaps(new QueryWrapper<SeckillActivityDO>()
+                .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
+                .in("spu_id", spuIds)
+                .eq("status", status)
+                .groupBy("spu_id"));
+    }
+
+    default List<SeckillActivityDO> selectListByIds(Collection<Long> ids) {
+        return selectList(new LambdaQueryWrapperX<SeckillActivityDO>()
+                .in(SeckillActivityDO::getId, ids)
+                .orderByDesc(SeckillActivityDO::getCreateTime));
+    }
 
 }

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

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
 
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -30,17 +31,32 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
     }
 
     /**
-     * 更新活动库存
+     * 更新活动库存(减少)
      *
      * @param id    活动编号
-     * @param count 扣减的库存数量
+     * @param count 扣减的库存数量(减少库存)
      * @return 影响的行数
      */
-    default int updateStock(Long id, int count) {
+    default int updateStockDecr(Long id, int count) {
+        Assert.isTrue(count > 0);
         return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
                 .eq(SeckillProductDO::getId, id)
-                .gt(SeckillProductDO::getStock, count)
+                .ge(SeckillProductDO::getStock, count)
                 .setSql("stock = stock - " + count));
     }
 
+    /**
+     * 更新活动库存(增加)
+     *
+     * @param id    活动编号
+     * @param count 需要增加的库存(增加库存)
+     * @return 影响的行数
+     */
+    default int updateStockIncr(Long id, int count) {
+        Assert.isTrue(count > 0);
+        return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
+                .eq(SeckillProductDO::getId, id)
+                .setSql("stock = stock + " + count));
+    }
+
 }

+ 30 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/combination/CombinationRecordExpireJob.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.promotion.job.combination;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 拼团过期 Job
+ *
+ * @author HUIHUI
+ */
+@Component
+public class CombinationRecordExpireJob implements JobHandler {
+
+    @Resource
+    private CombinationRecordService combinationRecordService;
+
+    @Override
+    @TenantJob
+    public String execute(String param) {
+        KeyValue<Integer, Integer> keyValue = combinationRecordService.expireCombinationRecord();
+        return StrUtil.format("过期拼团 {} 个, 虚拟成团 {} 个", keyValue.getKey(), keyValue.getValue());
+    }
+
+}

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

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.promotion.service.bargain;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -20,12 +22,11 @@ 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.*;
 
 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.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 
@@ -84,10 +85,14 @@ public class BargainActivityServiceImpl implements BargainActivityService {
 
     @Override
     public void updateBargainActivityStock(Long id, Integer count) {
-        // 更新库存。如果更新失败,则抛出异常
-        int updateCount = bargainActivityMapper.updateStock(id, count);
-        if (updateCount == 0) {
-            throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH);
+        if (count < 0) {
+            // 更新库存。如果更新失败,则抛出异常
+            int updateCount = bargainActivityMapper.updateStock(id, count);
+            if (updateCount == 0) {
+                throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH);
+            }
+        } else if (count > 0) {
+            bargainActivityMapper.updateStock(id, count);
         }
     }
 
@@ -139,7 +144,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
 
     @Override
     public List<BargainActivityDO> getBargainActivityList(Set<Long> ids) {
-         return bargainActivityMapper.selectBatchIds(ids);
+        return bargainActivityMapper.selectBatchIds(ids);
     }
 
     @Override
@@ -178,7 +183,14 @@ public class BargainActivityServiceImpl implements BargainActivityService {
 
     @Override
     public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
-        return bargainActivityMapper.selectListBySpuIds(spuIds, status);
+        // 1. 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
+        // TODO @puhui999:我想了下,这种是不是只展示当前正在进行中的。已经结束、或者未开始的,可能没啥意义?
+        List<Map<String, Object>> spuIdAndActivityIdMaps = bargainActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
+        if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
+            return Collections.emptyList();
+        }
+        // 2. 查询活动详情
+        return bargainActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
     }
 
 }

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

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.promotion.service.combination;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
@@ -25,12 +26,12 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 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.framework.common.util.collection.CollectionUtils.*;
 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.*;
@@ -228,7 +229,13 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
 
     @Override
     public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
-        return combinationActivityMapper.selectListBySpuIds(spuIds, status);
+        // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
+        List<Map<String, Object>> spuIdAndActivityIdMaps = combinationActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
+        if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
+            return Collections.emptyList();
+        }
+        // 2.查询活动详情
+        return combinationActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
     }
 
 }

+ 19 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
@@ -49,9 +50,9 @@ public interface CombinationRecordService {
      * 创建拼团记录
      *
      * @param reqDTO 创建信息
-     * @return 开团记录编号
+     * @return key 开团记录编号 value 团长编号
      */
-    Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
+    KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
 
     /**
      * 获得拼团记录
@@ -90,9 +91,10 @@ public interface CombinationRecordService {
      *
      * @param status       状态-允许为空
      * @param virtualGroup 是否虚拟成团-允许为空
+     * @param headId       团长编号,允许空。目的 headId 设置为 {@link CombinationRecordDO#HEAD_ID_GROUP} 时,可以设置
      * @return 记录数
      */
-    Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup);
+    Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId);
 
     /**
      * 获取最近的 count 条拼团记录
@@ -137,6 +139,14 @@ public interface CombinationRecordService {
     PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO);
 
     /**
+     * 获取拼团记录分页数据(通过团长查询)
+     *
+     * @param pageVO 分页请求
+     * @return 拼团记录分页数据(包括团长的)
+     */
+    PageResult<CombinationRecordDO> getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO);
+
+    /**
      * 【拼团活动】获得拼团记录数量 Map
      *
      * @param activityIds 活动记录编号数组
@@ -148,7 +158,6 @@ public interface CombinationRecordService {
                                                               @Nullable Integer status,
                                                               @Nullable Long headId);
 
-
     /**
      * 获取拼团记录
      *
@@ -167,5 +176,11 @@ public interface CombinationRecordService {
      */
     void cancelCombinationRecord(Long userId, Long id, Long headId);
 
+    /**
+     * 处理过期拼团
+     *
+     * @return key 过期拼团数量, value 虚拟成团数量
+     */
+    KeyValue<Integer, Integer> expireCombinationRecord();
 
 }

+ 158 - 42
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.combination;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -13,6 +14,7 @@ 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.CombinationRecordCreateReqDTO;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
@@ -21,18 +23,18 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR
 import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper;
 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
 import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
-import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Nullable;
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@@ -49,23 +51,20 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 public class CombinationRecordServiceImpl implements CombinationRecordService {
 
     @Resource
-    @Lazy
     private CombinationActivityService combinationActivityService;
     @Resource
-    private CombinationRecordMapper recordMapper;
+    private CombinationRecordMapper combinationRecordMapper;
 
     @Resource
     private MemberUserApi memberUserApi;
     @Resource
-    @Lazy
     private ProductSpuApi productSpuApi;
     @Resource
-    @Lazy
     private ProductSkuApi productSkuApi;
+
     @Resource
     private TradeOrderApi tradeOrderApi;
 
-    // TODO @芋艿:在详细预览下;
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) {
@@ -73,13 +72,14 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         CombinationRecordDO record = validateCombinationRecord(userId, orderId);
 
         // 更新状态
+        // TODO @puhui999:不要整个更新,new 一个出来;why?例如说,两个线程都去更新,这样存在相互覆盖的问题
         record.setStatus(status);
-        recordMapper.updateById(record);
+        combinationRecordMapper.updateById(record);
     }
 
     private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
         // 校验拼团是否存在
-        CombinationRecordDO recordDO = recordMapper.selectByUserIdAndOrderId(userId, orderId);
+        CombinationRecordDO recordDO = combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId);
         if (recordDO == null) {
             throw exception(COMBINATION_RECORD_NOT_EXISTS);
         }
@@ -108,7 +108,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         // 2. 父拼团是否存在,是否已经满了
         if (headId != null) {
             // 2.1. 查询进行中的父拼团
-            CombinationRecordDO record = recordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
+            CombinationRecordDO record = combinationRecordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
             if (record == null) {
                 throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
             }
@@ -143,7 +143,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         }
 
         // 6.1 校验是否有拼团记录
-        List<CombinationRecordDO> recordList = recordMapper.selectListByUserIdAndActivityId(userId, activityId);
+        List<CombinationRecordDO> recordList = combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId);
         recordList.removeIf(record -> CombinationRecordStatusEnum.isFailed(record.getStatus())); // 取消的订单,不算数
         if (CollUtil.isEmpty(recordList)) { // 如果为空,说明可以参与,直接返回
             return new KeyValue<>(activity, product);
@@ -164,32 +164,34 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
+    public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
         // 1. 校验拼团活动
         KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(),
                 reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount());
 
-        // 2.1 组合数据创建拼团记录
+        // 2. 组合数据创建拼团记录
         MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
         ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
         ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
         CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku);
-        // 2.2 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP
+        // 2.1. 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP
         if (record.getHeadId() == null) {
-            record.setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
+            record.setStartTime(LocalDateTime.now())
+                    .setExpireTime(keyValue.getKey().getStartTime().plusHours(keyValue.getKey().getLimitDuration()))
+                    .setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
+        } else {
+            // 2.2.有团长的情况下需要设置开始时间和过期时间为团长的
+            CombinationRecordDO headRecord = combinationRecordMapper.selectByHeadId(record.getHeadId(),
+                    CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); // 查询进行中的父拼团
+            record.setStartTime(headRecord.getStartTime()).setExpireTime(headRecord.getExpireTime());
         }
-        recordMapper.insert(record);
+        combinationRecordMapper.insert(record);
 
-        if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
-            return record.getId();
+        // 3. 更新拼团记录
+        if (ObjUtil.notEqual(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
+            updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
         }
-
-        // TODO @puhui:是不是这里的更新,放到 order 模块那;支付完成后;
-        // 4、更新拼团相关信息到订单
-        tradeOrderApi.updateOrderCombinationInfo(record.getOrderId(), record.getActivityId(), record.getId(), record.getHeadId());
-        // 4、更新拼团记录
-        updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
-        return record.getId();
+        return new KeyValue<>(record.getId(), record.getHeadId());
     }
 
     /**
@@ -204,31 +206,33 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         if (CollUtil.isEmpty(records)) {
             return;
         }
-        CombinationRecordDO headRecord = recordMapper.selectById(headId);
+        CombinationRecordDO headRecord = combinationRecordMapper.selectById(headId);
 
         // 2. 批量更新记录
         List<CombinationRecordDO> updateRecords = new ArrayList<>();
         records.add(headRecord); // 加入团长,团长也需要更新
         boolean isFull = records.size() >= activity.getUserSize();
+        LocalDateTime now = LocalDateTime.now();
         records.forEach(item -> {
             CombinationRecordDO updateRecord = new CombinationRecordDO();
             updateRecord.setId(item.getId()).setUserCount(records.size());
             if (isFull) {
                 updateRecord.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
+                updateRecord.setEndTime(now);
             }
             updateRecords.add(updateRecord);
         });
-        recordMapper.updateBatch(updateRecords);
+        combinationRecordMapper.updateBatch(updateRecords);
     }
 
     @Override
     public CombinationRecordDO getCombinationRecord(Long userId, Long orderId) {
-        return recordMapper.selectByUserIdAndOrderId(userId, orderId);
+        return combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId);
     }
 
     @Override
     public List<CombinationRecordDO> getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) {
-        return recordMapper.selectListByUserIdAndActivityId(userId, activityId);
+        return combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId);
     }
 
     @Override
@@ -241,52 +245,57 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
     }
 
     @Override
-    public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup) {
-        return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup);
+    public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId) {
+        return combinationRecordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup, headId);
     }
 
     @Override
     public List<CombinationRecordDO> getLatestCombinationRecordList(int count) {
-        return recordMapper.selectLatestList(count);
+        return combinationRecordMapper.selectLatestList(count);
     }
 
     @Override
     public List<CombinationRecordDO> getHeadCombinationRecordList(Long activityId, Integer status, Integer count) {
-        return recordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status,
+        return combinationRecordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status,
                 CombinationRecordDO.HEAD_ID_GROUP, count);
     }
 
     @Override
     public CombinationRecordDO getCombinationRecordById(Long id) {
-        return recordMapper.selectById(id);
+        return combinationRecordMapper.selectById(id);
     }
 
     @Override
     public List<CombinationRecordDO> getCombinationRecordListByHeadId(Long headId) {
-        return recordMapper.selectList(CombinationRecordDO::getHeadId, headId);
+        return combinationRecordMapper.selectList(CombinationRecordDO::getHeadId, headId);
     }
 
     @Override
     public PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO) {
-        return recordMapper.selectPage(pageVO);
+        return combinationRecordMapper.selectPage(pageVO);
+    }
+
+    @Override
+    public PageResult<CombinationRecordDO> getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO) {
+        return combinationRecordMapper.selectPage(pageVO);
     }
 
     @Override
     public Map<Long, Integer> getCombinationRecordCountMapByActivity(Collection<Long> activityIds,
                                                                      @Nullable Integer status, @Nullable Long headId) {
-        return recordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
+        return combinationRecordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
     }
 
     @Override
     public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) {
-        return recordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
+        return combinationRecordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void cancelCombinationRecord(Long userId, Long id, Long headId) {
         // 删除记录
-        recordMapper.deleteById(id);
+        combinationRecordMapper.deleteById(id);
 
         // 需要更新的记录
         List<CombinationRecordDO> updateRecords = new ArrayList<>();
@@ -313,7 +322,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
             });
         } else { // 情况二:团员
             // 团长
-            CombinationRecordDO recordHead = recordMapper.selectById(headId);
+            CombinationRecordDO recordHead = combinationRecordMapper.selectById(headId);
             // 团员
             List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId);
             if (CollUtil.isEmpty(records)) {
@@ -329,7 +338,114 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
         }
 
         // 更新拼团记录
-        recordMapper.updateBatch(updateRecords);
+        combinationRecordMapper.updateBatch(updateRecords);
+    }
+
+    @Override
+    public KeyValue<Integer, Integer> expireCombinationRecord() {
+        // TODO @puhui999:数字一般是 1. 2. 这种格式哈
+        // 1。获取所有正在进行中的过期的父拼团
+        List<CombinationRecordDO> headExpireRecords = combinationRecordMapper.selectListByHeadIdAndStatusAndExpireTimeLt(
+                CombinationRecordDO.HEAD_ID_GROUP, CombinationRecordStatusEnum.IN_PROGRESS.getStatus(), LocalDateTime.now());
+        if (CollUtil.isEmpty(headExpireRecords)) {
+            return new KeyValue<>(0, 0);
+        }
+
+        // 2. 获取拼团活动
+        // TODO @puhui999:在自己模块里,变量可以简略点;例如说 activityList
+        List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityListByIds(
+                convertSet(headExpireRecords, CombinationRecordDO::getActivityId));
+        Map<Long, CombinationActivityDO> activityMap = convertMap(combinationActivities, CombinationActivityDO::getId);
+
+        // TODO @puhui999:job 一般不建议异步跑;因为可能下次跑,结果上次还没跑完;
+        // TODO 这里,我们可以每个 record 处理下;然后按照是否需要虚拟拼团,各搞一个方法逻辑 + 事务;这样,保证 job 里面尽量不要大事务,而是 n 个独立小事务的处理。
+        // 3. 校验是否虚拟成团
+        List<CombinationRecordDO> virtualGroupHeadRecords = new ArrayList<>(); // 虚拟成团
+        for (Iterator<CombinationRecordDO> iterator = headExpireRecords.iterator(); iterator.hasNext(); ) {
+            CombinationRecordDO record = iterator.next();
+            // 3.1 不匹配,则直接跳过
+            CombinationActivityDO activity = activityMap.get(record.getActivityId());
+            if (activity == null || !activity.getVirtualGroup()) { // 取不到活动的或者不是虚拟拼团的
+                continue;
+            }
+            // 3.2 匹配,则移除,添加到虚拟成团中,并结束寻找
+            virtualGroupHeadRecords.add(record);
+            iterator.remove();
+            break;
+        }
+
+        // 4.处理过期的拼团
+        getSelf().handleExpireRecord(headExpireRecords);
+        // 5.虚拟成团
+        getSelf().handleVirtualGroupRecord(virtualGroupHeadRecords);
+        return new KeyValue<>(headExpireRecords.size(), virtualGroupHeadRecords.size());
+    }
+
+    @Async
+    protected void handleExpireRecord(List<CombinationRecordDO> headExpireRecords) {
+        if (CollUtil.isEmpty(headExpireRecords)) {
+            return;
+        }
+
+        // 1.更新拼团记录
+        List<CombinationRecordDO> headsAndRecords = updateBatchCombinationRecords(headExpireRecords,
+                CombinationRecordStatusEnum.FAILED);
+        if (headsAndRecords == null) {
+            return;
+        }
+
+        // 2.订单取消 TODO 以现在的取消回滚逻辑好像只能循环了
+        headsAndRecords.forEach(item -> {
+            tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId());
+        });
+    }
+
+    @Async
+    protected void handleVirtualGroupRecord(List<CombinationRecordDO> virtualGroupHeadRecords) {
+        if (CollUtil.isEmpty(virtualGroupHeadRecords)) {
+            return;
+        }
+
+        // 1. 团员补齐
+        combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualGroupList(virtualGroupHeadRecords));
+        // 2. 更新拼团记录
+        updateBatchCombinationRecords(virtualGroupHeadRecords, CombinationRecordStatusEnum.SUCCESS);
+    }
+
+    private List<CombinationRecordDO> updateBatchCombinationRecords(List<CombinationRecordDO> headRecords, CombinationRecordStatusEnum status) {
+        // 1. 查询团成员
+        List<CombinationRecordDO> records = combinationRecordMapper.selectListByHeadIds(
+                convertSet(headRecords, CombinationRecordDO::getId));
+        if (CollUtil.isEmpty(records)) {
+            return null;
+        }
+        Map<Long, List<CombinationRecordDO>> recordsMap = convertMultiMap(records, CombinationRecordDO::getHeadId);
+        headRecords.forEach(item -> {
+            recordsMap.get(item.getId()).add(item); // 把团长加进团里
+        });
+        // 2.批量更新拼团记录 status 和 失败/成团时间
+        List<CombinationRecordDO> headsAndRecords = mergeValuesFromMap(recordsMap);
+        List<CombinationRecordDO> updateRecords = new ArrayList<>(headsAndRecords.size());
+        LocalDateTime now = LocalDateTime.now();
+        headsAndRecords.forEach(item -> {
+            CombinationRecordDO record = new CombinationRecordDO().setId(item.getId())
+                    .setStatus(status.getStatus()).setEndTime(now);
+            if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数
+                record.setUserCount(record.getUserSize());
+            }
+            updateRecords.add(record);
+        });
+        combinationRecordMapper.updateBatch(updateRecords);
+        return headsAndRecords;
+    }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private CombinationRecordServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
     }
 
 }

+ 14 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java

@@ -36,13 +36,22 @@ public interface SeckillActivityService {
     void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
 
     /**
-     * 更新秒杀库存
+     * 更新秒杀库存(减少)
      *
      * @param id    活动编号
      * @param skuId sku 编号
-     * @param count 数量
+     * @param count 数量(正数)
      */
-    void updateSeckillStock(Long id, Long skuId, Integer count);
+    void updateSeckillStockDecr(Long id, Long skuId, Integer count);
+
+    /**
+     * 更新秒杀库存(增加)
+     *
+     * @param id    活动编号
+     * @param skuId sku 编号
+     * @param count 数量(正数)
+     */
+    void updateSeckillStockIncr(Long id, Long skuId, Integer count);
 
     /**
      * 关闭秒杀活动
@@ -113,8 +122,8 @@ public interface SeckillActivityService {
      * 如果校验失败,则抛出业务异常
      *
      * @param activityId 活动编号
-     * @param skuId SKU 编号
-     * @param count 数量
+     * @param skuId      SKU 编号
+     * @param count      数量
      * @return 秒杀信息
      */
     SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);

+ 23 - 4
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.promotion.service.seckill;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -27,6 +29,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -154,7 +157,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateSeckillStock(Long id, Long skuId, Integer count) {
+    public void updateSeckillStockDecr(Long id, Long skuId, Integer count) {
         // 1.1 校验活动库存是否充足
         SeckillActivityDO seckillActivity = validateSeckillActivityExists(id);
         if (count > seckillActivity.getTotalStock()) {
@@ -167,18 +170,28 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
         }
 
         // 2.1 更新活动商品库存
-        int updateCount = seckillProductMapper.updateStock(product.getId(), count);
+        int updateCount = seckillProductMapper.updateStockDecr(product.getId(), count);
         if (updateCount == 0) {
             throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
         }
 
         // 2.2 更新活动库存
-        updateCount = seckillActivityMapper.updateStock(seckillActivity.getId(), count);
+        updateCount = seckillActivityMapper.updateStockDecr(seckillActivity.getId(), count);
         if (updateCount == 0) {
             throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
+        SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(id, skuId);
+        // 更新活动商品库存
+        seckillProductMapper.updateStockIncr(product.getId(), count);
+        // 更新活动库存
+        seckillActivityMapper.updateStockIncr(id, count);
+    }
+
     /**
      * 更新秒杀商品
      *
@@ -312,7 +325,13 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
 
     @Override
     public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
-        return seckillActivityMapper.selectListBySpuIds(spuIds, status);
+        // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
+        List<Map<String, Object>> spuIdAndActivityIdMaps = seckillActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
+        if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
+            return Collections.emptyList();
+        }
+        // 2.查询活动详情
+        return seckillActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
     }
 
 }

+ 5 - 0
yudao-module-mall/yudao-module-statistics-biz/pom.xml

@@ -59,6 +59,11 @@
             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
+        </dependency>
+
         <!-- Web 相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 31 - 16
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java

@@ -1,21 +1,25 @@
 package cn.iocoder.yudao.module.statistics.controller.admin.member;
 
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
-import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO;
-import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
-import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberTerminalStatisticsRespVO;
-import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*;
+import cn.iocoder.yudao.module.statistics.service.member.MemberStatisticsService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.annotation.Resource;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
 @Tag(name = "管理后台 - 会员统计")
 @RestController
 @RequestMapping("/statistics/member")
@@ -23,36 +27,47 @@ import java.util.List;
 @Slf4j
 public class MemberStatisticsController {
 
-    // TODO @疯狂:一个类似 getTradeTrendSummaryComparison 的接口
-    // TODO @疯狂:一个类似 getTradeStatisticsList 的接口
+    @Resource
+    private MemberStatisticsService memberStatisticsService;
 
     @GetMapping("/summary")
     @Operation(summary = "获得会员统计")
-    public CommonResult<TradeStatisticsComparisonRespVO<MemberSummaryRespVO>> getMemberSummary() {
-        // TODO 疯狂:目前先直接计算;
-        return null;
+    @PreAuthorize("@ss.hasPermission('statistics:member:query')")
+    public CommonResult<MemberSummaryRespVO> getMemberSummary() {
+        return success(memberStatisticsService.getMemberSummary());
+    }
+
+    @GetMapping("/analyse")
+    @Operation(summary = "获得会员分析数据")
+    @PreAuthorize("@ss.hasPermission('statistics:member:query')")
+    public CommonResult<MemberAnalyseRespVO> getMemberAnalyse(MemberAnalyseReqVO reqVO) {
+        return success(memberStatisticsService.getMemberAnalyse(
+                ArrayUtil.get(reqVO.getTimes(), 0), ArrayUtil.get(reqVO.getTimes(), 1)));
     }
 
     @GetMapping("/get-area-statistics-list")
     @Operation(summary = "按照省份,获得会员统计列表")
+    @PreAuthorize("@ss.hasPermission('statistics:member:query')")
     public CommonResult<List<MemberAreaStatisticsRespVO>> getMemberAreaStatisticsList() {
-        // TODO 疯狂:目前先直接计算,进行统计;后续再考虑优化
-        return null;
+        return success(memberStatisticsService.getMemberAreaStatisticsList());
     }
 
     @GetMapping("/get-sex-statistics-list")
     @Operation(summary = "按照性别,获得会员统计列表")
+    @PreAuthorize("@ss.hasPermission('statistics:member:query')")
     public CommonResult<List<MemberSexStatisticsRespVO>> getMemberSexStatisticsList() {
-        // TODO 疯狂:目前先直接计算,进行统计;后续再考虑优化
-        return null;
+        return success(memberStatisticsService.getMemberSexStatisticsList());
     }
 
     @GetMapping("/get-terminal-statistics-list")
     @Operation(summary = "按照终端,获得会员统计列表")
+    @PreAuthorize("@ss.hasPermission('statistics:member:query')")
     public CommonResult<List<MemberTerminalStatisticsRespVO>> getMemberTerminalStatisticsList() {
-        // TODO 疯狂:目前先直接计算,进行统计;后续再考虑优化
         // TODO 疯狂:这个可以晚点写,因为 user = = 上还没记录 terminal
-        return null;
+        List<MemberTerminalStatisticsRespVO> list = convertList(TerminalEnum.values(),
+                t -> new MemberTerminalStatisticsRespVO()
+                        .setTerminal(t.getTerminal()).setUserCount(t.getTerminal() * 100));
+        return success(list);
     }
 
 }

+ 0 - 9
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/package-info.java

@@ -1,9 +0,0 @@
-/**
- * TODO
- * 1. 会员总数据
- * 2. 性别统计
- * 3. 渠道统计
- * 4. 地域统计
- * 5. 会员概览
- */
-package cn.iocoder.yudao.module.statistics.controller.admin.member;

+ 19 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseComparisonRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 会员分析对照数据 Response VO")
+@Data
+public class MemberAnalyseComparisonRespVO {
+
+    @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer userCount;
+
+    @Schema(description = "活跃用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer activeUserCount;
+
+    @Schema(description = "充值会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "221")
+    private Integer rechargeUserCount;
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
+
+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;
+
+@Schema(description = "管理后台 - 会员分析 Request VO")
+@Data
+public class MemberAnalyseReqVO {
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Schema(description = "时间范围")
+    private LocalDateTime[] times;
+
+}

+ 26 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseRespVO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
+
+import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 会员分析 Response VO")
+@Data
+public class MemberAnalyseRespVO {
+
+    @Schema(description = "访客数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer visitorCount;
+
+    @Schema(description = "下单用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer orderUserCount;
+
+    @Schema(description = "成交用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer payUserCount;
+
+    @Schema(description = "客单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer atv;
+
+    @Schema(description = "对照数据", requiredMode = Schema.RequiredMode.REQUIRED)
+    private TradeStatisticsComparisonRespVO<MemberAnalyseComparisonRespVO> comparison;
+
+}

+ 0 - 7
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSexStatisticsRespVO.java

@@ -13,11 +13,4 @@ public class MemberSexStatisticsRespVO {
     @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer userCount;
 
-    @Schema(description = "订单创建数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    private Integer orderCreateCount;
-    @Schema(description = "订单支付数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
-    private Integer orderPayCount;
-    @Schema(description = "订单支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "622")
-    private Integer orderPayPrice;
-
 }

+ 1 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSummaryRespVO.java

@@ -15,6 +15,7 @@ public class MemberSummaryRespVO {
 
     @Schema(description = "充值金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer rechargePrice;
+
     @Schema(description = "支出金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer expensePrice;
 

+ 1 - 8
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberTerminalStatisticsRespVO.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.statistics.controller.admin.member.vo;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-@Schema(description = "管理后台 - 终端性别统计 Response VO")
+@Schema(description = "管理后台 - 会员终端统计 Response VO")
 @Data
 public class MemberTerminalStatisticsRespVO {
 
@@ -13,11 +13,4 @@ public class MemberTerminalStatisticsRespVO {
     @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer userCount;
 
-    @Schema(description = "订单创建数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    private Integer orderCreateCount;
-    @Schema(description = "订单支付数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
-    private Integer orderPayCount;
-    @Schema(description = "订单支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "622")
-    private Integer orderPayPrice;
-
 }

+ 39 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/member/MemberStatisticsConvert.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.statistics.convert.member;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.ip.core.Area;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 会员统计 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberStatisticsConvert {
+
+    MemberStatisticsConvert INSTANCE = Mappers.getMapper(MemberStatisticsConvert.class);
+
+    default List<MemberAreaStatisticsRespVO> convertList(List<Area> areaList,
+                                                         Map<Integer, Integer> userCountMap,
+                                                         Map<Integer, MemberAreaStatisticsRespVO> orderMap) {
+        return CollectionUtils.convertList(areaList, area -> {
+            MemberAreaStatisticsRespVO orderVo = Optional.ofNullable(orderMap.get(area.getId())).orElseGet(MemberAreaStatisticsRespVO::new);
+            return new MemberAreaStatisticsRespVO()
+                    .setAreaId(area.getId()).setAreaName(area.getName())
+                    .setUserCount(MapUtil.getInt(userCountMap, area.getId(), 0))
+                    .setOrderCreateCount(ObjUtil.defaultIfNull(orderVo.getOrderCreateCount(), 0))
+                    .setOrderPayCount(ObjUtil.defaultIfNull(orderVo.getOrderPayCount(), 0))
+                    .setOrderPayPrice(ObjUtil.defaultIfNull(orderVo.getOrderPayPrice(), 0));
+        });
+    }
+
+}

+ 6 - 6
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/trade/TradeStatisticsConvert.java

@@ -1,14 +1,14 @@
 package cn.iocoder.yudao.module.statistics.convert.trade;
 
-import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryExcelVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
 import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
 import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO;
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
-import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
@@ -46,8 +46,8 @@ public interface TradeStatisticsConvert {
 
     List<TradeTrendSummaryExcelVO> convertList02(List<TradeTrendSummaryRespVO> list);
 
-    TradeStatisticsDO convert(LocalDateTime time, TradeOrderSummaryRespDTO orderSummary,
-                              AfterSaleSummaryRespDTO afterSaleSummary, Integer brokerageSettlementPrice,
-                              WalletSummaryRespDTO walletSummary);
+    TradeStatisticsDO convert(LocalDateTime time, TradeOrderSummaryRespBO orderSummary,
+                              AfterSaleSummaryRespBO afterSaleSummary, Integer brokerageSettlementPrice,
+                              WalletSummaryRespBO walletSummary);
 
 }

+ 23 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.statistics.dal.mysql.infra;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+
+/**
+ * API 访问日志的统计 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface ApiAccessLogStatisticsMapper extends BaseMapperX<Object> {
+
+    Integer selectCountByIp(@Param("beginTime") LocalDateTime beginTime,
+                            @Param("endTime") LocalDateTime endTime);
+
+    Integer selectCountByUserId(@Param("beginTime") LocalDateTime beginTime,
+                                @Param("endTime") LocalDateTime endTime);
+
+}

+ 27 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.statistics.dal.mysql.member;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 会员信息的统计 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberStatisticsMapper extends BaseMapperX<Object> {
+
+    List<MemberAreaStatisticsRespVO> selectSummaryListByAreaId();
+
+    List<MemberSexStatisticsRespVO> selectSummaryListBySex();
+
+    Integer selectUserCount(@Param("beginTime") LocalDateTime beginTime,
+                            @Param("endTime") LocalDateTime endTime);
+
+}

+ 37 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.statistics.dal.mysql.pay;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
+import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+
+/**
+ * 支付钱包的统计 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface PayWalletStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
+
+    WalletSummaryRespBO selectRechargeSummaryByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                                              @Param("endTime") LocalDateTime endTime,
+                                                              @Param("payStatus") Boolean payStatus);
+
+    WalletSummaryRespBO selectRechargeSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                                                 @Param("endTime") LocalDateTime endTime,
+                                                                 @Param("refundStatus") Integer refundStatus);
+
+    Integer selectPriceSummaryByBizTypeAndCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                                            @Param("endTime") LocalDateTime endTime,
+                                                            @Param("bizType") Integer bizType);
+
+    // TODO @疯狂:是不是搞个单独的 BO 呀;
+    MemberSummaryRespVO selectRechargeSummaryGroupByWalletId(@Param("beginTime") LocalDateTime beginTime,
+                                                             @Param("endTime") LocalDateTime endTime,
+                                                             @Param("payStatus") Boolean payStatus);
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.statistics.dal.mysql.trade;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+
+/**
+ * 售后订单的统计 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface AfterSaleStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
+
+    AfterSaleSummaryRespBO selectSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                                            @Param("endTime") LocalDateTime endTime);
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.statistics.dal.mysql.trade;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+
+/**
+ * 订单分销的统计 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface BrokerageStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
+
+    Integer selectSummaryPriceByStatusAndUnfreezeTimeBetween(@Param("bizType") Integer bizType,
+                                                             @Param("status") Integer status,
+                                                             @Param("beginTime") LocalDateTime beginTime,
+                                                             @Param("endTime") LocalDateTime endTime);
+
+}

+ 37 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.statistics.dal.mysql.trade;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
+import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 交易订单的统计 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeOrderStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
+
+    List<MemberAreaStatisticsRespVO> selectSummaryListByAreaId();
+
+    Integer selectCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                           @Param("endTime") LocalDateTime endTime);
+
+    Integer selectCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                        @Param("endTime") LocalDateTime endTime);
+
+    Integer selectSummaryPriceByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                               @Param("endTime") LocalDateTime endTime);
+
+    Integer selectUserCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                               @Param("endTime") LocalDateTime endTime);
+
+    Integer selectUserCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                            @Param("endTime") LocalDateTime endTime);
+
+}

+ 5 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java

@@ -19,6 +19,7 @@ import java.util.List;
 @Mapper
 public interface TradeStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
 
+    // TODO @疯狂:这个要不要也挪到 xml 里,保持统一?
     @Select("SELECT IFNULL(SUM(order_create_count), 0) AS count, IFNULL(SUM(order_pay_price), 0) AS summary " +
             "FROM trade_statistics " +
             "WHERE time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
@@ -30,4 +31,8 @@ public interface TradeStatisticsMapper extends BaseMapperX<TradeStatisticsDO> {
 
     List<TradeTrendSummaryRespVO> selectListByTimeBetween(@Param("beginTime") LocalDateTime beginTime,
                                                           @Param("endTime") LocalDateTime endTime);
+
+    Integer selectExpensePriceByTimeBetween(@Param("beginTime") LocalDateTime beginTime,
+                                            @Param("endTime") LocalDateTime endTime);
+
 }

+ 30 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.statistics.service.infra;
+
+import java.time.LocalDateTime;
+
+/**
+ * API 访问日志的统计 Service 接口
+ *
+ * @author owen
+ */
+public interface ApiAccessLogStatisticsService {
+
+    /**
+     * 获取活跃用户数量
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 活跃用户数量
+     */
+    Integer getActiveUserCount(LocalDateTime beginTime, LocalDateTime endTime);
+
+    /**
+     * 获取访问用户数量
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 访问用户数量
+     */
+    Integer getVisitorUserCount(LocalDateTime beginTime, LocalDateTime endTime);
+
+}

+ 31 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsServiceImpl.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.statistics.service.infra;
+
+import cn.iocoder.yudao.module.statistics.dal.mysql.infra.ApiAccessLogStatisticsMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+/**
+ * API 访问日志的统计 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class ApiAccessLogStatisticsServiceImpl implements ApiAccessLogStatisticsService {
+
+    @Resource
+    private ApiAccessLogStatisticsMapper apiAccessLogStatisticsMapper;
+
+    @Override
+    public Integer getActiveUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
+        return apiAccessLogStatisticsMapper.selectCountByUserId(beginTime, endTime);
+    }
+
+    @Override
+    public Integer getVisitorUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
+        return apiAccessLogStatisticsMapper.selectCountByIp(beginTime, endTime);
+    }
+}

+ 48 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.statistics.service.member;
+
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAnalyseRespVO;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 会员信息的统计 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberStatisticsService {
+
+    /**
+     * 获取会员统计
+     *
+     * @return 会员统计
+     */
+    MemberSummaryRespVO getMemberSummary();
+
+    /**
+     * 按照省份,获得会员统计列表
+     *
+     * @return 会员统计列表
+     */
+    List<MemberAreaStatisticsRespVO> getMemberAreaStatisticsList();
+
+    /**
+     * 按照性别,获得会员统计列表
+     *
+     * @return 会员统计列表
+     */
+    List<MemberSexStatisticsRespVO> getMemberSexStatisticsList();
+
+    /**
+     * 获取用户分析数据
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 用户分析数据
+     */
+    MemberAnalyseRespVO getMemberAnalyse(LocalDateTime beginTime, LocalDateTime endTime);
+
+}

+ 113 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsServiceImpl.java

@@ -0,0 +1,113 @@
+package cn.iocoder.yudao.module.statistics.service.member;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
+import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*;
+import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
+import cn.iocoder.yudao.module.statistics.convert.member.MemberStatisticsConvert;
+import cn.iocoder.yudao.module.statistics.dal.mysql.member.MemberStatisticsMapper;
+import cn.iocoder.yudao.module.statistics.service.infra.ApiAccessLogStatisticsService;
+import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService;
+import cn.iocoder.yudao.module.statistics.service.trade.TradeOrderStatisticsService;
+import cn.iocoder.yudao.module.statistics.service.trade.TradeStatisticsService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 会员信息的统计 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberStatisticsServiceImpl implements MemberStatisticsService {
+
+    @Resource
+    private MemberStatisticsMapper memberStatisticsMapper;
+
+    @Resource
+    private PayWalletStatisticsService payWalletStatisticsService;
+    @Resource
+    private TradeStatisticsService tradeStatisticsService;
+    @Resource
+    private TradeOrderStatisticsService tradeOrderStatisticsService;
+    @Resource
+    private ApiAccessLogStatisticsService apiAccessLogStatisticsService;
+
+    @Override
+    public MemberSummaryRespVO getMemberSummary() {
+        MemberSummaryRespVO vo = payWalletStatisticsService.getUserRechargeSummary(null, null);
+        Integer expensePrice = tradeStatisticsService.getExpensePrice(null, null);
+        Integer userCount = memberStatisticsMapper.selectUserCount(null, null);
+        // 拼接数据
+        if (vo == null) {
+            vo = new MemberSummaryRespVO().setRechargeUserCount(0).setRechargePrice(0);
+        }
+        return vo.setUserCount(userCount).setExpensePrice(expensePrice);
+    }
+
+    @Override
+    public List<MemberAreaStatisticsRespVO> getMemberAreaStatisticsList() {
+        // 统计用户
+        Map<Integer, Integer> userCountMap = convertMap(memberStatisticsMapper.selectSummaryListByAreaId(),
+                vo -> AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE),
+                MemberAreaStatisticsRespVO::getUserCount, Integer::sum);
+        // 统计订单
+        Map<Integer, MemberAreaStatisticsRespVO> orderMap = convertMap(tradeOrderStatisticsService.getSummaryListByAreaId(),
+                vo -> AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE),
+                vo -> vo,
+                (a, b) -> new MemberAreaStatisticsRespVO()
+                        .setOrderCreateCount(a.getOrderCreateCount() + b.getOrderCreateCount())
+                        .setOrderPayCount(a.getOrderPayCount() + b.getOrderPayCount())
+                        .setOrderPayPrice(a.getOrderPayPrice() + b.getOrderPayPrice()));
+        // 拼接数据
+        return MemberStatisticsConvert.INSTANCE.convertList(AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area), userCountMap, orderMap);
+    }
+
+    @Override
+    public List<MemberSexStatisticsRespVO> getMemberSexStatisticsList() {
+        return memberStatisticsMapper.selectSummaryListBySex();
+    }
+
+    @Override
+    public MemberAnalyseRespVO getMemberAnalyse(LocalDateTime beginTime, LocalDateTime endTime) {
+        // 对照数据
+        MemberAnalyseComparisonRespVO vo = getMemberAnalyseComparisonData(beginTime, endTime);
+        LocalDateTime referenceBeginTime = beginTime.minus(Duration.between(beginTime, endTime));
+        MemberAnalyseComparisonRespVO reference = getMemberAnalyseComparisonData(referenceBeginTime, beginTime);
+
+        Integer payUserCount = tradeOrderStatisticsService.getPayUserCount(beginTime, endTime);
+        // 计算客单价
+        int atv = 0;
+        if (payUserCount != null && payUserCount > 0) {
+            Integer payPrice = tradeOrderStatisticsService.getOrderPayPrice(beginTime, endTime);
+            atv = NumberUtil.div(payPrice, payUserCount).intValue();
+        }
+        return new MemberAnalyseRespVO()
+                .setVisitorCount(apiAccessLogStatisticsService.getVisitorUserCount(beginTime, endTime))
+                .setOrderUserCount(tradeOrderStatisticsService.getOrderUserCount(beginTime, endTime))
+                .setPayUserCount(payUserCount)
+                .setAtv(atv)
+                .setComparison(new TradeStatisticsComparisonRespVO<>(vo, reference));
+    }
+
+    private MemberAnalyseComparisonRespVO getMemberAnalyseComparisonData(LocalDateTime beginTime, LocalDateTime endTime) {
+        Integer rechargeUserCount = Optional.ofNullable(payWalletStatisticsService.getUserRechargeSummary(beginTime, endTime))
+                .map(MemberSummaryRespVO::getRechargeUserCount).orElse(0);
+        return new MemberAnalyseComparisonRespVO()
+                .setUserCount(memberStatisticsMapper.selectUserCount(beginTime, endTime))
+                .setActiveUserCount(apiAccessLogStatisticsService.getActiveUserCount(beginTime, endTime))
+                .setRechargeUserCount(rechargeUserCount);
+    }
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.statistics.service.pay;
+
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
+
+import java.time.LocalDateTime;
+
+/**
+ * 钱包的统计 Service 接口
+ *
+ * @author owen
+ */
+public interface PayWalletStatisticsService {
+
+    /**
+     * 获取钱包统计
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 钱包统计
+     */
+    WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime);
+
+    /**
+     * 获取钱包充值统计
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 钱包充值统计
+     */
+    MemberSummaryRespVO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime);
+
+}

+ 46 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsServiceImpl.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.statistics.service.pay;
+
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO;
+import cn.iocoder.yudao.module.statistics.dal.mysql.pay.PayWalletStatisticsMapper;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+/**
+ * 钱包的统计 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class PayWalletStatisticsServiceImpl implements PayWalletStatisticsService {
+
+    @Resource
+    private PayWalletStatisticsMapper payWalletStatisticsMapper;
+
+    @Override
+    public WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime) {
+        WalletSummaryRespBO paySummary = payWalletStatisticsMapper.selectRechargeSummaryByPayTimeBetween(
+                beginTime, endTime, true);
+        WalletSummaryRespBO refundSummary = payWalletStatisticsMapper.selectRechargeSummaryByRefundTimeBetween(
+                beginTime, endTime, PayRefundStatusEnum.SUCCESS.getStatus());
+        Integer walletPayPrice = payWalletStatisticsMapper.selectPriceSummaryByBizTypeAndCreateTimeBetween(
+                beginTime, endTime, PayWalletBizTypeEnum.PAYMENT.getType());
+        // 拼接
+        paySummary.setOrderWalletPayPrice(walletPayPrice)
+                .setRechargeRefundCount(refundSummary.getRechargeRefundCount())
+                .setRechargeRefundPrice(refundSummary.getRechargeRefundPrice());
+        return paySummary;
+    }
+
+    @Override
+    public MemberSummaryRespVO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime) {
+        return payWalletStatisticsMapper.selectRechargeSummaryGroupByWalletId(beginTime, endTime, true);
+    }
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.statistics.service.trade;
+
+import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
+
+import java.time.LocalDateTime;
+
+/**
+ * 售后统计 Service 接口
+ *
+ * @author owen
+ */
+public interface AfterSaleStatisticsService {
+
+    /**
+     * 获取售后单统计
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 售后统计结果
+     */
+    AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime);
+
+}

+ 28 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsServiceImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.statistics.service.trade;
+
+import cn.iocoder.yudao.module.statistics.dal.mysql.trade.AfterSaleStatisticsMapper;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+/**
+ * 售后统计 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class AfterSaleStatisticsServiceImpl implements AfterSaleStatisticsService {
+
+    @Resource
+    private AfterSaleStatisticsMapper afterSaleStatisticsMapper;
+
+    @Override
+    public AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) {
+        return afterSaleStatisticsMapper.selectSummaryByRefundTimeBetween(beginTime, endTime);
+    }
+
+}

+ 3 - 3
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/TradeBrokerageApi.java

@@ -1,13 +1,13 @@
-package cn.iocoder.yudao.module.trade.api.brokerage;
+package cn.iocoder.yudao.module.statistics.service.trade;
 
 import java.time.LocalDateTime;
 
 /**
- * 分销 API 接口
+ * 分销统计 Service 接口
  *
  * @author owen
  */
-public interface TradeBrokerageApi {
+public interface BrokerageStatisticsService {
 
     /**
      * 获取已结算的佣金金额

+ 31 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsServiceImpl.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.statistics.service.trade;
+
+import cn.iocoder.yudao.module.statistics.dal.mysql.trade.BrokerageStatisticsMapper;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+/**
+ * 分销统计 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class BrokerageStatisticsServiceImpl implements BrokerageStatisticsService {
+
+    @Resource
+    private BrokerageStatisticsMapper brokerageStatisticsMapper;
+
+    @Override
+    public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) {
+        return brokerageStatisticsMapper.selectSummaryPriceByStatusAndUnfreezeTimeBetween(
+                BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
+                beginTime, endTime);
+    }
+
+}

+ 59 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.statistics.service.trade;
+
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 交易订单的统计 Service 接口
+ *
+ * @author owen
+ */
+public interface TradeOrderStatisticsService {
+
+    /**
+     * 获取订单统计
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 订单统计结果
+     */
+    TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime);
+
+    /**
+     * 获取地区订单统计
+     *
+     * @return 订单统计结果
+     */
+    List<MemberAreaStatisticsRespVO> getSummaryListByAreaId();
+
+    /**
+     * 获取下单用户数量
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 支付下单数量
+     */
+    Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime);
+
+    /**
+     * 获取支付用户数量
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 支付用户数量
+     */
+    Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime);
+
+    /**
+     * 获取支付金额
+     *
+     * @param beginTime 起始时间
+     * @param endTime   截止时间
+     * @return 支付用户金额
+     */
+    Integer getOrderPayPrice(LocalDateTime beginTime, LocalDateTime endTime);
+
+}

+ 53 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsServiceImpl.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.statistics.service.trade;
+
+import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO;
+import cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeOrderStatisticsMapper;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 交易订单统计 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class TradeOrderStatisticsServiceImpl implements TradeOrderStatisticsService {
+
+    @Resource
+    private TradeOrderStatisticsMapper tradeOrderStatisticsMapper;
+
+    @Override
+    public TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) {
+        return new TradeOrderSummaryRespBO()
+                .setOrderCreateCount(tradeOrderStatisticsMapper.selectCountByCreateTimeBetween(beginTime, endTime))
+                .setOrderPayCount(tradeOrderStatisticsMapper.selectCountByPayTimeBetween(beginTime, endTime))
+                .setOrderPayPrice(tradeOrderStatisticsMapper.selectSummaryPriceByPayTimeBetween(beginTime, endTime));
+    }
+
+    @Override
+    public List<MemberAreaStatisticsRespVO> getSummaryListByAreaId() {
+        return tradeOrderStatisticsMapper.selectSummaryListByAreaId();
+    }
+
+    @Override
+    public Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
+        return tradeOrderStatisticsMapper.selectUserCountByCreateTimeBetween(beginTime, endTime);
+    }
+
+    @Override
+    public Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime) {
+        return tradeOrderStatisticsMapper.selectUserCountByPayTimeBetween(beginTime, endTime);
+    }
+
+    @Override
+    public Integer getOrderPayPrice(LocalDateTime beginTime, LocalDateTime endTime) {
+        return tradeOrderStatisticsMapper.selectSummaryPriceByPayTimeBetween(beginTime, endTime);
+    }
+
+}

+ 8 - 1
yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java

@@ -22,7 +22,7 @@ public interface TradeStatisticsService {
     TradeStatisticsComparisonRespVO<TradeSummaryRespVO> getTradeSummaryComparison();
 
     /**
-     * 获得交易状况统计
+     * 获得交易状况统计对照
      *
      * @return 统计数据对照
      */
@@ -30,6 +30,13 @@ public interface TradeStatisticsService {
             LocalDateTime beginTime, LocalDateTime endTime);
 
     /**
+     * 获得交易状况统计
+     *
+     * @return 统计数据对照
+     */
+    Integer getExpensePrice(LocalDateTime beginTime, LocalDateTime endTime);
+
+    /**
      * 获得交易状况明细
      *
      * @return 统计数据列表

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

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.statistics.service.trade;
 
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
-import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
+import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
 import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
@@ -11,11 +11,8 @@ import cn.iocoder.yudao.module.statistics.convert.trade.TradeStatisticsConvert;
 import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO;
 import cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeStatisticsMapper;
 import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO;
-import cn.iocoder.yudao.module.trade.api.aftersale.TradeAfterSaleApi;
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
-import cn.iocoder.yudao.module.trade.api.brokerage.TradeBrokerageApi;
-import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
-import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO;
+import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StopWatch;
 import org.springframework.validation.annotation.Validated;
@@ -37,31 +34,26 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
     @Resource
     private TradeStatisticsMapper tradeStatisticsMapper;
 
-    // TODO @疯狂:统计逻辑,自己服务 mapper 去统计,不要调用其它服务 API;
-    // 主要的考虑点,其它服务是在线的业务,统计是离线业务,尽量不占用他们的 db 资源;
-    // 统计服务,从建议使用从库,或者从 mysql 抽取到单独的 clickhouse 或者其它的大数据组件;
-
     @Resource
-    private TradeOrderApi tradeOrderApi;
+    private TradeOrderStatisticsService tradeOrderStatisticsService;
     @Resource
-    private TradeAfterSaleApi tradeAfterSaleApi;
+    private AfterSaleStatisticsService afterSaleStatisticsService;
     @Resource
-    private TradeBrokerageApi tradeBrokerageApi;
+    private BrokerageStatisticsService brokerageStatisticsService;
     @Resource
-    private PayWalletApi payWalletApi;
+    private PayWalletStatisticsService payWalletStatisticsService;
 
     @Override
     public TradeStatisticsComparisonRespVO<TradeSummaryRespVO> getTradeSummaryComparison() {
-        // 昨天的数据
+        // 1.1 昨天的数据
         TradeSummaryRespBO yesterdayData = getTradeSummaryByDays(-1);
-        // 前天的数据(用于对照昨天的数据)
+        // 1.2 前天的数据(用于对照昨天的数据)
         TradeSummaryRespBO beforeYesterdayData = getTradeSummaryByDays(-2);
-
-        // 本月数据;
+        // 2.1 本月数据
         TradeSummaryRespBO monthData = getTradeSummaryByMonths(0);
-        // 上月数据(用于对照本月的数据)
+        // 2.2 上月数据(用于对照本月的数据)
         TradeSummaryRespBO lastMonthData = getTradeSummaryByMonths(-1);
-
+        // 转换返回
         return TradeStatisticsConvert.INSTANCE.convert(yesterdayData, beforeYesterdayData, monthData, lastMonthData);
     }
 
@@ -77,37 +69,42 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
     }
 
     @Override
+    public Integer getExpensePrice(LocalDateTime beginTime, LocalDateTime endTime) {
+        return tradeStatisticsMapper.selectExpensePriceByTimeBetween(beginTime, endTime);
+    }
+
+    @Override
     public List<TradeTrendSummaryRespVO> getTradeStatisticsList(LocalDateTime beginTime, LocalDateTime endTime) {
         return tradeStatisticsMapper.selectListByTimeBetween(beginTime, endTime);
     }
 
     @Override
     public String statisticsYesterdayTrade() {
-        // 处理统计参数
         LocalDateTime yesterday = LocalDateTime.now().minusDays(1);
         LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
         LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
-        // 统计
+        // 1.1 统计订单
         StopWatch stopWatch = new StopWatch("交易统计");
         stopWatch.start("统计订单");
-        TradeOrderSummaryRespDTO orderSummary = tradeOrderApi.getOrderSummary(beginTime, endTime);
+        TradeOrderSummaryRespBO orderSummary = tradeOrderStatisticsService.getOrderSummary(beginTime, endTime);
         stopWatch.stop();
-
+        // 1.2 统计售后
         stopWatch.start("统计售后");
-        AfterSaleSummaryRespDTO afterSaleSummary = tradeAfterSaleApi.getAfterSaleSummary(beginTime, endTime);
+        AfterSaleSummaryRespBO afterSaleSummary = afterSaleStatisticsService.getAfterSaleSummary(beginTime, endTime);
         stopWatch.stop();
-
+        // 1.3 统计佣金
         stopWatch.start("统计佣金");
-        Integer brokerageSettlementPrice = tradeBrokerageApi.getBrokerageSettlementPriceSummary(beginTime, endTime);
+        Integer brokerageSettlementPrice = brokerageStatisticsService.getBrokerageSettlementPriceSummary(beginTime, endTime);
         stopWatch.stop();
-
+        // 1.4 统计充值
         stopWatch.start("统计充值");
-        WalletSummaryRespDTO walletSummary = payWalletApi.getWalletSummary(beginTime, endTime);
+        WalletSummaryRespBO walletSummary = payWalletStatisticsService.getWalletSummary(beginTime, endTime);
         stopWatch.stop();
-        // 插入数据
-        TradeStatisticsDO entity = TradeStatisticsConvert.INSTANCE.convert(yesterday, orderSummary, afterSaleSummary, brokerageSettlementPrice, walletSummary);
+
+        // 2. 插入数据
+        TradeStatisticsDO entity = TradeStatisticsConvert.INSTANCE.convert(yesterday, orderSummary, afterSaleSummary,
+                brokerageSettlementPrice, walletSummary);
         tradeStatisticsMapper.insert(entity);
-        // 返回计时结果
         return stopWatch.prettyPrint();
     }
 

+ 2 - 2
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/aftersale/dto/AfterSaleSummaryRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.trade.api.aftersale.dto;
+package cn.iocoder.yudao.module.statistics.service.trade.bo;
 
 import lombok.Data;
 
@@ -8,7 +8,7 @@ import lombok.Data;
  * @author owen
  */
 @Data
-public class AfterSaleSummaryRespDTO {
+public class AfterSaleSummaryRespBO {
 
     /**
      * 退款订单数

+ 4 - 4
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/dto/TradeOrderSummaryRespDTO.java

@@ -1,19 +1,19 @@
-package cn.iocoder.yudao.module.trade.api.order.dto;
+package cn.iocoder.yudao.module.statistics.service.trade.bo;
 
 import lombok.Data;
 
 /**
- * 订单统计 Response DTO
+ * 订单统计 Response BO
  *
  * @author owen
  */
 @Data
-public class TradeOrderSummaryRespDTO {
+public class TradeOrderSummaryRespBO {
 
     /**
      * 创建订单数
      */
-    private Long orderCreateCount;
+    private Integer orderCreateCount;
     /**
      * 支付订单商品数
      */

+ 3 - 2
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/WalletSummaryRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.pay.api.wallet.dto;
+package cn.iocoder.yudao.module.statistics.service.trade.bo;
 
 import lombok.Data;
 
@@ -8,7 +8,8 @@ import lombok.Data;
  * @author owen
  */
 @Data
-public class WalletSummaryRespDTO {
+public class WalletSummaryRespBO {
+
     /**
      * 总支付金额(余额),单位:分
      */

+ 22 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/infra/ApiAccessLogStatisticsMapper.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.statistics.dal.mysql.infra.ApiAccessLogStatisticsMapper">
+
+    <select id="selectCountByIp" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM infra_api_access_log
+        WHERE create_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+        GROUP BY user_ip
+    </select>
+
+    <select id="selectCountByUserId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM infra_api_access_log
+        WHERE user_id > 0
+          AND create_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+        GROUP BY user_id
+    </select>
+
+</mapper>

+ 37 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/member/MemberStatisticsMapper.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.statistics.dal.mysql.member.MemberStatisticsMapper">
+
+    <select id="selectSummaryListByAreaId"
+            resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO">
+        SELECT area_id, COUNT(1) AS userCount
+        FROM member_user
+        WHERE deleted = FALSE
+        GROUP BY area_id
+        <!-- TODO @疯狂:order by 是不是交给内存哈 -->
+        ORDER BY userCount DESC
+    </select>
+
+    <select id="selectSummaryListBySex"
+            resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO">
+        SELECT sex, COUNT(1) AS userCount
+        FROM member_user
+        WHERE deleted = FALSE
+        GROUP BY sex
+        <!-- TODO @疯狂:order by 是不是交给内存哈 -->
+        ORDER BY userCount DESC
+    </select>
+
+    <select id="selectUserCount" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM member_user
+        WHERE deleted = FALSE
+        <if test="beginTime != null">
+            AND create_time >= #{beginTime}
+        </if>
+        <if test="endTime != null">
+            AND create_time &lt;= #{endTime}
+        </if>
+    </select>
+
+</mapper>

+ 48 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/pay/PayWalletStatisticsMapper.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.statistics.dal.mysql.pay.PayWalletStatisticsMapper">
+    <select id="selectRechargeSummaryByPayTimeBetween"
+            resultType="cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO">
+        SELECT COUNT(1)       AS rechargePayCount,
+               SUM(pay_price) AS rechargePayPrice
+        FROM pay_wallet_recharge
+        WHERE pay_status = #{payStatus}
+          AND pay_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+    <select id="selectRechargeSummaryByRefundTimeBetween"
+            resultType="cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO">
+        SELECT COUNT(1)       AS rechargeRefundCount,
+               SUM(pay_price) AS rechargeRefundPrice
+        FROM pay_wallet_recharge
+        WHERE refund_status = #{refundStatus}
+          AND refund_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+    <select id="selectPriceSummaryByBizTypeAndCreateTimeBetween" resultType="java.lang.Integer">
+        SELECT SUM(price)
+        FROM pay_wallet_transaction
+        WHERE biz_type = #{bizType}
+          AND create_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+    <select id="selectRechargeSummaryGroupByWalletId"
+            resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO">
+        SELECT COUNT(1)       AS rechargeUserCount,
+               SUM(pay_price) AS rechargePrice
+        FROM pay_wallet_recharge
+        WHERE pay_status = #{payStatus}
+        <if test="beginTime != null">
+            AND pay_time >= #{beginTime}
+        </if>
+        <if test="endTime != null">
+            AND pay_time &lt;= #{endTime}
+        </if>
+            AND deleted = FALSE
+        GROUP BY wallet_id
+    </select>
+
+</mapper>

+ 14 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/AfterSaleStatisticsMapper.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.statistics.dal.mysql.trade.AfterSaleStatisticsMapper">
+
+    <select id="selectSummaryByRefundTimeBetween"
+            resultType="cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO">
+        SELECT COUNT(1)          AS afterSaleCount,
+               SUM(refund_price) AS afterSaleRefundPrice
+        FROM trade_after_sale
+        WHERE refund_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+</mapper>

+ 14 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/BrokerageStatisticsMapper.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.statistics.dal.mysql.trade.BrokerageStatisticsMapper">
+
+    <select id="selectSummaryPriceByStatusAndUnfreezeTimeBetween" resultType="java.lang.Integer">
+        SELECT SUM(price)
+        FROM trade_brokerage_record
+        WHERE biz_type = #{bizType}
+          AND status = #{status}
+          AND unfreeze_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+</mapper>

+ 64 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeOrderStatisticsMapper">
+
+    <select id="selectSummaryListByAreaId"
+            resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO">
+        SELECT receiver_area_id                                AS areaId,
+               (SELECT COUNT(1)
+                FROM trade_order AS s
+                WHERE s.receiver_area_id = m.receiver_area_id) AS orderCreateCount,
+               (SELECT COUNT(1)
+                FROM trade_order AS s
+                WHERE s.receiver_area_id = m.receiver_area_id
+                  AND s.pay_status = TRUE
+                  AND s.deleted = FALSE)                       AS orderPayCount,
+               (SELECT SUM(s.pay_price)
+                FROM trade_order AS s
+                WHERE s.receiver_area_id = m.receiver_area_id
+                  AND s.pay_status = TRUE
+                  AND s.deleted = FALSE)                       AS orderPayPrice
+        FROM trade_order m
+        WHERE deleted = FALSE
+        GROUP BY receiver_area_id
+    </select>
+
+    <select id="selectUserCountByCreateTimeBetween" resultType="java.lang.Integer">
+        SELECT COUNT(DISTINCT(user_id))
+        FROM trade_order
+        WHERE deleted = FALSE
+          AND create_time BETWEEN #{beginTime} AND #{endTime}
+    </select>
+
+    <select id="selectUserCountByPayTimeBetween" resultType="java.lang.Integer">
+        SELECT COUNT(DISTINCT(user_id))
+        FROM trade_order
+        WHERE pay_time BETWEEN #{beginTime} AND #{endTime}
+          AND pay_status = TRUE
+          AND deleted = FALSE
+    </select>
+
+    <select id="selectCountByCreateTimeBetween" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM trade_order
+        WHERE create_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+    <select id="selectCountByPayTimeBetween" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM trade_order
+        WHERE pay_status = TRUE
+          AND create_time BETWEEN #{beginTime} AND #{endTime}
+          AND deleted = FALSE
+    </select>
+
+    <select id="selectSummaryPriceByPayTimeBetween" resultType="java.lang.Integer">
+        SELECT SUM(pay_price)
+        FROM trade_order AS s
+        WHERE s.pay_status = TRUE
+          AND deleted = FALSE
+          AND create_time BETWEEN #{beginTime} AND #{endTime}
+    </select>
+
+</mapper>

+ 13 - 0
yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeStatisticsMapper.xml

@@ -37,4 +37,17 @@
         GROUP BY date
     </select>
 
+    <select id="selectExpensePriceByTimeBetween" resultType="java.lang.Integer">
+        SELECT -- 支出金额 = 余额支付金额 + 支付佣金金额 + 商品退款金额
+               SUM(order_wallet_pay_price + brokerage_settlement_price + after_sale_refund_price) AS expensePrice
+          FROM trade_statistics
+        WHERE deleted = FALSE
+        <if test="beginTime != null">
+            AND time >= #{beginTime}
+        </if>
+        <if test="endTime != null">
+            AND time &lt;= #{endTime}
+        </if>
+    </select>
+
 </mapper>

+ 0 - 23
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/aftersale/TradeAfterSaleApi.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.trade.api.aftersale;
-
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
-
-import java.time.LocalDateTime;
-
-/**
- * 售后 API 接口
- *
- * @author owen
- */
-public interface TradeAfterSaleApi {
-
-    /**
-     * 获取售后单统计
-     *
-     * @param beginTime 起始时间
-     * @param endTime   截止时间
-     * @return 售后统计结果
-     */
-    AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime);
-
-}

+ 1 - 21
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.trade.api.order;
 
 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
-import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
 
-import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -30,25 +28,7 @@ public interface TradeOrderApi {
      */
     TradeOrderRespDTO getOrder(Long id);
 
-    /**
-     * 获取订单统计
-     *
-     * @param beginTime 起始时间
-     * @param endTime   截止时间
-     * @return 订单统计结果
-     */
-    TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime);
-
-    /**
-     * 更新拼团相关信息到订单
-     *
-     * @param orderId             订单编号
-     * @param activityId          拼团活动编号
-     * @param combinationRecordId 拼团记录编号
-     * @param headId              团长编号
-     */
-    void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId);
-
+    // TODO 芋艿:需要优化下;
     /**
      * 取消支付订单
      *

+ 2 - 1
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -33,7 +33,8 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
     ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态");
     ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
-    ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单已发货");
+    ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
+    ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
 
     // ========== After Sale 模块 1-011-000-100 ==========
     ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");

+ 0 - 28
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/aftersale/TradeAfterSaleApiImpl.java

@@ -1,28 +0,0 @@
-package cn.iocoder.yudao.module.trade.api.aftersale;
-
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
-import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.time.LocalDateTime;
-
-/**
- * 售后 API 接口实现类
- *
- * @author owen
- */
-@Service
-@Validated
-public class TradeAfterSaleApiImpl implements TradeAfterSaleApi {
-
-    @Resource
-    private AfterSaleService afterSaleService;
-
-    @Override
-    public AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) {
-        return afterSaleService.getAfterSaleSummary(beginTime, endTime);
-    }
-
-}

+ 0 - 27
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/brokerage/TradeBrokerageApiImpl.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.trade.api.brokerage;
-
-import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.time.LocalDateTime;
-
-/**
- * 订单 API 接口实现类
- *
- * @author HUIHUI
- */
-@Service
-@Validated
-public class TradeBrokerageApiImpl implements TradeBrokerageApi {
-
-    @Resource
-    private BrokerageRecordService brokerageRecordService;
-
-    @Override
-    public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) {
-        return brokerageRecordService.getBrokerageSettlementPriceSummary(beginTime, endTime);
-    }
-
-}

+ 2 - 14
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.trade.api.order;
 
 import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
-import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
@@ -9,7 +8,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -23,9 +21,9 @@ import java.util.List;
 public class TradeOrderApiImpl implements TradeOrderApi {
 
     @Resource
-    private TradeOrderQueryService tradeOrderQueryService;
-    @Resource
     private TradeOrderUpdateService tradeOrderUpdateService;
+    @Resource
+    private TradeOrderQueryService tradeOrderQueryService;
 
     @Override
     public List<TradeOrderRespDTO> getOrderList(Collection<Long> ids) {
@@ -38,16 +36,6 @@ public class TradeOrderApiImpl implements TradeOrderApi {
     }
 
     @Override
-    public TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) {
-        return tradeOrderQueryService.getOrderSummary(beginTime, endTime);
-    }
-
-    @Override
-    public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId) {
-        tradeOrderUpdateService.updateOrderCombinationInfo(orderId, activityId, combinationRecordId, headId);
-    }
-
-    @Override
     public void cancelPaidOrder(Long userId, Long orderId) {
         tradeOrderUpdateService.cancelPaidOrder(userId, orderId);
     }

+ 0 - 13
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java

@@ -1,19 +1,14 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
 
-import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.time.LocalDateTime;
 import java.util.Collection;
 
 @Mapper
@@ -53,12 +48,4 @@ public interface AfterSaleMapper extends BaseMapperX<AfterSaleDO> {
                 .in(AfterSaleDO::getStatus, statuses));
     }
 
-    default AfterSaleSummaryRespDTO selectSummaryByRefundTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
-        return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<AfterSaleDO>lambdaJoin()
-                        .selectCount(AfterSaleDO::getId, AfterSaleSummaryRespDTO::getAfterSaleCount)
-                        .selectSum(AfterSaleDO::getRefundPrice, AfterSaleSummaryRespDTO::getAfterSaleRefundPrice)
-                        .between(AfterSaleDO::getRefundTime, beginTime, endTime)), 0),
-                AfterSaleSummaryRespDTO.class);
-    }
-
 }

+ 0 - 11
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.brokerage;
 
 import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.convert.Convert;
 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;
@@ -111,13 +109,4 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
                                  @Param("beginTime") LocalDateTime beginTime,
                                  @Param("endTime") LocalDateTime endTime);
 
-    default Integer selectSummaryPriceByStatusAndUnfreezeTimeBetween(Integer bizType, Integer status,
-                                                                     LocalDateTime beginTime, LocalDateTime endTime) {
-        return Convert.toInt(CollUtil.getFirst(selectObjs(MPJWrappers.<BrokerageRecordDO>lambdaJoin()
-                .selectSum(BrokerageRecordDO::getPrice)
-                .eq(BrokerageRecordDO::getBizType, bizType)
-                .eq(BrokerageRecordDO::getStatus, status)
-                .between(BrokerageRecordDO::getUnfreezeTime, beginTime, endTime))), 0);
-    }
-
 }

+ 13 - 17
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java

@@ -1,16 +1,12 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.order;
 
-import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.collection.CollUtil;
 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.trade.api.order.dto.TradeOrderSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.github.yulichang.toolkit.MPJWrappers;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.time.LocalDateTime;
@@ -93,21 +89,21 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
                 .eq(TradeOrderDO::getSeckillActivityId, seckillActivityId));
     }
 
-    default TradeOrderSummaryRespDTO selectSummaryByPayTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
-        return BeanUtil.copyProperties(CollUtil.get(selectMaps(MPJWrappers.<TradeOrderDO>lambdaJoin()
-                        .selectCount(TradeOrderDO::getId, TradeOrderSummaryRespDTO::getOrderPayCount)
-                        .selectSum(TradeOrderDO::getPayPrice, TradeOrderSummaryRespDTO::getOrderPayPrice)
-                        .between(TradeOrderDO::getPayTime, beginTime, endTime)), 0),
-                TradeOrderSummaryRespDTO.class);
-    }
-
-    default Long selectCountByCreateTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) {
-        return selectCount(new LambdaQueryWrapperX<TradeOrderDO>()
-                .between(TradeOrderDO::getCreateTime, beginTime, endTime));
-    }
-
     default TradeOrderDO selectOneByPickUpVerifyCode(String pickUpVerifyCode) {
         return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode);
     }
 
+    // TODO @hui999:是不是只针对 combinationActivityId 的查询呀?
+    default TradeOrderDO selectByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
+        return selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
+                .and(q -> q.eq(TradeOrderDO::getUserId, userId)
+                        .eq(TradeOrderDO::getStatus, status))
+                .and(q -> q.eq(TradeOrderDO::getCombinationActivityId, activityId)
+                        .or()
+                        .eq(TradeOrderDO::getSeckillActivityId, activityId)
+                        .or()
+                        .eq(TradeOrderDO::getBargainActivityId, activityId))
+        );
+    }
+
 }

+ 0 - 12
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
@@ -10,8 +9,6 @@ import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCre
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 
-import java.time.LocalDateTime;
-
 /**
  * 售后订单 Service 接口
  *
@@ -127,13 +124,4 @@ public interface AfterSaleService {
      */
     Long getApplyingAfterSaleCount(Long userId);
 
-    /**
-     * 获取售后单统计
-     *
-     * @param beginTime 起始时间
-     * @param endTime   截止时间
-     * @return 售后统计结果
-     */
-    AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime);
-
 }

+ 0 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java

@@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
-import cn.iocoder.yudao.module.trade.api.aftersale.dto.AfterSaleSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
@@ -411,9 +410,4 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         return tradeAfterSaleMapper.selectCountByUserIdAndStatus(userId, AfterSaleStatusEnum.APPLYING_STATUSES);
     }
 
-    @Override
-    public AfterSaleSummaryRespDTO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) {
-        return tradeAfterSaleMapper.selectSummaryByRefundTimeBetween(beginTime,endTime);
-    }
-
 }

+ 0 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java

@@ -156,13 +156,4 @@ public interface BrokerageRecordService {
      */
     AppBrokerageProductPriceRespVO calculateProductBrokeragePrice(Long userId, Long spuId);
 
-    /**
-     * 获取已结算的佣金金额
-     *
-     * @param beginTime 起始时间
-     * @param endTime   截止时间
-     * @return 已结算的佣金金额
-     */
-    Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime);
-
 }

+ 0 - 7
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java

@@ -356,13 +356,6 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         return respVO;
     }
 
-    @Override
-    public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) {
-        return brokerageRecordMapper.selectSummaryPriceByStatusAndUnfreezeTimeBetween(
-                BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
-                beginTime, endTime);
-    }
-
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      *

+ 11 - 12
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java

@@ -1,14 +1,12 @@
 package cn.iocoder.yudao.module.trade.service.order;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
 
-import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -41,6 +39,16 @@ public interface TradeOrderQueryService {
     TradeOrderDO getOrder(Long userId, Long id);
 
     /**
+     * 获得指定用户,指定活动,指定状态的交易订单
+     *
+     * @param userId     用户编号
+     * @param activityId 活动编号
+     * @param status     订单状态
+     * @return 交易订单
+     */
+    TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status);
+
+    /**
      * 获得订单列表
      *
      * @param ids 订单编号数组
@@ -95,7 +103,7 @@ public interface TradeOrderQueryService {
     /**
      * 【会员】在指定秒杀活动下,用户购买的商品数量
      *
-     * @param userId 用户编号
+     * @param userId     用户编号
      * @param activityId 活动编号
      * @return 秒杀商品数量
      */
@@ -138,13 +146,4 @@ public interface TradeOrderQueryService {
      */
     List<TradeOrderItemDO> getOrderItemListByOrderId(Collection<Long> orderIds);
 
-    /**
-     * 获取订单统计
-     *
-     * @param beginTime 起始时间
-     * @param endTime   截止时间
-     * @return 订单统计结果
-     */
-    TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime);
-
 }

+ 5 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java

@@ -7,7 +7,6 @@ import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
@@ -25,7 +24,6 @@ import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -73,6 +71,11 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
     }
 
     @Override
+    public TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
+        return tradeOrderMapper.selectByUserIdAndActivityIdAndStatus(userId, activityId, status);
+    }
+
+    @Override
     public List<TradeOrderDO> getOrderList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {
             return Collections.emptyList();
@@ -183,13 +186,6 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
                         .setPhone(receiverMobile));
     }
 
-    @Override
-    public TradeOrderSummaryRespDTO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) {
-        TradeOrderSummaryRespDTO dto = tradeOrderMapper.selectSummaryByPayTimeBetween(beginTime, endTime);
-        dto.setOrderCreateCount(tradeOrderMapper.selectCountByCreateTimeBetween(beginTime, endTime));
-        return dto;
-    }
-
 
     // =================== Order Item ===================
 

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

@@ -14,23 +14,12 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
-import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
-import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
-import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
-import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
-import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
-import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
-import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
-import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
-import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
-import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
@@ -40,7 +29,6 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
-import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -48,15 +36,11 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
 import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.*;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
 import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
-import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
-import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
-import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.trade.service.cart.CartService;
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
 import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
@@ -68,13 +52,15 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
 import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -109,30 +95,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     private DeliveryExpressService deliveryExpressService;
     @Resource
     private TradeMessageService tradeMessageService;
-    @Resource
-    private BrokerageUserService brokerageUserService;
-    @Resource
-    private BrokerageRecordService brokerageRecordService;
 
     @Resource
-    private ProductSpuApi productSpuApi;
-    @Resource
-    private ProductSkuApi productSkuApi;
-    @Resource
     private PayOrderApi payOrderApi;
     @Resource
     private AddressApi addressApi;
     @Resource
-    private CouponApi couponApi;
-    @Resource
-    private CombinationRecordApi combinationRecordApi;
-    @Resource
-    private MemberUserApi memberUserApi;
-    @Resource
-    private MemberLevelApi memberLevelApi;
-    @Resource
-    private MemberPointApi memberPointApi;
-    @Resource
     private ProductCommentApi productCommentApi;
 
     @Resource
@@ -199,7 +167,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         List<TradeOrderItemDO> orderItems = buildTradeOrderItems(order, calculateRespBO);
 
         // 2. 订单创建前的逻辑
-        beforeCreateTradeOrder(order, orderItems);
+        tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
 
         // 3. 保存订单
         tradeOrderMapper.insert(order);
@@ -234,11 +202,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
             order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile());
             order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码,长度为 8 位
         }
-        // 设置订单推广人
-        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
-        if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
-            order.setBrokerageUserId(brokerageUser.getBindUserId());
-        }
         return order;
     }
 
@@ -248,21 +211,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     }
 
     /**
-     * 订单创建前,执行前置逻辑
-     *
-     * @param order      订单
-     * @param orderItems 订单项
-     */
-    private void beforeCreateTradeOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        // 1. 执行订单创建前置处理器
-        // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
-        tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
-
-        // 2. 下单时扣减商品库存
-        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
-    }
-
-    /**
      * 订单创建后,执行后置逻辑
      * <p>
      * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
@@ -276,27 +224,16 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         // 1. 执行订单创建后置处理器
         tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems));
 
-        // 2. 有使用优惠券时更新
-        // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
-        if (order.getCouponId() != null) {
-            couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId())
-                    .setOrderId(order.getId()));
-        }
-
-        // 3. 扣减积分(抵扣)
-        // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
-        reduceUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
-
-        // 4. 删除购物车商品
+        // 2. 删除购物车商品
         Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
         if (CollUtil.isNotEmpty(cartIds)) {
             cartService.deleteCart(order.getUserId(), cartIds);
         }
 
-        // 5. 生成预支付
+        // 3. 生成预支付
         createPayOrder(order, orderItems);
 
-        // 6. 插入订单日志
+        // 4. 插入订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
 
         // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
@@ -330,18 +267,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
             throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
         }
 
-        // 3、订单支付成功后
+        // 3. 执行 TradeOrderHandler 的后置处理
         List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
         tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems));
 
-        // 4.1 增加用户积分(赠送)
-        addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, order.getId());
-        // 4.2 增加用户经验
-        getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
-        // 4.3 增加用户佣金
-        getSelf().addBrokerageAsync(order.getUserId(), order.getId());
-
-        // 5. 记录订单日志
+        // 4. 记录订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
         TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
     }
@@ -434,8 +364,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                         .put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build());
 
         // 4. 发送站内信
-        tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO().setOrderId(order.getId())
-                .setUserId(order.getUserId()).setMessage(null));
+        tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO()
+                .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null));
     }
 
     /**
@@ -448,18 +378,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
      */
     private TradeOrderDO validateOrderDeliverable(Long id) {
         TradeOrderDO order = validateOrderExists(id);
-        // 校验订单是否退款
+        // 1. 校验订单是否未发货
         if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
             throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
         }
-        // 订单类型:拼团
-        if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
-            // 校验订单拼团是否成功
-            if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
-                throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
-            }
-        }
 
+        // 2. 执行 TradeOrderHandler 前置处理
+        tradeOrderHandlers.forEach(handler -> handler.beforeDeliveryOrder(order));
         return order;
     }
 
@@ -616,30 +541,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
      * @param cancelType 取消类型
      */
     private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
-        Long id = order.getId();
         // 1. 更新 TradeOrderDO 状态为已取消
-        int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
+        int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
                 new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
                         .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now()));
         if (updateCount == 0) {
             throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
         }
 
-        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
-        // 3. 回滚库存
-        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
-        // 3.1、 活动相关的回滚
-        tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, orderItems));
-
-        // 4. 回滚优惠券
-        if (order.getCouponId() != null && order.getCouponId() > 0) {
-            couponApi.returnUsedCoupon(order.getCouponId());
-        }
-
-        // 5. 回滚积分(抵扣的)
-        addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId());
+        // 2. 执行 TradeOrderHandler 的后置处理
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
+        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
 
-        // 6. 增加订单日志
+        // 3. 增加订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
     }
 
@@ -660,8 +574,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
                 .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
 
-        // 2. 退还优惠券
-        couponApi.returnUsedCoupon(order.getCouponId());
+        // 2. 执行 TradeOrderHandler 的后置处理
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
+        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
     }
 
     @Override
@@ -742,9 +657,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
         // 校验交易订单
         TradeOrderDO order = validateOrderExists(reqVO.getId());
-        // 发货后,不允许修改;
-        // TODO @puhui999:只有待发货,可以执行 update
-        if (TradeOrderStatusEnum.isDelivered(order.getStatus())) {
+        // 只有待发货状态,才可以修改订单收货地址;
+        if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) {
             throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED);
         }
 
@@ -790,13 +704,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
-        // 1. 更新订单项
+        // 1.1 更新订单项
         updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
                 TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
-
-        // 2.1 更新订单的退款金额、积分
+        // 1.2 执行 TradeOrderHandler 的后置处理
         TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
         TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
+        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrderItem(order, orderItem));
+
+        // 2.1 更新订单的退款金额、积分
         Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
         Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint();
         Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ?
@@ -807,23 +723,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint));
         // 2.2 如果全部退款,则进行取消订单
         getSelf().cancelOrderByAfterSale(order, orderRefundPrice);
-
-
-        // 3. 回滚库存
-        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
-        // 3.1、 活动相关的回滚
-        tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, Collections.singletonList(orderItem)));
-
-        // 4.1 回滚积分:扣减用户积分(赠送的)
-        reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
-        // 4.2 回滚积分:增加用户积分(返还抵扣)
-        addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId());
-
-        // 5. 回滚经验:扣减用户经验
-        getSelf().reduceUserExperienceAsync(order.getUserId(), refundPrice, orderItem.getAfterSaleId());
-
-        // 6. 回滚佣金:更新分佣记录为已失效
-        getSelf().cancelBrokerageAsync(order.getUserId(), id);
     }
 
     @Override
@@ -840,6 +739,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         if (updateCount <= 0) {
             throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
         }
+
     }
 
     /**
@@ -923,11 +823,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void cancelPaidOrder(Long userId, Long orderId) {
+        // TODO 芋艿:这里实现要优化下;
         TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
         if (order == null) {
             throw exception(ORDER_NOT_FOUND);
         }
-
         cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
     }
 
@@ -940,7 +840,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT)
     public void createOrderItemCommentBySystemBySystem(TradeOrderDO order) {
         // 1. 查询未评论的订单项
-        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(order.getId(), Boolean.FALSE);
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(
+                order.getId(), Boolean.FALSE);
         if (CollUtil.isEmpty(orderItems)) {
             return;
         }
@@ -983,73 +884,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     // =================== 营销相关的操作 ===================
 
-    @Async
-    protected void addUserExperienceAsync(Long userId, Integer payPrice, Long orderId) {
-        int bizType = MemberExperienceBizTypeEnum.ORDER.getType();
-        memberLevelApi.addExperience(userId, payPrice, bizType, String.valueOf(orderId));
-    }
-
-    @Async
-    protected void reduceUserExperienceAsync(Long userId, Integer refundPrice, Long afterSaleId) {
-        int bizType = MemberExperienceBizTypeEnum.REFUND.getType();
-        memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
-    }
-
-    /**
-     * 添加用户积分
-     * <p>
-     * 目前是支付成功后,就会创建积分记录。
-     * <p>
-     * 业内还有两种做法,可以根据自己的业务调整:
-     * 1. 确认收货后,才创建积分记录
-     * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻
-     *
-     * @param userId  用户编号
-     * @param point   增加积分数量
-     * @param bizType 业务编号
-     * @param bizId   业务编号
-     */
-    protected void addUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
-        if (point != null && point > 0) {
-            memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
-        }
-    }
-
-    protected void reduceUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
-        if (point != null && point > 0) {
-            memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
-        }
-    }
-
-    /**
-     * 创建分销记录
-     * <p>
-     * 目前是支付成功后,就会创建分销记录。
-     * <p>
-     * 业内还有两种做法,可以根据自己的业务调整:
-     * 1. 确认收货后,才创建分销记录
-     * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     */
-    @Async
-    protected void addBrokerageAsync(Long userId, Long orderId) {
-        MemberUserRespDTO user = memberUserApi.getUser(userId);
-        Assert.notNull(user);
-        // 每一个订单项,都会去生成分销记录
-        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
-        List<BrokerageAddReqBO> addList = convertList(orderItems,
-                item -> TradeOrderConvert.INSTANCE.convert(user, item,
-                        productSpuApi.getSpu(item.getSpuId()), productSkuApi.getSku(item.getSkuId())));
-        brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
-    }
-
-    @Async
-    protected void cancelBrokerageAsync(Long userId, Long orderItemId) {
-        brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
-    }
-
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      *

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

@@ -1,55 +0,0 @@
-package cn.iocoder.yudao.module.trade.service.order.handler;
-
-import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
-import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 砍价订单 handler 实现类
- *
- * @author HUIHUI
- */
-@Component
-public class TradeBargainHandler implements TradeOrderHandler {
-
-    @Resource
-    private BargainActivityApi bargainActivityApi;
-    @Resource
-    private BargainRecordApi bargainRecordApi;
-
-    @Override
-    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isBargain(order.getType())) {
-            return;
-        }
-
-        // 扣减砍价活动的库存
-        bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
-                -orderItems.get(0).getCount());
-    }
-
-    @Override
-    public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isBargain(order.getType())) {
-            return;
-        }
-
-        // 记录砍价记录对应的订单编号
-        bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId());
-    }
-
-    @Override
-    public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isBargain(order.getType())) {
-            return;
-        }
-        // TODO 芋艿:取消订单时,需要增加库存
-    }
-
-}

+ 78 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java

@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
+import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 砍价订单的 {@link TradeOrderHandler} 实现类
+ *
+ * @author HUIHUI
+ */
+@Component
+public class TradeBargainOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private BargainActivityApi bargainActivityApi;
+    @Resource
+    private BargainRecordApi bargainRecordApi;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
+            return;
+        }
+        // 明确校验一下
+        Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
+
+        // 扣减砍价活动的库存
+        bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
+                -orderItems.get(0).getCount());
+    }
+
+    @Override
+    public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
+            return;
+        }
+        // 明确校验一下
+        Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
+
+        // 记录砍价记录对应的订单编号
+        bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId());
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
+            return;
+        }
+        // 明确校验一下
+        Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
+
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        afterCancelOrderItem(order, orderItems.get(0));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
+            return;
+        }
+        // 恢复(增加)砍价活动的库存
+        bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), orderItem.getCount());
+    }
+
+}

+ 118 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java

@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+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.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+/**
+ * 订单分销的 {@link TradeOrderHandler} 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TradeBrokerageOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private MemberUserApi memberUserApi;
+    @Resource
+    private ProductSpuApi productSpuApi;
+    @Resource
+    private ProductSkuApi productSkuApi;
+
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
+    @Resource
+    private BrokerageUserService brokerageUserService;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 设置订单推广人
+        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
+        if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
+            order.setBrokerageUserId(brokerageUser.getBindUserId());
+        }
+    }
+
+    @Override
+    public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (order.getBrokerageUserId() == null) {
+            return;
+        }
+        addBrokerage(order.getUserId(), orderItems);
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 如果是未支付的订单,不会产生分销结果,所以直接 return
+        if (!order.getPayStatus()) {
+            return;
+        }
+        if (order.getBrokerageUserId() == null) {
+            return;
+        }
+
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        orderItems.forEach(orderItem -> afterCancelOrderItem(order, orderItem));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        if (order.getBrokerageUserId() == null) {
+            return;
+        }
+        cancelBrokerage(order.getId(), orderItem.getOrderId());
+    }
+
+    /**
+     * 创建分销记录
+     * <p>
+     * 目前是支付成功后,就会创建分销记录。
+     * <p>
+     * 业内还有两种做法,可以根据自己的业务调整:
+     * 1. 确认收货后,才创建分销记录
+     * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
+     *
+     * @param userId  用户编号
+     * @param orderItems 订单项
+     */
+    protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
+        MemberUserRespDTO user = memberUserApi.getUser(userId);
+        Assert.notNull(user);
+        ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());
+        Assert.notNull(spu);
+        ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId());
+
+        // 每一个订单项,都会去生成分销记录
+        List<BrokerageAddReqBO> addList = convertList(orderItems,
+                item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku));
+        brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
+    }
+
+    protected void cancelBrokerage(Long userId, Long orderItemId) {
+        brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
+    }
+
+}

+ 0 - 61
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java

@@ -1,61 +0,0 @@
-package cn.iocoder.yudao.module.trade.service.order.handler;
-
-import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
-import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 拼团订单 handler 接口实现类
- *
- * @author HUIHUI
- */
-@Component
-public class TradeCombinationHandler implements TradeOrderHandler {
-
-    @Resource
-    private CombinationRecordApi combinationRecordApi;
-
-    @Override
-    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        // 如果不是拼团订单则结束
-        if (TradeOrderTypeEnum.isCombination(order.getType())) {
-            return;
-        }
-        Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
-
-        // 校验是否满足拼团活动相关限制
-        TradeOrderItemDO item = orderItems.get(0);
-        combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
-                order.getCombinationHeadId(), item.getSkuId(), item.getCount());
-        // TODO @puhui999:这里还要限制下,是不是已经 createOrder;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了;
-    }
-
-    @Override
-    public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        // 如果不是拼团订单则结束
-        if (TradeOrderTypeEnum.isCombination(order.getType())) {
-            return;
-        }
-        Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
-
-        // 获取商品信息
-        TradeOrderItemDO item = orderItems.get(0);
-        // 创建拼团记录
-        combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item));
-    }
-
-    @Override
-    public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isCombination(order.getType())) {
-            return;
-        }
-    }
-
-}

+ 91 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_FAIL_EXIST_UNPAID;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS;
+
+/**
+ * 拼团订单的 {@link TradeOrderHandler} 实现类
+ *
+ * @author HUIHUI
+ */
+@Component
+public class TradeCombinationOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private TradeOrderUpdateService orderUpdateService;
+    @Resource
+    private TradeOrderQueryService orderQueryService;
+
+    @Resource
+    private CombinationRecordApi combinationRecordApi;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 如果不是拼团订单则结束
+        if (!TradeOrderTypeEnum.isCombination(order.getType())) {
+            return;
+        }
+        Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
+
+        // 1. 校验是否满足拼团活动相关限制
+        TradeOrderItemDO item = orderItems.get(0);
+        combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
+                order.getCombinationHeadId(), item.getSkuId(), item.getCount());
+
+        // 2. 校验该用户是否存在未支付的拼团活动订单,避免一个拼团可以下多个单子了
+        // TODO @puhui999:只校验未支付的拼团订单噢
+        TradeOrderDO activityOrder = orderQueryService.getActivityOrderByUserIdAndActivityIdAndStatus(
+                order.getUserId(), order.getCombinationActivityId(), TradeOrderStatusEnum.UNPAID.getStatus());
+        if (activityOrder != null) {
+            throw exception(ORDER_CREATE_FAIL_EXIST_UNPAID);
+        }
+    }
+
+    @Override
+    public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 1.如果不是拼团订单则结束
+        if (!TradeOrderTypeEnum.isCombination(order.getType())) {
+            return;
+        }
+        Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
+
+        // 2. 创建拼团记录
+        TradeOrderItemDO item = orderItems.get(0);
+        KeyValue<Long, Long> recordIdAndHeadId = combinationRecordApi.createCombinationRecord(
+                TradeOrderConvert.INSTANCE.convert(order, item));
+
+        // 3. 更新拼团相关信息到订单
+        // TODO 芋艿,只需要更新 record
+        orderUpdateService.updateOrderCombinationInfo(order.getId(), order.getCombinationActivityId(),
+                recordIdAndHeadId.getKey(), recordIdAndHeadId.getValue());
+    }
+
+    @Override
+    public void beforeDeliveryOrder(TradeOrderDO order) {
+        if (!TradeOrderTypeEnum.isCombination(order.getType())) {
+            return;
+        }
+        // 校验订单拼团是否成功
+        if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
+            throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
+        }
+    }
+
+}
+

+ 42 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
+import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 优惠劵的 {@link TradeOrderHandler} 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TradeCouponOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private CouponApi couponApi;
+
+    @Override
+    public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (order.getCouponId() == null || order.getCouponId() <= 0) {
+            return;
+        }
+        // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
+        couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId())
+                .setOrderId(order.getId()));
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (order.getCouponId() == null || order.getCouponId() <= 0) {
+            return;
+        }
+        // 退回优惠劵
+        couponApi.returnUsedCoupon(order.getCouponId());
+    }
+
+}

+ 120 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java

@@ -0,0 +1,120 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
+import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
+
+/**
+ * 会员积分、等级的 {@link TradeOrderHandler} 实现类
+ *
+ * @author owen
+ */
+@Component
+public class TradeMemberPointOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private MemberPointApi memberPointApi;
+    @Resource
+    private MemberLevelApi memberLevelApi;
+
+    @Resource
+    private AfterSaleService afterSaleService;
+
+    @Override
+    public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 扣减用户积分(订单抵扣)。不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
+        reducePoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
+    }
+
+    @Override
+    public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 增加用户积分(订单赠送)
+        addPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE,
+                order.getId());
+
+        // 增加用户经验
+        memberLevelApi.addExperience(order.getUserId(), order.getPayPrice(),
+                MemberExperienceBizTypeEnum.ORDER_GIVE.getType(), String.valueOf(order.getId()));
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+
+        // 增加(回滚)用户积分(订单抵扣)
+        Integer usePoint = getSumValue(orderItems, TradeOrderItemDO::getUsePoint, Integer::sum);
+        addPoint(order.getUserId(), usePoint, MemberPointBizTypeEnum.ORDER_USE_CANCEL,
+                order.getId());
+
+        // 如下的返还,需要经过支持,也就是经历 afterPayOrder 流程
+        if (!order.getPayStatus()) {
+            return;
+        }
+        // 扣减(回滚)积分(订单赠送)
+        Integer givePoint = getSumValue(orderItems, TradeOrderItemDO::getGivePoint, Integer::sum);
+        reducePoint(order.getUserId(), givePoint, MemberPointBizTypeEnum.ORDER_GIVE_CANCEL,
+                order.getId());
+        // 扣减(回滚)用户经验
+        int payPrice = order.getPayPrice() - order.getRefundPrice();
+        memberLevelApi.addExperience(order.getUserId(), payPrice,
+                MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL.getType(), String.valueOf(order.getId()));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        // 扣减(回滚)积分(订单赠送)
+        reducePoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE_CANCEL_ITEM,
+                orderItem.getId());
+        // 增加(回滚)积分(订单抵扣)
+        addPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE_CANCEL_ITEM,
+                orderItem.getId());
+
+        // 扣减(回滚)用户经验
+        AfterSaleDO afterSale = afterSaleService.getAfterSale(orderItem.getAfterSaleId());
+        memberLevelApi.reduceExperience(order.getUserId(), afterSale.getRefundPrice(),
+                MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL_ITEM.getType(), String.valueOf(orderItem.getId()));
+    }
+
+    /**
+     * 添加用户积分
+     * <p>
+     * 目前是支付成功后,就会创建积分记录。
+     * <p>
+     * 业内还有两种做法,可以根据自己的业务调整:
+     * 1. 确认收货后,才创建积分记录
+     * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻
+     *
+     * @param userId  用户编号
+     * @param point   增加积分数量
+     * @param bizType 业务编号
+     * @param bizId   业务编号
+     */
+    protected void addPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
+        if (point != null && point > 0) {
+            memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
+        }
+    }
+
+    protected void reducePoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
+        if (point != null && point > 0) {
+            memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
+        }
+    }
+
+}

+ 32 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.trade.service.order.handler;
 
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
 
 import java.util.List;
 
@@ -35,16 +37,42 @@ public interface TradeOrderHandler {
      * @param order 订单
      * @param orderItems 订单项
      */
-    default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-    }
+    default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {}
 
     /**
-     * 订单取消
+     * 订单取消
      *
      * @param order 订单
      * @param orderItems 订单项
      */
-    default void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+    default void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {}
+
+    /**
+     * 订单项取消后
+     *
+     * @param order 订单
+     * @param orderItem 订单项
+     */
+    default void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {}
+
+    /**
+     * 订单发货前
+     *
+     * @param order 订单
+     */
+    default void beforeDeliveryOrder(TradeOrderDO order) {}
+
+    // ========== 公用方法 ==========
+
+    /**
+     * 过滤“未售后”的订单项列表
+     *
+     * @param orderItems 订单项列表
+     * @return 过滤后的订单项列表
+     */
+    default List<TradeOrderItemDO> filterOrderItemListByNoneAfterSale(List<TradeOrderItemDO> orderItems) {
+        return CollectionUtils.filterList(orderItems,
+                item -> TradeOrderItemAfterSaleStatusEnum.isNone(item.getAfterSaleStatus()));
     }
 
 }

+ 46 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+
+/**
+ * 商品 SKU 库存的 {@link TradeOrderHandler} 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TradeProductSkuOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private ProductSkuApi productSkuApi;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(singletonList(orderItem)));
+    }
+
+}

+ 0 - 42
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java

@@ -1,42 +0,0 @@
-package cn.iocoder.yudao.module.trade.service.order.handler;
-
-import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
-import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
-import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 秒杀订单 handler 实现类
- *
- * @author HUIHUI
- */
-@Component
-public class TradeSeckillHandler implements TradeOrderHandler {
-
-    @Resource
-    private SeckillActivityApi seckillActivityApi;
-
-    @Override
-    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isSeckill(order.getType())) {
-            return;
-        }
-
-        // 扣减秒杀活动的库存
-        seckillActivityApi.updateSeckillStock(order.getSeckillActivityId(),
-                orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
-    }
-
-    @Override
-    public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isSeckill(order.getType())) {
-            return;
-        }
-
-    }
-
-}

+ 64 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 秒杀订单的 {@link TradeOrderHandler} 实现类
+ *
+ * @author HUIHUI
+ */
+@Component
+public class TradeSeckillOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private SeckillActivityApi seckillActivityApi;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
+            return;
+        }
+        // 明确校验一下
+        Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
+
+        // 扣减秒杀活动的库存
+        seckillActivityApi.updateSeckillStockDecr(order.getSeckillActivityId(),
+                orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
+            return;
+        }
+        // 明确校验一下
+        Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
+
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        afterCancelOrderItem(order, orderItems.get(0));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
+            return;
+        }
+        // 恢复秒杀活动的库存
+        seckillActivityApi.updateSeckillStockIncr(order.getSeckillActivityId(),
+                orderItem.getSkuId(), orderItem.getCount());
+    }
+
+}

+ 10 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java

@@ -28,4 +28,14 @@ public interface MemberLevelApi {
      */
     void addExperience(Long userId, Integer experience, Integer bizType, String bizId);
 
+    /**
+     * 扣减会员经验
+     *
+     * @param userId     会员ID
+     * @param experience 经验
+     * @param bizType    业务类型 {@link MemberExperienceBizTypeEnum}
+     * @param bizId      业务编号
+     */
+    void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId);
+
 }

+ 3 - 2
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java

@@ -20,10 +20,11 @@ public enum MemberExperienceBizTypeEnum {
      */
     ADMIN(0, "管理员调整", "管理员调整获得 {} 经验", true),
     INVITE_REGISTER(1, "邀新奖励", "邀请好友获得 {} 经验", true),
-    ORDER(2, "下单奖励", "下单获得 {} 经验", true),
-    REFUND(3, "退单扣除", "退单获得 {} 经验", false),
     SIGN_IN(4, "签到奖励", "签到获得 {} 经验", true),
     LOTTERY(5, "抽奖奖励", "抽奖获得 {} 经验", true),
+    ORDER_GIVE(11, "下单奖励", "下单获得 {} 经验", true),
+    ORDER_GIVE_CANCEL(12, "下单奖励(整单取消)", "取消订单获得 {} 经验", false), // ORDER_GIVE 的取消
+    ORDER_GIVE_CANCEL_ITEM(13, "下单奖励(单个退款)", "退款订单获得 {} 经验", false), // ORDER_GIVE 的取消
     ;
 
     /**

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

@@ -18,11 +18,14 @@ public enum MemberPointBizTypeEnum implements IntArrayValuable {
 
     SIGN(1, "签到", "签到获得 {} 积分", true),
     ADMIN(2, "管理员修改", "管理员修改 {} 积分", true),
-    ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分
-    ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时,退回积分
-    ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时,扣减积分
-    AFTER_SALE_REFUND_USED(13, "订单退款", "订单退款,退还 {} 积分", true), // 售后订单成功时,退回积分(对应 ORDER_USE 操作)
-    AFTER_SALE_DEDUCT_GIVE(14, "订单退款", "订单退款,扣除赠送的 {} 积分", false), // 售后订单成功时,扣减积分(对应 ORDER_GIVE 操作)
+
+    ORDER_USE(11, "订单积分抵扣", "下单使用 {} 积分", false), // 下单时,扣减积分
+    ORDER_USE_CANCEL(12, "订单积分抵扣(整单取消)", "订单取消,退还 {} 积分", true), // ORDER_USE 的取消
+    ORDER_USE_CANCEL_ITEM(13, "订单积分抵扣(单个退款)", "订单退款,退还 {} 积分", true), // ORDER_USE 的取消
+
+    ORDER_GIVE(21, "订单积分奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分
+    ORDER_GIVE_CANCEL(22, "订单积分奖励(整单取消)", "订单取消,退还 {} 积分", false), // ORDER_GIVE 的取消
+    ORDER_GIVE_CANCEL_ITEM(23, "订单积分奖励(单个退款)", "订单退款,扣除赠送的 {} 积分", false) // ORDER_GIVE 的取消
     ;
 
     /**

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

@@ -38,4 +38,9 @@ public class MemberLevelApiImpl implements MemberLevelApi {
         memberLevelService.addExperience(userId, experience, bizTypeEnum, bizId);
     }
 
+    @Override
+    public void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId) {
+        addExperience(userId, -experience, bizType, bizId);
+    }
+
 }

+ 0 - 23
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.pay.api.wallet;
-
-import cn.iocoder.yudao.module.pay.api.wallet.dto.WalletSummaryRespDTO;
-
-import java.time.LocalDateTime;
-
-/**
- * 钱包 API 接口
- *
- * @author owen
- */
-public interface PayWalletApi {
-
-    /**
-     * 获取钱包统计
-     *
-     * @param beginTime 起始时间
-     * @param endTime   截止时间
-     * @return 钱包统计
-     */
-    WalletSummaryRespDTO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime);
-
-}

+ 0 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java


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